1 /***********************************************************
2 * Artsoft Retro-Game Library *
3 *----------------------------------------------------------*
4 * (c) 1994-2006 Artsoft Entertainment *
6 * Detmolder Strasse 189 *
9 * e-mail: info@artsoft.org *
10 *----------------------------------------------------------*
12 ***********************************************************/
14 #include <sys/types.h>
22 #if !defined(PLATFORM_WIN32)
24 #include <sys/param.h>
34 #define NUM_LEVELCLASS_DESC 8
36 static char *levelclass_desc[NUM_LEVELCLASS_DESC] =
49 #define LEVELCOLOR(n) (IS_LEVELCLASS_TUTORIAL(n) ? FC_BLUE : \
50 IS_LEVELCLASS_CLASSICS(n) ? FC_RED : \
51 IS_LEVELCLASS_BD(n) ? FC_YELLOW : \
52 IS_LEVELCLASS_EM(n) ? FC_YELLOW : \
53 IS_LEVELCLASS_SP(n) ? FC_YELLOW : \
54 IS_LEVELCLASS_DX(n) ? FC_YELLOW : \
55 IS_LEVELCLASS_SB(n) ? FC_YELLOW : \
56 IS_LEVELCLASS_CONTRIB(n) ? FC_GREEN : \
57 IS_LEVELCLASS_PRIVATE(n) ? FC_RED : \
60 #define LEVELSORTING(n) (IS_LEVELCLASS_TUTORIAL(n) ? 0 : \
61 IS_LEVELCLASS_CLASSICS(n) ? 1 : \
62 IS_LEVELCLASS_BD(n) ? 2 : \
63 IS_LEVELCLASS_EM(n) ? 3 : \
64 IS_LEVELCLASS_SP(n) ? 4 : \
65 IS_LEVELCLASS_DX(n) ? 5 : \
66 IS_LEVELCLASS_SB(n) ? 6 : \
67 IS_LEVELCLASS_CONTRIB(n) ? 7 : \
68 IS_LEVELCLASS_PRIVATE(n) ? 8 : \
71 #define ARTWORKCOLOR(n) (IS_ARTWORKCLASS_CLASSICS(n) ? FC_RED : \
72 IS_ARTWORKCLASS_CONTRIB(n) ? FC_GREEN : \
73 IS_ARTWORKCLASS_PRIVATE(n) ? FC_RED : \
74 IS_ARTWORKCLASS_LEVEL(n) ? FC_YELLOW : \
77 #define ARTWORKSORTING(n) (IS_ARTWORKCLASS_CLASSICS(n) ? 0 : \
78 IS_ARTWORKCLASS_LEVEL(n) ? 1 : \
79 IS_ARTWORKCLASS_CONTRIB(n) ? 2 : \
80 IS_ARTWORKCLASS_PRIVATE(n) ? 3 : \
83 #define TOKEN_VALUE_POSITION_SHORT 32
84 #define TOKEN_VALUE_POSITION_DEFAULT 40
85 #define TOKEN_COMMENT_POSITION_DEFAULT 60
87 #define MAX_COOKIE_LEN 256
90 static void setTreeInfoToDefaults(TreeInfo *, int);
91 static TreeInfo *getTreeInfoCopy(TreeInfo *ti);
92 static int compareTreeInfoEntries(const void *, const void *);
94 static int token_value_position = TOKEN_VALUE_POSITION_DEFAULT;
95 static int token_comment_position = TOKEN_COMMENT_POSITION_DEFAULT;
97 static SetupFileHash *artworkinfo_cache_old = NULL;
98 static SetupFileHash *artworkinfo_cache_new = NULL;
99 static boolean use_artworkinfo_cache = TRUE;
102 /* ------------------------------------------------------------------------- */
104 /* ------------------------------------------------------------------------- */
106 static char *getLevelClassDescription(TreeInfo *ti)
108 int position = ti->sort_priority / 100;
110 if (position >= 0 && position < NUM_LEVELCLASS_DESC)
111 return levelclass_desc[position];
113 return "Unknown Level Class";
116 static char *getUserLevelDir(char *level_subdir)
118 static char *userlevel_dir = NULL;
119 char *data_dir = getUserGameDataDir();
120 char *userlevel_subdir = LEVELS_DIRECTORY;
122 checked_free(userlevel_dir);
124 if (level_subdir != NULL)
125 userlevel_dir = getPath3(data_dir, userlevel_subdir, level_subdir);
127 userlevel_dir = getPath2(data_dir, userlevel_subdir);
129 return userlevel_dir;
132 static char *getScoreDir(char *level_subdir)
134 static char *score_dir = NULL;
135 char *data_dir = getCommonDataDir();
136 char *score_subdir = SCORES_DIRECTORY;
138 checked_free(score_dir);
140 if (level_subdir != NULL)
141 score_dir = getPath3(data_dir, score_subdir, level_subdir);
143 score_dir = getPath2(data_dir, score_subdir);
148 static char *getLevelSetupDir(char *level_subdir)
150 static char *levelsetup_dir = NULL;
151 char *data_dir = getUserGameDataDir();
152 char *levelsetup_subdir = LEVELSETUP_DIRECTORY;
154 checked_free(levelsetup_dir);
156 if (level_subdir != NULL)
157 levelsetup_dir = getPath3(data_dir, levelsetup_subdir, level_subdir);
159 levelsetup_dir = getPath2(data_dir, levelsetup_subdir);
161 return levelsetup_dir;
164 static char *getCacheDir()
166 static char *cache_dir = NULL;
168 if (cache_dir == NULL)
169 cache_dir = getPath2(getUserGameDataDir(), CACHE_DIRECTORY);
174 static char *getLevelDirFromTreeInfo(TreeInfo *node)
176 static char *level_dir = NULL;
179 return options.level_directory;
181 checked_free(level_dir);
183 level_dir = getPath2((node->in_user_dir ? getUserLevelDir(NULL) :
184 options.level_directory), node->fullpath);
189 char *getCurrentLevelDir()
191 return getLevelDirFromTreeInfo(leveldir_current);
194 static char *getTapeDir(char *level_subdir)
196 static char *tape_dir = NULL;
197 char *data_dir = getUserGameDataDir();
198 char *tape_subdir = TAPES_DIRECTORY;
200 checked_free(tape_dir);
202 if (level_subdir != NULL)
203 tape_dir = getPath3(data_dir, tape_subdir, level_subdir);
205 tape_dir = getPath2(data_dir, tape_subdir);
210 static char *getSolutionTapeDir()
212 static char *tape_dir = NULL;
213 char *data_dir = getCurrentLevelDir();
214 char *tape_subdir = TAPES_DIRECTORY;
216 checked_free(tape_dir);
218 tape_dir = getPath2(data_dir, tape_subdir);
223 static char *getDefaultGraphicsDir(char *graphics_subdir)
225 static char *graphics_dir = NULL;
227 if (graphics_subdir == NULL)
228 return options.graphics_directory;
230 checked_free(graphics_dir);
232 graphics_dir = getPath2(options.graphics_directory, graphics_subdir);
237 static char *getDefaultSoundsDir(char *sounds_subdir)
239 static char *sounds_dir = NULL;
241 if (sounds_subdir == NULL)
242 return options.sounds_directory;
244 checked_free(sounds_dir);
246 sounds_dir = getPath2(options.sounds_directory, sounds_subdir);
251 static char *getDefaultMusicDir(char *music_subdir)
253 static char *music_dir = NULL;
255 if (music_subdir == NULL)
256 return options.music_directory;
258 checked_free(music_dir);
260 music_dir = getPath2(options.music_directory, music_subdir);
265 static char *getDefaultArtworkSet(int type)
267 return (type == TREE_TYPE_GRAPHICS_DIR ? GFX_CLASSIC_SUBDIR :
268 type == TREE_TYPE_SOUNDS_DIR ? SND_CLASSIC_SUBDIR :
269 type == TREE_TYPE_MUSIC_DIR ? MUS_CLASSIC_SUBDIR : "");
272 static char *getDefaultArtworkDir(int type)
274 return (type == TREE_TYPE_GRAPHICS_DIR ?
275 getDefaultGraphicsDir(GFX_CLASSIC_SUBDIR) :
276 type == TREE_TYPE_SOUNDS_DIR ?
277 getDefaultSoundsDir(SND_CLASSIC_SUBDIR) :
278 type == TREE_TYPE_MUSIC_DIR ?
279 getDefaultMusicDir(MUS_CLASSIC_SUBDIR) : "");
282 static char *getUserGraphicsDir()
284 static char *usergraphics_dir = NULL;
286 if (usergraphics_dir == NULL)
287 usergraphics_dir = getPath2(getUserGameDataDir(), GRAPHICS_DIRECTORY);
289 return usergraphics_dir;
292 static char *getUserSoundsDir()
294 static char *usersounds_dir = NULL;
296 if (usersounds_dir == NULL)
297 usersounds_dir = getPath2(getUserGameDataDir(), SOUNDS_DIRECTORY);
299 return usersounds_dir;
302 static char *getUserMusicDir()
304 static char *usermusic_dir = NULL;
306 if (usermusic_dir == NULL)
307 usermusic_dir = getPath2(getUserGameDataDir(), MUSIC_DIRECTORY);
309 return usermusic_dir;
312 static char *getSetupArtworkDir(TreeInfo *ti)
314 static char *artwork_dir = NULL;
316 checked_free(artwork_dir);
318 artwork_dir = getPath2(ti->basepath, ti->fullpath);
323 char *setLevelArtworkDir(TreeInfo *ti)
325 char **artwork_path_ptr, **artwork_set_ptr;
326 TreeInfo *level_artwork;
328 if (ti == NULL || leveldir_current == NULL)
331 artwork_path_ptr = LEVELDIR_ARTWORK_PATH_PTR(leveldir_current, ti->type);
332 artwork_set_ptr = LEVELDIR_ARTWORK_SET_PTR( leveldir_current, ti->type);
334 checked_free(*artwork_path_ptr);
336 if ((level_artwork = getTreeInfoFromIdentifier(ti, *artwork_set_ptr)))
337 *artwork_path_ptr = getStringCopy(getSetupArtworkDir(level_artwork));
340 /* No (or non-existing) artwork configured in "levelinfo.conf". This would
341 normally result in using the artwork configured in the setup menu. But
342 if an artwork subdirectory exists (which might contain custom artwork
343 or an artwork configuration file), this level artwork must be treated
344 as relative to the default "classic" artwork, not to the artwork that
345 is currently configured in the setup menu. */
347 char *dir = getPath2(getCurrentLevelDir(), ARTWORK_DIRECTORY(ti->type));
349 checked_free(*artwork_set_ptr);
353 *artwork_path_ptr = getStringCopy(getDefaultArtworkDir(ti->type));
354 *artwork_set_ptr = getStringCopy(getDefaultArtworkSet(ti->type));
358 *artwork_path_ptr = getStringCopy(UNDEFINED_FILENAME);
359 *artwork_set_ptr = NULL;
365 return *artwork_set_ptr;
368 inline static char *getLevelArtworkSet(int type)
370 if (leveldir_current == NULL)
373 return LEVELDIR_ARTWORK_SET(leveldir_current, type);
376 inline static char *getLevelArtworkDir(int type)
378 if (leveldir_current == NULL)
379 return UNDEFINED_FILENAME;
381 return LEVELDIR_ARTWORK_PATH(leveldir_current, type);
384 char *getTapeFilename(int nr)
386 static char *filename = NULL;
387 char basename[MAX_FILENAME_LEN];
389 checked_free(filename);
391 sprintf(basename, "%03d.%s", nr, TAPEFILE_EXTENSION);
392 filename = getPath2(getTapeDir(leveldir_current->subdir), basename);
397 char *getSolutionTapeFilename(int nr)
399 static char *filename = NULL;
400 char basename[MAX_FILENAME_LEN];
402 checked_free(filename);
404 sprintf(basename, "%03d.%s", nr, TAPEFILE_EXTENSION);
405 filename = getPath2(getSolutionTapeDir(), basename);
410 char *getScoreFilename(int nr)
412 static char *filename = NULL;
413 char basename[MAX_FILENAME_LEN];
415 checked_free(filename);
417 sprintf(basename, "%03d.%s", nr, SCOREFILE_EXTENSION);
418 filename = getPath2(getScoreDir(leveldir_current->subdir), basename);
423 char *getSetupFilename()
425 static char *filename = NULL;
427 checked_free(filename);
429 filename = getPath2(getSetupDir(), SETUP_FILENAME);
434 char *getEditorSetupFilename()
436 static char *filename = NULL;
438 checked_free(filename);
439 filename = getPath2(getCurrentLevelDir(), EDITORSETUP_FILENAME);
441 if (fileExists(filename))
444 checked_free(filename);
445 filename = getPath2(getSetupDir(), EDITORSETUP_FILENAME);
450 char *getHelpAnimFilename()
452 static char *filename = NULL;
454 checked_free(filename);
456 filename = getPath2(getCurrentLevelDir(), HELPANIM_FILENAME);
461 char *getHelpTextFilename()
463 static char *filename = NULL;
465 checked_free(filename);
467 filename = getPath2(getCurrentLevelDir(), HELPTEXT_FILENAME);
472 char *getLevelSetInfoFilename()
474 static char *filename = NULL;
489 for (i = 0; basenames[i] != NULL; i++)
491 checked_free(filename);
492 filename = getPath2(getCurrentLevelDir(), basenames[i]);
494 if (fileExists(filename))
501 char *getLevelSetTitleMessageFilename(int nr, boolean initial)
503 static char *filename = NULL;
506 sprintf(basename, "%s_%d.txt",
507 (initial ? "titlemessage_initial" : "titlemessage"), nr + 1);
509 checked_free(filename);
510 filename = getPath2(getCurrentLevelDir(), basename);
512 if (fileExists(filename))
518 static char *getCorrectedArtworkBasename(char *basename)
520 char *basename_corrected = basename;
522 #if defined(PLATFORM_MSDOS)
523 if (program.filename_prefix != NULL)
525 int prefix_len = strlen(program.filename_prefix);
527 if (strncmp(basename, program.filename_prefix, prefix_len) == 0)
528 basename_corrected = &basename[prefix_len];
530 /* if corrected filename is still longer than standard MS-DOS filename
531 size (8 characters + 1 dot + 3 characters file extension), shorten
532 filename by writing file extension after 8th basename character */
533 if (strlen(basename_corrected) > 8 + 1 + 3)
535 static char *msdos_filename = NULL;
537 checked_free(msdos_filename);
539 msdos_filename = getStringCopy(basename_corrected);
540 strncpy(&msdos_filename[8], &basename[strlen(basename) - (1+3)], 1+3 +1);
542 basename_corrected = msdos_filename;
547 return basename_corrected;
550 char *getCustomImageFilename(char *basename)
552 static char *filename = NULL;
553 boolean skip_setup_artwork = FALSE;
555 checked_free(filename);
557 basename = getCorrectedArtworkBasename(basename);
559 if (!setup.override_level_graphics)
561 /* 1st try: look for special artwork in current level series directory */
562 filename = getPath3(getCurrentLevelDir(), GRAPHICS_DIRECTORY, basename);
563 if (fileExists(filename))
568 /* check if there is special artwork configured in level series config */
569 if (getLevelArtworkSet(ARTWORK_TYPE_GRAPHICS) != NULL)
571 /* 2nd try: look for special artwork configured in level series config */
572 filename = getPath2(getLevelArtworkDir(ARTWORK_TYPE_GRAPHICS), basename);
573 if (fileExists(filename))
578 /* take missing artwork configured in level set config from default */
579 skip_setup_artwork = TRUE;
583 if (!skip_setup_artwork)
585 /* 3rd try: look for special artwork in configured artwork directory */
586 filename = getPath2(getSetupArtworkDir(artwork.gfx_current), basename);
587 if (fileExists(filename))
593 /* 4th try: look for default artwork in new default artwork directory */
594 filename = getPath2(getDefaultGraphicsDir(GFX_CLASSIC_SUBDIR), basename);
595 if (fileExists(filename))
600 /* 5th try: look for default artwork in old default artwork directory */
601 filename = getPath2(options.graphics_directory, basename);
602 if (fileExists(filename))
605 return NULL; /* cannot find specified artwork file anywhere */
608 char *getCustomSoundFilename(char *basename)
610 static char *filename = NULL;
611 boolean skip_setup_artwork = FALSE;
613 checked_free(filename);
615 basename = getCorrectedArtworkBasename(basename);
617 if (!setup.override_level_sounds)
619 /* 1st try: look for special artwork in current level series directory */
620 filename = getPath3(getCurrentLevelDir(), SOUNDS_DIRECTORY, basename);
621 if (fileExists(filename))
626 /* check if there is special artwork configured in level series config */
627 if (getLevelArtworkSet(ARTWORK_TYPE_SOUNDS) != NULL)
629 /* 2nd try: look for special artwork configured in level series config */
630 filename = getPath2(getLevelArtworkDir(TREE_TYPE_SOUNDS_DIR), basename);
631 if (fileExists(filename))
636 /* take missing artwork configured in level set config from default */
637 skip_setup_artwork = TRUE;
641 if (!skip_setup_artwork)
643 /* 3rd try: look for special artwork in configured artwork directory */
644 filename = getPath2(getSetupArtworkDir(artwork.snd_current), basename);
645 if (fileExists(filename))
651 /* 4th try: look for default artwork in new default artwork directory */
652 filename = getPath2(getDefaultSoundsDir(SND_CLASSIC_SUBDIR), basename);
653 if (fileExists(filename))
658 /* 5th try: look for default artwork in old default artwork directory */
659 filename = getPath2(options.sounds_directory, basename);
660 if (fileExists(filename))
663 return NULL; /* cannot find specified artwork file anywhere */
666 char *getCustomMusicFilename(char *basename)
668 static char *filename = NULL;
669 boolean skip_setup_artwork = FALSE;
671 checked_free(filename);
673 basename = getCorrectedArtworkBasename(basename);
675 if (!setup.override_level_music)
677 /* 1st try: look for special artwork in current level series directory */
678 filename = getPath3(getCurrentLevelDir(), MUSIC_DIRECTORY, basename);
679 if (fileExists(filename))
684 /* check if there is special artwork configured in level series config */
685 if (getLevelArtworkSet(ARTWORK_TYPE_MUSIC) != NULL)
687 /* 2nd try: look for special artwork configured in level series config */
688 filename = getPath2(getLevelArtworkDir(TREE_TYPE_MUSIC_DIR), basename);
689 if (fileExists(filename))
694 /* take missing artwork configured in level set config from default */
695 skip_setup_artwork = TRUE;
699 if (!skip_setup_artwork)
701 /* 3rd try: look for special artwork in configured artwork directory */
702 filename = getPath2(getSetupArtworkDir(artwork.mus_current), basename);
703 if (fileExists(filename))
709 /* 4th try: look for default artwork in new default artwork directory */
710 filename = getPath2(getDefaultMusicDir(MUS_CLASSIC_SUBDIR), basename);
711 if (fileExists(filename))
716 /* 5th try: look for default artwork in old default artwork directory */
717 filename = getPath2(options.music_directory, basename);
718 if (fileExists(filename))
721 return NULL; /* cannot find specified artwork file anywhere */
724 char *getCustomArtworkFilename(char *basename, int type)
726 if (type == ARTWORK_TYPE_GRAPHICS)
727 return getCustomImageFilename(basename);
728 else if (type == ARTWORK_TYPE_SOUNDS)
729 return getCustomSoundFilename(basename);
730 else if (type == ARTWORK_TYPE_MUSIC)
731 return getCustomMusicFilename(basename);
733 return UNDEFINED_FILENAME;
736 char *getCustomArtworkConfigFilename(int type)
738 return getCustomArtworkFilename(ARTWORKINFO_FILENAME(type), type);
741 char *getCustomArtworkLevelConfigFilename(int type)
743 static char *filename = NULL;
745 checked_free(filename);
747 filename = getPath2(getLevelArtworkDir(type), ARTWORKINFO_FILENAME(type));
752 char *getCustomMusicDirectory(void)
754 static char *directory = NULL;
755 boolean skip_setup_artwork = FALSE;
757 checked_free(directory);
759 if (!setup.override_level_music)
761 /* 1st try: look for special artwork in current level series directory */
762 directory = getPath2(getCurrentLevelDir(), MUSIC_DIRECTORY);
763 if (fileExists(directory))
768 /* check if there is special artwork configured in level series config */
769 if (getLevelArtworkSet(ARTWORK_TYPE_MUSIC) != NULL)
771 /* 2nd try: look for special artwork configured in level series config */
772 directory = getStringCopy(getLevelArtworkDir(TREE_TYPE_MUSIC_DIR));
773 if (fileExists(directory))
778 /* take missing artwork configured in level set config from default */
779 skip_setup_artwork = TRUE;
783 if (!skip_setup_artwork)
785 /* 3rd try: look for special artwork in configured artwork directory */
786 directory = getStringCopy(getSetupArtworkDir(artwork.mus_current));
787 if (fileExists(directory))
793 /* 4th try: look for default artwork in new default artwork directory */
794 directory = getStringCopy(getDefaultMusicDir(MUS_CLASSIC_SUBDIR));
795 if (fileExists(directory))
800 /* 5th try: look for default artwork in old default artwork directory */
801 directory = getStringCopy(options.music_directory);
802 if (fileExists(directory))
805 return NULL; /* cannot find specified artwork file anywhere */
808 void InitTapeDirectory(char *level_subdir)
810 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
811 createDirectory(getTapeDir(NULL), "main tape", PERMS_PRIVATE);
812 createDirectory(getTapeDir(level_subdir), "level tape", PERMS_PRIVATE);
815 void InitScoreDirectory(char *level_subdir)
817 createDirectory(getCommonDataDir(), "common data", PERMS_PUBLIC);
818 createDirectory(getScoreDir(NULL), "main score", PERMS_PUBLIC);
819 createDirectory(getScoreDir(level_subdir), "level score", PERMS_PUBLIC);
822 static void SaveUserLevelInfo();
824 void InitUserLevelDirectory(char *level_subdir)
826 if (!fileExists(getUserLevelDir(level_subdir)))
828 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
829 createDirectory(getUserLevelDir(NULL), "main user level", PERMS_PRIVATE);
830 createDirectory(getUserLevelDir(level_subdir), "user level", PERMS_PRIVATE);
836 void InitLevelSetupDirectory(char *level_subdir)
838 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
839 createDirectory(getLevelSetupDir(NULL), "main level setup", PERMS_PRIVATE);
840 createDirectory(getLevelSetupDir(level_subdir), "level setup", PERMS_PRIVATE);
843 void InitCacheDirectory()
845 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
846 createDirectory(getCacheDir(), "cache data", PERMS_PRIVATE);
850 /* ------------------------------------------------------------------------- */
851 /* some functions to handle lists of level and artwork directories */
852 /* ------------------------------------------------------------------------- */
854 TreeInfo *newTreeInfo()
856 return checked_calloc(sizeof(TreeInfo));
859 TreeInfo *newTreeInfo_setDefaults(int type)
861 TreeInfo *ti = newTreeInfo();
863 setTreeInfoToDefaults(ti, type);
868 void pushTreeInfo(TreeInfo **node_first, TreeInfo *node_new)
870 node_new->next = *node_first;
871 *node_first = node_new;
874 int numTreeInfo(TreeInfo *node)
887 boolean validLevelSeries(TreeInfo *node)
889 return (node != NULL && !node->node_group && !node->parent_link);
892 TreeInfo *getFirstValidTreeInfoEntry(TreeInfo *node)
897 if (node->node_group) /* enter level group (step down into tree) */
898 return getFirstValidTreeInfoEntry(node->node_group);
899 else if (node->parent_link) /* skip start entry of level group */
901 if (node->next) /* get first real level series entry */
902 return getFirstValidTreeInfoEntry(node->next);
903 else /* leave empty level group and go on */
904 return getFirstValidTreeInfoEntry(node->node_parent->next);
906 else /* this seems to be a regular level series */
910 TreeInfo *getTreeInfoFirstGroupEntry(TreeInfo *node)
915 if (node->node_parent == NULL) /* top level group */
916 return *node->node_top;
917 else /* sub level group */
918 return node->node_parent->node_group;
921 int numTreeInfoInGroup(TreeInfo *node)
923 return numTreeInfo(getTreeInfoFirstGroupEntry(node));
926 int posTreeInfo(TreeInfo *node)
928 TreeInfo *node_cmp = getTreeInfoFirstGroupEntry(node);
933 if (node_cmp == node)
937 node_cmp = node_cmp->next;
943 TreeInfo *getTreeInfoFromPos(TreeInfo *node, int pos)
945 TreeInfo *node_default = node;
960 TreeInfo *getTreeInfoFromIdentifier(TreeInfo *node, char *identifier)
962 if (identifier == NULL)
967 if (node->node_group)
969 TreeInfo *node_group;
971 node_group = getTreeInfoFromIdentifier(node->node_group, identifier);
976 else if (!node->parent_link)
978 if (strEqual(identifier, node->identifier))
988 TreeInfo *cloneTreeNode(TreeInfo **node_top, TreeInfo *node_parent,
989 TreeInfo *node, boolean skip_sets_without_levels)
996 if (!node->parent_link && !node->level_group &&
997 skip_sets_without_levels && node->levels == 0)
998 return cloneTreeNode(node_top, node_parent, node->next,
999 skip_sets_without_levels);
1002 node_new = getTreeInfoCopy(node); /* copy complete node */
1004 node_new = newTreeInfo();
1006 *node_new = *node; /* copy complete node */
1009 node_new->node_top = node_top; /* correct top node link */
1010 node_new->node_parent = node_parent; /* correct parent node link */
1012 if (node->level_group)
1013 node_new->node_group = cloneTreeNode(node_top, node_new, node->node_group,
1014 skip_sets_without_levels);
1016 node_new->next = cloneTreeNode(node_top, node_parent, node->next,
1017 skip_sets_without_levels);
1022 void cloneTree(TreeInfo **ti_new, TreeInfo *ti, boolean skip_empty_sets)
1024 TreeInfo *ti_cloned = cloneTreeNode(ti_new, NULL, ti, skip_empty_sets);
1026 *ti_new = ti_cloned;
1029 static boolean adjustTreeGraphicsForEMC(TreeInfo *node)
1031 boolean settings_changed = FALSE;
1035 if (node->graphics_set_ecs && !setup.prefer_aga_graphics &&
1036 !strEqual(node->graphics_set, node->graphics_set_ecs))
1038 setString(&node->graphics_set, node->graphics_set_ecs);
1039 settings_changed = TRUE;
1041 else if (node->graphics_set_aga && setup.prefer_aga_graphics &&
1042 !strEqual(node->graphics_set, node->graphics_set_aga))
1044 setString(&node->graphics_set, node->graphics_set_aga);
1045 settings_changed = TRUE;
1048 if (node->node_group != NULL)
1049 settings_changed |= adjustTreeGraphicsForEMC(node->node_group);
1054 return settings_changed;
1057 void dumpTreeInfo(TreeInfo *node, int depth)
1061 printf("Dumping TreeInfo:\n");
1065 for (i = 0; i < (depth + 1) * 3; i++)
1068 printf("subdir == '%s' ['%s', '%s'] [%d])\n",
1069 node->subdir, node->fullpath, node->basepath, node->in_user_dir);
1071 if (node->node_group != NULL)
1072 dumpTreeInfo(node->node_group, depth + 1);
1078 void sortTreeInfoBySortFunction(TreeInfo **node_first,
1079 int (*compare_function)(const void *,
1082 int num_nodes = numTreeInfo(*node_first);
1083 TreeInfo **sort_array;
1084 TreeInfo *node = *node_first;
1090 /* allocate array for sorting structure pointers */
1091 sort_array = checked_calloc(num_nodes * sizeof(TreeInfo *));
1093 /* writing structure pointers to sorting array */
1094 while (i < num_nodes && node) /* double boundary check... */
1096 sort_array[i] = node;
1102 /* sorting the structure pointers in the sorting array */
1103 qsort(sort_array, num_nodes, sizeof(TreeInfo *),
1106 /* update the linkage of list elements with the sorted node array */
1107 for (i = 0; i < num_nodes - 1; i++)
1108 sort_array[i]->next = sort_array[i + 1];
1109 sort_array[num_nodes - 1]->next = NULL;
1111 /* update the linkage of the main list anchor pointer */
1112 *node_first = sort_array[0];
1116 /* now recursively sort the level group structures */
1120 if (node->node_group != NULL)
1121 sortTreeInfoBySortFunction(&node->node_group, compare_function);
1127 void sortTreeInfo(TreeInfo **node_first)
1129 sortTreeInfoBySortFunction(node_first, compareTreeInfoEntries);
1133 /* ========================================================================= */
1134 /* some stuff from "files.c" */
1135 /* ========================================================================= */
1137 #if defined(PLATFORM_WIN32)
1139 #define S_IRGRP S_IRUSR
1142 #define S_IROTH S_IRUSR
1145 #define S_IWGRP S_IWUSR
1148 #define S_IWOTH S_IWUSR
1151 #define S_IXGRP S_IXUSR
1154 #define S_IXOTH S_IXUSR
1157 #define S_IRWXG (S_IRGRP | S_IWGRP | S_IXGRP)
1162 #endif /* PLATFORM_WIN32 */
1164 /* file permissions for newly written files */
1165 #define MODE_R_ALL (S_IRUSR | S_IRGRP | S_IROTH)
1166 #define MODE_W_ALL (S_IWUSR | S_IWGRP | S_IWOTH)
1167 #define MODE_X_ALL (S_IXUSR | S_IXGRP | S_IXOTH)
1169 #define MODE_W_PRIVATE (S_IWUSR)
1170 #define MODE_W_PUBLIC (S_IWUSR | S_IWGRP)
1171 #define MODE_W_PUBLIC_DIR (S_IWUSR | S_IWGRP | S_ISGID)
1173 #define DIR_PERMS_PRIVATE (MODE_R_ALL | MODE_X_ALL | MODE_W_PRIVATE)
1174 #define DIR_PERMS_PUBLIC (MODE_R_ALL | MODE_X_ALL | MODE_W_PUBLIC_DIR)
1176 #define FILE_PERMS_PRIVATE (MODE_R_ALL | MODE_W_PRIVATE)
1177 #define FILE_PERMS_PUBLIC (MODE_R_ALL | MODE_W_PUBLIC)
1181 static char *dir = NULL;
1183 #if defined(PLATFORM_WIN32)
1186 dir = checked_malloc(MAX_PATH + 1);
1188 if (!SUCCEEDED(SHGetFolderPath(NULL, CSIDL_PERSONAL, NULL, 0, dir)))
1191 #elif defined(PLATFORM_UNIX)
1194 if ((dir = getenv("HOME")) == NULL)
1198 if ((pwd = getpwuid(getuid())) != NULL)
1199 dir = getStringCopy(pwd->pw_dir);
1211 char *getCommonDataDir(void)
1213 static char *common_data_dir = NULL;
1215 #if defined(PLATFORM_WIN32)
1216 if (common_data_dir == NULL)
1218 char *dir = checked_malloc(MAX_PATH + 1);
1220 if (SUCCEEDED(SHGetFolderPath(NULL, CSIDL_COMMON_DOCUMENTS, NULL, 0, dir))
1221 && !strEqual(dir, "")) /* empty for Windows 95/98 */
1222 common_data_dir = getPath2(dir, program.userdata_subdir);
1224 common_data_dir = options.rw_base_directory;
1227 if (common_data_dir == NULL)
1228 common_data_dir = options.rw_base_directory;
1231 return common_data_dir;
1234 char *getPersonalDataDir(void)
1236 static char *personal_data_dir = NULL;
1238 #if defined(PLATFORM_MACOSX)
1239 if (personal_data_dir == NULL)
1240 personal_data_dir = getPath2(getHomeDir(), "Documents");
1242 if (personal_data_dir == NULL)
1243 personal_data_dir = getHomeDir();
1246 return personal_data_dir;
1249 char *getUserGameDataDir(void)
1251 static char *user_game_data_dir = NULL;
1253 if (user_game_data_dir == NULL)
1254 user_game_data_dir = getPath2(getPersonalDataDir(),
1255 program.userdata_subdir);
1257 return user_game_data_dir;
1260 void updateUserGameDataDir()
1262 #if defined(PLATFORM_MACOSX)
1263 char *userdata_dir_old = getPath2(getHomeDir(), program.userdata_subdir_unix);
1264 char *userdata_dir_new = getUserGameDataDir(); /* do not free() this */
1266 /* convert old Unix style game data directory to Mac OS X style, if needed */
1267 if (fileExists(userdata_dir_old) && !fileExists(userdata_dir_new))
1269 if (rename(userdata_dir_old, userdata_dir_new) != 0)
1271 Error(ERR_WARN, "cannot move game data directory '%s' to '%s'",
1272 userdata_dir_old, userdata_dir_new);
1274 /* continue using Unix style data directory -- this should not happen */
1275 program.userdata_path = getPath2(getPersonalDataDir(),
1276 program.userdata_subdir_unix);
1280 free(userdata_dir_old);
1286 return getUserGameDataDir();
1289 static mode_t posix_umask(mode_t mask)
1291 #if defined(PLATFORM_UNIX)
1298 static int posix_mkdir(const char *pathname, mode_t mode)
1300 #if defined(PLATFORM_WIN32)
1301 return mkdir(pathname);
1303 return mkdir(pathname, mode);
1307 void createDirectory(char *dir, char *text, int permission_class)
1309 /* leave "other" permissions in umask untouched, but ensure group parts
1310 of USERDATA_DIR_MODE are not masked */
1311 mode_t dir_mode = (permission_class == PERMS_PRIVATE ?
1312 DIR_PERMS_PRIVATE : DIR_PERMS_PUBLIC);
1313 mode_t normal_umask = posix_umask(0);
1314 mode_t group_umask = ~(dir_mode & S_IRWXG);
1315 posix_umask(normal_umask & group_umask);
1317 if (!fileExists(dir))
1318 if (posix_mkdir(dir, dir_mode) != 0)
1319 Error(ERR_WARN, "cannot create %s directory '%s'", text, dir);
1321 posix_umask(normal_umask); /* reset normal umask */
1324 void InitUserDataDirectory()
1326 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
1329 void SetFilePermissions(char *filename, int permission_class)
1331 chmod(filename, (permission_class == PERMS_PRIVATE ?
1332 FILE_PERMS_PRIVATE : FILE_PERMS_PUBLIC));
1335 char *getCookie(char *file_type)
1337 static char cookie[MAX_COOKIE_LEN + 1];
1339 if (strlen(program.cookie_prefix) + 1 +
1340 strlen(file_type) + strlen("_FILE_VERSION_x.x") > MAX_COOKIE_LEN)
1341 return "[COOKIE ERROR]"; /* should never happen */
1343 sprintf(cookie, "%s_%s_FILE_VERSION_%d.%d",
1344 program.cookie_prefix, file_type,
1345 program.version_major, program.version_minor);
1350 int getFileVersionFromCookieString(const char *cookie)
1352 const char *ptr_cookie1, *ptr_cookie2;
1353 const char *pattern1 = "_FILE_VERSION_";
1354 const char *pattern2 = "?.?";
1355 const int len_cookie = strlen(cookie);
1356 const int len_pattern1 = strlen(pattern1);
1357 const int len_pattern2 = strlen(pattern2);
1358 const int len_pattern = len_pattern1 + len_pattern2;
1359 int version_major, version_minor;
1361 if (len_cookie <= len_pattern)
1364 ptr_cookie1 = &cookie[len_cookie - len_pattern];
1365 ptr_cookie2 = &cookie[len_cookie - len_pattern2];
1367 if (strncmp(ptr_cookie1, pattern1, len_pattern1) != 0)
1370 if (ptr_cookie2[0] < '0' || ptr_cookie2[0] > '9' ||
1371 ptr_cookie2[1] != '.' ||
1372 ptr_cookie2[2] < '0' || ptr_cookie2[2] > '9')
1375 version_major = ptr_cookie2[0] - '0';
1376 version_minor = ptr_cookie2[2] - '0';
1378 return VERSION_IDENT(version_major, version_minor, 0, 0);
1381 boolean checkCookieString(const char *cookie, const char *template)
1383 const char *pattern = "_FILE_VERSION_?.?";
1384 const int len_cookie = strlen(cookie);
1385 const int len_template = strlen(template);
1386 const int len_pattern = strlen(pattern);
1388 if (len_cookie != len_template)
1391 if (strncmp(cookie, template, len_cookie - len_pattern) != 0)
1397 /* ------------------------------------------------------------------------- */
1398 /* setup file list and hash handling functions */
1399 /* ------------------------------------------------------------------------- */
1401 char *getFormattedSetupEntry(char *token, char *value)
1404 static char entry[MAX_LINE_LEN];
1406 /* if value is an empty string, just return token without value */
1410 /* start with the token and some spaces to format output line */
1411 sprintf(entry, "%s:", token);
1412 for (i = strlen(entry); i < token_value_position; i++)
1415 /* continue with the token's value */
1416 strcat(entry, value);
1421 SetupFileList *newSetupFileList(char *token, char *value)
1423 SetupFileList *new = checked_malloc(sizeof(SetupFileList));
1425 new->token = getStringCopy(token);
1426 new->value = getStringCopy(value);
1433 void freeSetupFileList(SetupFileList *list)
1438 checked_free(list->token);
1439 checked_free(list->value);
1442 freeSetupFileList(list->next);
1447 char *getListEntry(SetupFileList *list, char *token)
1452 if (strEqual(list->token, token))
1455 return getListEntry(list->next, token);
1458 SetupFileList *setListEntry(SetupFileList *list, char *token, char *value)
1463 if (strEqual(list->token, token))
1465 checked_free(list->value);
1467 list->value = getStringCopy(value);
1471 else if (list->next == NULL)
1472 return (list->next = newSetupFileList(token, value));
1474 return setListEntry(list->next, token, value);
1477 SetupFileList *addListEntry(SetupFileList *list, char *token, char *value)
1482 if (list->next == NULL)
1483 return (list->next = newSetupFileList(token, value));
1485 return addListEntry(list->next, token, value);
1489 static void printSetupFileList(SetupFileList *list)
1494 printf("token: '%s'\n", list->token);
1495 printf("value: '%s'\n", list->value);
1497 printSetupFileList(list->next);
1502 DEFINE_HASHTABLE_INSERT(insert_hash_entry, char, char);
1503 DEFINE_HASHTABLE_SEARCH(search_hash_entry, char, char);
1504 DEFINE_HASHTABLE_CHANGE(change_hash_entry, char, char);
1505 DEFINE_HASHTABLE_REMOVE(remove_hash_entry, char, char);
1507 #define insert_hash_entry hashtable_insert
1508 #define search_hash_entry hashtable_search
1509 #define change_hash_entry hashtable_change
1510 #define remove_hash_entry hashtable_remove
1513 static unsigned int get_hash_from_key(void *key)
1518 This algorithm (k=33) was first reported by Dan Bernstein many years ago in
1519 'comp.lang.c'. Another version of this algorithm (now favored by Bernstein)
1520 uses XOR: hash(i) = hash(i - 1) * 33 ^ str[i]; the magic of number 33 (why
1521 it works better than many other constants, prime or not) has never been
1522 adequately explained.
1524 If you just want to have a good hash function, and cannot wait, djb2
1525 is one of the best string hash functions i know. It has excellent
1526 distribution and speed on many different sets of keys and table sizes.
1527 You are not likely to do better with one of the "well known" functions
1528 such as PJW, K&R, etc.
1530 Ozan (oz) Yigit [http://www.cs.yorku.ca/~oz/hash.html]
1533 char *str = (char *)key;
1534 unsigned int hash = 5381;
1537 while ((c = *str++))
1538 hash = ((hash << 5) + hash) + c; /* hash * 33 + c */
1543 static int keys_are_equal(void *key1, void *key2)
1545 return (strEqual((char *)key1, (char *)key2));
1548 SetupFileHash *newSetupFileHash()
1550 SetupFileHash *new_hash =
1551 create_hashtable(16, 0.75, get_hash_from_key, keys_are_equal);
1553 if (new_hash == NULL)
1554 Error(ERR_EXIT, "create_hashtable() failed -- out of memory");
1559 void freeSetupFileHash(SetupFileHash *hash)
1564 hashtable_destroy(hash, 1); /* 1 == also free values stored in hash */
1567 char *getHashEntry(SetupFileHash *hash, char *token)
1572 return search_hash_entry(hash, token);
1575 void setHashEntry(SetupFileHash *hash, char *token, char *value)
1582 value_copy = getStringCopy(value);
1584 /* change value; if it does not exist, insert it as new */
1585 if (!change_hash_entry(hash, token, value_copy))
1586 if (!insert_hash_entry(hash, getStringCopy(token), value_copy))
1587 Error(ERR_EXIT, "cannot insert into hash -- aborting");
1590 char *removeHashEntry(SetupFileHash *hash, char *token)
1595 return remove_hash_entry(hash, token);
1599 static void printSetupFileHash(SetupFileHash *hash)
1601 BEGIN_HASH_ITERATION(hash, itr)
1603 printf("token: '%s'\n", HASH_ITERATION_TOKEN(itr));
1604 printf("value: '%s'\n", HASH_ITERATION_VALUE(itr));
1606 END_HASH_ITERATION(hash, itr)
1610 #define ALLOW_TOKEN_VALUE_SEPARATOR_BEING_WHITESPACE 1
1611 #define CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING 0
1613 static boolean token_value_separator_found = FALSE;
1614 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
1615 static boolean token_value_separator_warning = FALSE;
1618 static boolean getTokenValueFromSetupLineExt(char *line,
1619 char **token_ptr, char **value_ptr,
1620 char *filename, char *line_raw,
1622 boolean separator_required)
1624 static char line_copy[MAX_LINE_LEN + 1], line_raw_copy[MAX_LINE_LEN + 1];
1625 char *token, *value, *line_ptr;
1627 /* when externally invoked via ReadTokenValueFromLine(), copy line buffers */
1628 if (line_raw == NULL)
1630 strncpy(line_copy, line, MAX_LINE_LEN);
1631 line_copy[MAX_LINE_LEN] = '\0';
1634 strcpy(line_raw_copy, line_copy);
1635 line_raw = line_raw_copy;
1638 /* cut trailing comment from input line */
1639 for (line_ptr = line; *line_ptr; line_ptr++)
1641 if (*line_ptr == '#')
1648 /* cut trailing whitespaces from input line */
1649 for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
1650 if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
1653 /* ignore empty lines */
1657 /* cut leading whitespaces from token */
1658 for (token = line; *token; token++)
1659 if (*token != ' ' && *token != '\t')
1662 /* start with empty value as reliable default */
1665 token_value_separator_found = FALSE;
1667 /* find end of token to determine start of value */
1668 for (line_ptr = token; *line_ptr; line_ptr++)
1671 /* first look for an explicit token/value separator, like ':' or '=' */
1672 if (*line_ptr == ':' || *line_ptr == '=')
1674 if (*line_ptr == ' ' || *line_ptr == '\t' || *line_ptr == ':')
1677 *line_ptr = '\0'; /* terminate token string */
1678 value = line_ptr + 1; /* set beginning of value */
1680 token_value_separator_found = TRUE;
1686 #if ALLOW_TOKEN_VALUE_SEPARATOR_BEING_WHITESPACE
1687 /* fallback: if no token/value separator found, also allow whitespaces */
1688 if (!token_value_separator_found && !separator_required)
1690 for (line_ptr = token; *line_ptr; line_ptr++)
1692 if (*line_ptr == ' ' || *line_ptr == '\t')
1694 *line_ptr = '\0'; /* terminate token string */
1695 value = line_ptr + 1; /* set beginning of value */
1697 token_value_separator_found = TRUE;
1703 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
1704 if (token_value_separator_found)
1706 if (!token_value_separator_warning)
1708 Error(ERR_INFO_LINE, "-");
1710 if (filename != NULL)
1712 Error(ERR_WARN, "missing token/value separator(s) in config file:");
1713 Error(ERR_INFO, "- config file: '%s'", filename);
1717 Error(ERR_WARN, "missing token/value separator(s):");
1720 token_value_separator_warning = TRUE;
1723 if (filename != NULL)
1724 Error(ERR_INFO, "- line %d: '%s'", line_nr, line_raw);
1726 Error(ERR_INFO, "- line: '%s'", line_raw);
1732 /* cut trailing whitespaces from token */
1733 for (line_ptr = &token[strlen(token)]; line_ptr >= token; line_ptr--)
1734 if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
1737 /* cut leading whitespaces from value */
1738 for (; *value; value++)
1739 if (*value != ' ' && *value != '\t')
1744 value = "true"; /* treat tokens without value as "true" */
1753 boolean getTokenValueFromSetupLine(char *line, char **token, char **value)
1755 /* while the internal (old) interface does not require a token/value
1756 separator (for downwards compatibility with existing files which
1757 don't use them), it is mandatory for the external (new) interface */
1759 return getTokenValueFromSetupLineExt(line, token, value, NULL, NULL, 0, TRUE);
1763 static void loadSetupFileData(void *setup_file_data, char *filename,
1764 boolean top_recursion_level, boolean is_hash)
1766 static SetupFileHash *include_filename_hash = NULL;
1767 char line[MAX_LINE_LEN], line_raw[MAX_LINE_LEN], previous_line[MAX_LINE_LEN];
1768 char *token, *value, *line_ptr;
1769 void *insert_ptr = NULL;
1770 boolean read_continued_line = FALSE;
1773 int token_count = 0;
1775 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
1776 token_value_separator_warning = FALSE;
1779 if (!(file = fopen(filename, MODE_READ)))
1781 Error(ERR_WARN, "cannot open configuration file '%s'", filename);
1786 /* use "insert pointer" to store list end for constant insertion complexity */
1788 insert_ptr = setup_file_data;
1790 /* on top invocation, create hash to mark included files (to prevent loops) */
1791 if (top_recursion_level)
1792 include_filename_hash = newSetupFileHash();
1794 /* mark this file as already included (to prevent including it again) */
1795 setHashEntry(include_filename_hash, getBaseNamePtr(filename), "true");
1799 /* read next line of input file */
1800 if (!fgets(line, MAX_LINE_LEN, file))
1803 /* check if line was completely read and is terminated by line break */
1804 if (strlen(line) > 0 && line[strlen(line) - 1] == '\n')
1807 /* cut trailing line break (this can be newline and/or carriage return) */
1808 for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
1809 if ((*line_ptr == '\n' || *line_ptr == '\r') && *(line_ptr + 1) == '\0')
1812 /* copy raw input line for later use (mainly debugging output) */
1813 strcpy(line_raw, line);
1815 if (read_continued_line)
1818 /* !!! ??? WHY ??? !!! */
1819 /* cut leading whitespaces from input line */
1820 for (line_ptr = line; *line_ptr; line_ptr++)
1821 if (*line_ptr != ' ' && *line_ptr != '\t')
1825 /* append new line to existing line, if there is enough space */
1826 if (strlen(previous_line) + strlen(line_ptr) < MAX_LINE_LEN)
1827 strcat(previous_line, line_ptr);
1829 strcpy(line, previous_line); /* copy storage buffer to line */
1831 read_continued_line = FALSE;
1834 /* if the last character is '\', continue at next line */
1835 if (strlen(line) > 0 && line[strlen(line) - 1] == '\\')
1837 line[strlen(line) - 1] = '\0'; /* cut off trailing backslash */
1838 strcpy(previous_line, line); /* copy line to storage buffer */
1840 read_continued_line = TRUE;
1845 if (!getTokenValueFromSetupLineExt(line, &token, &value, filename,
1846 line_raw, line_nr, FALSE))
1851 if (strEqual(token, "include"))
1853 if (getHashEntry(include_filename_hash, value) == NULL)
1855 char *basepath = getBasePath(filename);
1856 char *basename = getBaseName(value);
1857 char *filename_include = getPath2(basepath, basename);
1860 Error(ERR_INFO, "[including file '%s']", filename_include);
1863 loadSetupFileData(setup_file_data, filename_include, FALSE, is_hash);
1867 free(filename_include);
1871 Error(ERR_WARN, "ignoring already processed file '%s'", value);
1877 setHashEntry((SetupFileHash *)setup_file_data, token, value);
1879 insert_ptr = addListEntry((SetupFileList *)insert_ptr, token, value);
1888 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
1889 if (token_value_separator_warning)
1890 Error(ERR_INFO_LINE, "-");
1893 if (token_count == 0)
1894 Error(ERR_WARN, "configuration file '%s' is empty", filename);
1896 if (top_recursion_level)
1897 freeSetupFileHash(include_filename_hash);
1902 static void loadSetupFileData(void *setup_file_data, char *filename,
1903 boolean top_recursion_level, boolean is_hash)
1905 static SetupFileHash *include_filename_hash = NULL;
1906 char line[MAX_LINE_LEN], line_raw[MAX_LINE_LEN], previous_line[MAX_LINE_LEN];
1907 char *token, *value, *line_ptr;
1908 void *insert_ptr = NULL;
1909 boolean read_continued_line = FALSE;
1912 int token_count = 0;
1914 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
1915 token_value_separator_warning = FALSE;
1918 if (!(file = fopen(filename, MODE_READ)))
1920 Error(ERR_WARN, "cannot open configuration file '%s'", filename);
1925 /* use "insert pointer" to store list end for constant insertion complexity */
1927 insert_ptr = setup_file_data;
1929 /* on top invocation, create hash to mark included files (to prevent loops) */
1930 if (top_recursion_level)
1931 include_filename_hash = newSetupFileHash();
1933 /* mark this file as already included (to prevent including it again) */
1934 setHashEntry(include_filename_hash, getBaseNamePtr(filename), "true");
1938 /* read next line of input file */
1939 if (!fgets(line, MAX_LINE_LEN, file))
1942 /* check if line was completely read and is terminated by line break */
1943 if (strlen(line) > 0 && line[strlen(line) - 1] == '\n')
1946 /* cut trailing line break (this can be newline and/or carriage return) */
1947 for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
1948 if ((*line_ptr == '\n' || *line_ptr == '\r') && *(line_ptr + 1) == '\0')
1951 /* copy raw input line for later use (mainly debugging output) */
1952 strcpy(line_raw, line);
1954 if (read_continued_line)
1956 /* cut leading whitespaces from input line */
1957 for (line_ptr = line; *line_ptr; line_ptr++)
1958 if (*line_ptr != ' ' && *line_ptr != '\t')
1961 /* append new line to existing line, if there is enough space */
1962 if (strlen(previous_line) + strlen(line_ptr) < MAX_LINE_LEN)
1963 strcat(previous_line, line_ptr);
1965 strcpy(line, previous_line); /* copy storage buffer to line */
1967 read_continued_line = FALSE;
1970 /* if the last character is '\', continue at next line */
1971 if (strlen(line) > 0 && line[strlen(line) - 1] == '\\')
1973 line[strlen(line) - 1] = '\0'; /* cut off trailing backslash */
1974 strcpy(previous_line, line); /* copy line to storage buffer */
1976 read_continued_line = TRUE;
1981 /* cut trailing comment from input line */
1982 for (line_ptr = line; *line_ptr; line_ptr++)
1984 if (*line_ptr == '#')
1991 /* cut trailing whitespaces from input line */
1992 for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
1993 if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
1996 /* ignore empty lines */
2000 /* cut leading whitespaces from token */
2001 for (token = line; *token; token++)
2002 if (*token != ' ' && *token != '\t')
2005 /* start with empty value as reliable default */
2008 token_value_separator_found = FALSE;
2010 /* find end of token to determine start of value */
2011 for (line_ptr = token; *line_ptr; line_ptr++)
2014 /* first look for an explicit token/value separator, like ':' or '=' */
2015 if (*line_ptr == ':' || *line_ptr == '=')
2017 if (*line_ptr == ' ' || *line_ptr == '\t' || *line_ptr == ':')
2020 *line_ptr = '\0'; /* terminate token string */
2021 value = line_ptr + 1; /* set beginning of value */
2023 token_value_separator_found = TRUE;
2029 #if ALLOW_TOKEN_VALUE_SEPARATOR_BEING_WHITESPACE
2030 /* fallback: if no token/value separator found, also allow whitespaces */
2031 if (!token_value_separator_found)
2033 for (line_ptr = token; *line_ptr; line_ptr++)
2035 if (*line_ptr == ' ' || *line_ptr == '\t')
2037 *line_ptr = '\0'; /* terminate token string */
2038 value = line_ptr + 1; /* set beginning of value */
2040 token_value_separator_found = TRUE;
2046 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2047 if (token_value_separator_found)
2049 if (!token_value_separator_warning)
2051 Error(ERR_INFO_LINE, "-");
2052 Error(ERR_WARN, "missing token/value separator(s) in config file:");
2053 Error(ERR_INFO, "- config file: '%s'", filename);
2055 token_value_separator_warning = TRUE;
2058 Error(ERR_INFO, "- line %d: '%s'", line_nr, line_raw);
2064 /* cut trailing whitespaces from token */
2065 for (line_ptr = &token[strlen(token)]; line_ptr >= token; line_ptr--)
2066 if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
2069 /* cut leading whitespaces from value */
2070 for (; *value; value++)
2071 if (*value != ' ' && *value != '\t')
2076 value = "true"; /* treat tokens without value as "true" */
2081 if (strEqual(token, "include"))
2083 if (getHashEntry(include_filename_hash, value) == NULL)
2085 char *basepath = getBasePath(filename);
2086 char *basename = getBaseName(value);
2087 char *filename_include = getPath2(basepath, basename);
2090 Error(ERR_INFO, "[including file '%s']", filename_include);
2093 loadSetupFileData(setup_file_data, filename_include, FALSE, is_hash);
2097 free(filename_include);
2101 Error(ERR_WARN, "ignoring already processed file '%s'", value);
2107 setHashEntry((SetupFileHash *)setup_file_data, token, value);
2109 insert_ptr = addListEntry((SetupFileList *)insert_ptr, token, value);
2118 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2119 if (token_value_separator_warning)
2120 Error(ERR_INFO_LINE, "-");
2123 if (token_count == 0)
2124 Error(ERR_WARN, "configuration file '%s' is empty", filename);
2126 if (top_recursion_level)
2127 freeSetupFileHash(include_filename_hash);
2131 void saveSetupFileHash(SetupFileHash *hash, char *filename)
2135 if (!(file = fopen(filename, MODE_WRITE)))
2137 Error(ERR_WARN, "cannot write configuration file '%s'", filename);
2142 BEGIN_HASH_ITERATION(hash, itr)
2144 fprintf(file, "%s\n", getFormattedSetupEntry(HASH_ITERATION_TOKEN(itr),
2145 HASH_ITERATION_VALUE(itr)));
2147 END_HASH_ITERATION(hash, itr)
2152 SetupFileList *loadSetupFileList(char *filename)
2154 SetupFileList *setup_file_list = newSetupFileList("", "");
2155 SetupFileList *first_valid_list_entry;
2157 loadSetupFileData(setup_file_list, filename, TRUE, FALSE);
2159 first_valid_list_entry = setup_file_list->next;
2161 /* free empty list header */
2162 setup_file_list->next = NULL;
2163 freeSetupFileList(setup_file_list);
2165 return first_valid_list_entry;
2168 SetupFileHash *loadSetupFileHash(char *filename)
2170 SetupFileHash *setup_file_hash = newSetupFileHash();
2172 loadSetupFileData(setup_file_hash, filename, TRUE, TRUE);
2174 return setup_file_hash;
2177 void checkSetupFileHashIdentifier(SetupFileHash *setup_file_hash,
2178 char *filename, char *identifier)
2180 char *value = getHashEntry(setup_file_hash, TOKEN_STR_FILE_IDENTIFIER);
2183 Error(ERR_WARN, "config file '%s' has no file identifier", filename);
2184 else if (!checkCookieString(value, identifier))
2185 Error(ERR_WARN, "config file '%s' has wrong file identifier", filename);
2189 /* ========================================================================= */
2190 /* setup file stuff */
2191 /* ========================================================================= */
2193 #define TOKEN_STR_LAST_LEVEL_SERIES "last_level_series"
2194 #define TOKEN_STR_LAST_PLAYED_LEVEL "last_played_level"
2195 #define TOKEN_STR_HANDICAP_LEVEL "handicap_level"
2197 /* level directory info */
2198 #define LEVELINFO_TOKEN_IDENTIFIER 0
2199 #define LEVELINFO_TOKEN_NAME 1
2200 #define LEVELINFO_TOKEN_NAME_SORTING 2
2201 #define LEVELINFO_TOKEN_AUTHOR 3
2202 #define LEVELINFO_TOKEN_YEAR 4
2203 #define LEVELINFO_TOKEN_IMPORTED_FROM 5
2204 #define LEVELINFO_TOKEN_IMPORTED_BY 6
2205 #define LEVELINFO_TOKEN_TESTED_BY 7
2206 #define LEVELINFO_TOKEN_LEVELS 8
2207 #define LEVELINFO_TOKEN_FIRST_LEVEL 9
2208 #define LEVELINFO_TOKEN_SORT_PRIORITY 10
2209 #define LEVELINFO_TOKEN_LATEST_ENGINE 11
2210 #define LEVELINFO_TOKEN_LEVEL_GROUP 12
2211 #define LEVELINFO_TOKEN_READONLY 13
2212 #define LEVELINFO_TOKEN_GRAPHICS_SET_ECS 14
2213 #define LEVELINFO_TOKEN_GRAPHICS_SET_AGA 15
2214 #define LEVELINFO_TOKEN_GRAPHICS_SET 16
2215 #define LEVELINFO_TOKEN_SOUNDS_SET 17
2216 #define LEVELINFO_TOKEN_MUSIC_SET 18
2217 #define LEVELINFO_TOKEN_FILENAME 19
2218 #define LEVELINFO_TOKEN_FILETYPE 20
2219 #define LEVELINFO_TOKEN_HANDICAP 21
2220 #define LEVELINFO_TOKEN_SKIP_LEVELS 22
2222 #define NUM_LEVELINFO_TOKENS 23
2224 static LevelDirTree ldi;
2226 static struct TokenInfo levelinfo_tokens[] =
2228 /* level directory info */
2229 { TYPE_STRING, &ldi.identifier, "identifier" },
2230 { TYPE_STRING, &ldi.name, "name" },
2231 { TYPE_STRING, &ldi.name_sorting, "name_sorting" },
2232 { TYPE_STRING, &ldi.author, "author" },
2233 { TYPE_STRING, &ldi.year, "year" },
2234 { TYPE_STRING, &ldi.imported_from, "imported_from" },
2235 { TYPE_STRING, &ldi.imported_by, "imported_by" },
2236 { TYPE_STRING, &ldi.tested_by, "tested_by" },
2237 { TYPE_INTEGER, &ldi.levels, "levels" },
2238 { TYPE_INTEGER, &ldi.first_level, "first_level" },
2239 { TYPE_INTEGER, &ldi.sort_priority, "sort_priority" },
2240 { TYPE_BOOLEAN, &ldi.latest_engine, "latest_engine" },
2241 { TYPE_BOOLEAN, &ldi.level_group, "level_group" },
2242 { TYPE_BOOLEAN, &ldi.readonly, "readonly" },
2243 { TYPE_STRING, &ldi.graphics_set_ecs, "graphics_set.ecs" },
2244 { TYPE_STRING, &ldi.graphics_set_aga, "graphics_set.aga" },
2245 { TYPE_STRING, &ldi.graphics_set, "graphics_set" },
2246 { TYPE_STRING, &ldi.sounds_set, "sounds_set" },
2247 { TYPE_STRING, &ldi.music_set, "music_set" },
2248 { TYPE_STRING, &ldi.level_filename, "filename" },
2249 { TYPE_STRING, &ldi.level_filetype, "filetype" },
2250 { TYPE_BOOLEAN, &ldi.handicap, "handicap" },
2251 { TYPE_BOOLEAN, &ldi.skip_levels, "skip_levels" }
2254 static struct TokenInfo artworkinfo_tokens[] =
2256 /* artwork directory info */
2257 { TYPE_STRING, &ldi.identifier, "identifier" },
2258 { TYPE_STRING, &ldi.subdir, "subdir" },
2259 { TYPE_STRING, &ldi.name, "name" },
2260 { TYPE_STRING, &ldi.name_sorting, "name_sorting" },
2261 { TYPE_STRING, &ldi.author, "author" },
2262 { TYPE_INTEGER, &ldi.sort_priority, "sort_priority" },
2263 { TYPE_STRING, &ldi.basepath, "basepath" },
2264 { TYPE_STRING, &ldi.fullpath, "fullpath" },
2265 { TYPE_BOOLEAN, &ldi.in_user_dir, "in_user_dir" },
2266 { TYPE_INTEGER, &ldi.color, "color" },
2267 { TYPE_STRING, &ldi.class_desc, "class_desc" },
2272 static void setTreeInfoToDefaults(TreeInfo *ti, int type)
2276 ti->node_top = (ti->type == TREE_TYPE_LEVEL_DIR ? &leveldir_first :
2277 ti->type == TREE_TYPE_GRAPHICS_DIR ? &artwork.gfx_first :
2278 ti->type == TREE_TYPE_SOUNDS_DIR ? &artwork.snd_first :
2279 ti->type == TREE_TYPE_MUSIC_DIR ? &artwork.mus_first :
2282 ti->node_parent = NULL;
2283 ti->node_group = NULL;
2290 ti->fullpath = NULL;
2291 ti->basepath = NULL;
2292 ti->identifier = NULL;
2293 ti->name = getStringCopy(ANONYMOUS_NAME);
2294 ti->name_sorting = NULL;
2295 ti->author = getStringCopy(ANONYMOUS_NAME);
2298 ti->sort_priority = LEVELCLASS_UNDEFINED; /* default: least priority */
2299 ti->latest_engine = FALSE; /* default: get from level */
2300 ti->parent_link = FALSE;
2301 ti->in_user_dir = FALSE;
2302 ti->user_defined = FALSE;
2304 ti->class_desc = NULL;
2306 ti->infotext = getStringCopy(TREE_INFOTEXT(ti->type));
2308 if (ti->type == TREE_TYPE_LEVEL_DIR)
2310 ti->imported_from = NULL;
2311 ti->imported_by = NULL;
2312 ti->tested_by = NULL;
2314 ti->graphics_set_ecs = NULL;
2315 ti->graphics_set_aga = NULL;
2316 ti->graphics_set = NULL;
2317 ti->sounds_set = NULL;
2318 ti->music_set = NULL;
2319 ti->graphics_path = getStringCopy(UNDEFINED_FILENAME);
2320 ti->sounds_path = getStringCopy(UNDEFINED_FILENAME);
2321 ti->music_path = getStringCopy(UNDEFINED_FILENAME);
2323 ti->level_filename = NULL;
2324 ti->level_filetype = NULL;
2327 ti->first_level = 0;
2329 ti->level_group = FALSE;
2330 ti->handicap_level = 0;
2331 ti->readonly = TRUE;
2332 ti->handicap = TRUE;
2333 ti->skip_levels = FALSE;
2337 static void setTreeInfoToDefaultsFromParent(TreeInfo *ti, TreeInfo *parent)
2341 Error(ERR_WARN, "setTreeInfoToDefaultsFromParent(): parent == NULL");
2343 setTreeInfoToDefaults(ti, TREE_TYPE_UNDEFINED);
2348 /* copy all values from the parent structure */
2350 ti->type = parent->type;
2352 ti->node_top = parent->node_top;
2353 ti->node_parent = parent;
2354 ti->node_group = NULL;
2361 ti->fullpath = NULL;
2362 ti->basepath = NULL;
2363 ti->identifier = NULL;
2364 ti->name = getStringCopy(ANONYMOUS_NAME);
2365 ti->name_sorting = NULL;
2366 ti->author = getStringCopy(parent->author);
2367 ti->year = getStringCopy(parent->year);
2369 ti->sort_priority = parent->sort_priority;
2370 ti->latest_engine = parent->latest_engine;
2371 ti->parent_link = FALSE;
2372 ti->in_user_dir = parent->in_user_dir;
2373 ti->user_defined = parent->user_defined;
2374 ti->color = parent->color;
2375 ti->class_desc = getStringCopy(parent->class_desc);
2377 ti->infotext = getStringCopy(parent->infotext);
2379 if (ti->type == TREE_TYPE_LEVEL_DIR)
2381 ti->imported_from = getStringCopy(parent->imported_from);
2382 ti->imported_by = getStringCopy(parent->imported_by);
2383 ti->tested_by = getStringCopy(parent->tested_by);
2385 ti->graphics_set_ecs = NULL;
2386 ti->graphics_set_aga = NULL;
2387 ti->graphics_set = NULL;
2388 ti->sounds_set = NULL;
2389 ti->music_set = NULL;
2390 ti->graphics_path = getStringCopy(UNDEFINED_FILENAME);
2391 ti->sounds_path = getStringCopy(UNDEFINED_FILENAME);
2392 ti->music_path = getStringCopy(UNDEFINED_FILENAME);
2394 ti->level_filename = NULL;
2395 ti->level_filetype = NULL;
2398 ti->first_level = 0;
2400 ti->level_group = FALSE;
2401 ti->handicap_level = 0;
2402 ti->readonly = TRUE;
2403 ti->handicap = TRUE;
2404 ti->skip_levels = FALSE;
2408 static TreeInfo *getTreeInfoCopy(TreeInfo *ti)
2410 TreeInfo *ti_copy = newTreeInfo();
2412 /* copy all values from the original structure */
2414 ti_copy->type = ti->type;
2416 ti_copy->node_top = ti->node_top;
2417 ti_copy->node_parent = ti->node_parent;
2418 ti_copy->node_group = ti->node_group;
2419 ti_copy->next = ti->next;
2421 ti_copy->cl_first = ti->cl_first;
2422 ti_copy->cl_cursor = ti->cl_cursor;
2424 ti_copy->subdir = getStringCopy(ti->subdir);
2425 ti_copy->fullpath = getStringCopy(ti->fullpath);
2426 ti_copy->basepath = getStringCopy(ti->basepath);
2427 ti_copy->identifier = getStringCopy(ti->identifier);
2428 ti_copy->name = getStringCopy(ti->name);
2429 ti_copy->name_sorting = getStringCopy(ti->name_sorting);
2430 ti_copy->author = getStringCopy(ti->author);
2431 ti_copy->year = getStringCopy(ti->year);
2432 ti_copy->imported_from = getStringCopy(ti->imported_from);
2433 ti_copy->imported_by = getStringCopy(ti->imported_by);
2434 ti_copy->tested_by = getStringCopy(ti->tested_by);
2436 ti_copy->graphics_set_ecs = getStringCopy(ti->graphics_set_ecs);
2437 ti_copy->graphics_set_aga = getStringCopy(ti->graphics_set_aga);
2438 ti_copy->graphics_set = getStringCopy(ti->graphics_set);
2439 ti_copy->sounds_set = getStringCopy(ti->sounds_set);
2440 ti_copy->music_set = getStringCopy(ti->music_set);
2441 ti_copy->graphics_path = getStringCopy(ti->graphics_path);
2442 ti_copy->sounds_path = getStringCopy(ti->sounds_path);
2443 ti_copy->music_path = getStringCopy(ti->music_path);
2445 ti_copy->level_filename = getStringCopy(ti->level_filename);
2446 ti_copy->level_filetype = getStringCopy(ti->level_filetype);
2448 ti_copy->levels = ti->levels;
2449 ti_copy->first_level = ti->first_level;
2450 ti_copy->last_level = ti->last_level;
2451 ti_copy->sort_priority = ti->sort_priority;
2453 ti_copy->latest_engine = ti->latest_engine;
2455 ti_copy->level_group = ti->level_group;
2456 ti_copy->parent_link = ti->parent_link;
2457 ti_copy->in_user_dir = ti->in_user_dir;
2458 ti_copy->user_defined = ti->user_defined;
2459 ti_copy->readonly = ti->readonly;
2460 ti_copy->handicap = ti->handicap;
2461 ti_copy->skip_levels = ti->skip_levels;
2463 ti_copy->color = ti->color;
2464 ti_copy->class_desc = getStringCopy(ti->class_desc);
2465 ti_copy->handicap_level = ti->handicap_level;
2467 ti_copy->infotext = getStringCopy(ti->infotext);
2472 static void freeTreeInfo(TreeInfo *ti)
2477 checked_free(ti->subdir);
2478 checked_free(ti->fullpath);
2479 checked_free(ti->basepath);
2480 checked_free(ti->identifier);
2482 checked_free(ti->name);
2483 checked_free(ti->name_sorting);
2484 checked_free(ti->author);
2485 checked_free(ti->year);
2487 checked_free(ti->class_desc);
2489 checked_free(ti->infotext);
2491 if (ti->type == TREE_TYPE_LEVEL_DIR)
2493 checked_free(ti->imported_from);
2494 checked_free(ti->imported_by);
2495 checked_free(ti->tested_by);
2497 checked_free(ti->graphics_set_ecs);
2498 checked_free(ti->graphics_set_aga);
2499 checked_free(ti->graphics_set);
2500 checked_free(ti->sounds_set);
2501 checked_free(ti->music_set);
2503 checked_free(ti->graphics_path);
2504 checked_free(ti->sounds_path);
2505 checked_free(ti->music_path);
2507 checked_free(ti->level_filename);
2508 checked_free(ti->level_filetype);
2514 void setSetupInfo(struct TokenInfo *token_info,
2515 int token_nr, char *token_value)
2517 int token_type = token_info[token_nr].type;
2518 void *setup_value = token_info[token_nr].value;
2520 if (token_value == NULL)
2523 /* set setup field to corresponding token value */
2528 *(boolean *)setup_value = get_boolean_from_string(token_value);
2532 *(Key *)setup_value = getKeyFromKeyName(token_value);
2536 *(Key *)setup_value = getKeyFromX11KeyName(token_value);
2540 *(int *)setup_value = get_integer_from_string(token_value);
2544 checked_free(*(char **)setup_value);
2545 *(char **)setup_value = getStringCopy(token_value);
2553 static int compareTreeInfoEntries(const void *object1, const void *object2)
2555 const TreeInfo *entry1 = *((TreeInfo **)object1);
2556 const TreeInfo *entry2 = *((TreeInfo **)object2);
2557 int class_sorting1, class_sorting2;
2560 if (entry1->type == TREE_TYPE_LEVEL_DIR)
2562 class_sorting1 = LEVELSORTING(entry1);
2563 class_sorting2 = LEVELSORTING(entry2);
2567 class_sorting1 = ARTWORKSORTING(entry1);
2568 class_sorting2 = ARTWORKSORTING(entry2);
2571 if (entry1->parent_link || entry2->parent_link)
2572 compare_result = (entry1->parent_link ? -1 : +1);
2573 else if (entry1->sort_priority == entry2->sort_priority)
2575 char *name1 = getStringToLower(entry1->name_sorting);
2576 char *name2 = getStringToLower(entry2->name_sorting);
2578 compare_result = strcmp(name1, name2);
2583 else if (class_sorting1 == class_sorting2)
2584 compare_result = entry1->sort_priority - entry2->sort_priority;
2586 compare_result = class_sorting1 - class_sorting2;
2588 return compare_result;
2591 static void createParentTreeInfoNode(TreeInfo *node_parent)
2595 if (node_parent == NULL)
2598 ti_new = newTreeInfo();
2599 setTreeInfoToDefaults(ti_new, node_parent->type);
2601 ti_new->node_parent = node_parent;
2602 ti_new->parent_link = TRUE;
2604 setString(&ti_new->identifier, node_parent->identifier);
2605 setString(&ti_new->name, ".. (parent directory)");
2606 setString(&ti_new->name_sorting, ti_new->name);
2608 setString(&ti_new->subdir, "..");
2609 setString(&ti_new->fullpath, node_parent->fullpath);
2611 ti_new->sort_priority = node_parent->sort_priority;
2612 ti_new->latest_engine = node_parent->latest_engine;
2614 setString(&ti_new->class_desc, getLevelClassDescription(ti_new));
2616 pushTreeInfo(&node_parent->node_group, ti_new);
2620 /* -------------------------------------------------------------------------- */
2621 /* functions for handling level and custom artwork info cache */
2622 /* -------------------------------------------------------------------------- */
2624 static void LoadArtworkInfoCache()
2626 InitCacheDirectory();
2628 if (artworkinfo_cache_old == NULL)
2630 char *filename = getPath2(getCacheDir(), ARTWORKINFO_CACHE_FILE);
2632 /* try to load artwork info hash from already existing cache file */
2633 artworkinfo_cache_old = loadSetupFileHash(filename);
2635 /* if no artwork info cache file was found, start with empty hash */
2636 if (artworkinfo_cache_old == NULL)
2637 artworkinfo_cache_old = newSetupFileHash();
2642 if (artworkinfo_cache_new == NULL)
2643 artworkinfo_cache_new = newSetupFileHash();
2646 static void SaveArtworkInfoCache()
2648 char *filename = getPath2(getCacheDir(), ARTWORKINFO_CACHE_FILE);
2650 InitCacheDirectory();
2652 saveSetupFileHash(artworkinfo_cache_new, filename);
2657 static char *getCacheTokenPrefix(char *prefix1, char *prefix2)
2659 static char *prefix = NULL;
2661 checked_free(prefix);
2663 prefix = getStringCat2WithSeparator(prefix1, prefix2, ".");
2668 /* (identical to above function, but separate string buffer needed -- nasty) */
2669 static char *getCacheToken(char *prefix, char *suffix)
2671 static char *token = NULL;
2673 checked_free(token);
2675 token = getStringCat2WithSeparator(prefix, suffix, ".");
2680 static char *getFileTimestamp(char *filename)
2682 struct stat file_status;
2684 if (stat(filename, &file_status) != 0) /* cannot stat file */
2685 return getStringCopy(i_to_a(0));
2687 return getStringCopy(i_to_a(file_status.st_mtime));
2690 static boolean modifiedFileTimestamp(char *filename, char *timestamp_string)
2692 struct stat file_status;
2694 if (timestamp_string == NULL)
2697 if (stat(filename, &file_status) != 0) /* cannot stat file */
2700 return (file_status.st_mtime != atoi(timestamp_string));
2703 static TreeInfo *getArtworkInfoCacheEntry(LevelDirTree *level_node, int type)
2705 char *identifier = level_node->subdir;
2706 char *type_string = ARTWORK_DIRECTORY(type);
2707 char *token_prefix = getCacheTokenPrefix(type_string, identifier);
2708 char *token_main = getCacheToken(token_prefix, "CACHED");
2709 char *cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
2710 boolean cached = (cache_entry != NULL && strEqual(cache_entry, "true"));
2711 TreeInfo *artwork_info = NULL;
2713 if (!use_artworkinfo_cache)
2720 artwork_info = newTreeInfo();
2721 setTreeInfoToDefaults(artwork_info, type);
2723 /* set all structure fields according to the token/value pairs */
2724 ldi = *artwork_info;
2725 for (i = 0; artworkinfo_tokens[i].type != -1; i++)
2727 char *token = getCacheToken(token_prefix, artworkinfo_tokens[i].text);
2728 char *value = getHashEntry(artworkinfo_cache_old, token);
2730 setSetupInfo(artworkinfo_tokens, i, value);
2732 /* check if cache entry for this item is invalid or incomplete */
2736 Error(ERR_WARN, "cache entry '%s' invalid", token);
2743 *artwork_info = ldi;
2748 char *filename_levelinfo = getPath2(getLevelDirFromTreeInfo(level_node),
2749 LEVELINFO_FILENAME);
2750 char *filename_artworkinfo = getPath2(getSetupArtworkDir(artwork_info),
2751 ARTWORKINFO_FILENAME(type));
2753 /* check if corresponding "levelinfo.conf" file has changed */
2754 token_main = getCacheToken(token_prefix, "TIMESTAMP_LEVELINFO");
2755 cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
2757 if (modifiedFileTimestamp(filename_levelinfo, cache_entry))
2760 /* check if corresponding "<artworkinfo>.conf" file has changed */
2761 token_main = getCacheToken(token_prefix, "TIMESTAMP_ARTWORKINFO");
2762 cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
2764 if (modifiedFileTimestamp(filename_artworkinfo, cache_entry))
2769 printf("::: '%s': INVALIDATED FROM CACHE BY TIMESTAMP\n", identifier);
2772 checked_free(filename_levelinfo);
2773 checked_free(filename_artworkinfo);
2776 if (!cached && artwork_info != NULL)
2778 freeTreeInfo(artwork_info);
2783 return artwork_info;
2786 static void setArtworkInfoCacheEntry(TreeInfo *artwork_info,
2787 LevelDirTree *level_node, int type)
2789 char *identifier = level_node->subdir;
2790 char *type_string = ARTWORK_DIRECTORY(type);
2791 char *token_prefix = getCacheTokenPrefix(type_string, identifier);
2792 char *token_main = getCacheToken(token_prefix, "CACHED");
2793 boolean set_cache_timestamps = TRUE;
2796 setHashEntry(artworkinfo_cache_new, token_main, "true");
2798 if (set_cache_timestamps)
2800 char *filename_levelinfo = getPath2(getLevelDirFromTreeInfo(level_node),
2801 LEVELINFO_FILENAME);
2802 char *filename_artworkinfo = getPath2(getSetupArtworkDir(artwork_info),
2803 ARTWORKINFO_FILENAME(type));
2804 char *timestamp_levelinfo = getFileTimestamp(filename_levelinfo);
2805 char *timestamp_artworkinfo = getFileTimestamp(filename_artworkinfo);
2807 token_main = getCacheToken(token_prefix, "TIMESTAMP_LEVELINFO");
2808 setHashEntry(artworkinfo_cache_new, token_main, timestamp_levelinfo);
2810 token_main = getCacheToken(token_prefix, "TIMESTAMP_ARTWORKINFO");
2811 setHashEntry(artworkinfo_cache_new, token_main, timestamp_artworkinfo);
2813 checked_free(filename_levelinfo);
2814 checked_free(filename_artworkinfo);
2815 checked_free(timestamp_levelinfo);
2816 checked_free(timestamp_artworkinfo);
2819 ldi = *artwork_info;
2820 for (i = 0; artworkinfo_tokens[i].type != -1; i++)
2822 char *token = getCacheToken(token_prefix, artworkinfo_tokens[i].text);
2823 char *value = getSetupValue(artworkinfo_tokens[i].type,
2824 artworkinfo_tokens[i].value);
2826 setHashEntry(artworkinfo_cache_new, token, value);
2831 /* -------------------------------------------------------------------------- */
2832 /* functions for loading level info and custom artwork info */
2833 /* -------------------------------------------------------------------------- */
2835 /* forward declaration for recursive call by "LoadLevelInfoFromLevelDir()" */
2836 static void LoadLevelInfoFromLevelDir(TreeInfo **, TreeInfo *, char *);
2838 static boolean LoadLevelInfoFromLevelConf(TreeInfo **node_first,
2839 TreeInfo *node_parent,
2840 char *level_directory,
2841 char *directory_name)
2843 static unsigned long progress_delay = 0;
2844 unsigned long progress_delay_value = 100; /* (in milliseconds) */
2845 char *directory_path = getPath2(level_directory, directory_name);
2846 char *filename = getPath2(directory_path, LEVELINFO_FILENAME);
2847 SetupFileHash *setup_file_hash;
2848 LevelDirTree *leveldir_new = NULL;
2851 /* unless debugging, silently ignore directories without "levelinfo.conf" */
2852 if (!options.debug && !fileExists(filename))
2854 free(directory_path);
2860 setup_file_hash = loadSetupFileHash(filename);
2862 if (setup_file_hash == NULL)
2864 Error(ERR_WARN, "ignoring level directory '%s'", directory_path);
2866 free(directory_path);
2872 leveldir_new = newTreeInfo();
2875 setTreeInfoToDefaultsFromParent(leveldir_new, node_parent);
2877 setTreeInfoToDefaults(leveldir_new, TREE_TYPE_LEVEL_DIR);
2879 leveldir_new->subdir = getStringCopy(directory_name);
2881 checkSetupFileHashIdentifier(setup_file_hash, filename,
2882 getCookie("LEVELINFO"));
2884 /* set all structure fields according to the token/value pairs */
2885 ldi = *leveldir_new;
2886 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
2887 setSetupInfo(levelinfo_tokens, i,
2888 getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
2889 *leveldir_new = ldi;
2891 if (strEqual(leveldir_new->name, ANONYMOUS_NAME))
2892 setString(&leveldir_new->name, leveldir_new->subdir);
2894 if (leveldir_new->identifier == NULL)
2895 leveldir_new->identifier = getStringCopy(leveldir_new->subdir);
2897 if (leveldir_new->name_sorting == NULL)
2898 leveldir_new->name_sorting = getStringCopy(leveldir_new->name);
2900 if (node_parent == NULL) /* top level group */
2902 leveldir_new->basepath = getStringCopy(level_directory);
2903 leveldir_new->fullpath = getStringCopy(leveldir_new->subdir);
2905 else /* sub level group */
2907 leveldir_new->basepath = getStringCopy(node_parent->basepath);
2908 leveldir_new->fullpath = getPath2(node_parent->fullpath, directory_name);
2912 if (leveldir_new->levels < 1)
2913 leveldir_new->levels = 1;
2916 leveldir_new->last_level =
2917 leveldir_new->first_level + leveldir_new->levels - 1;
2919 leveldir_new->in_user_dir =
2920 (!strEqual(leveldir_new->basepath, options.level_directory));
2922 /* adjust some settings if user's private level directory was detected */
2923 if (leveldir_new->sort_priority == LEVELCLASS_UNDEFINED &&
2924 leveldir_new->in_user_dir &&
2925 (strEqual(leveldir_new->subdir, getLoginName()) ||
2926 strEqual(leveldir_new->name, getLoginName()) ||
2927 strEqual(leveldir_new->author, getRealName())))
2929 leveldir_new->sort_priority = LEVELCLASS_PRIVATE_START;
2930 leveldir_new->readonly = FALSE;
2933 leveldir_new->user_defined =
2934 (leveldir_new->in_user_dir && IS_LEVELCLASS_PRIVATE(leveldir_new));
2936 leveldir_new->color = LEVELCOLOR(leveldir_new);
2938 setString(&leveldir_new->class_desc, getLevelClassDescription(leveldir_new));
2940 leveldir_new->handicap_level = /* set handicap to default value */
2941 (leveldir_new->user_defined || !leveldir_new->handicap ?
2942 leveldir_new->last_level : leveldir_new->first_level);
2945 if (leveldir_new->level_group ||
2946 DelayReached(&progress_delay, progress_delay_value))
2947 DrawInitText(leveldir_new->name, 150, FC_YELLOW);
2949 DrawInitText(leveldir_new->name, 150, FC_YELLOW);
2953 /* !!! don't skip sets without levels (else artwork base sets are missing) */
2955 if (leveldir_new->levels < 1 && !leveldir_new->level_group)
2957 /* skip level sets without levels (which are probably artwork base sets) */
2959 freeSetupFileHash(setup_file_hash);
2960 free(directory_path);
2968 pushTreeInfo(node_first, leveldir_new);
2970 freeSetupFileHash(setup_file_hash);
2972 if (leveldir_new->level_group)
2974 /* create node to link back to current level directory */
2975 createParentTreeInfoNode(leveldir_new);
2977 /* recursively step into sub-directory and look for more level series */
2978 LoadLevelInfoFromLevelDir(&leveldir_new->node_group,
2979 leveldir_new, directory_path);
2982 free(directory_path);
2988 static void LoadLevelInfoFromLevelDir(TreeInfo **node_first,
2989 TreeInfo *node_parent,
2990 char *level_directory)
2993 struct dirent *dir_entry;
2994 boolean valid_entry_found = FALSE;
2996 if ((dir = opendir(level_directory)) == NULL)
2998 Error(ERR_WARN, "cannot read level directory '%s'", level_directory);
3002 while ((dir_entry = readdir(dir)) != NULL) /* loop until last dir entry */
3004 struct stat file_status;
3005 char *directory_name = dir_entry->d_name;
3006 char *directory_path = getPath2(level_directory, directory_name);
3008 /* skip entries for current and parent directory */
3009 if (strEqual(directory_name, ".") ||
3010 strEqual(directory_name, ".."))
3012 free(directory_path);
3016 /* find out if directory entry is itself a directory */
3017 if (stat(directory_path, &file_status) != 0 || /* cannot stat file */
3018 (file_status.st_mode & S_IFMT) != S_IFDIR) /* not a directory */
3020 free(directory_path);
3024 free(directory_path);
3026 if (strEqual(directory_name, GRAPHICS_DIRECTORY) ||
3027 strEqual(directory_name, SOUNDS_DIRECTORY) ||
3028 strEqual(directory_name, MUSIC_DIRECTORY))
3031 valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
3038 /* special case: top level directory may directly contain "levelinfo.conf" */
3039 if (node_parent == NULL && !valid_entry_found)
3041 /* check if this directory directly contains a file "levelinfo.conf" */
3042 valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
3043 level_directory, ".");
3046 if (!valid_entry_found)
3047 Error(ERR_WARN, "cannot find any valid level series in directory '%s'",
3051 boolean AdjustGraphicsForEMC()
3053 boolean settings_changed = FALSE;
3055 settings_changed |= adjustTreeGraphicsForEMC(leveldir_first_all);
3056 settings_changed |= adjustTreeGraphicsForEMC(leveldir_first);
3058 return settings_changed;
3061 void LoadLevelInfo()
3063 InitUserLevelDirectory(getLoginName());
3065 DrawInitText("Loading level series", 120, FC_GREEN);
3067 LoadLevelInfoFromLevelDir(&leveldir_first, NULL, options.level_directory);
3068 LoadLevelInfoFromLevelDir(&leveldir_first, NULL, getUserLevelDir(NULL));
3070 /* after loading all level set information, clone the level directory tree
3071 and remove all level sets without levels (these may still contain artwork
3072 to be offered in the setup menu as "custom artwork", and are therefore
3073 checked for existing artwork in the function "LoadLevelArtworkInfo()") */
3074 leveldir_first_all = leveldir_first;
3075 cloneTree(&leveldir_first, leveldir_first_all, TRUE);
3077 AdjustGraphicsForEMC();
3079 /* before sorting, the first entries will be from the user directory */
3080 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
3082 if (leveldir_first == NULL)
3083 Error(ERR_EXIT, "cannot find any valid level series in any directory");
3085 sortTreeInfo(&leveldir_first);
3088 dumpTreeInfo(leveldir_first, 0);
3092 static boolean LoadArtworkInfoFromArtworkConf(TreeInfo **node_first,
3093 TreeInfo *node_parent,
3094 char *base_directory,
3095 char *directory_name, int type)
3097 char *directory_path = getPath2(base_directory, directory_name);
3098 char *filename = getPath2(directory_path, ARTWORKINFO_FILENAME(type));
3099 SetupFileHash *setup_file_hash = NULL;
3100 TreeInfo *artwork_new = NULL;
3103 if (fileExists(filename))
3104 setup_file_hash = loadSetupFileHash(filename);
3106 if (setup_file_hash == NULL) /* no config file -- look for artwork files */
3109 struct dirent *dir_entry;
3110 boolean valid_file_found = FALSE;
3112 if ((dir = opendir(directory_path)) != NULL)
3114 while ((dir_entry = readdir(dir)) != NULL)
3116 char *entry_name = dir_entry->d_name;
3118 if (FileIsArtworkType(entry_name, type))
3120 valid_file_found = TRUE;
3128 if (!valid_file_found)
3130 if (!strEqual(directory_name, "."))
3131 Error(ERR_WARN, "ignoring artwork directory '%s'", directory_path);
3133 free(directory_path);
3140 artwork_new = newTreeInfo();
3143 setTreeInfoToDefaultsFromParent(artwork_new, node_parent);
3145 setTreeInfoToDefaults(artwork_new, type);
3147 artwork_new->subdir = getStringCopy(directory_name);
3149 if (setup_file_hash) /* (before defining ".color" and ".class_desc") */
3152 checkSetupFileHashIdentifier(setup_file_hash, filename, getCookie("..."));
3155 /* set all structure fields according to the token/value pairs */
3157 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
3158 setSetupInfo(levelinfo_tokens, i,
3159 getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
3162 if (strEqual(artwork_new->name, ANONYMOUS_NAME))
3163 setString(&artwork_new->name, artwork_new->subdir);
3165 if (artwork_new->identifier == NULL)
3166 artwork_new->identifier = getStringCopy(artwork_new->subdir);
3168 if (artwork_new->name_sorting == NULL)
3169 artwork_new->name_sorting = getStringCopy(artwork_new->name);
3172 if (node_parent == NULL) /* top level group */
3174 artwork_new->basepath = getStringCopy(base_directory);
3175 artwork_new->fullpath = getStringCopy(artwork_new->subdir);
3177 else /* sub level group */
3179 artwork_new->basepath = getStringCopy(node_parent->basepath);
3180 artwork_new->fullpath = getPath2(node_parent->fullpath, directory_name);
3183 artwork_new->in_user_dir =
3184 (!strEqual(artwork_new->basepath, OPTIONS_ARTWORK_DIRECTORY(type)));
3186 /* (may use ".sort_priority" from "setup_file_hash" above) */
3187 artwork_new->color = ARTWORKCOLOR(artwork_new);
3189 setString(&artwork_new->class_desc, getLevelClassDescription(artwork_new));
3191 if (setup_file_hash == NULL) /* (after determining ".user_defined") */
3193 if (strEqual(artwork_new->subdir, "."))
3195 if (artwork_new->user_defined)
3197 setString(&artwork_new->identifier, "private");
3198 artwork_new->sort_priority = ARTWORKCLASS_PRIVATE;
3202 setString(&artwork_new->identifier, "classic");
3203 artwork_new->sort_priority = ARTWORKCLASS_CLASSICS;
3206 /* set to new values after changing ".sort_priority" */
3207 artwork_new->color = ARTWORKCOLOR(artwork_new);
3209 setString(&artwork_new->class_desc,
3210 getLevelClassDescription(artwork_new));
3214 setString(&artwork_new->identifier, artwork_new->subdir);
3217 setString(&artwork_new->name, artwork_new->identifier);
3218 setString(&artwork_new->name_sorting, artwork_new->name);
3222 DrawInitText(artwork_new->name, 150, FC_YELLOW);
3225 pushTreeInfo(node_first, artwork_new);
3227 freeSetupFileHash(setup_file_hash);
3229 free(directory_path);
3235 static void LoadArtworkInfoFromArtworkDir(TreeInfo **node_first,
3236 TreeInfo *node_parent,
3237 char *base_directory, int type)
3240 struct dirent *dir_entry;
3241 boolean valid_entry_found = FALSE;
3243 if ((dir = opendir(base_directory)) == NULL)
3245 /* display error if directory is main "options.graphics_directory" etc. */
3246 if (base_directory == OPTIONS_ARTWORK_DIRECTORY(type))
3247 Error(ERR_WARN, "cannot read directory '%s'", base_directory);
3252 while ((dir_entry = readdir(dir)) != NULL) /* loop until last dir entry */
3254 struct stat file_status;
3255 char *directory_name = dir_entry->d_name;
3256 char *directory_path = getPath2(base_directory, directory_name);
3258 /* skip directory entries for current and parent directory */
3259 if (strEqual(directory_name, ".") ||
3260 strEqual(directory_name, ".."))
3262 free(directory_path);
3266 /* skip directory entries which are not a directory or are not accessible */
3267 if (stat(directory_path, &file_status) != 0 || /* cannot stat file */
3268 (file_status.st_mode & S_IFMT) != S_IFDIR) /* not a directory */
3270 free(directory_path);
3274 free(directory_path);
3276 /* check if this directory contains artwork with or without config file */
3277 valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
3279 directory_name, type);
3284 /* check if this directory directly contains artwork itself */
3285 valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
3286 base_directory, ".",
3288 if (!valid_entry_found)
3289 Error(ERR_WARN, "cannot find any valid artwork in directory '%s'",
3293 static TreeInfo *getDummyArtworkInfo(int type)
3295 /* this is only needed when there is completely no artwork available */
3296 TreeInfo *artwork_new = newTreeInfo();
3298 setTreeInfoToDefaults(artwork_new, type);
3300 setString(&artwork_new->subdir, UNDEFINED_FILENAME);
3301 setString(&artwork_new->fullpath, UNDEFINED_FILENAME);
3302 setString(&artwork_new->basepath, UNDEFINED_FILENAME);
3304 setString(&artwork_new->identifier, UNDEFINED_FILENAME);
3305 setString(&artwork_new->name, UNDEFINED_FILENAME);
3306 setString(&artwork_new->name_sorting, UNDEFINED_FILENAME);
3311 void LoadArtworkInfo()
3313 LoadArtworkInfoCache();
3315 DrawInitText("Looking for custom artwork", 120, FC_GREEN);
3317 LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
3318 options.graphics_directory,
3319 TREE_TYPE_GRAPHICS_DIR);
3320 LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
3321 getUserGraphicsDir(),
3322 TREE_TYPE_GRAPHICS_DIR);
3324 LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
3325 options.sounds_directory,
3326 TREE_TYPE_SOUNDS_DIR);
3327 LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
3329 TREE_TYPE_SOUNDS_DIR);
3331 LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
3332 options.music_directory,
3333 TREE_TYPE_MUSIC_DIR);
3334 LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
3336 TREE_TYPE_MUSIC_DIR);
3338 if (artwork.gfx_first == NULL)
3339 artwork.gfx_first = getDummyArtworkInfo(TREE_TYPE_GRAPHICS_DIR);
3340 if (artwork.snd_first == NULL)
3341 artwork.snd_first = getDummyArtworkInfo(TREE_TYPE_SOUNDS_DIR);
3342 if (artwork.mus_first == NULL)
3343 artwork.mus_first = getDummyArtworkInfo(TREE_TYPE_MUSIC_DIR);
3345 /* before sorting, the first entries will be from the user directory */
3346 artwork.gfx_current =
3347 getTreeInfoFromIdentifier(artwork.gfx_first, setup.graphics_set);
3348 if (artwork.gfx_current == NULL)
3349 artwork.gfx_current =
3350 getTreeInfoFromIdentifier(artwork.gfx_first, GFX_CLASSIC_SUBDIR);
3351 if (artwork.gfx_current == NULL)
3352 artwork.gfx_current = getFirstValidTreeInfoEntry(artwork.gfx_first);
3354 artwork.snd_current =
3355 getTreeInfoFromIdentifier(artwork.snd_first, setup.sounds_set);
3356 if (artwork.snd_current == NULL)
3357 artwork.snd_current =
3358 getTreeInfoFromIdentifier(artwork.snd_first, SND_CLASSIC_SUBDIR);
3359 if (artwork.snd_current == NULL)
3360 artwork.snd_current = getFirstValidTreeInfoEntry(artwork.snd_first);
3362 artwork.mus_current =
3363 getTreeInfoFromIdentifier(artwork.mus_first, setup.music_set);
3364 if (artwork.mus_current == NULL)
3365 artwork.mus_current =
3366 getTreeInfoFromIdentifier(artwork.mus_first, MUS_CLASSIC_SUBDIR);
3367 if (artwork.mus_current == NULL)
3368 artwork.mus_current = getFirstValidTreeInfoEntry(artwork.mus_first);
3370 artwork.gfx_current_identifier = artwork.gfx_current->identifier;
3371 artwork.snd_current_identifier = artwork.snd_current->identifier;
3372 artwork.mus_current_identifier = artwork.mus_current->identifier;
3375 printf("graphics set == %s\n\n", artwork.gfx_current_identifier);
3376 printf("sounds set == %s\n\n", artwork.snd_current_identifier);
3377 printf("music set == %s\n\n", artwork.mus_current_identifier);
3380 sortTreeInfo(&artwork.gfx_first);
3381 sortTreeInfo(&artwork.snd_first);
3382 sortTreeInfo(&artwork.mus_first);
3385 dumpTreeInfo(artwork.gfx_first, 0);
3386 dumpTreeInfo(artwork.snd_first, 0);
3387 dumpTreeInfo(artwork.mus_first, 0);
3391 void LoadArtworkInfoFromLevelInfo(ArtworkDirTree **artwork_node,
3392 LevelDirTree *level_node)
3394 static unsigned long progress_delay = 0;
3395 unsigned long progress_delay_value = 100; /* (in milliseconds) */
3396 int type = (*artwork_node)->type;
3398 /* recursively check all level directories for artwork sub-directories */
3402 /* check all tree entries for artwork, but skip parent link entries */
3403 if (!level_node->parent_link)
3405 TreeInfo *artwork_new = getArtworkInfoCacheEntry(level_node, type);
3406 boolean cached = (artwork_new != NULL);
3410 pushTreeInfo(artwork_node, artwork_new);
3414 TreeInfo *topnode_last = *artwork_node;
3415 char *path = getPath2(getLevelDirFromTreeInfo(level_node),
3416 ARTWORK_DIRECTORY(type));
3418 LoadArtworkInfoFromArtworkDir(artwork_node, NULL, path, type);
3420 if (topnode_last != *artwork_node) /* check for newly added node */
3422 artwork_new = *artwork_node;
3424 setString(&artwork_new->identifier, level_node->subdir);
3425 setString(&artwork_new->name, level_node->name);
3426 setString(&artwork_new->name_sorting, level_node->name_sorting);
3428 artwork_new->sort_priority = level_node->sort_priority;
3429 artwork_new->color = LEVELCOLOR(artwork_new);
3435 /* insert artwork info (from old cache or filesystem) into new cache */
3436 if (artwork_new != NULL)
3437 setArtworkInfoCacheEntry(artwork_new, level_node, type);
3441 if (level_node->level_group ||
3442 DelayReached(&progress_delay, progress_delay_value))
3443 DrawInitText(level_node->name, 150, FC_YELLOW);
3446 if (level_node->node_group != NULL)
3447 LoadArtworkInfoFromLevelInfo(artwork_node, level_node->node_group);
3449 level_node = level_node->next;
3453 void LoadLevelArtworkInfo()
3455 DrawInitText("Looking for custom level artwork", 120, FC_GREEN);
3457 LoadArtworkInfoFromLevelInfo(&artwork.gfx_first, leveldir_first_all);
3458 LoadArtworkInfoFromLevelInfo(&artwork.snd_first, leveldir_first_all);
3459 LoadArtworkInfoFromLevelInfo(&artwork.mus_first, leveldir_first_all);
3461 SaveArtworkInfoCache();
3463 /* needed for reloading level artwork not known at ealier stage */
3465 if (!strEqual(artwork.gfx_current_identifier, setup.graphics_set))
3467 artwork.gfx_current =
3468 getTreeInfoFromIdentifier(artwork.gfx_first, setup.graphics_set);
3469 if (artwork.gfx_current == NULL)
3470 artwork.gfx_current =
3471 getTreeInfoFromIdentifier(artwork.gfx_first, GFX_CLASSIC_SUBDIR);
3472 if (artwork.gfx_current == NULL)
3473 artwork.gfx_current = getFirstValidTreeInfoEntry(artwork.gfx_first);
3476 if (!strEqual(artwork.snd_current_identifier, setup.sounds_set))
3478 artwork.snd_current =
3479 getTreeInfoFromIdentifier(artwork.snd_first, setup.sounds_set);
3480 if (artwork.snd_current == NULL)
3481 artwork.snd_current =
3482 getTreeInfoFromIdentifier(artwork.snd_first, SND_CLASSIC_SUBDIR);
3483 if (artwork.snd_current == NULL)
3484 artwork.snd_current = getFirstValidTreeInfoEntry(artwork.snd_first);
3487 if (!strEqual(artwork.mus_current_identifier, setup.music_set))
3489 artwork.mus_current =
3490 getTreeInfoFromIdentifier(artwork.mus_first, setup.music_set);
3491 if (artwork.mus_current == NULL)
3492 artwork.mus_current =
3493 getTreeInfoFromIdentifier(artwork.mus_first, MUS_CLASSIC_SUBDIR);
3494 if (artwork.mus_current == NULL)
3495 artwork.mus_current = getFirstValidTreeInfoEntry(artwork.mus_first);
3498 sortTreeInfo(&artwork.gfx_first);
3499 sortTreeInfo(&artwork.snd_first);
3500 sortTreeInfo(&artwork.mus_first);
3503 dumpTreeInfo(artwork.gfx_first, 0);
3504 dumpTreeInfo(artwork.snd_first, 0);
3505 dumpTreeInfo(artwork.mus_first, 0);
3509 static void SaveUserLevelInfo()
3511 LevelDirTree *level_info;
3516 filename = getPath2(getUserLevelDir(getLoginName()), LEVELINFO_FILENAME);
3518 if (!(file = fopen(filename, MODE_WRITE)))
3520 Error(ERR_WARN, "cannot write level info file '%s'", filename);
3525 level_info = newTreeInfo();
3527 /* always start with reliable default values */
3528 setTreeInfoToDefaults(level_info, TREE_TYPE_LEVEL_DIR);
3530 setString(&level_info->name, getLoginName());
3531 setString(&level_info->author, getRealName());
3532 level_info->levels = 100;
3533 level_info->first_level = 1;
3535 token_value_position = TOKEN_VALUE_POSITION_SHORT;
3537 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER,
3538 getCookie("LEVELINFO")));
3541 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
3543 if (i == LEVELINFO_TOKEN_NAME ||
3544 i == LEVELINFO_TOKEN_AUTHOR ||
3545 i == LEVELINFO_TOKEN_LEVELS ||
3546 i == LEVELINFO_TOKEN_FIRST_LEVEL)
3547 fprintf(file, "%s\n", getSetupLine(levelinfo_tokens, "", i));
3549 /* just to make things nicer :) */
3550 if (i == LEVELINFO_TOKEN_AUTHOR)
3551 fprintf(file, "\n");
3554 token_value_position = TOKEN_VALUE_POSITION_DEFAULT;
3558 SetFilePermissions(filename, PERMS_PRIVATE);
3560 freeTreeInfo(level_info);
3564 char *getSetupValue(int type, void *value)
3566 static char value_string[MAX_LINE_LEN];
3574 strcpy(value_string, (*(boolean *)value ? "true" : "false"));
3578 strcpy(value_string, (*(boolean *)value ? "on" : "off"));
3582 strcpy(value_string, (*(boolean *)value ? "yes" : "no"));
3586 strcpy(value_string, (*(boolean *)value ? "AGA" : "ECS"));
3590 strcpy(value_string, getKeyNameFromKey(*(Key *)value));
3594 strcpy(value_string, getX11KeyNameFromKey(*(Key *)value));
3598 sprintf(value_string, "%d", *(int *)value);
3602 if (*(char **)value == NULL)
3605 strcpy(value_string, *(char **)value);
3609 value_string[0] = '\0';
3613 if (type & TYPE_GHOSTED)
3614 strcpy(value_string, "n/a");
3616 return value_string;
3619 char *getSetupLine(struct TokenInfo *token_info, char *prefix, int token_nr)
3623 static char token_string[MAX_LINE_LEN];
3624 int token_type = token_info[token_nr].type;
3625 void *setup_value = token_info[token_nr].value;
3626 char *token_text = token_info[token_nr].text;
3627 char *value_string = getSetupValue(token_type, setup_value);
3629 /* build complete token string */
3630 sprintf(token_string, "%s%s", prefix, token_text);
3632 /* build setup entry line */
3633 line = getFormattedSetupEntry(token_string, value_string);
3635 if (token_type == TYPE_KEY_X11)
3637 Key key = *(Key *)setup_value;
3638 char *keyname = getKeyNameFromKey(key);
3640 /* add comment, if useful */
3641 if (!strEqual(keyname, "(undefined)") &&
3642 !strEqual(keyname, "(unknown)"))
3644 /* add at least one whitespace */
3646 for (i = strlen(line); i < token_comment_position; i++)
3650 strcat(line, keyname);
3657 void LoadLevelSetup_LastSeries()
3659 /* ----------------------------------------------------------------------- */
3660 /* ~/.<program>/levelsetup.conf */
3661 /* ----------------------------------------------------------------------- */
3663 char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
3664 SetupFileHash *level_setup_hash = NULL;
3666 /* always start with reliable default values */
3667 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
3669 if ((level_setup_hash = loadSetupFileHash(filename)))
3671 char *last_level_series =
3672 getHashEntry(level_setup_hash, TOKEN_STR_LAST_LEVEL_SERIES);
3674 leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
3676 if (leveldir_current == NULL)
3677 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
3679 checkSetupFileHashIdentifier(level_setup_hash, filename,
3680 getCookie("LEVELSETUP"));
3682 freeSetupFileHash(level_setup_hash);
3685 Error(ERR_WARN, "using default setup values");
3690 void SaveLevelSetup_LastSeries()
3692 /* ----------------------------------------------------------------------- */
3693 /* ~/.<program>/levelsetup.conf */
3694 /* ----------------------------------------------------------------------- */
3696 char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
3697 char *level_subdir = leveldir_current->subdir;
3700 InitUserDataDirectory();
3702 if (!(file = fopen(filename, MODE_WRITE)))
3704 Error(ERR_WARN, "cannot write setup file '%s'", filename);
3709 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER,
3710 getCookie("LEVELSETUP")));
3711 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_LEVEL_SERIES,
3716 SetFilePermissions(filename, PERMS_PRIVATE);
3721 static void checkSeriesInfo()
3723 static char *level_directory = NULL;
3725 struct dirent *dir_entry;
3727 /* check for more levels besides the 'levels' field of 'levelinfo.conf' */
3729 level_directory = getPath2((leveldir_current->in_user_dir ?
3730 getUserLevelDir(NULL) :
3731 options.level_directory),
3732 leveldir_current->fullpath);
3734 if ((dir = opendir(level_directory)) == NULL)
3736 Error(ERR_WARN, "cannot read level directory '%s'", level_directory);
3740 while ((dir_entry = readdir(dir)) != NULL) /* last directory entry */
3742 if (strlen(dir_entry->d_name) > 4 &&
3743 dir_entry->d_name[3] == '.' &&
3744 strEqual(&dir_entry->d_name[4], LEVELFILE_EXTENSION))
3746 char levelnum_str[4];
3749 strncpy(levelnum_str, dir_entry->d_name, 3);
3750 levelnum_str[3] = '\0';
3752 levelnum_value = atoi(levelnum_str);
3755 if (levelnum_value < leveldir_current->first_level)
3757 Error(ERR_WARN, "additional level %d found", levelnum_value);
3758 leveldir_current->first_level = levelnum_value;
3760 else if (levelnum_value > leveldir_current->last_level)
3762 Error(ERR_WARN, "additional level %d found", levelnum_value);
3763 leveldir_current->last_level = levelnum_value;
3772 void LoadLevelSetup_SeriesInfo()
3775 SetupFileHash *level_setup_hash = NULL;
3776 char *level_subdir = leveldir_current->subdir;
3778 /* always start with reliable default values */
3779 level_nr = leveldir_current->first_level;
3781 checkSeriesInfo(leveldir_current);
3783 /* ----------------------------------------------------------------------- */
3784 /* ~/.<program>/levelsetup/<level series>/levelsetup.conf */
3785 /* ----------------------------------------------------------------------- */
3787 level_subdir = leveldir_current->subdir;
3789 filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
3791 if ((level_setup_hash = loadSetupFileHash(filename)))
3795 token_value = getHashEntry(level_setup_hash, TOKEN_STR_LAST_PLAYED_LEVEL);
3799 level_nr = atoi(token_value);
3801 if (level_nr < leveldir_current->first_level)
3802 level_nr = leveldir_current->first_level;
3803 if (level_nr > leveldir_current->last_level)
3804 level_nr = leveldir_current->last_level;
3807 token_value = getHashEntry(level_setup_hash, TOKEN_STR_HANDICAP_LEVEL);
3811 int level_nr = atoi(token_value);
3813 if (level_nr < leveldir_current->first_level)
3814 level_nr = leveldir_current->first_level;
3815 if (level_nr > leveldir_current->last_level + 1)
3816 level_nr = leveldir_current->last_level;
3818 if (leveldir_current->user_defined || !leveldir_current->handicap)
3819 level_nr = leveldir_current->last_level;
3821 leveldir_current->handicap_level = level_nr;
3824 checkSetupFileHashIdentifier(level_setup_hash, filename,
3825 getCookie("LEVELSETUP"));
3827 freeSetupFileHash(level_setup_hash);
3830 Error(ERR_WARN, "using default setup values");
3835 void SaveLevelSetup_SeriesInfo()
3838 char *level_subdir = leveldir_current->subdir;
3839 char *level_nr_str = int2str(level_nr, 0);
3840 char *handicap_level_str = int2str(leveldir_current->handicap_level, 0);
3843 /* ----------------------------------------------------------------------- */
3844 /* ~/.<program>/levelsetup/<level series>/levelsetup.conf */
3845 /* ----------------------------------------------------------------------- */
3847 InitLevelSetupDirectory(level_subdir);
3849 filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
3851 if (!(file = fopen(filename, MODE_WRITE)))
3853 Error(ERR_WARN, "cannot write setup file '%s'", filename);
3858 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER,
3859 getCookie("LEVELSETUP")));
3860 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_PLAYED_LEVEL,
3862 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_HANDICAP_LEVEL,
3863 handicap_level_str));
3867 SetFilePermissions(filename, PERMS_PRIVATE);