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
1612 #define CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH 0
1614 static boolean token_value_separator_found = FALSE;
1615 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
1616 static boolean token_value_separator_warning = FALSE;
1618 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
1619 static boolean token_already_exists_warning = FALSE;
1622 static boolean getTokenValueFromSetupLineExt(char *line,
1623 char **token_ptr, char **value_ptr,
1624 char *filename, char *line_raw,
1626 boolean separator_required)
1628 static char line_copy[MAX_LINE_LEN + 1], line_raw_copy[MAX_LINE_LEN + 1];
1629 char *token, *value, *line_ptr;
1631 /* when externally invoked via ReadTokenValueFromLine(), copy line buffers */
1632 if (line_raw == NULL)
1634 strncpy(line_copy, line, MAX_LINE_LEN);
1635 line_copy[MAX_LINE_LEN] = '\0';
1638 strcpy(line_raw_copy, line_copy);
1639 line_raw = line_raw_copy;
1642 /* cut trailing comment from input line */
1643 for (line_ptr = line; *line_ptr; line_ptr++)
1645 if (*line_ptr == '#')
1652 /* cut trailing whitespaces from input line */
1653 for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
1654 if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
1657 /* ignore empty lines */
1661 /* cut leading whitespaces from token */
1662 for (token = line; *token; token++)
1663 if (*token != ' ' && *token != '\t')
1666 /* start with empty value as reliable default */
1669 token_value_separator_found = FALSE;
1671 /* find end of token to determine start of value */
1672 for (line_ptr = token; *line_ptr; line_ptr++)
1675 /* first look for an explicit token/value separator, like ':' or '=' */
1676 if (*line_ptr == ':' || *line_ptr == '=')
1678 if (*line_ptr == ' ' || *line_ptr == '\t' || *line_ptr == ':')
1681 *line_ptr = '\0'; /* terminate token string */
1682 value = line_ptr + 1; /* set beginning of value */
1684 token_value_separator_found = TRUE;
1690 #if ALLOW_TOKEN_VALUE_SEPARATOR_BEING_WHITESPACE
1691 /* fallback: if no token/value separator found, also allow whitespaces */
1692 if (!token_value_separator_found && !separator_required)
1694 for (line_ptr = token; *line_ptr; line_ptr++)
1696 if (*line_ptr == ' ' || *line_ptr == '\t')
1698 *line_ptr = '\0'; /* terminate token string */
1699 value = line_ptr + 1; /* set beginning of value */
1701 token_value_separator_found = TRUE;
1707 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
1708 if (token_value_separator_found)
1710 if (!token_value_separator_warning)
1712 Error(ERR_INFO_LINE, "-");
1714 if (filename != NULL)
1716 Error(ERR_WARN, "missing token/value separator(s) in config file:");
1717 Error(ERR_INFO, "- config file: '%s'", filename);
1721 Error(ERR_WARN, "missing token/value separator(s):");
1724 token_value_separator_warning = TRUE;
1727 if (filename != NULL)
1728 Error(ERR_INFO, "- line %d: '%s'", line_nr, line_raw);
1730 Error(ERR_INFO, "- line: '%s'", line_raw);
1736 /* cut trailing whitespaces from token */
1737 for (line_ptr = &token[strlen(token)]; line_ptr >= token; line_ptr--)
1738 if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
1741 /* cut leading whitespaces from value */
1742 for (; *value; value++)
1743 if (*value != ' ' && *value != '\t')
1748 value = "true"; /* treat tokens without value as "true" */
1757 boolean getTokenValueFromSetupLine(char *line, char **token, char **value)
1759 /* while the internal (old) interface does not require a token/value
1760 separator (for downwards compatibility with existing files which
1761 don't use them), it is mandatory for the external (new) interface */
1763 return getTokenValueFromSetupLineExt(line, token, value, NULL, NULL, 0, TRUE);
1767 static boolean loadSetupFileData(void *setup_file_data, char *filename,
1768 boolean top_recursion_level, boolean is_hash)
1770 static SetupFileHash *include_filename_hash = NULL;
1771 char line[MAX_LINE_LEN], line_raw[MAX_LINE_LEN], previous_line[MAX_LINE_LEN];
1772 char *token, *value, *line_ptr;
1773 void *insert_ptr = NULL;
1774 boolean read_continued_line = FALSE;
1777 int token_count = 0;
1779 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
1780 token_value_separator_warning = FALSE;
1783 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
1784 token_already_exists_warning = FALSE;
1787 if (!(file = fopen(filename, MODE_READ)))
1789 Error(ERR_WARN, "cannot open configuration file '%s'", filename);
1794 /* use "insert pointer" to store list end for constant insertion complexity */
1796 insert_ptr = setup_file_data;
1798 /* on top invocation, create hash to mark included files (to prevent loops) */
1799 if (top_recursion_level)
1800 include_filename_hash = newSetupFileHash();
1802 /* mark this file as already included (to prevent including it again) */
1803 setHashEntry(include_filename_hash, getBaseNamePtr(filename), "true");
1807 /* read next line of input file */
1808 if (!fgets(line, MAX_LINE_LEN, file))
1811 /* check if line was completely read and is terminated by line break */
1812 if (strlen(line) > 0 && line[strlen(line) - 1] == '\n')
1815 /* cut trailing line break (this can be newline and/or carriage return) */
1816 for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
1817 if ((*line_ptr == '\n' || *line_ptr == '\r') && *(line_ptr + 1) == '\0')
1820 /* copy raw input line for later use (mainly debugging output) */
1821 strcpy(line_raw, line);
1823 if (read_continued_line)
1826 /* !!! ??? WHY ??? !!! */
1827 /* cut leading whitespaces from input line */
1828 for (line_ptr = line; *line_ptr; line_ptr++)
1829 if (*line_ptr != ' ' && *line_ptr != '\t')
1833 /* append new line to existing line, if there is enough space */
1834 if (strlen(previous_line) + strlen(line_ptr) < MAX_LINE_LEN)
1835 strcat(previous_line, line_ptr);
1837 strcpy(line, previous_line); /* copy storage buffer to line */
1839 read_continued_line = FALSE;
1842 /* if the last character is '\', continue at next line */
1843 if (strlen(line) > 0 && line[strlen(line) - 1] == '\\')
1845 line[strlen(line) - 1] = '\0'; /* cut off trailing backslash */
1846 strcpy(previous_line, line); /* copy line to storage buffer */
1848 read_continued_line = TRUE;
1853 if (!getTokenValueFromSetupLineExt(line, &token, &value, filename,
1854 line_raw, line_nr, FALSE))
1859 if (strEqual(token, "include"))
1861 if (getHashEntry(include_filename_hash, value) == NULL)
1863 char *basepath = getBasePath(filename);
1864 char *basename = getBaseName(value);
1865 char *filename_include = getPath2(basepath, basename);
1868 Error(ERR_INFO, "[including file '%s']", filename_include);
1871 loadSetupFileData(setup_file_data, filename_include, FALSE, is_hash);
1875 free(filename_include);
1879 Error(ERR_WARN, "ignoring already processed file '%s'", value);
1886 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
1888 getHashEntry((SetupFileHash *)setup_file_data, token);
1890 if (old_value != NULL)
1892 if (!token_already_exists_warning)
1894 Error(ERR_INFO_LINE, "-");
1895 Error(ERR_WARN, "duplicate token(s) found in config file:");
1896 Error(ERR_INFO, "- config file: '%s'", filename);
1898 token_already_exists_warning = TRUE;
1901 Error(ERR_INFO, "- token: '%s' (in line %d)", token, line_nr);
1902 Error(ERR_INFO, " old value: '%s'", old_value);
1903 Error(ERR_INFO, " new value: '%s'", value);
1907 setHashEntry((SetupFileHash *)setup_file_data, token, value);
1911 insert_ptr = addListEntry((SetupFileList *)insert_ptr, token, value);
1921 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
1922 if (token_value_separator_warning)
1923 Error(ERR_INFO_LINE, "-");
1926 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
1927 if (token_already_exists_warning)
1928 Error(ERR_INFO_LINE, "-");
1931 if (token_count == 0)
1932 Error(ERR_WARN, "configuration file '%s' is empty", filename);
1934 if (top_recursion_level)
1935 freeSetupFileHash(include_filename_hash);
1942 static boolean loadSetupFileData(void *setup_file_data, char *filename,
1943 boolean top_recursion_level, boolean is_hash)
1945 static SetupFileHash *include_filename_hash = NULL;
1946 char line[MAX_LINE_LEN], line_raw[MAX_LINE_LEN], previous_line[MAX_LINE_LEN];
1947 char *token, *value, *line_ptr;
1948 void *insert_ptr = NULL;
1949 boolean read_continued_line = FALSE;
1952 int token_count = 0;
1954 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
1955 token_value_separator_warning = FALSE;
1958 if (!(file = fopen(filename, MODE_READ)))
1960 Error(ERR_WARN, "cannot open configuration file '%s'", filename);
1965 /* use "insert pointer" to store list end for constant insertion complexity */
1967 insert_ptr = setup_file_data;
1969 /* on top invocation, create hash to mark included files (to prevent loops) */
1970 if (top_recursion_level)
1971 include_filename_hash = newSetupFileHash();
1973 /* mark this file as already included (to prevent including it again) */
1974 setHashEntry(include_filename_hash, getBaseNamePtr(filename), "true");
1978 /* read next line of input file */
1979 if (!fgets(line, MAX_LINE_LEN, file))
1982 /* check if line was completely read and is terminated by line break */
1983 if (strlen(line) > 0 && line[strlen(line) - 1] == '\n')
1986 /* cut trailing line break (this can be newline and/or carriage return) */
1987 for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
1988 if ((*line_ptr == '\n' || *line_ptr == '\r') && *(line_ptr + 1) == '\0')
1991 /* copy raw input line for later use (mainly debugging output) */
1992 strcpy(line_raw, line);
1994 if (read_continued_line)
1996 /* cut leading whitespaces from input line */
1997 for (line_ptr = line; *line_ptr; line_ptr++)
1998 if (*line_ptr != ' ' && *line_ptr != '\t')
2001 /* append new line to existing line, if there is enough space */
2002 if (strlen(previous_line) + strlen(line_ptr) < MAX_LINE_LEN)
2003 strcat(previous_line, line_ptr);
2005 strcpy(line, previous_line); /* copy storage buffer to line */
2007 read_continued_line = FALSE;
2010 /* if the last character is '\', continue at next line */
2011 if (strlen(line) > 0 && line[strlen(line) - 1] == '\\')
2013 line[strlen(line) - 1] = '\0'; /* cut off trailing backslash */
2014 strcpy(previous_line, line); /* copy line to storage buffer */
2016 read_continued_line = TRUE;
2021 /* cut trailing comment from input line */
2022 for (line_ptr = line; *line_ptr; line_ptr++)
2024 if (*line_ptr == '#')
2031 /* cut trailing whitespaces from input line */
2032 for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
2033 if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
2036 /* ignore empty lines */
2040 /* cut leading whitespaces from token */
2041 for (token = line; *token; token++)
2042 if (*token != ' ' && *token != '\t')
2045 /* start with empty value as reliable default */
2048 token_value_separator_found = FALSE;
2050 /* find end of token to determine start of value */
2051 for (line_ptr = token; *line_ptr; line_ptr++)
2054 /* first look for an explicit token/value separator, like ':' or '=' */
2055 if (*line_ptr == ':' || *line_ptr == '=')
2057 if (*line_ptr == ' ' || *line_ptr == '\t' || *line_ptr == ':')
2060 *line_ptr = '\0'; /* terminate token string */
2061 value = line_ptr + 1; /* set beginning of value */
2063 token_value_separator_found = TRUE;
2069 #if ALLOW_TOKEN_VALUE_SEPARATOR_BEING_WHITESPACE
2070 /* fallback: if no token/value separator found, also allow whitespaces */
2071 if (!token_value_separator_found)
2073 for (line_ptr = token; *line_ptr; line_ptr++)
2075 if (*line_ptr == ' ' || *line_ptr == '\t')
2077 *line_ptr = '\0'; /* terminate token string */
2078 value = line_ptr + 1; /* set beginning of value */
2080 token_value_separator_found = TRUE;
2086 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2087 if (token_value_separator_found)
2089 if (!token_value_separator_warning)
2091 Error(ERR_INFO_LINE, "-");
2092 Error(ERR_WARN, "missing token/value separator(s) in config file:");
2093 Error(ERR_INFO, "- config file: '%s'", filename);
2095 token_value_separator_warning = TRUE;
2098 Error(ERR_INFO, "- line %d: '%s'", line_nr, line_raw);
2104 /* cut trailing whitespaces from token */
2105 for (line_ptr = &token[strlen(token)]; line_ptr >= token; line_ptr--)
2106 if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
2109 /* cut leading whitespaces from value */
2110 for (; *value; value++)
2111 if (*value != ' ' && *value != '\t')
2116 value = "true"; /* treat tokens without value as "true" */
2121 if (strEqual(token, "include"))
2123 if (getHashEntry(include_filename_hash, value) == NULL)
2125 char *basepath = getBasePath(filename);
2126 char *basename = getBaseName(value);
2127 char *filename_include = getPath2(basepath, basename);
2130 Error(ERR_INFO, "[including file '%s']", filename_include);
2133 loadSetupFileData(setup_file_data, filename_include, FALSE, is_hash);
2137 free(filename_include);
2141 Error(ERR_WARN, "ignoring already processed file '%s'", value);
2147 setHashEntry((SetupFileHash *)setup_file_data, token, value);
2149 insert_ptr = addListEntry((SetupFileList *)insert_ptr, token, value);
2158 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2159 if (token_value_separator_warning)
2160 Error(ERR_INFO_LINE, "-");
2163 if (token_count == 0)
2164 Error(ERR_WARN, "configuration file '%s' is empty", filename);
2166 if (top_recursion_level)
2167 freeSetupFileHash(include_filename_hash);
2173 void saveSetupFileHash(SetupFileHash *hash, char *filename)
2177 if (!(file = fopen(filename, MODE_WRITE)))
2179 Error(ERR_WARN, "cannot write configuration file '%s'", filename);
2184 BEGIN_HASH_ITERATION(hash, itr)
2186 fprintf(file, "%s\n", getFormattedSetupEntry(HASH_ITERATION_TOKEN(itr),
2187 HASH_ITERATION_VALUE(itr)));
2189 END_HASH_ITERATION(hash, itr)
2194 SetupFileList *loadSetupFileList(char *filename)
2196 SetupFileList *setup_file_list = newSetupFileList("", "");
2197 SetupFileList *first_valid_list_entry;
2199 if (!loadSetupFileData(setup_file_list, filename, TRUE, FALSE))
2201 freeSetupFileList(setup_file_list);
2206 first_valid_list_entry = setup_file_list->next;
2208 /* free empty list header */
2209 setup_file_list->next = NULL;
2210 freeSetupFileList(setup_file_list);
2212 return first_valid_list_entry;
2215 SetupFileHash *loadSetupFileHash(char *filename)
2217 SetupFileHash *setup_file_hash = newSetupFileHash();
2219 if (!loadSetupFileData(setup_file_hash, filename, TRUE, TRUE))
2221 freeSetupFileHash(setup_file_hash);
2226 return setup_file_hash;
2229 void checkSetupFileHashIdentifier(SetupFileHash *setup_file_hash,
2230 char *filename, char *identifier)
2232 char *value = getHashEntry(setup_file_hash, TOKEN_STR_FILE_IDENTIFIER);
2235 Error(ERR_WARN, "config file '%s' has no file identifier", filename);
2236 else if (!checkCookieString(value, identifier))
2237 Error(ERR_WARN, "config file '%s' has wrong file identifier", filename);
2241 /* ========================================================================= */
2242 /* setup file stuff */
2243 /* ========================================================================= */
2245 #define TOKEN_STR_LAST_LEVEL_SERIES "last_level_series"
2246 #define TOKEN_STR_LAST_PLAYED_LEVEL "last_played_level"
2247 #define TOKEN_STR_HANDICAP_LEVEL "handicap_level"
2249 /* level directory info */
2250 #define LEVELINFO_TOKEN_IDENTIFIER 0
2251 #define LEVELINFO_TOKEN_NAME 1
2252 #define LEVELINFO_TOKEN_NAME_SORTING 2
2253 #define LEVELINFO_TOKEN_AUTHOR 3
2254 #define LEVELINFO_TOKEN_YEAR 4
2255 #define LEVELINFO_TOKEN_IMPORTED_FROM 5
2256 #define LEVELINFO_TOKEN_IMPORTED_BY 6
2257 #define LEVELINFO_TOKEN_TESTED_BY 7
2258 #define LEVELINFO_TOKEN_LEVELS 8
2259 #define LEVELINFO_TOKEN_FIRST_LEVEL 9
2260 #define LEVELINFO_TOKEN_SORT_PRIORITY 10
2261 #define LEVELINFO_TOKEN_LATEST_ENGINE 11
2262 #define LEVELINFO_TOKEN_LEVEL_GROUP 12
2263 #define LEVELINFO_TOKEN_READONLY 13
2264 #define LEVELINFO_TOKEN_GRAPHICS_SET_ECS 14
2265 #define LEVELINFO_TOKEN_GRAPHICS_SET_AGA 15
2266 #define LEVELINFO_TOKEN_GRAPHICS_SET 16
2267 #define LEVELINFO_TOKEN_SOUNDS_SET 17
2268 #define LEVELINFO_TOKEN_MUSIC_SET 18
2269 #define LEVELINFO_TOKEN_FILENAME 19
2270 #define LEVELINFO_TOKEN_FILETYPE 20
2271 #define LEVELINFO_TOKEN_HANDICAP 21
2272 #define LEVELINFO_TOKEN_SKIP_LEVELS 22
2274 #define NUM_LEVELINFO_TOKENS 23
2276 static LevelDirTree ldi;
2278 static struct TokenInfo levelinfo_tokens[] =
2280 /* level directory info */
2281 { TYPE_STRING, &ldi.identifier, "identifier" },
2282 { TYPE_STRING, &ldi.name, "name" },
2283 { TYPE_STRING, &ldi.name_sorting, "name_sorting" },
2284 { TYPE_STRING, &ldi.author, "author" },
2285 { TYPE_STRING, &ldi.year, "year" },
2286 { TYPE_STRING, &ldi.imported_from, "imported_from" },
2287 { TYPE_STRING, &ldi.imported_by, "imported_by" },
2288 { TYPE_STRING, &ldi.tested_by, "tested_by" },
2289 { TYPE_INTEGER, &ldi.levels, "levels" },
2290 { TYPE_INTEGER, &ldi.first_level, "first_level" },
2291 { TYPE_INTEGER, &ldi.sort_priority, "sort_priority" },
2292 { TYPE_BOOLEAN, &ldi.latest_engine, "latest_engine" },
2293 { TYPE_BOOLEAN, &ldi.level_group, "level_group" },
2294 { TYPE_BOOLEAN, &ldi.readonly, "readonly" },
2295 { TYPE_STRING, &ldi.graphics_set_ecs, "graphics_set.ecs" },
2296 { TYPE_STRING, &ldi.graphics_set_aga, "graphics_set.aga" },
2297 { TYPE_STRING, &ldi.graphics_set, "graphics_set" },
2298 { TYPE_STRING, &ldi.sounds_set, "sounds_set" },
2299 { TYPE_STRING, &ldi.music_set, "music_set" },
2300 { TYPE_STRING, &ldi.level_filename, "filename" },
2301 { TYPE_STRING, &ldi.level_filetype, "filetype" },
2302 { TYPE_BOOLEAN, &ldi.handicap, "handicap" },
2303 { TYPE_BOOLEAN, &ldi.skip_levels, "skip_levels" }
2306 static struct TokenInfo artworkinfo_tokens[] =
2308 /* artwork directory info */
2309 { TYPE_STRING, &ldi.identifier, "identifier" },
2310 { TYPE_STRING, &ldi.subdir, "subdir" },
2311 { TYPE_STRING, &ldi.name, "name" },
2312 { TYPE_STRING, &ldi.name_sorting, "name_sorting" },
2313 { TYPE_STRING, &ldi.author, "author" },
2314 { TYPE_INTEGER, &ldi.sort_priority, "sort_priority" },
2315 { TYPE_STRING, &ldi.basepath, "basepath" },
2316 { TYPE_STRING, &ldi.fullpath, "fullpath" },
2317 { TYPE_BOOLEAN, &ldi.in_user_dir, "in_user_dir" },
2318 { TYPE_INTEGER, &ldi.color, "color" },
2319 { TYPE_STRING, &ldi.class_desc, "class_desc" },
2324 static void setTreeInfoToDefaults(TreeInfo *ti, int type)
2328 ti->node_top = (ti->type == TREE_TYPE_LEVEL_DIR ? &leveldir_first :
2329 ti->type == TREE_TYPE_GRAPHICS_DIR ? &artwork.gfx_first :
2330 ti->type == TREE_TYPE_SOUNDS_DIR ? &artwork.snd_first :
2331 ti->type == TREE_TYPE_MUSIC_DIR ? &artwork.mus_first :
2334 ti->node_parent = NULL;
2335 ti->node_group = NULL;
2342 ti->fullpath = NULL;
2343 ti->basepath = NULL;
2344 ti->identifier = NULL;
2345 ti->name = getStringCopy(ANONYMOUS_NAME);
2346 ti->name_sorting = NULL;
2347 ti->author = getStringCopy(ANONYMOUS_NAME);
2350 ti->sort_priority = LEVELCLASS_UNDEFINED; /* default: least priority */
2351 ti->latest_engine = FALSE; /* default: get from level */
2352 ti->parent_link = FALSE;
2353 ti->in_user_dir = FALSE;
2354 ti->user_defined = FALSE;
2356 ti->class_desc = NULL;
2358 ti->infotext = getStringCopy(TREE_INFOTEXT(ti->type));
2360 if (ti->type == TREE_TYPE_LEVEL_DIR)
2362 ti->imported_from = NULL;
2363 ti->imported_by = NULL;
2364 ti->tested_by = NULL;
2366 ti->graphics_set_ecs = NULL;
2367 ti->graphics_set_aga = NULL;
2368 ti->graphics_set = NULL;
2369 ti->sounds_set = NULL;
2370 ti->music_set = NULL;
2371 ti->graphics_path = getStringCopy(UNDEFINED_FILENAME);
2372 ti->sounds_path = getStringCopy(UNDEFINED_FILENAME);
2373 ti->music_path = getStringCopy(UNDEFINED_FILENAME);
2375 ti->level_filename = NULL;
2376 ti->level_filetype = NULL;
2379 ti->first_level = 0;
2381 ti->level_group = FALSE;
2382 ti->handicap_level = 0;
2383 ti->readonly = TRUE;
2384 ti->handicap = TRUE;
2385 ti->skip_levels = FALSE;
2389 static void setTreeInfoToDefaultsFromParent(TreeInfo *ti, TreeInfo *parent)
2393 Error(ERR_WARN, "setTreeInfoToDefaultsFromParent(): parent == NULL");
2395 setTreeInfoToDefaults(ti, TREE_TYPE_UNDEFINED);
2400 /* copy all values from the parent structure */
2402 ti->type = parent->type;
2404 ti->node_top = parent->node_top;
2405 ti->node_parent = parent;
2406 ti->node_group = NULL;
2413 ti->fullpath = NULL;
2414 ti->basepath = NULL;
2415 ti->identifier = NULL;
2416 ti->name = getStringCopy(ANONYMOUS_NAME);
2417 ti->name_sorting = NULL;
2418 ti->author = getStringCopy(parent->author);
2419 ti->year = getStringCopy(parent->year);
2421 ti->sort_priority = parent->sort_priority;
2422 ti->latest_engine = parent->latest_engine;
2423 ti->parent_link = FALSE;
2424 ti->in_user_dir = parent->in_user_dir;
2425 ti->user_defined = parent->user_defined;
2426 ti->color = parent->color;
2427 ti->class_desc = getStringCopy(parent->class_desc);
2429 ti->infotext = getStringCopy(parent->infotext);
2431 if (ti->type == TREE_TYPE_LEVEL_DIR)
2433 ti->imported_from = getStringCopy(parent->imported_from);
2434 ti->imported_by = getStringCopy(parent->imported_by);
2435 ti->tested_by = getStringCopy(parent->tested_by);
2437 ti->graphics_set_ecs = NULL;
2438 ti->graphics_set_aga = NULL;
2439 ti->graphics_set = NULL;
2440 ti->sounds_set = NULL;
2441 ti->music_set = NULL;
2442 ti->graphics_path = getStringCopy(UNDEFINED_FILENAME);
2443 ti->sounds_path = getStringCopy(UNDEFINED_FILENAME);
2444 ti->music_path = getStringCopy(UNDEFINED_FILENAME);
2446 ti->level_filename = NULL;
2447 ti->level_filetype = NULL;
2450 ti->first_level = 0;
2452 ti->level_group = FALSE;
2453 ti->handicap_level = 0;
2454 ti->readonly = TRUE;
2455 ti->handicap = TRUE;
2456 ti->skip_levels = FALSE;
2460 static TreeInfo *getTreeInfoCopy(TreeInfo *ti)
2462 TreeInfo *ti_copy = newTreeInfo();
2464 /* copy all values from the original structure */
2466 ti_copy->type = ti->type;
2468 ti_copy->node_top = ti->node_top;
2469 ti_copy->node_parent = ti->node_parent;
2470 ti_copy->node_group = ti->node_group;
2471 ti_copy->next = ti->next;
2473 ti_copy->cl_first = ti->cl_first;
2474 ti_copy->cl_cursor = ti->cl_cursor;
2476 ti_copy->subdir = getStringCopy(ti->subdir);
2477 ti_copy->fullpath = getStringCopy(ti->fullpath);
2478 ti_copy->basepath = getStringCopy(ti->basepath);
2479 ti_copy->identifier = getStringCopy(ti->identifier);
2480 ti_copy->name = getStringCopy(ti->name);
2481 ti_copy->name_sorting = getStringCopy(ti->name_sorting);
2482 ti_copy->author = getStringCopy(ti->author);
2483 ti_copy->year = getStringCopy(ti->year);
2484 ti_copy->imported_from = getStringCopy(ti->imported_from);
2485 ti_copy->imported_by = getStringCopy(ti->imported_by);
2486 ti_copy->tested_by = getStringCopy(ti->tested_by);
2488 ti_copy->graphics_set_ecs = getStringCopy(ti->graphics_set_ecs);
2489 ti_copy->graphics_set_aga = getStringCopy(ti->graphics_set_aga);
2490 ti_copy->graphics_set = getStringCopy(ti->graphics_set);
2491 ti_copy->sounds_set = getStringCopy(ti->sounds_set);
2492 ti_copy->music_set = getStringCopy(ti->music_set);
2493 ti_copy->graphics_path = getStringCopy(ti->graphics_path);
2494 ti_copy->sounds_path = getStringCopy(ti->sounds_path);
2495 ti_copy->music_path = getStringCopy(ti->music_path);
2497 ti_copy->level_filename = getStringCopy(ti->level_filename);
2498 ti_copy->level_filetype = getStringCopy(ti->level_filetype);
2500 ti_copy->levels = ti->levels;
2501 ti_copy->first_level = ti->first_level;
2502 ti_copy->last_level = ti->last_level;
2503 ti_copy->sort_priority = ti->sort_priority;
2505 ti_copy->latest_engine = ti->latest_engine;
2507 ti_copy->level_group = ti->level_group;
2508 ti_copy->parent_link = ti->parent_link;
2509 ti_copy->in_user_dir = ti->in_user_dir;
2510 ti_copy->user_defined = ti->user_defined;
2511 ti_copy->readonly = ti->readonly;
2512 ti_copy->handicap = ti->handicap;
2513 ti_copy->skip_levels = ti->skip_levels;
2515 ti_copy->color = ti->color;
2516 ti_copy->class_desc = getStringCopy(ti->class_desc);
2517 ti_copy->handicap_level = ti->handicap_level;
2519 ti_copy->infotext = getStringCopy(ti->infotext);
2524 static void freeTreeInfo(TreeInfo *ti)
2529 checked_free(ti->subdir);
2530 checked_free(ti->fullpath);
2531 checked_free(ti->basepath);
2532 checked_free(ti->identifier);
2534 checked_free(ti->name);
2535 checked_free(ti->name_sorting);
2536 checked_free(ti->author);
2537 checked_free(ti->year);
2539 checked_free(ti->class_desc);
2541 checked_free(ti->infotext);
2543 if (ti->type == TREE_TYPE_LEVEL_DIR)
2545 checked_free(ti->imported_from);
2546 checked_free(ti->imported_by);
2547 checked_free(ti->tested_by);
2549 checked_free(ti->graphics_set_ecs);
2550 checked_free(ti->graphics_set_aga);
2551 checked_free(ti->graphics_set);
2552 checked_free(ti->sounds_set);
2553 checked_free(ti->music_set);
2555 checked_free(ti->graphics_path);
2556 checked_free(ti->sounds_path);
2557 checked_free(ti->music_path);
2559 checked_free(ti->level_filename);
2560 checked_free(ti->level_filetype);
2566 void setSetupInfo(struct TokenInfo *token_info,
2567 int token_nr, char *token_value)
2569 int token_type = token_info[token_nr].type;
2570 void *setup_value = token_info[token_nr].value;
2572 if (token_value == NULL)
2575 /* set setup field to corresponding token value */
2580 *(boolean *)setup_value = get_boolean_from_string(token_value);
2584 *(Key *)setup_value = getKeyFromKeyName(token_value);
2588 *(Key *)setup_value = getKeyFromX11KeyName(token_value);
2592 *(int *)setup_value = get_integer_from_string(token_value);
2596 checked_free(*(char **)setup_value);
2597 *(char **)setup_value = getStringCopy(token_value);
2605 static int compareTreeInfoEntries(const void *object1, const void *object2)
2607 const TreeInfo *entry1 = *((TreeInfo **)object1);
2608 const TreeInfo *entry2 = *((TreeInfo **)object2);
2609 int class_sorting1, class_sorting2;
2612 if (entry1->type == TREE_TYPE_LEVEL_DIR)
2614 class_sorting1 = LEVELSORTING(entry1);
2615 class_sorting2 = LEVELSORTING(entry2);
2619 class_sorting1 = ARTWORKSORTING(entry1);
2620 class_sorting2 = ARTWORKSORTING(entry2);
2623 if (entry1->parent_link || entry2->parent_link)
2624 compare_result = (entry1->parent_link ? -1 : +1);
2625 else if (entry1->sort_priority == entry2->sort_priority)
2627 char *name1 = getStringToLower(entry1->name_sorting);
2628 char *name2 = getStringToLower(entry2->name_sorting);
2630 compare_result = strcmp(name1, name2);
2635 else if (class_sorting1 == class_sorting2)
2636 compare_result = entry1->sort_priority - entry2->sort_priority;
2638 compare_result = class_sorting1 - class_sorting2;
2640 return compare_result;
2643 static void createParentTreeInfoNode(TreeInfo *node_parent)
2647 if (node_parent == NULL)
2650 ti_new = newTreeInfo();
2651 setTreeInfoToDefaults(ti_new, node_parent->type);
2653 ti_new->node_parent = node_parent;
2654 ti_new->parent_link = TRUE;
2656 setString(&ti_new->identifier, node_parent->identifier);
2657 setString(&ti_new->name, ".. (parent directory)");
2658 setString(&ti_new->name_sorting, ti_new->name);
2660 setString(&ti_new->subdir, "..");
2661 setString(&ti_new->fullpath, node_parent->fullpath);
2663 ti_new->sort_priority = node_parent->sort_priority;
2664 ti_new->latest_engine = node_parent->latest_engine;
2666 setString(&ti_new->class_desc, getLevelClassDescription(ti_new));
2668 pushTreeInfo(&node_parent->node_group, ti_new);
2672 /* -------------------------------------------------------------------------- */
2673 /* functions for handling level and custom artwork info cache */
2674 /* -------------------------------------------------------------------------- */
2676 static void LoadArtworkInfoCache()
2678 InitCacheDirectory();
2680 if (artworkinfo_cache_old == NULL)
2682 char *filename = getPath2(getCacheDir(), ARTWORKINFO_CACHE_FILE);
2684 /* try to load artwork info hash from already existing cache file */
2685 artworkinfo_cache_old = loadSetupFileHash(filename);
2687 /* if no artwork info cache file was found, start with empty hash */
2688 if (artworkinfo_cache_old == NULL)
2689 artworkinfo_cache_old = newSetupFileHash();
2694 if (artworkinfo_cache_new == NULL)
2695 artworkinfo_cache_new = newSetupFileHash();
2698 static void SaveArtworkInfoCache()
2700 char *filename = getPath2(getCacheDir(), ARTWORKINFO_CACHE_FILE);
2702 InitCacheDirectory();
2704 saveSetupFileHash(artworkinfo_cache_new, filename);
2709 static char *getCacheTokenPrefix(char *prefix1, char *prefix2)
2711 static char *prefix = NULL;
2713 checked_free(prefix);
2715 prefix = getStringCat2WithSeparator(prefix1, prefix2, ".");
2720 /* (identical to above function, but separate string buffer needed -- nasty) */
2721 static char *getCacheToken(char *prefix, char *suffix)
2723 static char *token = NULL;
2725 checked_free(token);
2727 token = getStringCat2WithSeparator(prefix, suffix, ".");
2732 static char *getFileTimestamp(char *filename)
2734 struct stat file_status;
2736 if (stat(filename, &file_status) != 0) /* cannot stat file */
2737 return getStringCopy(i_to_a(0));
2739 return getStringCopy(i_to_a(file_status.st_mtime));
2742 static boolean modifiedFileTimestamp(char *filename, char *timestamp_string)
2744 struct stat file_status;
2746 if (timestamp_string == NULL)
2749 if (stat(filename, &file_status) != 0) /* cannot stat file */
2752 return (file_status.st_mtime != atoi(timestamp_string));
2755 static TreeInfo *getArtworkInfoCacheEntry(LevelDirTree *level_node, int type)
2757 char *identifier = level_node->subdir;
2758 char *type_string = ARTWORK_DIRECTORY(type);
2759 char *token_prefix = getCacheTokenPrefix(type_string, identifier);
2760 char *token_main = getCacheToken(token_prefix, "CACHED");
2761 char *cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
2762 boolean cached = (cache_entry != NULL && strEqual(cache_entry, "true"));
2763 TreeInfo *artwork_info = NULL;
2765 if (!use_artworkinfo_cache)
2772 artwork_info = newTreeInfo();
2773 setTreeInfoToDefaults(artwork_info, type);
2775 /* set all structure fields according to the token/value pairs */
2776 ldi = *artwork_info;
2777 for (i = 0; artworkinfo_tokens[i].type != -1; i++)
2779 char *token = getCacheToken(token_prefix, artworkinfo_tokens[i].text);
2780 char *value = getHashEntry(artworkinfo_cache_old, token);
2782 setSetupInfo(artworkinfo_tokens, i, value);
2784 /* check if cache entry for this item is invalid or incomplete */
2788 Error(ERR_WARN, "cache entry '%s' invalid", token);
2795 *artwork_info = ldi;
2800 char *filename_levelinfo = getPath2(getLevelDirFromTreeInfo(level_node),
2801 LEVELINFO_FILENAME);
2802 char *filename_artworkinfo = getPath2(getSetupArtworkDir(artwork_info),
2803 ARTWORKINFO_FILENAME(type));
2805 /* check if corresponding "levelinfo.conf" file has changed */
2806 token_main = getCacheToken(token_prefix, "TIMESTAMP_LEVELINFO");
2807 cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
2809 if (modifiedFileTimestamp(filename_levelinfo, cache_entry))
2812 /* check if corresponding "<artworkinfo>.conf" file has changed */
2813 token_main = getCacheToken(token_prefix, "TIMESTAMP_ARTWORKINFO");
2814 cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
2816 if (modifiedFileTimestamp(filename_artworkinfo, cache_entry))
2821 printf("::: '%s': INVALIDATED FROM CACHE BY TIMESTAMP\n", identifier);
2824 checked_free(filename_levelinfo);
2825 checked_free(filename_artworkinfo);
2828 if (!cached && artwork_info != NULL)
2830 freeTreeInfo(artwork_info);
2835 return artwork_info;
2838 static void setArtworkInfoCacheEntry(TreeInfo *artwork_info,
2839 LevelDirTree *level_node, int type)
2841 char *identifier = level_node->subdir;
2842 char *type_string = ARTWORK_DIRECTORY(type);
2843 char *token_prefix = getCacheTokenPrefix(type_string, identifier);
2844 char *token_main = getCacheToken(token_prefix, "CACHED");
2845 boolean set_cache_timestamps = TRUE;
2848 setHashEntry(artworkinfo_cache_new, token_main, "true");
2850 if (set_cache_timestamps)
2852 char *filename_levelinfo = getPath2(getLevelDirFromTreeInfo(level_node),
2853 LEVELINFO_FILENAME);
2854 char *filename_artworkinfo = getPath2(getSetupArtworkDir(artwork_info),
2855 ARTWORKINFO_FILENAME(type));
2856 char *timestamp_levelinfo = getFileTimestamp(filename_levelinfo);
2857 char *timestamp_artworkinfo = getFileTimestamp(filename_artworkinfo);
2859 token_main = getCacheToken(token_prefix, "TIMESTAMP_LEVELINFO");
2860 setHashEntry(artworkinfo_cache_new, token_main, timestamp_levelinfo);
2862 token_main = getCacheToken(token_prefix, "TIMESTAMP_ARTWORKINFO");
2863 setHashEntry(artworkinfo_cache_new, token_main, timestamp_artworkinfo);
2865 checked_free(filename_levelinfo);
2866 checked_free(filename_artworkinfo);
2867 checked_free(timestamp_levelinfo);
2868 checked_free(timestamp_artworkinfo);
2871 ldi = *artwork_info;
2872 for (i = 0; artworkinfo_tokens[i].type != -1; i++)
2874 char *token = getCacheToken(token_prefix, artworkinfo_tokens[i].text);
2875 char *value = getSetupValue(artworkinfo_tokens[i].type,
2876 artworkinfo_tokens[i].value);
2878 setHashEntry(artworkinfo_cache_new, token, value);
2883 /* -------------------------------------------------------------------------- */
2884 /* functions for loading level info and custom artwork info */
2885 /* -------------------------------------------------------------------------- */
2887 /* forward declaration for recursive call by "LoadLevelInfoFromLevelDir()" */
2888 static void LoadLevelInfoFromLevelDir(TreeInfo **, TreeInfo *, char *);
2890 static boolean LoadLevelInfoFromLevelConf(TreeInfo **node_first,
2891 TreeInfo *node_parent,
2892 char *level_directory,
2893 char *directory_name)
2895 static unsigned long progress_delay = 0;
2896 unsigned long progress_delay_value = 100; /* (in milliseconds) */
2897 char *directory_path = getPath2(level_directory, directory_name);
2898 char *filename = getPath2(directory_path, LEVELINFO_FILENAME);
2899 SetupFileHash *setup_file_hash;
2900 LevelDirTree *leveldir_new = NULL;
2903 /* unless debugging, silently ignore directories without "levelinfo.conf" */
2904 if (!options.debug && !fileExists(filename))
2906 free(directory_path);
2912 setup_file_hash = loadSetupFileHash(filename);
2914 if (setup_file_hash == NULL)
2916 Error(ERR_WARN, "ignoring level directory '%s'", directory_path);
2918 free(directory_path);
2924 leveldir_new = newTreeInfo();
2927 setTreeInfoToDefaultsFromParent(leveldir_new, node_parent);
2929 setTreeInfoToDefaults(leveldir_new, TREE_TYPE_LEVEL_DIR);
2931 leveldir_new->subdir = getStringCopy(directory_name);
2933 checkSetupFileHashIdentifier(setup_file_hash, filename,
2934 getCookie("LEVELINFO"));
2936 /* set all structure fields according to the token/value pairs */
2937 ldi = *leveldir_new;
2938 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
2939 setSetupInfo(levelinfo_tokens, i,
2940 getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
2941 *leveldir_new = ldi;
2943 if (strEqual(leveldir_new->name, ANONYMOUS_NAME))
2944 setString(&leveldir_new->name, leveldir_new->subdir);
2946 if (leveldir_new->identifier == NULL)
2947 leveldir_new->identifier = getStringCopy(leveldir_new->subdir);
2949 if (leveldir_new->name_sorting == NULL)
2950 leveldir_new->name_sorting = getStringCopy(leveldir_new->name);
2952 if (node_parent == NULL) /* top level group */
2954 leveldir_new->basepath = getStringCopy(level_directory);
2955 leveldir_new->fullpath = getStringCopy(leveldir_new->subdir);
2957 else /* sub level group */
2959 leveldir_new->basepath = getStringCopy(node_parent->basepath);
2960 leveldir_new->fullpath = getPath2(node_parent->fullpath, directory_name);
2964 if (leveldir_new->levels < 1)
2965 leveldir_new->levels = 1;
2968 leveldir_new->last_level =
2969 leveldir_new->first_level + leveldir_new->levels - 1;
2971 leveldir_new->in_user_dir =
2972 (!strEqual(leveldir_new->basepath, options.level_directory));
2974 /* adjust some settings if user's private level directory was detected */
2975 if (leveldir_new->sort_priority == LEVELCLASS_UNDEFINED &&
2976 leveldir_new->in_user_dir &&
2977 (strEqual(leveldir_new->subdir, getLoginName()) ||
2978 strEqual(leveldir_new->name, getLoginName()) ||
2979 strEqual(leveldir_new->author, getRealName())))
2981 leveldir_new->sort_priority = LEVELCLASS_PRIVATE_START;
2982 leveldir_new->readonly = FALSE;
2985 leveldir_new->user_defined =
2986 (leveldir_new->in_user_dir && IS_LEVELCLASS_PRIVATE(leveldir_new));
2988 leveldir_new->color = LEVELCOLOR(leveldir_new);
2990 setString(&leveldir_new->class_desc, getLevelClassDescription(leveldir_new));
2992 leveldir_new->handicap_level = /* set handicap to default value */
2993 (leveldir_new->user_defined || !leveldir_new->handicap ?
2994 leveldir_new->last_level : leveldir_new->first_level);
2997 if (leveldir_new->level_group ||
2998 DelayReached(&progress_delay, progress_delay_value))
2999 DrawInitText(leveldir_new->name, 150, FC_YELLOW);
3001 DrawInitText(leveldir_new->name, 150, FC_YELLOW);
3005 /* !!! don't skip sets without levels (else artwork base sets are missing) */
3007 if (leveldir_new->levels < 1 && !leveldir_new->level_group)
3009 /* skip level sets without levels (which are probably artwork base sets) */
3011 freeSetupFileHash(setup_file_hash);
3012 free(directory_path);
3020 pushTreeInfo(node_first, leveldir_new);
3022 freeSetupFileHash(setup_file_hash);
3024 if (leveldir_new->level_group)
3026 /* create node to link back to current level directory */
3027 createParentTreeInfoNode(leveldir_new);
3029 /* recursively step into sub-directory and look for more level series */
3030 LoadLevelInfoFromLevelDir(&leveldir_new->node_group,
3031 leveldir_new, directory_path);
3034 free(directory_path);
3040 static void LoadLevelInfoFromLevelDir(TreeInfo **node_first,
3041 TreeInfo *node_parent,
3042 char *level_directory)
3045 struct dirent *dir_entry;
3046 boolean valid_entry_found = FALSE;
3048 if ((dir = opendir(level_directory)) == NULL)
3050 Error(ERR_WARN, "cannot read level directory '%s'", level_directory);
3054 while ((dir_entry = readdir(dir)) != NULL) /* loop until last dir entry */
3056 struct stat file_status;
3057 char *directory_name = dir_entry->d_name;
3058 char *directory_path = getPath2(level_directory, directory_name);
3060 /* skip entries for current and parent directory */
3061 if (strEqual(directory_name, ".") ||
3062 strEqual(directory_name, ".."))
3064 free(directory_path);
3068 /* find out if directory entry is itself a directory */
3069 if (stat(directory_path, &file_status) != 0 || /* cannot stat file */
3070 (file_status.st_mode & S_IFMT) != S_IFDIR) /* not a directory */
3072 free(directory_path);
3076 free(directory_path);
3078 if (strEqual(directory_name, GRAPHICS_DIRECTORY) ||
3079 strEqual(directory_name, SOUNDS_DIRECTORY) ||
3080 strEqual(directory_name, MUSIC_DIRECTORY))
3083 valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
3090 /* special case: top level directory may directly contain "levelinfo.conf" */
3091 if (node_parent == NULL && !valid_entry_found)
3093 /* check if this directory directly contains a file "levelinfo.conf" */
3094 valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
3095 level_directory, ".");
3098 if (!valid_entry_found)
3099 Error(ERR_WARN, "cannot find any valid level series in directory '%s'",
3103 boolean AdjustGraphicsForEMC()
3105 boolean settings_changed = FALSE;
3107 settings_changed |= adjustTreeGraphicsForEMC(leveldir_first_all);
3108 settings_changed |= adjustTreeGraphicsForEMC(leveldir_first);
3110 return settings_changed;
3113 void LoadLevelInfo()
3115 InitUserLevelDirectory(getLoginName());
3117 DrawInitText("Loading level series", 120, FC_GREEN);
3119 LoadLevelInfoFromLevelDir(&leveldir_first, NULL, options.level_directory);
3120 LoadLevelInfoFromLevelDir(&leveldir_first, NULL, getUserLevelDir(NULL));
3122 /* after loading all level set information, clone the level directory tree
3123 and remove all level sets without levels (these may still contain artwork
3124 to be offered in the setup menu as "custom artwork", and are therefore
3125 checked for existing artwork in the function "LoadLevelArtworkInfo()") */
3126 leveldir_first_all = leveldir_first;
3127 cloneTree(&leveldir_first, leveldir_first_all, TRUE);
3129 AdjustGraphicsForEMC();
3131 /* before sorting, the first entries will be from the user directory */
3132 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
3134 if (leveldir_first == NULL)
3135 Error(ERR_EXIT, "cannot find any valid level series in any directory");
3137 sortTreeInfo(&leveldir_first);
3140 dumpTreeInfo(leveldir_first, 0);
3144 static boolean LoadArtworkInfoFromArtworkConf(TreeInfo **node_first,
3145 TreeInfo *node_parent,
3146 char *base_directory,
3147 char *directory_name, int type)
3149 char *directory_path = getPath2(base_directory, directory_name);
3150 char *filename = getPath2(directory_path, ARTWORKINFO_FILENAME(type));
3151 SetupFileHash *setup_file_hash = NULL;
3152 TreeInfo *artwork_new = NULL;
3155 if (fileExists(filename))
3156 setup_file_hash = loadSetupFileHash(filename);
3158 if (setup_file_hash == NULL) /* no config file -- look for artwork files */
3161 struct dirent *dir_entry;
3162 boolean valid_file_found = FALSE;
3164 if ((dir = opendir(directory_path)) != NULL)
3166 while ((dir_entry = readdir(dir)) != NULL)
3168 char *entry_name = dir_entry->d_name;
3170 if (FileIsArtworkType(entry_name, type))
3172 valid_file_found = TRUE;
3180 if (!valid_file_found)
3182 if (!strEqual(directory_name, "."))
3183 Error(ERR_WARN, "ignoring artwork directory '%s'", directory_path);
3185 free(directory_path);
3192 artwork_new = newTreeInfo();
3195 setTreeInfoToDefaultsFromParent(artwork_new, node_parent);
3197 setTreeInfoToDefaults(artwork_new, type);
3199 artwork_new->subdir = getStringCopy(directory_name);
3201 if (setup_file_hash) /* (before defining ".color" and ".class_desc") */
3204 checkSetupFileHashIdentifier(setup_file_hash, filename, getCookie("..."));
3207 /* set all structure fields according to the token/value pairs */
3209 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
3210 setSetupInfo(levelinfo_tokens, i,
3211 getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
3214 if (strEqual(artwork_new->name, ANONYMOUS_NAME))
3215 setString(&artwork_new->name, artwork_new->subdir);
3217 if (artwork_new->identifier == NULL)
3218 artwork_new->identifier = getStringCopy(artwork_new->subdir);
3220 if (artwork_new->name_sorting == NULL)
3221 artwork_new->name_sorting = getStringCopy(artwork_new->name);
3224 if (node_parent == NULL) /* top level group */
3226 artwork_new->basepath = getStringCopy(base_directory);
3227 artwork_new->fullpath = getStringCopy(artwork_new->subdir);
3229 else /* sub level group */
3231 artwork_new->basepath = getStringCopy(node_parent->basepath);
3232 artwork_new->fullpath = getPath2(node_parent->fullpath, directory_name);
3235 artwork_new->in_user_dir =
3236 (!strEqual(artwork_new->basepath, OPTIONS_ARTWORK_DIRECTORY(type)));
3238 /* (may use ".sort_priority" from "setup_file_hash" above) */
3239 artwork_new->color = ARTWORKCOLOR(artwork_new);
3241 setString(&artwork_new->class_desc, getLevelClassDescription(artwork_new));
3243 if (setup_file_hash == NULL) /* (after determining ".user_defined") */
3245 if (strEqual(artwork_new->subdir, "."))
3247 if (artwork_new->user_defined)
3249 setString(&artwork_new->identifier, "private");
3250 artwork_new->sort_priority = ARTWORKCLASS_PRIVATE;
3254 setString(&artwork_new->identifier, "classic");
3255 artwork_new->sort_priority = ARTWORKCLASS_CLASSICS;
3258 /* set to new values after changing ".sort_priority" */
3259 artwork_new->color = ARTWORKCOLOR(artwork_new);
3261 setString(&artwork_new->class_desc,
3262 getLevelClassDescription(artwork_new));
3266 setString(&artwork_new->identifier, artwork_new->subdir);
3269 setString(&artwork_new->name, artwork_new->identifier);
3270 setString(&artwork_new->name_sorting, artwork_new->name);
3274 DrawInitText(artwork_new->name, 150, FC_YELLOW);
3277 pushTreeInfo(node_first, artwork_new);
3279 freeSetupFileHash(setup_file_hash);
3281 free(directory_path);
3287 static void LoadArtworkInfoFromArtworkDir(TreeInfo **node_first,
3288 TreeInfo *node_parent,
3289 char *base_directory, int type)
3292 struct dirent *dir_entry;
3293 boolean valid_entry_found = FALSE;
3295 if ((dir = opendir(base_directory)) == NULL)
3297 /* display error if directory is main "options.graphics_directory" etc. */
3298 if (base_directory == OPTIONS_ARTWORK_DIRECTORY(type))
3299 Error(ERR_WARN, "cannot read directory '%s'", base_directory);
3304 while ((dir_entry = readdir(dir)) != NULL) /* loop until last dir entry */
3306 struct stat file_status;
3307 char *directory_name = dir_entry->d_name;
3308 char *directory_path = getPath2(base_directory, directory_name);
3310 /* skip directory entries for current and parent directory */
3311 if (strEqual(directory_name, ".") ||
3312 strEqual(directory_name, ".."))
3314 free(directory_path);
3318 /* skip directory entries which are not a directory or are not accessible */
3319 if (stat(directory_path, &file_status) != 0 || /* cannot stat file */
3320 (file_status.st_mode & S_IFMT) != S_IFDIR) /* not a directory */
3322 free(directory_path);
3326 free(directory_path);
3328 /* check if this directory contains artwork with or without config file */
3329 valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
3331 directory_name, type);
3336 /* check if this directory directly contains artwork itself */
3337 valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
3338 base_directory, ".",
3340 if (!valid_entry_found)
3341 Error(ERR_WARN, "cannot find any valid artwork in directory '%s'",
3345 static TreeInfo *getDummyArtworkInfo(int type)
3347 /* this is only needed when there is completely no artwork available */
3348 TreeInfo *artwork_new = newTreeInfo();
3350 setTreeInfoToDefaults(artwork_new, type);
3352 setString(&artwork_new->subdir, UNDEFINED_FILENAME);
3353 setString(&artwork_new->fullpath, UNDEFINED_FILENAME);
3354 setString(&artwork_new->basepath, UNDEFINED_FILENAME);
3356 setString(&artwork_new->identifier, UNDEFINED_FILENAME);
3357 setString(&artwork_new->name, UNDEFINED_FILENAME);
3358 setString(&artwork_new->name_sorting, UNDEFINED_FILENAME);
3363 void LoadArtworkInfo()
3365 LoadArtworkInfoCache();
3367 DrawInitText("Looking for custom artwork", 120, FC_GREEN);
3369 LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
3370 options.graphics_directory,
3371 TREE_TYPE_GRAPHICS_DIR);
3372 LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
3373 getUserGraphicsDir(),
3374 TREE_TYPE_GRAPHICS_DIR);
3376 LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
3377 options.sounds_directory,
3378 TREE_TYPE_SOUNDS_DIR);
3379 LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
3381 TREE_TYPE_SOUNDS_DIR);
3383 LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
3384 options.music_directory,
3385 TREE_TYPE_MUSIC_DIR);
3386 LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
3388 TREE_TYPE_MUSIC_DIR);
3390 if (artwork.gfx_first == NULL)
3391 artwork.gfx_first = getDummyArtworkInfo(TREE_TYPE_GRAPHICS_DIR);
3392 if (artwork.snd_first == NULL)
3393 artwork.snd_first = getDummyArtworkInfo(TREE_TYPE_SOUNDS_DIR);
3394 if (artwork.mus_first == NULL)
3395 artwork.mus_first = getDummyArtworkInfo(TREE_TYPE_MUSIC_DIR);
3397 /* before sorting, the first entries will be from the user directory */
3398 artwork.gfx_current =
3399 getTreeInfoFromIdentifier(artwork.gfx_first, setup.graphics_set);
3400 if (artwork.gfx_current == NULL)
3401 artwork.gfx_current =
3402 getTreeInfoFromIdentifier(artwork.gfx_first, GFX_CLASSIC_SUBDIR);
3403 if (artwork.gfx_current == NULL)
3404 artwork.gfx_current = getFirstValidTreeInfoEntry(artwork.gfx_first);
3406 artwork.snd_current =
3407 getTreeInfoFromIdentifier(artwork.snd_first, setup.sounds_set);
3408 if (artwork.snd_current == NULL)
3409 artwork.snd_current =
3410 getTreeInfoFromIdentifier(artwork.snd_first, SND_CLASSIC_SUBDIR);
3411 if (artwork.snd_current == NULL)
3412 artwork.snd_current = getFirstValidTreeInfoEntry(artwork.snd_first);
3414 artwork.mus_current =
3415 getTreeInfoFromIdentifier(artwork.mus_first, setup.music_set);
3416 if (artwork.mus_current == NULL)
3417 artwork.mus_current =
3418 getTreeInfoFromIdentifier(artwork.mus_first, MUS_CLASSIC_SUBDIR);
3419 if (artwork.mus_current == NULL)
3420 artwork.mus_current = getFirstValidTreeInfoEntry(artwork.mus_first);
3422 artwork.gfx_current_identifier = artwork.gfx_current->identifier;
3423 artwork.snd_current_identifier = artwork.snd_current->identifier;
3424 artwork.mus_current_identifier = artwork.mus_current->identifier;
3427 printf("graphics set == %s\n\n", artwork.gfx_current_identifier);
3428 printf("sounds set == %s\n\n", artwork.snd_current_identifier);
3429 printf("music set == %s\n\n", artwork.mus_current_identifier);
3432 sortTreeInfo(&artwork.gfx_first);
3433 sortTreeInfo(&artwork.snd_first);
3434 sortTreeInfo(&artwork.mus_first);
3437 dumpTreeInfo(artwork.gfx_first, 0);
3438 dumpTreeInfo(artwork.snd_first, 0);
3439 dumpTreeInfo(artwork.mus_first, 0);
3443 void LoadArtworkInfoFromLevelInfo(ArtworkDirTree **artwork_node,
3444 LevelDirTree *level_node)
3446 static unsigned long progress_delay = 0;
3447 unsigned long progress_delay_value = 100; /* (in milliseconds) */
3448 int type = (*artwork_node)->type;
3450 /* recursively check all level directories for artwork sub-directories */
3454 /* check all tree entries for artwork, but skip parent link entries */
3455 if (!level_node->parent_link)
3457 TreeInfo *artwork_new = getArtworkInfoCacheEntry(level_node, type);
3458 boolean cached = (artwork_new != NULL);
3462 pushTreeInfo(artwork_node, artwork_new);
3466 TreeInfo *topnode_last = *artwork_node;
3467 char *path = getPath2(getLevelDirFromTreeInfo(level_node),
3468 ARTWORK_DIRECTORY(type));
3470 LoadArtworkInfoFromArtworkDir(artwork_node, NULL, path, type);
3472 if (topnode_last != *artwork_node) /* check for newly added node */
3474 artwork_new = *artwork_node;
3476 setString(&artwork_new->identifier, level_node->subdir);
3477 setString(&artwork_new->name, level_node->name);
3478 setString(&artwork_new->name_sorting, level_node->name_sorting);
3480 artwork_new->sort_priority = level_node->sort_priority;
3481 artwork_new->color = LEVELCOLOR(artwork_new);
3487 /* insert artwork info (from old cache or filesystem) into new cache */
3488 if (artwork_new != NULL)
3489 setArtworkInfoCacheEntry(artwork_new, level_node, type);
3493 if (level_node->level_group ||
3494 DelayReached(&progress_delay, progress_delay_value))
3495 DrawInitText(level_node->name, 150, FC_YELLOW);
3498 if (level_node->node_group != NULL)
3499 LoadArtworkInfoFromLevelInfo(artwork_node, level_node->node_group);
3501 level_node = level_node->next;
3505 void LoadLevelArtworkInfo()
3507 DrawInitText("Looking for custom level artwork", 120, FC_GREEN);
3509 LoadArtworkInfoFromLevelInfo(&artwork.gfx_first, leveldir_first_all);
3510 LoadArtworkInfoFromLevelInfo(&artwork.snd_first, leveldir_first_all);
3511 LoadArtworkInfoFromLevelInfo(&artwork.mus_first, leveldir_first_all);
3513 SaveArtworkInfoCache();
3515 /* needed for reloading level artwork not known at ealier stage */
3517 if (!strEqual(artwork.gfx_current_identifier, setup.graphics_set))
3519 artwork.gfx_current =
3520 getTreeInfoFromIdentifier(artwork.gfx_first, setup.graphics_set);
3521 if (artwork.gfx_current == NULL)
3522 artwork.gfx_current =
3523 getTreeInfoFromIdentifier(artwork.gfx_first, GFX_CLASSIC_SUBDIR);
3524 if (artwork.gfx_current == NULL)
3525 artwork.gfx_current = getFirstValidTreeInfoEntry(artwork.gfx_first);
3528 if (!strEqual(artwork.snd_current_identifier, setup.sounds_set))
3530 artwork.snd_current =
3531 getTreeInfoFromIdentifier(artwork.snd_first, setup.sounds_set);
3532 if (artwork.snd_current == NULL)
3533 artwork.snd_current =
3534 getTreeInfoFromIdentifier(artwork.snd_first, SND_CLASSIC_SUBDIR);
3535 if (artwork.snd_current == NULL)
3536 artwork.snd_current = getFirstValidTreeInfoEntry(artwork.snd_first);
3539 if (!strEqual(artwork.mus_current_identifier, setup.music_set))
3541 artwork.mus_current =
3542 getTreeInfoFromIdentifier(artwork.mus_first, setup.music_set);
3543 if (artwork.mus_current == NULL)
3544 artwork.mus_current =
3545 getTreeInfoFromIdentifier(artwork.mus_first, MUS_CLASSIC_SUBDIR);
3546 if (artwork.mus_current == NULL)
3547 artwork.mus_current = getFirstValidTreeInfoEntry(artwork.mus_first);
3550 sortTreeInfo(&artwork.gfx_first);
3551 sortTreeInfo(&artwork.snd_first);
3552 sortTreeInfo(&artwork.mus_first);
3555 dumpTreeInfo(artwork.gfx_first, 0);
3556 dumpTreeInfo(artwork.snd_first, 0);
3557 dumpTreeInfo(artwork.mus_first, 0);
3561 static void SaveUserLevelInfo()
3563 LevelDirTree *level_info;
3568 filename = getPath2(getUserLevelDir(getLoginName()), LEVELINFO_FILENAME);
3570 if (!(file = fopen(filename, MODE_WRITE)))
3572 Error(ERR_WARN, "cannot write level info file '%s'", filename);
3577 level_info = newTreeInfo();
3579 /* always start with reliable default values */
3580 setTreeInfoToDefaults(level_info, TREE_TYPE_LEVEL_DIR);
3582 setString(&level_info->name, getLoginName());
3583 setString(&level_info->author, getRealName());
3584 level_info->levels = 100;
3585 level_info->first_level = 1;
3587 token_value_position = TOKEN_VALUE_POSITION_SHORT;
3589 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER,
3590 getCookie("LEVELINFO")));
3593 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
3595 if (i == LEVELINFO_TOKEN_NAME ||
3596 i == LEVELINFO_TOKEN_AUTHOR ||
3597 i == LEVELINFO_TOKEN_LEVELS ||
3598 i == LEVELINFO_TOKEN_FIRST_LEVEL)
3599 fprintf(file, "%s\n", getSetupLine(levelinfo_tokens, "", i));
3601 /* just to make things nicer :) */
3602 if (i == LEVELINFO_TOKEN_AUTHOR)
3603 fprintf(file, "\n");
3606 token_value_position = TOKEN_VALUE_POSITION_DEFAULT;
3610 SetFilePermissions(filename, PERMS_PRIVATE);
3612 freeTreeInfo(level_info);
3616 char *getSetupValue(int type, void *value)
3618 static char value_string[MAX_LINE_LEN];
3626 strcpy(value_string, (*(boolean *)value ? "true" : "false"));
3630 strcpy(value_string, (*(boolean *)value ? "on" : "off"));
3634 strcpy(value_string, (*(boolean *)value ? "yes" : "no"));
3638 strcpy(value_string, (*(boolean *)value ? "AGA" : "ECS"));
3642 strcpy(value_string, getKeyNameFromKey(*(Key *)value));
3646 strcpy(value_string, getX11KeyNameFromKey(*(Key *)value));
3650 sprintf(value_string, "%d", *(int *)value);
3654 if (*(char **)value == NULL)
3657 strcpy(value_string, *(char **)value);
3661 value_string[0] = '\0';
3665 if (type & TYPE_GHOSTED)
3666 strcpy(value_string, "n/a");
3668 return value_string;
3671 char *getSetupLine(struct TokenInfo *token_info, char *prefix, int token_nr)
3675 static char token_string[MAX_LINE_LEN];
3676 int token_type = token_info[token_nr].type;
3677 void *setup_value = token_info[token_nr].value;
3678 char *token_text = token_info[token_nr].text;
3679 char *value_string = getSetupValue(token_type, setup_value);
3681 /* build complete token string */
3682 sprintf(token_string, "%s%s", prefix, token_text);
3684 /* build setup entry line */
3685 line = getFormattedSetupEntry(token_string, value_string);
3687 if (token_type == TYPE_KEY_X11)
3689 Key key = *(Key *)setup_value;
3690 char *keyname = getKeyNameFromKey(key);
3692 /* add comment, if useful */
3693 if (!strEqual(keyname, "(undefined)") &&
3694 !strEqual(keyname, "(unknown)"))
3696 /* add at least one whitespace */
3698 for (i = strlen(line); i < token_comment_position; i++)
3702 strcat(line, keyname);
3709 void LoadLevelSetup_LastSeries()
3711 /* ----------------------------------------------------------------------- */
3712 /* ~/.<program>/levelsetup.conf */
3713 /* ----------------------------------------------------------------------- */
3715 char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
3716 SetupFileHash *level_setup_hash = NULL;
3718 /* always start with reliable default values */
3719 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
3721 if ((level_setup_hash = loadSetupFileHash(filename)))
3723 char *last_level_series =
3724 getHashEntry(level_setup_hash, TOKEN_STR_LAST_LEVEL_SERIES);
3726 leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
3728 if (leveldir_current == NULL)
3729 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
3731 checkSetupFileHashIdentifier(level_setup_hash, filename,
3732 getCookie("LEVELSETUP"));
3734 freeSetupFileHash(level_setup_hash);
3737 Error(ERR_WARN, "using default setup values");
3742 void SaveLevelSetup_LastSeries()
3744 /* ----------------------------------------------------------------------- */
3745 /* ~/.<program>/levelsetup.conf */
3746 /* ----------------------------------------------------------------------- */
3748 char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
3749 char *level_subdir = leveldir_current->subdir;
3752 InitUserDataDirectory();
3754 if (!(file = fopen(filename, MODE_WRITE)))
3756 Error(ERR_WARN, "cannot write setup file '%s'", filename);
3761 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER,
3762 getCookie("LEVELSETUP")));
3763 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_LEVEL_SERIES,
3768 SetFilePermissions(filename, PERMS_PRIVATE);
3773 static void checkSeriesInfo()
3775 static char *level_directory = NULL;
3777 struct dirent *dir_entry;
3779 /* check for more levels besides the 'levels' field of 'levelinfo.conf' */
3781 level_directory = getPath2((leveldir_current->in_user_dir ?
3782 getUserLevelDir(NULL) :
3783 options.level_directory),
3784 leveldir_current->fullpath);
3786 if ((dir = opendir(level_directory)) == NULL)
3788 Error(ERR_WARN, "cannot read level directory '%s'", level_directory);
3792 while ((dir_entry = readdir(dir)) != NULL) /* last directory entry */
3794 if (strlen(dir_entry->d_name) > 4 &&
3795 dir_entry->d_name[3] == '.' &&
3796 strEqual(&dir_entry->d_name[4], LEVELFILE_EXTENSION))
3798 char levelnum_str[4];
3801 strncpy(levelnum_str, dir_entry->d_name, 3);
3802 levelnum_str[3] = '\0';
3804 levelnum_value = atoi(levelnum_str);
3807 if (levelnum_value < leveldir_current->first_level)
3809 Error(ERR_WARN, "additional level %d found", levelnum_value);
3810 leveldir_current->first_level = levelnum_value;
3812 else if (levelnum_value > leveldir_current->last_level)
3814 Error(ERR_WARN, "additional level %d found", levelnum_value);
3815 leveldir_current->last_level = levelnum_value;
3824 void LoadLevelSetup_SeriesInfo()
3827 SetupFileHash *level_setup_hash = NULL;
3828 char *level_subdir = leveldir_current->subdir;
3830 /* always start with reliable default values */
3831 level_nr = leveldir_current->first_level;
3833 checkSeriesInfo(leveldir_current);
3835 /* ----------------------------------------------------------------------- */
3836 /* ~/.<program>/levelsetup/<level series>/levelsetup.conf */
3837 /* ----------------------------------------------------------------------- */
3839 level_subdir = leveldir_current->subdir;
3841 filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
3843 if ((level_setup_hash = loadSetupFileHash(filename)))
3847 token_value = getHashEntry(level_setup_hash, TOKEN_STR_LAST_PLAYED_LEVEL);
3851 level_nr = atoi(token_value);
3853 if (level_nr < leveldir_current->first_level)
3854 level_nr = leveldir_current->first_level;
3855 if (level_nr > leveldir_current->last_level)
3856 level_nr = leveldir_current->last_level;
3859 token_value = getHashEntry(level_setup_hash, TOKEN_STR_HANDICAP_LEVEL);
3863 int level_nr = atoi(token_value);
3865 if (level_nr < leveldir_current->first_level)
3866 level_nr = leveldir_current->first_level;
3867 if (level_nr > leveldir_current->last_level + 1)
3868 level_nr = leveldir_current->last_level;
3870 if (leveldir_current->user_defined || !leveldir_current->handicap)
3871 level_nr = leveldir_current->last_level;
3873 leveldir_current->handicap_level = level_nr;
3876 checkSetupFileHashIdentifier(level_setup_hash, filename,
3877 getCookie("LEVELSETUP"));
3879 freeSetupFileHash(level_setup_hash);
3882 Error(ERR_WARN, "using default setup values");
3887 void SaveLevelSetup_SeriesInfo()
3890 char *level_subdir = leveldir_current->subdir;
3891 char *level_nr_str = int2str(level_nr, 0);
3892 char *handicap_level_str = int2str(leveldir_current->handicap_level, 0);
3895 /* ----------------------------------------------------------------------- */
3896 /* ~/.<program>/levelsetup/<level series>/levelsetup.conf */
3897 /* ----------------------------------------------------------------------- */
3899 InitLevelSetupDirectory(level_subdir);
3901 filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
3903 if (!(file = fopen(filename, MODE_WRITE)))
3905 Error(ERR_WARN, "cannot write setup file '%s'", filename);
3910 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER,
3911 getCookie("LEVELSETUP")));
3912 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_PLAYED_LEVEL,
3914 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_HANDICAP_LEVEL,
3915 handicap_level_str));
3919 SetFilePermissions(filename, PERMS_PRIVATE);