1 /***********************************************************
2 * Artsoft Retro-Game Library *
3 *----------------------------------------------------------*
4 * (c) 1994-2006 Artsoft Entertainment *
6 * Detmolder Strasse 189 *
9 * e-mail: info@artsoft.org *
10 *----------------------------------------------------------*
12 ***********************************************************/
14 #include <sys/types.h>
22 #if !defined(PLATFORM_WIN32)
24 #include <sys/param.h>
34 #define NUM_LEVELCLASS_DESC 8
36 static char *levelclass_desc[NUM_LEVELCLASS_DESC] =
49 #define LEVELCOLOR(n) (IS_LEVELCLASS_TUTORIAL(n) ? FC_BLUE : \
50 IS_LEVELCLASS_CLASSICS(n) ? FC_RED : \
51 IS_LEVELCLASS_BD(n) ? FC_YELLOW : \
52 IS_LEVELCLASS_EM(n) ? FC_YELLOW : \
53 IS_LEVELCLASS_SP(n) ? FC_YELLOW : \
54 IS_LEVELCLASS_DX(n) ? FC_YELLOW : \
55 IS_LEVELCLASS_SB(n) ? FC_YELLOW : \
56 IS_LEVELCLASS_CONTRIB(n) ? FC_GREEN : \
57 IS_LEVELCLASS_PRIVATE(n) ? FC_RED : \
60 #define LEVELSORTING(n) (IS_LEVELCLASS_TUTORIAL(n) ? 0 : \
61 IS_LEVELCLASS_CLASSICS(n) ? 1 : \
62 IS_LEVELCLASS_BD(n) ? 2 : \
63 IS_LEVELCLASS_EM(n) ? 3 : \
64 IS_LEVELCLASS_SP(n) ? 4 : \
65 IS_LEVELCLASS_DX(n) ? 5 : \
66 IS_LEVELCLASS_SB(n) ? 6 : \
67 IS_LEVELCLASS_CONTRIB(n) ? 7 : \
68 IS_LEVELCLASS_PRIVATE(n) ? 8 : \
71 #define ARTWORKCOLOR(n) (IS_ARTWORKCLASS_CLASSICS(n) ? FC_RED : \
72 IS_ARTWORKCLASS_CONTRIB(n) ? FC_GREEN : \
73 IS_ARTWORKCLASS_PRIVATE(n) ? FC_RED : \
74 IS_ARTWORKCLASS_LEVEL(n) ? FC_YELLOW : \
77 #define ARTWORKSORTING(n) (IS_ARTWORKCLASS_CLASSICS(n) ? 0 : \
78 IS_ARTWORKCLASS_LEVEL(n) ? 1 : \
79 IS_ARTWORKCLASS_CONTRIB(n) ? 2 : \
80 IS_ARTWORKCLASS_PRIVATE(n) ? 3 : \
83 #define TOKEN_VALUE_POSITION_SHORT 32
84 #define TOKEN_VALUE_POSITION_DEFAULT 40
85 #define TOKEN_COMMENT_POSITION_DEFAULT 60
87 #define MAX_COOKIE_LEN 256
90 static void setTreeInfoToDefaults(TreeInfo *, int);
91 static TreeInfo *getTreeInfoCopy(TreeInfo *ti);
92 static int compareTreeInfoEntries(const void *, const void *);
94 static int token_value_position = TOKEN_VALUE_POSITION_DEFAULT;
95 static int token_comment_position = TOKEN_COMMENT_POSITION_DEFAULT;
97 static SetupFileHash *artworkinfo_cache_old = NULL;
98 static SetupFileHash *artworkinfo_cache_new = NULL;
99 static boolean use_artworkinfo_cache = TRUE;
102 /* ------------------------------------------------------------------------- */
104 /* ------------------------------------------------------------------------- */
106 static char *getLevelClassDescription(TreeInfo *ti)
108 int position = ti->sort_priority / 100;
110 if (position >= 0 && position < NUM_LEVELCLASS_DESC)
111 return levelclass_desc[position];
113 return "Unknown Level Class";
116 static char *getUserLevelDir(char *level_subdir)
118 static char *userlevel_dir = NULL;
119 char *data_dir = getUserGameDataDir();
120 char *userlevel_subdir = LEVELS_DIRECTORY;
122 checked_free(userlevel_dir);
124 if (level_subdir != NULL)
125 userlevel_dir = getPath3(data_dir, userlevel_subdir, level_subdir);
127 userlevel_dir = getPath2(data_dir, userlevel_subdir);
129 return userlevel_dir;
132 static char *getScoreDir(char *level_subdir)
134 static char *score_dir = NULL;
135 char *data_dir = getCommonDataDir();
136 char *score_subdir = SCORES_DIRECTORY;
138 checked_free(score_dir);
140 if (level_subdir != NULL)
141 score_dir = getPath3(data_dir, score_subdir, level_subdir);
143 score_dir = getPath2(data_dir, score_subdir);
148 static char *getLevelSetupDir(char *level_subdir)
150 static char *levelsetup_dir = NULL;
151 char *data_dir = getUserGameDataDir();
152 char *levelsetup_subdir = LEVELSETUP_DIRECTORY;
154 checked_free(levelsetup_dir);
156 if (level_subdir != NULL)
157 levelsetup_dir = getPath3(data_dir, levelsetup_subdir, level_subdir);
159 levelsetup_dir = getPath2(data_dir, levelsetup_subdir);
161 return levelsetup_dir;
164 static char *getCacheDir()
166 static char *cache_dir = NULL;
168 if (cache_dir == NULL)
169 cache_dir = getPath2(getUserGameDataDir(), CACHE_DIRECTORY);
174 static char *getLevelDirFromTreeInfo(TreeInfo *node)
176 static char *level_dir = NULL;
179 return options.level_directory;
181 checked_free(level_dir);
183 level_dir = getPath2((node->in_user_dir ? getUserLevelDir(NULL) :
184 options.level_directory), node->fullpath);
189 char *getCurrentLevelDir()
191 return getLevelDirFromTreeInfo(leveldir_current);
194 static char *getTapeDir(char *level_subdir)
196 static char *tape_dir = NULL;
197 char *data_dir = getUserGameDataDir();
198 char *tape_subdir = TAPES_DIRECTORY;
200 checked_free(tape_dir);
202 if (level_subdir != NULL)
203 tape_dir = getPath3(data_dir, tape_subdir, level_subdir);
205 tape_dir = getPath2(data_dir, tape_subdir);
210 static char *getSolutionTapeDir()
212 static char *tape_dir = NULL;
213 char *data_dir = getCurrentLevelDir();
214 char *tape_subdir = TAPES_DIRECTORY;
216 checked_free(tape_dir);
218 tape_dir = getPath2(data_dir, tape_subdir);
223 static char *getDefaultGraphicsDir(char *graphics_subdir)
225 static char *graphics_dir = NULL;
227 if (graphics_subdir == NULL)
228 return options.graphics_directory;
230 checked_free(graphics_dir);
232 graphics_dir = getPath2(options.graphics_directory, graphics_subdir);
237 static char *getDefaultSoundsDir(char *sounds_subdir)
239 static char *sounds_dir = NULL;
241 if (sounds_subdir == NULL)
242 return options.sounds_directory;
244 checked_free(sounds_dir);
246 sounds_dir = getPath2(options.sounds_directory, sounds_subdir);
251 static char *getDefaultMusicDir(char *music_subdir)
253 static char *music_dir = NULL;
255 if (music_subdir == NULL)
256 return options.music_directory;
258 checked_free(music_dir);
260 music_dir = getPath2(options.music_directory, music_subdir);
265 static char *getDefaultArtworkSet(int type)
267 return (type == TREE_TYPE_GRAPHICS_DIR ? GFX_CLASSIC_SUBDIR :
268 type == TREE_TYPE_SOUNDS_DIR ? SND_CLASSIC_SUBDIR :
269 type == TREE_TYPE_MUSIC_DIR ? MUS_CLASSIC_SUBDIR : "");
272 static char *getDefaultArtworkDir(int type)
274 return (type == TREE_TYPE_GRAPHICS_DIR ?
275 getDefaultGraphicsDir(GFX_CLASSIC_SUBDIR) :
276 type == TREE_TYPE_SOUNDS_DIR ?
277 getDefaultSoundsDir(SND_CLASSIC_SUBDIR) :
278 type == TREE_TYPE_MUSIC_DIR ?
279 getDefaultMusicDir(MUS_CLASSIC_SUBDIR) : "");
282 static char *getUserGraphicsDir()
284 static char *usergraphics_dir = NULL;
286 if (usergraphics_dir == NULL)
287 usergraphics_dir = getPath2(getUserGameDataDir(), GRAPHICS_DIRECTORY);
289 return usergraphics_dir;
292 static char *getUserSoundsDir()
294 static char *usersounds_dir = NULL;
296 if (usersounds_dir == NULL)
297 usersounds_dir = getPath2(getUserGameDataDir(), SOUNDS_DIRECTORY);
299 return usersounds_dir;
302 static char *getUserMusicDir()
304 static char *usermusic_dir = NULL;
306 if (usermusic_dir == NULL)
307 usermusic_dir = getPath2(getUserGameDataDir(), MUSIC_DIRECTORY);
309 return usermusic_dir;
312 static char *getSetupArtworkDir(TreeInfo *ti)
314 static char *artwork_dir = NULL;
316 checked_free(artwork_dir);
318 artwork_dir = getPath2(ti->basepath, ti->fullpath);
323 char *setLevelArtworkDir(TreeInfo *ti)
325 char **artwork_path_ptr, **artwork_set_ptr;
326 TreeInfo *level_artwork;
328 if (ti == NULL || leveldir_current == NULL)
331 artwork_path_ptr = LEVELDIR_ARTWORK_PATH_PTR(leveldir_current, ti->type);
332 artwork_set_ptr = LEVELDIR_ARTWORK_SET_PTR( leveldir_current, ti->type);
334 checked_free(*artwork_path_ptr);
336 if ((level_artwork = getTreeInfoFromIdentifier(ti, *artwork_set_ptr)))
337 *artwork_path_ptr = getStringCopy(getSetupArtworkDir(level_artwork));
340 /* No (or non-existing) artwork configured in "levelinfo.conf". This would
341 normally result in using the artwork configured in the setup menu. But
342 if an artwork subdirectory exists (which might contain custom artwork
343 or an artwork configuration file), this level artwork must be treated
344 as relative to the default "classic" artwork, not to the artwork that
345 is currently configured in the setup menu. */
347 char *dir = getPath2(getCurrentLevelDir(), ARTWORK_DIRECTORY(ti->type));
349 checked_free(*artwork_set_ptr);
353 *artwork_path_ptr = getStringCopy(getDefaultArtworkDir(ti->type));
354 *artwork_set_ptr = getStringCopy(getDefaultArtworkSet(ti->type));
358 *artwork_path_ptr = getStringCopy(UNDEFINED_FILENAME);
359 *artwork_set_ptr = NULL;
365 return *artwork_set_ptr;
368 inline static char *getLevelArtworkSet(int type)
370 if (leveldir_current == NULL)
373 return LEVELDIR_ARTWORK_SET(leveldir_current, type);
376 inline static char *getLevelArtworkDir(int type)
378 if (leveldir_current == NULL)
379 return UNDEFINED_FILENAME;
381 return LEVELDIR_ARTWORK_PATH(leveldir_current, type);
384 char *getTapeFilename(int nr)
386 static char *filename = NULL;
387 char basename[MAX_FILENAME_LEN];
389 checked_free(filename);
391 sprintf(basename, "%03d.%s", nr, TAPEFILE_EXTENSION);
392 filename = getPath2(getTapeDir(leveldir_current->subdir), basename);
397 char *getSolutionTapeFilename(int nr)
399 static char *filename = NULL;
400 char basename[MAX_FILENAME_LEN];
402 checked_free(filename);
404 sprintf(basename, "%03d.%s", nr, TAPEFILE_EXTENSION);
405 filename = getPath2(getSolutionTapeDir(), basename);
410 char *getScoreFilename(int nr)
412 static char *filename = NULL;
413 char basename[MAX_FILENAME_LEN];
415 checked_free(filename);
417 sprintf(basename, "%03d.%s", nr, SCOREFILE_EXTENSION);
418 filename = getPath2(getScoreDir(leveldir_current->subdir), basename);
423 char *getSetupFilename()
425 static char *filename = NULL;
427 checked_free(filename);
429 filename = getPath2(getSetupDir(), SETUP_FILENAME);
434 char *getEditorSetupFilename()
436 static char *filename = NULL;
438 checked_free(filename);
439 filename = getPath2(getCurrentLevelDir(), EDITORSETUP_FILENAME);
441 if (fileExists(filename))
444 checked_free(filename);
445 filename = getPath2(getSetupDir(), EDITORSETUP_FILENAME);
450 char *getHelpAnimFilename()
452 static char *filename = NULL;
454 checked_free(filename);
456 filename = getPath2(getCurrentLevelDir(), HELPANIM_FILENAME);
461 char *getHelpTextFilename()
463 static char *filename = NULL;
465 checked_free(filename);
467 filename = getPath2(getCurrentLevelDir(), HELPTEXT_FILENAME);
472 char *getLevelSetInfoFilename()
474 static char *filename = NULL;
489 for (i = 0; basenames[i] != NULL; i++)
491 checked_free(filename);
492 filename = getPath2(getCurrentLevelDir(), basenames[i]);
494 if (fileExists(filename))
501 char *getLevelSetTitleMessageFilename(int nr, boolean initial)
503 static char *filename = NULL;
506 sprintf(basename, "%s_%d.txt",
507 (initial ? "titlemessage_initial" : "titlemessage"), nr + 1);
509 checked_free(filename);
510 filename = getPath2(getCurrentLevelDir(), basename);
512 if (fileExists(filename))
518 static char *getCorrectedArtworkBasename(char *basename)
520 char *basename_corrected = basename;
522 #if defined(PLATFORM_MSDOS)
523 if (program.filename_prefix != NULL)
525 int prefix_len = strlen(program.filename_prefix);
527 if (strncmp(basename, program.filename_prefix, prefix_len) == 0)
528 basename_corrected = &basename[prefix_len];
530 /* if corrected filename is still longer than standard MS-DOS filename
531 size (8 characters + 1 dot + 3 characters file extension), shorten
532 filename by writing file extension after 8th basename character */
533 if (strlen(basename_corrected) > 8 + 1 + 3)
535 static char *msdos_filename = NULL;
537 checked_free(msdos_filename);
539 msdos_filename = getStringCopy(basename_corrected);
540 strncpy(&msdos_filename[8], &basename[strlen(basename) - (1+3)], 1+3 +1);
542 basename_corrected = msdos_filename;
547 return basename_corrected;
550 char *getCustomImageFilename(char *basename)
552 static char *filename = NULL;
553 boolean skip_setup_artwork = FALSE;
555 checked_free(filename);
557 basename = getCorrectedArtworkBasename(basename);
559 if (!setup.override_level_graphics)
561 /* 1st try: look for special artwork in current level series directory */
562 filename = getPath3(getCurrentLevelDir(), GRAPHICS_DIRECTORY, basename);
563 if (fileExists(filename))
568 /* check if there is special artwork configured in level series config */
569 if (getLevelArtworkSet(ARTWORK_TYPE_GRAPHICS) != NULL)
571 /* 2nd try: look for special artwork configured in level series config */
572 filename = getPath2(getLevelArtworkDir(ARTWORK_TYPE_GRAPHICS), basename);
573 if (fileExists(filename))
578 /* take missing artwork configured in level set config from default */
579 skip_setup_artwork = TRUE;
583 if (!skip_setup_artwork)
585 /* 3rd try: look for special artwork in configured artwork directory */
586 filename = getPath2(getSetupArtworkDir(artwork.gfx_current), basename);
587 if (fileExists(filename))
593 /* 4th try: look for default artwork in new default artwork directory */
594 filename = getPath2(getDefaultGraphicsDir(GFX_CLASSIC_SUBDIR), basename);
595 if (fileExists(filename))
600 /* 5th try: look for default artwork in old default artwork directory */
601 filename = getPath2(options.graphics_directory, basename);
602 if (fileExists(filename))
605 return NULL; /* cannot find specified artwork file anywhere */
608 char *getCustomSoundFilename(char *basename)
610 static char *filename = NULL;
611 boolean skip_setup_artwork = FALSE;
613 checked_free(filename);
615 basename = getCorrectedArtworkBasename(basename);
617 if (!setup.override_level_sounds)
619 /* 1st try: look for special artwork in current level series directory */
620 filename = getPath3(getCurrentLevelDir(), SOUNDS_DIRECTORY, basename);
621 if (fileExists(filename))
626 /* check if there is special artwork configured in level series config */
627 if (getLevelArtworkSet(ARTWORK_TYPE_SOUNDS) != NULL)
629 /* 2nd try: look for special artwork configured in level series config */
630 filename = getPath2(getLevelArtworkDir(TREE_TYPE_SOUNDS_DIR), basename);
631 if (fileExists(filename))
636 /* take missing artwork configured in level set config from default */
637 skip_setup_artwork = TRUE;
641 if (!skip_setup_artwork)
643 /* 3rd try: look for special artwork in configured artwork directory */
644 filename = getPath2(getSetupArtworkDir(artwork.snd_current), basename);
645 if (fileExists(filename))
651 /* 4th try: look for default artwork in new default artwork directory */
652 filename = getPath2(getDefaultSoundsDir(SND_CLASSIC_SUBDIR), basename);
653 if (fileExists(filename))
658 /* 5th try: look for default artwork in old default artwork directory */
659 filename = getPath2(options.sounds_directory, basename);
660 if (fileExists(filename))
663 return NULL; /* cannot find specified artwork file anywhere */
666 char *getCustomMusicFilename(char *basename)
668 static char *filename = NULL;
669 boolean skip_setup_artwork = FALSE;
671 checked_free(filename);
673 basename = getCorrectedArtworkBasename(basename);
675 if (!setup.override_level_music)
677 /* 1st try: look for special artwork in current level series directory */
678 filename = getPath3(getCurrentLevelDir(), MUSIC_DIRECTORY, basename);
679 if (fileExists(filename))
684 /* check if there is special artwork configured in level series config */
685 if (getLevelArtworkSet(ARTWORK_TYPE_MUSIC) != NULL)
687 /* 2nd try: look for special artwork configured in level series config */
688 filename = getPath2(getLevelArtworkDir(TREE_TYPE_MUSIC_DIR), basename);
689 if (fileExists(filename))
694 /* take missing artwork configured in level set config from default */
695 skip_setup_artwork = TRUE;
699 if (!skip_setup_artwork)
701 /* 3rd try: look for special artwork in configured artwork directory */
702 filename = getPath2(getSetupArtworkDir(artwork.mus_current), basename);
703 if (fileExists(filename))
709 /* 4th try: look for default artwork in new default artwork directory */
710 filename = getPath2(getDefaultMusicDir(MUS_CLASSIC_SUBDIR), basename);
711 if (fileExists(filename))
716 /* 5th try: look for default artwork in old default artwork directory */
717 filename = getPath2(options.music_directory, basename);
718 if (fileExists(filename))
721 return NULL; /* cannot find specified artwork file anywhere */
724 char *getCustomArtworkFilename(char *basename, int type)
726 if (type == ARTWORK_TYPE_GRAPHICS)
727 return getCustomImageFilename(basename);
728 else if (type == ARTWORK_TYPE_SOUNDS)
729 return getCustomSoundFilename(basename);
730 else if (type == ARTWORK_TYPE_MUSIC)
731 return getCustomMusicFilename(basename);
733 return UNDEFINED_FILENAME;
736 char *getCustomArtworkConfigFilename(int type)
738 return getCustomArtworkFilename(ARTWORKINFO_FILENAME(type), type);
741 char *getCustomArtworkLevelConfigFilename(int type)
743 static char *filename = NULL;
745 checked_free(filename);
747 filename = getPath2(getLevelArtworkDir(type), ARTWORKINFO_FILENAME(type));
752 char *getCustomMusicDirectory(void)
754 static char *directory = NULL;
755 boolean skip_setup_artwork = FALSE;
757 checked_free(directory);
759 if (!setup.override_level_music)
761 /* 1st try: look for special artwork in current level series directory */
762 directory = getPath2(getCurrentLevelDir(), MUSIC_DIRECTORY);
763 if (fileExists(directory))
768 /* check if there is special artwork configured in level series config */
769 if (getLevelArtworkSet(ARTWORK_TYPE_MUSIC) != NULL)
771 /* 2nd try: look for special artwork configured in level series config */
772 directory = getStringCopy(getLevelArtworkDir(TREE_TYPE_MUSIC_DIR));
773 if (fileExists(directory))
778 /* take missing artwork configured in level set config from default */
779 skip_setup_artwork = TRUE;
783 if (!skip_setup_artwork)
785 /* 3rd try: look for special artwork in configured artwork directory */
786 directory = getStringCopy(getSetupArtworkDir(artwork.mus_current));
787 if (fileExists(directory))
793 /* 4th try: look for default artwork in new default artwork directory */
794 directory = getStringCopy(getDefaultMusicDir(MUS_CLASSIC_SUBDIR));
795 if (fileExists(directory))
800 /* 5th try: look for default artwork in old default artwork directory */
801 directory = getStringCopy(options.music_directory);
802 if (fileExists(directory))
805 return NULL; /* cannot find specified artwork file anywhere */
808 void InitTapeDirectory(char *level_subdir)
810 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
811 createDirectory(getTapeDir(NULL), "main tape", PERMS_PRIVATE);
812 createDirectory(getTapeDir(level_subdir), "level tape", PERMS_PRIVATE);
815 void InitScoreDirectory(char *level_subdir)
817 createDirectory(getCommonDataDir(), "common data", PERMS_PUBLIC);
818 createDirectory(getScoreDir(NULL), "main score", PERMS_PUBLIC);
819 createDirectory(getScoreDir(level_subdir), "level score", PERMS_PUBLIC);
822 static void SaveUserLevelInfo();
824 void InitUserLevelDirectory(char *level_subdir)
826 if (!fileExists(getUserLevelDir(level_subdir)))
828 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
829 createDirectory(getUserLevelDir(NULL), "main user level", PERMS_PRIVATE);
830 createDirectory(getUserLevelDir(level_subdir), "user level", PERMS_PRIVATE);
836 void InitLevelSetupDirectory(char *level_subdir)
838 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
839 createDirectory(getLevelSetupDir(NULL), "main level setup", PERMS_PRIVATE);
840 createDirectory(getLevelSetupDir(level_subdir), "level setup", PERMS_PRIVATE);
843 void InitCacheDirectory()
845 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
846 createDirectory(getCacheDir(), "cache data", PERMS_PRIVATE);
850 /* ------------------------------------------------------------------------- */
851 /* some functions to handle lists of level and artwork directories */
852 /* ------------------------------------------------------------------------- */
854 TreeInfo *newTreeInfo()
856 return checked_calloc(sizeof(TreeInfo));
859 TreeInfo *newTreeInfo_setDefaults(int type)
861 TreeInfo *ti = newTreeInfo();
863 setTreeInfoToDefaults(ti, type);
868 void pushTreeInfo(TreeInfo **node_first, TreeInfo *node_new)
870 node_new->next = *node_first;
871 *node_first = node_new;
874 int numTreeInfo(TreeInfo *node)
887 boolean validLevelSeries(TreeInfo *node)
889 return (node != NULL && !node->node_group && !node->parent_link);
892 TreeInfo *getFirstValidTreeInfoEntry(TreeInfo *node)
897 if (node->node_group) /* enter level group (step down into tree) */
898 return getFirstValidTreeInfoEntry(node->node_group);
899 else if (node->parent_link) /* skip start entry of level group */
901 if (node->next) /* get first real level series entry */
902 return getFirstValidTreeInfoEntry(node->next);
903 else /* leave empty level group and go on */
904 return getFirstValidTreeInfoEntry(node->node_parent->next);
906 else /* this seems to be a regular level series */
910 TreeInfo *getTreeInfoFirstGroupEntry(TreeInfo *node)
915 if (node->node_parent == NULL) /* top level group */
916 return *node->node_top;
917 else /* sub level group */
918 return node->node_parent->node_group;
921 int numTreeInfoInGroup(TreeInfo *node)
923 return numTreeInfo(getTreeInfoFirstGroupEntry(node));
926 int posTreeInfo(TreeInfo *node)
928 TreeInfo *node_cmp = getTreeInfoFirstGroupEntry(node);
933 if (node_cmp == node)
937 node_cmp = node_cmp->next;
943 TreeInfo *getTreeInfoFromPos(TreeInfo *node, int pos)
945 TreeInfo *node_default = node;
960 TreeInfo *getTreeInfoFromIdentifier(TreeInfo *node, char *identifier)
962 if (identifier == NULL)
967 if (node->node_group)
969 TreeInfo *node_group;
971 node_group = getTreeInfoFromIdentifier(node->node_group, identifier);
976 else if (!node->parent_link)
978 if (strEqual(identifier, node->identifier))
988 TreeInfo *cloneTreeNode(TreeInfo **node_top, TreeInfo *node_parent,
989 TreeInfo *node, boolean skip_sets_without_levels)
996 if (!node->parent_link && !node->level_group &&
997 skip_sets_without_levels && node->levels == 0)
998 return cloneTreeNode(node_top, node_parent, node->next,
999 skip_sets_without_levels);
1002 node_new = getTreeInfoCopy(node); /* copy complete node */
1004 node_new = newTreeInfo();
1006 *node_new = *node; /* copy complete node */
1009 node_new->node_top = node_top; /* correct top node link */
1010 node_new->node_parent = node_parent; /* correct parent node link */
1012 if (node->level_group)
1013 node_new->node_group = cloneTreeNode(node_top, node_new, node->node_group,
1014 skip_sets_without_levels);
1016 node_new->next = cloneTreeNode(node_top, node_parent, node->next,
1017 skip_sets_without_levels);
1022 void cloneTree(TreeInfo **ti_new, TreeInfo *ti, boolean skip_empty_sets)
1024 TreeInfo *ti_cloned = cloneTreeNode(ti_new, NULL, ti, skip_empty_sets);
1026 *ti_new = ti_cloned;
1029 static boolean adjustTreeGraphicsForEMC(TreeInfo *node)
1031 boolean settings_changed = FALSE;
1035 if (node->graphics_set_ecs && !setup.prefer_aga_graphics &&
1036 !strEqual(node->graphics_set, node->graphics_set_ecs))
1038 setString(&node->graphics_set, node->graphics_set_ecs);
1039 settings_changed = TRUE;
1041 else if (node->graphics_set_aga && setup.prefer_aga_graphics &&
1042 !strEqual(node->graphics_set, node->graphics_set_aga))
1044 setString(&node->graphics_set, node->graphics_set_aga);
1045 settings_changed = TRUE;
1048 if (node->node_group != NULL)
1049 settings_changed |= adjustTreeGraphicsForEMC(node->node_group);
1054 return settings_changed;
1057 void dumpTreeInfo(TreeInfo *node, int depth)
1061 printf("Dumping TreeInfo:\n");
1065 for (i = 0; i < (depth + 1) * 3; i++)
1068 printf("subdir == '%s' ['%s', '%s'] [%d])\n",
1069 node->subdir, node->fullpath, node->basepath, node->in_user_dir);
1071 if (node->node_group != NULL)
1072 dumpTreeInfo(node->node_group, depth + 1);
1078 void sortTreeInfoBySortFunction(TreeInfo **node_first,
1079 int (*compare_function)(const void *,
1082 int num_nodes = numTreeInfo(*node_first);
1083 TreeInfo **sort_array;
1084 TreeInfo *node = *node_first;
1090 /* allocate array for sorting structure pointers */
1091 sort_array = checked_calloc(num_nodes * sizeof(TreeInfo *));
1093 /* writing structure pointers to sorting array */
1094 while (i < num_nodes && node) /* double boundary check... */
1096 sort_array[i] = node;
1102 /* sorting the structure pointers in the sorting array */
1103 qsort(sort_array, num_nodes, sizeof(TreeInfo *),
1106 /* update the linkage of list elements with the sorted node array */
1107 for (i = 0; i < num_nodes - 1; i++)
1108 sort_array[i]->next = sort_array[i + 1];
1109 sort_array[num_nodes - 1]->next = NULL;
1111 /* update the linkage of the main list anchor pointer */
1112 *node_first = sort_array[0];
1116 /* now recursively sort the level group structures */
1120 if (node->node_group != NULL)
1121 sortTreeInfoBySortFunction(&node->node_group, compare_function);
1127 void sortTreeInfo(TreeInfo **node_first)
1129 sortTreeInfoBySortFunction(node_first, compareTreeInfoEntries);
1133 /* ========================================================================= */
1134 /* some stuff from "files.c" */
1135 /* ========================================================================= */
1137 #if defined(PLATFORM_WIN32)
1139 #define S_IRGRP S_IRUSR
1142 #define S_IROTH S_IRUSR
1145 #define S_IWGRP S_IWUSR
1148 #define S_IWOTH S_IWUSR
1151 #define S_IXGRP S_IXUSR
1154 #define S_IXOTH S_IXUSR
1157 #define S_IRWXG (S_IRGRP | S_IWGRP | S_IXGRP)
1162 #endif /* PLATFORM_WIN32 */
1164 /* file permissions for newly written files */
1165 #define MODE_R_ALL (S_IRUSR | S_IRGRP | S_IROTH)
1166 #define MODE_W_ALL (S_IWUSR | S_IWGRP | S_IWOTH)
1167 #define MODE_X_ALL (S_IXUSR | S_IXGRP | S_IXOTH)
1169 #define MODE_W_PRIVATE (S_IWUSR)
1170 #define MODE_W_PUBLIC (S_IWUSR | S_IWGRP)
1171 #define MODE_W_PUBLIC_DIR (S_IWUSR | S_IWGRP | S_ISGID)
1173 #define DIR_PERMS_PRIVATE (MODE_R_ALL | MODE_X_ALL | MODE_W_PRIVATE)
1174 #define DIR_PERMS_PUBLIC (MODE_R_ALL | MODE_X_ALL | MODE_W_PUBLIC_DIR)
1176 #define FILE_PERMS_PRIVATE (MODE_R_ALL | MODE_W_PRIVATE)
1177 #define FILE_PERMS_PUBLIC (MODE_R_ALL | MODE_W_PUBLIC)
1181 static char *dir = NULL;
1183 #if defined(PLATFORM_WIN32)
1186 dir = checked_malloc(MAX_PATH + 1);
1188 if (!SUCCEEDED(SHGetFolderPath(NULL, CSIDL_PERSONAL, NULL, 0, dir)))
1191 #elif defined(PLATFORM_UNIX)
1194 if ((dir = getenv("HOME")) == NULL)
1198 if ((pwd = getpwuid(getuid())) != NULL)
1199 dir = getStringCopy(pwd->pw_dir);
1211 char *getCommonDataDir(void)
1213 static char *common_data_dir = NULL;
1215 #if defined(PLATFORM_WIN32)
1216 if (common_data_dir == NULL)
1218 char *dir = checked_malloc(MAX_PATH + 1);
1220 if (SUCCEEDED(SHGetFolderPath(NULL, CSIDL_COMMON_DOCUMENTS, NULL, 0, dir))
1221 && !strEqual(dir, "")) /* empty for Windows 95/98 */
1222 common_data_dir = getPath2(dir, program.userdata_subdir);
1224 common_data_dir = options.rw_base_directory;
1227 if (common_data_dir == NULL)
1228 common_data_dir = options.rw_base_directory;
1231 return common_data_dir;
1234 char *getPersonalDataDir(void)
1236 static char *personal_data_dir = NULL;
1238 #if defined(PLATFORM_MACOSX)
1239 if (personal_data_dir == NULL)
1240 personal_data_dir = getPath2(getHomeDir(), "Documents");
1242 if (personal_data_dir == NULL)
1243 personal_data_dir = getHomeDir();
1246 return personal_data_dir;
1249 char *getUserGameDataDir(void)
1251 static char *user_game_data_dir = NULL;
1253 if (user_game_data_dir == NULL)
1254 user_game_data_dir = getPath2(getPersonalDataDir(),
1255 program.userdata_subdir);
1257 return user_game_data_dir;
1260 void updateUserGameDataDir()
1262 #if defined(PLATFORM_MACOSX)
1263 char *userdata_dir_old = getPath2(getHomeDir(), program.userdata_subdir_unix);
1264 char *userdata_dir_new = getUserGameDataDir(); /* do not free() this */
1266 /* convert old Unix style game data directory to Mac OS X style, if needed */
1267 if (fileExists(userdata_dir_old) && !fileExists(userdata_dir_new))
1269 if (rename(userdata_dir_old, userdata_dir_new) != 0)
1271 Error(ERR_WARN, "cannot move game data directory '%s' to '%s'",
1272 userdata_dir_old, userdata_dir_new);
1274 /* continue using Unix style data directory -- this should not happen */
1275 program.userdata_path = getPath2(getPersonalDataDir(),
1276 program.userdata_subdir_unix);
1280 free(userdata_dir_old);
1286 return getUserGameDataDir();
1289 static mode_t posix_umask(mode_t mask)
1291 #if defined(PLATFORM_UNIX)
1298 static int posix_mkdir(const char *pathname, mode_t mode)
1300 #if defined(PLATFORM_WIN32)
1301 return mkdir(pathname);
1303 return mkdir(pathname, mode);
1307 void createDirectory(char *dir, char *text, int permission_class)
1309 /* leave "other" permissions in umask untouched, but ensure group parts
1310 of USERDATA_DIR_MODE are not masked */
1311 mode_t dir_mode = (permission_class == PERMS_PRIVATE ?
1312 DIR_PERMS_PRIVATE : DIR_PERMS_PUBLIC);
1313 mode_t normal_umask = posix_umask(0);
1314 mode_t group_umask = ~(dir_mode & S_IRWXG);
1315 posix_umask(normal_umask & group_umask);
1317 if (!fileExists(dir))
1318 if (posix_mkdir(dir, dir_mode) != 0)
1319 Error(ERR_WARN, "cannot create %s directory '%s'", text, dir);
1321 posix_umask(normal_umask); /* reset normal umask */
1324 void InitUserDataDirectory()
1326 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
1329 void SetFilePermissions(char *filename, int permission_class)
1331 chmod(filename, (permission_class == PERMS_PRIVATE ?
1332 FILE_PERMS_PRIVATE : FILE_PERMS_PUBLIC));
1335 char *getCookie(char *file_type)
1337 static char cookie[MAX_COOKIE_LEN + 1];
1339 if (strlen(program.cookie_prefix) + 1 +
1340 strlen(file_type) + strlen("_FILE_VERSION_x.x") > MAX_COOKIE_LEN)
1341 return "[COOKIE ERROR]"; /* should never happen */
1343 sprintf(cookie, "%s_%s_FILE_VERSION_%d.%d",
1344 program.cookie_prefix, file_type,
1345 program.version_major, program.version_minor);
1350 int getFileVersionFromCookieString(const char *cookie)
1352 const char *ptr_cookie1, *ptr_cookie2;
1353 const char *pattern1 = "_FILE_VERSION_";
1354 const char *pattern2 = "?.?";
1355 const int len_cookie = strlen(cookie);
1356 const int len_pattern1 = strlen(pattern1);
1357 const int len_pattern2 = strlen(pattern2);
1358 const int len_pattern = len_pattern1 + len_pattern2;
1359 int version_major, version_minor;
1361 if (len_cookie <= len_pattern)
1364 ptr_cookie1 = &cookie[len_cookie - len_pattern];
1365 ptr_cookie2 = &cookie[len_cookie - len_pattern2];
1367 if (strncmp(ptr_cookie1, pattern1, len_pattern1) != 0)
1370 if (ptr_cookie2[0] < '0' || ptr_cookie2[0] > '9' ||
1371 ptr_cookie2[1] != '.' ||
1372 ptr_cookie2[2] < '0' || ptr_cookie2[2] > '9')
1375 version_major = ptr_cookie2[0] - '0';
1376 version_minor = ptr_cookie2[2] - '0';
1378 return VERSION_IDENT(version_major, version_minor, 0, 0);
1381 boolean checkCookieString(const char *cookie, const char *template)
1383 const char *pattern = "_FILE_VERSION_?.?";
1384 const int len_cookie = strlen(cookie);
1385 const int len_template = strlen(template);
1386 const int len_pattern = strlen(pattern);
1388 if (len_cookie != len_template)
1391 if (strncmp(cookie, template, len_cookie - len_pattern) != 0)
1397 /* ------------------------------------------------------------------------- */
1398 /* setup file list and hash handling functions */
1399 /* ------------------------------------------------------------------------- */
1401 char *getFormattedSetupEntry(char *token, char *value)
1404 static char entry[MAX_LINE_LEN];
1406 /* if value is an empty string, just return token without value */
1410 /* start with the token and some spaces to format output line */
1411 sprintf(entry, "%s:", token);
1412 for (i = strlen(entry); i < token_value_position; i++)
1415 /* continue with the token's value */
1416 strcat(entry, value);
1421 SetupFileList *newSetupFileList(char *token, char *value)
1423 SetupFileList *new = checked_malloc(sizeof(SetupFileList));
1425 new->token = getStringCopy(token);
1426 new->value = getStringCopy(value);
1433 void freeSetupFileList(SetupFileList *list)
1438 checked_free(list->token);
1439 checked_free(list->value);
1442 freeSetupFileList(list->next);
1447 char *getListEntry(SetupFileList *list, char *token)
1452 if (strEqual(list->token, token))
1455 return getListEntry(list->next, token);
1458 SetupFileList *setListEntry(SetupFileList *list, char *token, char *value)
1463 if (strEqual(list->token, token))
1465 checked_free(list->value);
1467 list->value = getStringCopy(value);
1471 else if (list->next == NULL)
1472 return (list->next = newSetupFileList(token, value));
1474 return setListEntry(list->next, token, value);
1477 SetupFileList *addListEntry(SetupFileList *list, char *token, char *value)
1482 if (list->next == NULL)
1483 return (list->next = newSetupFileList(token, value));
1485 return addListEntry(list->next, token, value);
1489 static void printSetupFileList(SetupFileList *list)
1494 printf("token: '%s'\n", list->token);
1495 printf("value: '%s'\n", list->value);
1497 printSetupFileList(list->next);
1502 DEFINE_HASHTABLE_INSERT(insert_hash_entry, char, char);
1503 DEFINE_HASHTABLE_SEARCH(search_hash_entry, char, char);
1504 DEFINE_HASHTABLE_CHANGE(change_hash_entry, char, char);
1505 DEFINE_HASHTABLE_REMOVE(remove_hash_entry, char, char);
1507 #define insert_hash_entry hashtable_insert
1508 #define search_hash_entry hashtable_search
1509 #define change_hash_entry hashtable_change
1510 #define remove_hash_entry hashtable_remove
1513 static unsigned int get_hash_from_key(void *key)
1518 This algorithm (k=33) was first reported by Dan Bernstein many years ago in
1519 'comp.lang.c'. Another version of this algorithm (now favored by Bernstein)
1520 uses XOR: hash(i) = hash(i - 1) * 33 ^ str[i]; the magic of number 33 (why
1521 it works better than many other constants, prime or not) has never been
1522 adequately explained.
1524 If you just want to have a good hash function, and cannot wait, djb2
1525 is one of the best string hash functions i know. It has excellent
1526 distribution and speed on many different sets of keys and table sizes.
1527 You are not likely to do better with one of the "well known" functions
1528 such as PJW, K&R, etc.
1530 Ozan (oz) Yigit [http://www.cs.yorku.ca/~oz/hash.html]
1533 char *str = (char *)key;
1534 unsigned int hash = 5381;
1537 while ((c = *str++))
1538 hash = ((hash << 5) + hash) + c; /* hash * 33 + c */
1543 static int keys_are_equal(void *key1, void *key2)
1545 return (strEqual((char *)key1, (char *)key2));
1548 SetupFileHash *newSetupFileHash()
1550 SetupFileHash *new_hash =
1551 create_hashtable(16, 0.75, get_hash_from_key, keys_are_equal);
1553 if (new_hash == NULL)
1554 Error(ERR_EXIT, "create_hashtable() failed -- out of memory");
1559 void freeSetupFileHash(SetupFileHash *hash)
1564 hashtable_destroy(hash, 1); /* 1 == also free values stored in hash */
1567 char *getHashEntry(SetupFileHash *hash, char *token)
1572 return search_hash_entry(hash, token);
1575 void setHashEntry(SetupFileHash *hash, char *token, char *value)
1582 value_copy = getStringCopy(value);
1584 /* change value; if it does not exist, insert it as new */
1585 if (!change_hash_entry(hash, token, value_copy))
1586 if (!insert_hash_entry(hash, getStringCopy(token), value_copy))
1587 Error(ERR_EXIT, "cannot insert into hash -- aborting");
1590 char *removeHashEntry(SetupFileHash *hash, char *token)
1595 return remove_hash_entry(hash, token);
1599 static void printSetupFileHash(SetupFileHash *hash)
1601 BEGIN_HASH_ITERATION(hash, itr)
1603 printf("token: '%s'\n", HASH_ITERATION_TOKEN(itr));
1604 printf("value: '%s'\n", HASH_ITERATION_VALUE(itr));
1606 END_HASH_ITERATION(hash, itr)
1610 #define ALLOW_TOKEN_VALUE_SEPARATOR_BEING_WHITESPACE 1
1611 #define CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING 0
1612 #define CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH 0
1614 static boolean token_value_separator_found = FALSE;
1615 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
1616 static boolean token_value_separator_warning = FALSE;
1618 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
1619 static boolean token_already_exists_warning = FALSE;
1622 static boolean getTokenValueFromSetupLineExt(char *line,
1623 char **token_ptr, char **value_ptr,
1624 char *filename, char *line_raw,
1626 boolean separator_required)
1628 static char line_copy[MAX_LINE_LEN + 1], line_raw_copy[MAX_LINE_LEN + 1];
1629 char *token, *value, *line_ptr;
1631 /* when externally invoked via ReadTokenValueFromLine(), copy line buffers */
1632 if (line_raw == NULL)
1634 strncpy(line_copy, line, MAX_LINE_LEN);
1635 line_copy[MAX_LINE_LEN] = '\0';
1638 strcpy(line_raw_copy, line_copy);
1639 line_raw = line_raw_copy;
1642 /* cut trailing comment from input line */
1643 for (line_ptr = line; *line_ptr; line_ptr++)
1645 if (*line_ptr == '#')
1652 /* cut trailing whitespaces from input line */
1653 for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
1654 if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
1657 /* ignore empty lines */
1661 /* cut leading whitespaces from token */
1662 for (token = line; *token; token++)
1663 if (*token != ' ' && *token != '\t')
1666 /* start with empty value as reliable default */
1669 token_value_separator_found = FALSE;
1671 /* find end of token to determine start of value */
1672 for (line_ptr = token; *line_ptr; line_ptr++)
1675 /* first look for an explicit token/value separator, like ':' or '=' */
1676 if (*line_ptr == ':' || *line_ptr == '=')
1678 if (*line_ptr == ' ' || *line_ptr == '\t' || *line_ptr == ':')
1681 *line_ptr = '\0'; /* terminate token string */
1682 value = line_ptr + 1; /* set beginning of value */
1684 token_value_separator_found = TRUE;
1690 #if ALLOW_TOKEN_VALUE_SEPARATOR_BEING_WHITESPACE
1691 /* fallback: if no token/value separator found, also allow whitespaces */
1692 if (!token_value_separator_found && !separator_required)
1694 for (line_ptr = token; *line_ptr; line_ptr++)
1696 if (*line_ptr == ' ' || *line_ptr == '\t')
1698 *line_ptr = '\0'; /* terminate token string */
1699 value = line_ptr + 1; /* set beginning of value */
1701 token_value_separator_found = TRUE;
1707 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
1708 if (token_value_separator_found)
1710 if (!token_value_separator_warning)
1712 Error(ERR_INFO_LINE, "-");
1714 if (filename != NULL)
1716 Error(ERR_WARN, "missing token/value separator(s) in config file:");
1717 Error(ERR_INFO, "- config file: '%s'", filename);
1721 Error(ERR_WARN, "missing token/value separator(s):");
1724 token_value_separator_warning = TRUE;
1727 if (filename != NULL)
1728 Error(ERR_INFO, "- line %d: '%s'", line_nr, line_raw);
1730 Error(ERR_INFO, "- line: '%s'", line_raw);
1736 /* cut trailing whitespaces from token */
1737 for (line_ptr = &token[strlen(token)]; line_ptr >= token; line_ptr--)
1738 if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
1741 /* cut leading whitespaces from value */
1742 for (; *value; value++)
1743 if (*value != ' ' && *value != '\t')
1748 value = "true"; /* treat tokens without value as "true" */
1757 boolean getTokenValueFromSetupLine(char *line, char **token, char **value)
1759 /* while the internal (old) interface does not require a token/value
1760 separator (for downwards compatibility with existing files which
1761 don't use them), it is mandatory for the external (new) interface */
1763 return getTokenValueFromSetupLineExt(line, token, value, NULL, NULL, 0, TRUE);
1767 static boolean loadSetupFileData(void *setup_file_data, char *filename,
1768 boolean top_recursion_level, boolean is_hash)
1770 static SetupFileHash *include_filename_hash = NULL;
1771 char line[MAX_LINE_LEN], line_raw[MAX_LINE_LEN], previous_line[MAX_LINE_LEN];
1772 char *token, *value, *line_ptr;
1773 void *insert_ptr = NULL;
1774 boolean read_continued_line = FALSE;
1776 int line_nr = 0, token_count = 0, include_count = 0;
1778 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
1779 token_value_separator_warning = FALSE;
1782 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
1783 token_already_exists_warning = FALSE;
1786 if (!(file = fopen(filename, MODE_READ)))
1788 Error(ERR_WARN, "cannot open configuration file '%s'", filename);
1793 /* use "insert pointer" to store list end for constant insertion complexity */
1795 insert_ptr = setup_file_data;
1797 /* on top invocation, create hash to mark included files (to prevent loops) */
1798 if (top_recursion_level)
1799 include_filename_hash = newSetupFileHash();
1801 /* mark this file as already included (to prevent including it again) */
1802 setHashEntry(include_filename_hash, getBaseNamePtr(filename), "true");
1806 /* read next line of input file */
1807 if (!fgets(line, MAX_LINE_LEN, file))
1810 /* check if line was completely read and is terminated by line break */
1811 if (strlen(line) > 0 && line[strlen(line) - 1] == '\n')
1814 /* cut trailing line break (this can be newline and/or carriage return) */
1815 for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
1816 if ((*line_ptr == '\n' || *line_ptr == '\r') && *(line_ptr + 1) == '\0')
1819 /* copy raw input line for later use (mainly debugging output) */
1820 strcpy(line_raw, line);
1822 if (read_continued_line)
1825 /* !!! ??? WHY ??? !!! */
1826 /* cut leading whitespaces from input line */
1827 for (line_ptr = line; *line_ptr; line_ptr++)
1828 if (*line_ptr != ' ' && *line_ptr != '\t')
1832 /* append new line to existing line, if there is enough space */
1833 if (strlen(previous_line) + strlen(line_ptr) < MAX_LINE_LEN)
1834 strcat(previous_line, line_ptr);
1836 strcpy(line, previous_line); /* copy storage buffer to line */
1838 read_continued_line = FALSE;
1841 /* if the last character is '\', continue at next line */
1842 if (strlen(line) > 0 && line[strlen(line) - 1] == '\\')
1844 line[strlen(line) - 1] = '\0'; /* cut off trailing backslash */
1845 strcpy(previous_line, line); /* copy line to storage buffer */
1847 read_continued_line = TRUE;
1852 if (!getTokenValueFromSetupLineExt(line, &token, &value, filename,
1853 line_raw, line_nr, FALSE))
1858 if (strEqual(token, "include"))
1860 if (getHashEntry(include_filename_hash, value) == NULL)
1862 char *basepath = getBasePath(filename);
1863 char *basename = getBaseName(value);
1864 char *filename_include = getPath2(basepath, basename);
1867 Error(ERR_INFO, "[including file '%s']", filename_include);
1870 loadSetupFileData(setup_file_data, filename_include, FALSE, is_hash);
1874 free(filename_include);
1880 Error(ERR_WARN, "ignoring already processed file '%s'", value);
1887 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
1889 getHashEntry((SetupFileHash *)setup_file_data, token);
1891 if (old_value != NULL)
1893 if (!token_already_exists_warning)
1895 Error(ERR_INFO_LINE, "-");
1896 Error(ERR_WARN, "duplicate token(s) found in config file:");
1897 Error(ERR_INFO, "- config file: '%s'", filename);
1899 token_already_exists_warning = TRUE;
1902 Error(ERR_INFO, "- token: '%s' (in line %d)", token, line_nr);
1903 Error(ERR_INFO, " old value: '%s'", old_value);
1904 Error(ERR_INFO, " new value: '%s'", value);
1908 setHashEntry((SetupFileHash *)setup_file_data, token, value);
1912 insert_ptr = addListEntry((SetupFileList *)insert_ptr, token, value);
1922 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
1923 if (token_value_separator_warning)
1924 Error(ERR_INFO_LINE, "-");
1927 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
1928 if (token_already_exists_warning)
1929 Error(ERR_INFO_LINE, "-");
1932 if (token_count == 0 && include_count == 0)
1933 Error(ERR_WARN, "configuration file '%s' is empty", filename);
1935 if (top_recursion_level)
1936 freeSetupFileHash(include_filename_hash);
1943 static boolean loadSetupFileData(void *setup_file_data, char *filename,
1944 boolean top_recursion_level, boolean is_hash)
1946 static SetupFileHash *include_filename_hash = NULL;
1947 char line[MAX_LINE_LEN], line_raw[MAX_LINE_LEN], previous_line[MAX_LINE_LEN];
1948 char *token, *value, *line_ptr;
1949 void *insert_ptr = NULL;
1950 boolean read_continued_line = FALSE;
1953 int token_count = 0;
1955 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
1956 token_value_separator_warning = FALSE;
1959 if (!(file = fopen(filename, MODE_READ)))
1961 Error(ERR_WARN, "cannot open configuration file '%s'", filename);
1966 /* use "insert pointer" to store list end for constant insertion complexity */
1968 insert_ptr = setup_file_data;
1970 /* on top invocation, create hash to mark included files (to prevent loops) */
1971 if (top_recursion_level)
1972 include_filename_hash = newSetupFileHash();
1974 /* mark this file as already included (to prevent including it again) */
1975 setHashEntry(include_filename_hash, getBaseNamePtr(filename), "true");
1979 /* read next line of input file */
1980 if (!fgets(line, MAX_LINE_LEN, file))
1983 /* check if line was completely read and is terminated by line break */
1984 if (strlen(line) > 0 && line[strlen(line) - 1] == '\n')
1987 /* cut trailing line break (this can be newline and/or carriage return) */
1988 for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
1989 if ((*line_ptr == '\n' || *line_ptr == '\r') && *(line_ptr + 1) == '\0')
1992 /* copy raw input line for later use (mainly debugging output) */
1993 strcpy(line_raw, line);
1995 if (read_continued_line)
1997 /* cut leading whitespaces from input line */
1998 for (line_ptr = line; *line_ptr; line_ptr++)
1999 if (*line_ptr != ' ' && *line_ptr != '\t')
2002 /* append new line to existing line, if there is enough space */
2003 if (strlen(previous_line) + strlen(line_ptr) < MAX_LINE_LEN)
2004 strcat(previous_line, line_ptr);
2006 strcpy(line, previous_line); /* copy storage buffer to line */
2008 read_continued_line = FALSE;
2011 /* if the last character is '\', continue at next line */
2012 if (strlen(line) > 0 && line[strlen(line) - 1] == '\\')
2014 line[strlen(line) - 1] = '\0'; /* cut off trailing backslash */
2015 strcpy(previous_line, line); /* copy line to storage buffer */
2017 read_continued_line = TRUE;
2022 /* cut trailing comment from input line */
2023 for (line_ptr = line; *line_ptr; line_ptr++)
2025 if (*line_ptr == '#')
2032 /* cut trailing whitespaces from input line */
2033 for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
2034 if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
2037 /* ignore empty lines */
2041 /* cut leading whitespaces from token */
2042 for (token = line; *token; token++)
2043 if (*token != ' ' && *token != '\t')
2046 /* start with empty value as reliable default */
2049 token_value_separator_found = FALSE;
2051 /* find end of token to determine start of value */
2052 for (line_ptr = token; *line_ptr; line_ptr++)
2055 /* first look for an explicit token/value separator, like ':' or '=' */
2056 if (*line_ptr == ':' || *line_ptr == '=')
2058 if (*line_ptr == ' ' || *line_ptr == '\t' || *line_ptr == ':')
2061 *line_ptr = '\0'; /* terminate token string */
2062 value = line_ptr + 1; /* set beginning of value */
2064 token_value_separator_found = TRUE;
2070 #if ALLOW_TOKEN_VALUE_SEPARATOR_BEING_WHITESPACE
2071 /* fallback: if no token/value separator found, also allow whitespaces */
2072 if (!token_value_separator_found)
2074 for (line_ptr = token; *line_ptr; line_ptr++)
2076 if (*line_ptr == ' ' || *line_ptr == '\t')
2078 *line_ptr = '\0'; /* terminate token string */
2079 value = line_ptr + 1; /* set beginning of value */
2081 token_value_separator_found = TRUE;
2087 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2088 if (token_value_separator_found)
2090 if (!token_value_separator_warning)
2092 Error(ERR_INFO_LINE, "-");
2093 Error(ERR_WARN, "missing token/value separator(s) in config file:");
2094 Error(ERR_INFO, "- config file: '%s'", filename);
2096 token_value_separator_warning = TRUE;
2099 Error(ERR_INFO, "- line %d: '%s'", line_nr, line_raw);
2105 /* cut trailing whitespaces from token */
2106 for (line_ptr = &token[strlen(token)]; line_ptr >= token; line_ptr--)
2107 if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
2110 /* cut leading whitespaces from value */
2111 for (; *value; value++)
2112 if (*value != ' ' && *value != '\t')
2117 value = "true"; /* treat tokens without value as "true" */
2122 if (strEqual(token, "include"))
2124 if (getHashEntry(include_filename_hash, value) == NULL)
2126 char *basepath = getBasePath(filename);
2127 char *basename = getBaseName(value);
2128 char *filename_include = getPath2(basepath, basename);
2131 Error(ERR_INFO, "[including file '%s']", filename_include);
2134 loadSetupFileData(setup_file_data, filename_include, FALSE, is_hash);
2138 free(filename_include);
2142 Error(ERR_WARN, "ignoring already processed file '%s'", value);
2148 setHashEntry((SetupFileHash *)setup_file_data, token, value);
2150 insert_ptr = addListEntry((SetupFileList *)insert_ptr, token, value);
2159 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2160 if (token_value_separator_warning)
2161 Error(ERR_INFO_LINE, "-");
2164 if (token_count == 0)
2165 Error(ERR_WARN, "configuration file '%s' is empty", filename);
2167 if (top_recursion_level)
2168 freeSetupFileHash(include_filename_hash);
2174 void saveSetupFileHash(SetupFileHash *hash, char *filename)
2178 if (!(file = fopen(filename, MODE_WRITE)))
2180 Error(ERR_WARN, "cannot write configuration file '%s'", filename);
2185 BEGIN_HASH_ITERATION(hash, itr)
2187 fprintf(file, "%s\n", getFormattedSetupEntry(HASH_ITERATION_TOKEN(itr),
2188 HASH_ITERATION_VALUE(itr)));
2190 END_HASH_ITERATION(hash, itr)
2195 SetupFileList *loadSetupFileList(char *filename)
2197 SetupFileList *setup_file_list = newSetupFileList("", "");
2198 SetupFileList *first_valid_list_entry;
2200 if (!loadSetupFileData(setup_file_list, filename, TRUE, FALSE))
2202 freeSetupFileList(setup_file_list);
2207 first_valid_list_entry = setup_file_list->next;
2209 /* free empty list header */
2210 setup_file_list->next = NULL;
2211 freeSetupFileList(setup_file_list);
2213 return first_valid_list_entry;
2216 SetupFileHash *loadSetupFileHash(char *filename)
2218 SetupFileHash *setup_file_hash = newSetupFileHash();
2220 if (!loadSetupFileData(setup_file_hash, filename, TRUE, TRUE))
2222 freeSetupFileHash(setup_file_hash);
2227 return setup_file_hash;
2230 void checkSetupFileHashIdentifier(SetupFileHash *setup_file_hash,
2231 char *filename, char *identifier)
2233 char *value = getHashEntry(setup_file_hash, TOKEN_STR_FILE_IDENTIFIER);
2236 Error(ERR_WARN, "config file '%s' has no file identifier", filename);
2237 else if (!checkCookieString(value, identifier))
2238 Error(ERR_WARN, "config file '%s' has wrong file identifier", filename);
2242 /* ========================================================================= */
2243 /* setup file stuff */
2244 /* ========================================================================= */
2246 #define TOKEN_STR_LAST_LEVEL_SERIES "last_level_series"
2247 #define TOKEN_STR_LAST_PLAYED_LEVEL "last_played_level"
2248 #define TOKEN_STR_HANDICAP_LEVEL "handicap_level"
2250 /* level directory info */
2251 #define LEVELINFO_TOKEN_IDENTIFIER 0
2252 #define LEVELINFO_TOKEN_NAME 1
2253 #define LEVELINFO_TOKEN_NAME_SORTING 2
2254 #define LEVELINFO_TOKEN_AUTHOR 3
2255 #define LEVELINFO_TOKEN_YEAR 4
2256 #define LEVELINFO_TOKEN_IMPORTED_FROM 5
2257 #define LEVELINFO_TOKEN_IMPORTED_BY 6
2258 #define LEVELINFO_TOKEN_TESTED_BY 7
2259 #define LEVELINFO_TOKEN_LEVELS 8
2260 #define LEVELINFO_TOKEN_FIRST_LEVEL 9
2261 #define LEVELINFO_TOKEN_SORT_PRIORITY 10
2262 #define LEVELINFO_TOKEN_LATEST_ENGINE 11
2263 #define LEVELINFO_TOKEN_LEVEL_GROUP 12
2264 #define LEVELINFO_TOKEN_READONLY 13
2265 #define LEVELINFO_TOKEN_GRAPHICS_SET_ECS 14
2266 #define LEVELINFO_TOKEN_GRAPHICS_SET_AGA 15
2267 #define LEVELINFO_TOKEN_GRAPHICS_SET 16
2268 #define LEVELINFO_TOKEN_SOUNDS_SET 17
2269 #define LEVELINFO_TOKEN_MUSIC_SET 18
2270 #define LEVELINFO_TOKEN_FILENAME 19
2271 #define LEVELINFO_TOKEN_FILETYPE 20
2272 #define LEVELINFO_TOKEN_HANDICAP 21
2273 #define LEVELINFO_TOKEN_SKIP_LEVELS 22
2275 #define NUM_LEVELINFO_TOKENS 23
2277 static LevelDirTree ldi;
2279 static struct TokenInfo levelinfo_tokens[] =
2281 /* level directory info */
2282 { TYPE_STRING, &ldi.identifier, "identifier" },
2283 { TYPE_STRING, &ldi.name, "name" },
2284 { TYPE_STRING, &ldi.name_sorting, "name_sorting" },
2285 { TYPE_STRING, &ldi.author, "author" },
2286 { TYPE_STRING, &ldi.year, "year" },
2287 { TYPE_STRING, &ldi.imported_from, "imported_from" },
2288 { TYPE_STRING, &ldi.imported_by, "imported_by" },
2289 { TYPE_STRING, &ldi.tested_by, "tested_by" },
2290 { TYPE_INTEGER, &ldi.levels, "levels" },
2291 { TYPE_INTEGER, &ldi.first_level, "first_level" },
2292 { TYPE_INTEGER, &ldi.sort_priority, "sort_priority" },
2293 { TYPE_BOOLEAN, &ldi.latest_engine, "latest_engine" },
2294 { TYPE_BOOLEAN, &ldi.level_group, "level_group" },
2295 { TYPE_BOOLEAN, &ldi.readonly, "readonly" },
2296 { TYPE_STRING, &ldi.graphics_set_ecs, "graphics_set.ecs" },
2297 { TYPE_STRING, &ldi.graphics_set_aga, "graphics_set.aga" },
2298 { TYPE_STRING, &ldi.graphics_set, "graphics_set" },
2299 { TYPE_STRING, &ldi.sounds_set, "sounds_set" },
2300 { TYPE_STRING, &ldi.music_set, "music_set" },
2301 { TYPE_STRING, &ldi.level_filename, "filename" },
2302 { TYPE_STRING, &ldi.level_filetype, "filetype" },
2303 { TYPE_BOOLEAN, &ldi.handicap, "handicap" },
2304 { TYPE_BOOLEAN, &ldi.skip_levels, "skip_levels" }
2307 static struct TokenInfo artworkinfo_tokens[] =
2309 /* artwork directory info */
2310 { TYPE_STRING, &ldi.identifier, "identifier" },
2311 { TYPE_STRING, &ldi.subdir, "subdir" },
2312 { TYPE_STRING, &ldi.name, "name" },
2313 { TYPE_STRING, &ldi.name_sorting, "name_sorting" },
2314 { TYPE_STRING, &ldi.author, "author" },
2315 { TYPE_INTEGER, &ldi.sort_priority, "sort_priority" },
2316 { TYPE_STRING, &ldi.basepath, "basepath" },
2317 { TYPE_STRING, &ldi.fullpath, "fullpath" },
2318 { TYPE_BOOLEAN, &ldi.in_user_dir, "in_user_dir" },
2319 { TYPE_INTEGER, &ldi.color, "color" },
2320 { TYPE_STRING, &ldi.class_desc, "class_desc" },
2325 static void setTreeInfoToDefaults(TreeInfo *ti, int type)
2329 ti->node_top = (ti->type == TREE_TYPE_LEVEL_DIR ? &leveldir_first :
2330 ti->type == TREE_TYPE_GRAPHICS_DIR ? &artwork.gfx_first :
2331 ti->type == TREE_TYPE_SOUNDS_DIR ? &artwork.snd_first :
2332 ti->type == TREE_TYPE_MUSIC_DIR ? &artwork.mus_first :
2335 ti->node_parent = NULL;
2336 ti->node_group = NULL;
2343 ti->fullpath = NULL;
2344 ti->basepath = NULL;
2345 ti->identifier = NULL;
2346 ti->name = getStringCopy(ANONYMOUS_NAME);
2347 ti->name_sorting = NULL;
2348 ti->author = getStringCopy(ANONYMOUS_NAME);
2351 ti->sort_priority = LEVELCLASS_UNDEFINED; /* default: least priority */
2352 ti->latest_engine = FALSE; /* default: get from level */
2353 ti->parent_link = FALSE;
2354 ti->in_user_dir = FALSE;
2355 ti->user_defined = FALSE;
2357 ti->class_desc = NULL;
2359 ti->infotext = getStringCopy(TREE_INFOTEXT(ti->type));
2361 if (ti->type == TREE_TYPE_LEVEL_DIR)
2363 ti->imported_from = NULL;
2364 ti->imported_by = NULL;
2365 ti->tested_by = NULL;
2367 ti->graphics_set_ecs = NULL;
2368 ti->graphics_set_aga = NULL;
2369 ti->graphics_set = NULL;
2370 ti->sounds_set = NULL;
2371 ti->music_set = NULL;
2372 ti->graphics_path = getStringCopy(UNDEFINED_FILENAME);
2373 ti->sounds_path = getStringCopy(UNDEFINED_FILENAME);
2374 ti->music_path = getStringCopy(UNDEFINED_FILENAME);
2376 ti->level_filename = NULL;
2377 ti->level_filetype = NULL;
2380 ti->first_level = 0;
2382 ti->level_group = FALSE;
2383 ti->handicap_level = 0;
2384 ti->readonly = TRUE;
2385 ti->handicap = TRUE;
2386 ti->skip_levels = FALSE;
2390 static void setTreeInfoToDefaultsFromParent(TreeInfo *ti, TreeInfo *parent)
2394 Error(ERR_WARN, "setTreeInfoToDefaultsFromParent(): parent == NULL");
2396 setTreeInfoToDefaults(ti, TREE_TYPE_UNDEFINED);
2401 /* copy all values from the parent structure */
2403 ti->type = parent->type;
2405 ti->node_top = parent->node_top;
2406 ti->node_parent = parent;
2407 ti->node_group = NULL;
2414 ti->fullpath = NULL;
2415 ti->basepath = NULL;
2416 ti->identifier = NULL;
2417 ti->name = getStringCopy(ANONYMOUS_NAME);
2418 ti->name_sorting = NULL;
2419 ti->author = getStringCopy(parent->author);
2420 ti->year = getStringCopy(parent->year);
2422 ti->sort_priority = parent->sort_priority;
2423 ti->latest_engine = parent->latest_engine;
2424 ti->parent_link = FALSE;
2425 ti->in_user_dir = parent->in_user_dir;
2426 ti->user_defined = parent->user_defined;
2427 ti->color = parent->color;
2428 ti->class_desc = getStringCopy(parent->class_desc);
2430 ti->infotext = getStringCopy(parent->infotext);
2432 if (ti->type == TREE_TYPE_LEVEL_DIR)
2434 ti->imported_from = getStringCopy(parent->imported_from);
2435 ti->imported_by = getStringCopy(parent->imported_by);
2436 ti->tested_by = getStringCopy(parent->tested_by);
2438 ti->graphics_set_ecs = NULL;
2439 ti->graphics_set_aga = NULL;
2440 ti->graphics_set = NULL;
2441 ti->sounds_set = NULL;
2442 ti->music_set = NULL;
2443 ti->graphics_path = getStringCopy(UNDEFINED_FILENAME);
2444 ti->sounds_path = getStringCopy(UNDEFINED_FILENAME);
2445 ti->music_path = getStringCopy(UNDEFINED_FILENAME);
2447 ti->level_filename = NULL;
2448 ti->level_filetype = NULL;
2451 ti->first_level = 0;
2453 ti->level_group = FALSE;
2454 ti->handicap_level = 0;
2455 ti->readonly = TRUE;
2456 ti->handicap = TRUE;
2457 ti->skip_levels = FALSE;
2461 static TreeInfo *getTreeInfoCopy(TreeInfo *ti)
2463 TreeInfo *ti_copy = newTreeInfo();
2465 /* copy all values from the original structure */
2467 ti_copy->type = ti->type;
2469 ti_copy->node_top = ti->node_top;
2470 ti_copy->node_parent = ti->node_parent;
2471 ti_copy->node_group = ti->node_group;
2472 ti_copy->next = ti->next;
2474 ti_copy->cl_first = ti->cl_first;
2475 ti_copy->cl_cursor = ti->cl_cursor;
2477 ti_copy->subdir = getStringCopy(ti->subdir);
2478 ti_copy->fullpath = getStringCopy(ti->fullpath);
2479 ti_copy->basepath = getStringCopy(ti->basepath);
2480 ti_copy->identifier = getStringCopy(ti->identifier);
2481 ti_copy->name = getStringCopy(ti->name);
2482 ti_copy->name_sorting = getStringCopy(ti->name_sorting);
2483 ti_copy->author = getStringCopy(ti->author);
2484 ti_copy->year = getStringCopy(ti->year);
2485 ti_copy->imported_from = getStringCopy(ti->imported_from);
2486 ti_copy->imported_by = getStringCopy(ti->imported_by);
2487 ti_copy->tested_by = getStringCopy(ti->tested_by);
2489 ti_copy->graphics_set_ecs = getStringCopy(ti->graphics_set_ecs);
2490 ti_copy->graphics_set_aga = getStringCopy(ti->graphics_set_aga);
2491 ti_copy->graphics_set = getStringCopy(ti->graphics_set);
2492 ti_copy->sounds_set = getStringCopy(ti->sounds_set);
2493 ti_copy->music_set = getStringCopy(ti->music_set);
2494 ti_copy->graphics_path = getStringCopy(ti->graphics_path);
2495 ti_copy->sounds_path = getStringCopy(ti->sounds_path);
2496 ti_copy->music_path = getStringCopy(ti->music_path);
2498 ti_copy->level_filename = getStringCopy(ti->level_filename);
2499 ti_copy->level_filetype = getStringCopy(ti->level_filetype);
2501 ti_copy->levels = ti->levels;
2502 ti_copy->first_level = ti->first_level;
2503 ti_copy->last_level = ti->last_level;
2504 ti_copy->sort_priority = ti->sort_priority;
2506 ti_copy->latest_engine = ti->latest_engine;
2508 ti_copy->level_group = ti->level_group;
2509 ti_copy->parent_link = ti->parent_link;
2510 ti_copy->in_user_dir = ti->in_user_dir;
2511 ti_copy->user_defined = ti->user_defined;
2512 ti_copy->readonly = ti->readonly;
2513 ti_copy->handicap = ti->handicap;
2514 ti_copy->skip_levels = ti->skip_levels;
2516 ti_copy->color = ti->color;
2517 ti_copy->class_desc = getStringCopy(ti->class_desc);
2518 ti_copy->handicap_level = ti->handicap_level;
2520 ti_copy->infotext = getStringCopy(ti->infotext);
2525 static void freeTreeInfo(TreeInfo *ti)
2530 checked_free(ti->subdir);
2531 checked_free(ti->fullpath);
2532 checked_free(ti->basepath);
2533 checked_free(ti->identifier);
2535 checked_free(ti->name);
2536 checked_free(ti->name_sorting);
2537 checked_free(ti->author);
2538 checked_free(ti->year);
2540 checked_free(ti->class_desc);
2542 checked_free(ti->infotext);
2544 if (ti->type == TREE_TYPE_LEVEL_DIR)
2546 checked_free(ti->imported_from);
2547 checked_free(ti->imported_by);
2548 checked_free(ti->tested_by);
2550 checked_free(ti->graphics_set_ecs);
2551 checked_free(ti->graphics_set_aga);
2552 checked_free(ti->graphics_set);
2553 checked_free(ti->sounds_set);
2554 checked_free(ti->music_set);
2556 checked_free(ti->graphics_path);
2557 checked_free(ti->sounds_path);
2558 checked_free(ti->music_path);
2560 checked_free(ti->level_filename);
2561 checked_free(ti->level_filetype);
2567 void setSetupInfo(struct TokenInfo *token_info,
2568 int token_nr, char *token_value)
2570 int token_type = token_info[token_nr].type;
2571 void *setup_value = token_info[token_nr].value;
2573 if (token_value == NULL)
2576 /* set setup field to corresponding token value */
2581 *(boolean *)setup_value = get_boolean_from_string(token_value);
2585 *(Key *)setup_value = getKeyFromKeyName(token_value);
2589 *(Key *)setup_value = getKeyFromX11KeyName(token_value);
2593 *(int *)setup_value = get_integer_from_string(token_value);
2597 checked_free(*(char **)setup_value);
2598 *(char **)setup_value = getStringCopy(token_value);
2606 static int compareTreeInfoEntries(const void *object1, const void *object2)
2608 const TreeInfo *entry1 = *((TreeInfo **)object1);
2609 const TreeInfo *entry2 = *((TreeInfo **)object2);
2610 int class_sorting1, class_sorting2;
2613 if (entry1->type == TREE_TYPE_LEVEL_DIR)
2615 class_sorting1 = LEVELSORTING(entry1);
2616 class_sorting2 = LEVELSORTING(entry2);
2620 class_sorting1 = ARTWORKSORTING(entry1);
2621 class_sorting2 = ARTWORKSORTING(entry2);
2624 if (entry1->parent_link || entry2->parent_link)
2625 compare_result = (entry1->parent_link ? -1 : +1);
2626 else if (entry1->sort_priority == entry2->sort_priority)
2628 char *name1 = getStringToLower(entry1->name_sorting);
2629 char *name2 = getStringToLower(entry2->name_sorting);
2631 compare_result = strcmp(name1, name2);
2636 else if (class_sorting1 == class_sorting2)
2637 compare_result = entry1->sort_priority - entry2->sort_priority;
2639 compare_result = class_sorting1 - class_sorting2;
2641 return compare_result;
2644 static void createParentTreeInfoNode(TreeInfo *node_parent)
2648 if (node_parent == NULL)
2651 ti_new = newTreeInfo();
2652 setTreeInfoToDefaults(ti_new, node_parent->type);
2654 ti_new->node_parent = node_parent;
2655 ti_new->parent_link = TRUE;
2657 setString(&ti_new->identifier, node_parent->identifier);
2658 setString(&ti_new->name, ".. (parent directory)");
2659 setString(&ti_new->name_sorting, ti_new->name);
2661 setString(&ti_new->subdir, "..");
2662 setString(&ti_new->fullpath, node_parent->fullpath);
2664 ti_new->sort_priority = node_parent->sort_priority;
2665 ti_new->latest_engine = node_parent->latest_engine;
2667 setString(&ti_new->class_desc, getLevelClassDescription(ti_new));
2669 pushTreeInfo(&node_parent->node_group, ti_new);
2673 /* -------------------------------------------------------------------------- */
2674 /* functions for handling level and custom artwork info cache */
2675 /* -------------------------------------------------------------------------- */
2677 static void LoadArtworkInfoCache()
2679 InitCacheDirectory();
2681 if (artworkinfo_cache_old == NULL)
2683 char *filename = getPath2(getCacheDir(), ARTWORKINFO_CACHE_FILE);
2685 /* try to load artwork info hash from already existing cache file */
2686 artworkinfo_cache_old = loadSetupFileHash(filename);
2688 /* if no artwork info cache file was found, start with empty hash */
2689 if (artworkinfo_cache_old == NULL)
2690 artworkinfo_cache_old = newSetupFileHash();
2695 if (artworkinfo_cache_new == NULL)
2696 artworkinfo_cache_new = newSetupFileHash();
2699 static void SaveArtworkInfoCache()
2701 char *filename = getPath2(getCacheDir(), ARTWORKINFO_CACHE_FILE);
2703 InitCacheDirectory();
2705 saveSetupFileHash(artworkinfo_cache_new, filename);
2710 static char *getCacheTokenPrefix(char *prefix1, char *prefix2)
2712 static char *prefix = NULL;
2714 checked_free(prefix);
2716 prefix = getStringCat2WithSeparator(prefix1, prefix2, ".");
2721 /* (identical to above function, but separate string buffer needed -- nasty) */
2722 static char *getCacheToken(char *prefix, char *suffix)
2724 static char *token = NULL;
2726 checked_free(token);
2728 token = getStringCat2WithSeparator(prefix, suffix, ".");
2733 static char *getFileTimestamp(char *filename)
2735 struct stat file_status;
2737 if (stat(filename, &file_status) != 0) /* cannot stat file */
2738 return getStringCopy(i_to_a(0));
2740 return getStringCopy(i_to_a(file_status.st_mtime));
2743 static boolean modifiedFileTimestamp(char *filename, char *timestamp_string)
2745 struct stat file_status;
2747 if (timestamp_string == NULL)
2750 if (stat(filename, &file_status) != 0) /* cannot stat file */
2753 return (file_status.st_mtime != atoi(timestamp_string));
2756 static TreeInfo *getArtworkInfoCacheEntry(LevelDirTree *level_node, int type)
2758 char *identifier = level_node->subdir;
2759 char *type_string = ARTWORK_DIRECTORY(type);
2760 char *token_prefix = getCacheTokenPrefix(type_string, identifier);
2761 char *token_main = getCacheToken(token_prefix, "CACHED");
2762 char *cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
2763 boolean cached = (cache_entry != NULL && strEqual(cache_entry, "true"));
2764 TreeInfo *artwork_info = NULL;
2766 if (!use_artworkinfo_cache)
2773 artwork_info = newTreeInfo();
2774 setTreeInfoToDefaults(artwork_info, type);
2776 /* set all structure fields according to the token/value pairs */
2777 ldi = *artwork_info;
2778 for (i = 0; artworkinfo_tokens[i].type != -1; i++)
2780 char *token = getCacheToken(token_prefix, artworkinfo_tokens[i].text);
2781 char *value = getHashEntry(artworkinfo_cache_old, token);
2783 setSetupInfo(artworkinfo_tokens, i, value);
2785 /* check if cache entry for this item is invalid or incomplete */
2789 Error(ERR_WARN, "cache entry '%s' invalid", token);
2796 *artwork_info = ldi;
2801 char *filename_levelinfo = getPath2(getLevelDirFromTreeInfo(level_node),
2802 LEVELINFO_FILENAME);
2803 char *filename_artworkinfo = getPath2(getSetupArtworkDir(artwork_info),
2804 ARTWORKINFO_FILENAME(type));
2806 /* check if corresponding "levelinfo.conf" file has changed */
2807 token_main = getCacheToken(token_prefix, "TIMESTAMP_LEVELINFO");
2808 cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
2810 if (modifiedFileTimestamp(filename_levelinfo, cache_entry))
2813 /* check if corresponding "<artworkinfo>.conf" file has changed */
2814 token_main = getCacheToken(token_prefix, "TIMESTAMP_ARTWORKINFO");
2815 cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
2817 if (modifiedFileTimestamp(filename_artworkinfo, cache_entry))
2822 printf("::: '%s': INVALIDATED FROM CACHE BY TIMESTAMP\n", identifier);
2825 checked_free(filename_levelinfo);
2826 checked_free(filename_artworkinfo);
2829 if (!cached && artwork_info != NULL)
2831 freeTreeInfo(artwork_info);
2836 return artwork_info;
2839 static void setArtworkInfoCacheEntry(TreeInfo *artwork_info,
2840 LevelDirTree *level_node, int type)
2842 char *identifier = level_node->subdir;
2843 char *type_string = ARTWORK_DIRECTORY(type);
2844 char *token_prefix = getCacheTokenPrefix(type_string, identifier);
2845 char *token_main = getCacheToken(token_prefix, "CACHED");
2846 boolean set_cache_timestamps = TRUE;
2849 setHashEntry(artworkinfo_cache_new, token_main, "true");
2851 if (set_cache_timestamps)
2853 char *filename_levelinfo = getPath2(getLevelDirFromTreeInfo(level_node),
2854 LEVELINFO_FILENAME);
2855 char *filename_artworkinfo = getPath2(getSetupArtworkDir(artwork_info),
2856 ARTWORKINFO_FILENAME(type));
2857 char *timestamp_levelinfo = getFileTimestamp(filename_levelinfo);
2858 char *timestamp_artworkinfo = getFileTimestamp(filename_artworkinfo);
2860 token_main = getCacheToken(token_prefix, "TIMESTAMP_LEVELINFO");
2861 setHashEntry(artworkinfo_cache_new, token_main, timestamp_levelinfo);
2863 token_main = getCacheToken(token_prefix, "TIMESTAMP_ARTWORKINFO");
2864 setHashEntry(artworkinfo_cache_new, token_main, timestamp_artworkinfo);
2866 checked_free(filename_levelinfo);
2867 checked_free(filename_artworkinfo);
2868 checked_free(timestamp_levelinfo);
2869 checked_free(timestamp_artworkinfo);
2872 ldi = *artwork_info;
2873 for (i = 0; artworkinfo_tokens[i].type != -1; i++)
2875 char *token = getCacheToken(token_prefix, artworkinfo_tokens[i].text);
2876 char *value = getSetupValue(artworkinfo_tokens[i].type,
2877 artworkinfo_tokens[i].value);
2879 setHashEntry(artworkinfo_cache_new, token, value);
2884 /* -------------------------------------------------------------------------- */
2885 /* functions for loading level info and custom artwork info */
2886 /* -------------------------------------------------------------------------- */
2888 /* forward declaration for recursive call by "LoadLevelInfoFromLevelDir()" */
2889 static void LoadLevelInfoFromLevelDir(TreeInfo **, TreeInfo *, char *);
2891 static boolean LoadLevelInfoFromLevelConf(TreeInfo **node_first,
2892 TreeInfo *node_parent,
2893 char *level_directory,
2894 char *directory_name)
2896 static unsigned long progress_delay = 0;
2897 unsigned long progress_delay_value = 100; /* (in milliseconds) */
2898 char *directory_path = getPath2(level_directory, directory_name);
2899 char *filename = getPath2(directory_path, LEVELINFO_FILENAME);
2900 SetupFileHash *setup_file_hash;
2901 LevelDirTree *leveldir_new = NULL;
2904 /* unless debugging, silently ignore directories without "levelinfo.conf" */
2905 if (!options.debug && !fileExists(filename))
2907 free(directory_path);
2913 setup_file_hash = loadSetupFileHash(filename);
2915 if (setup_file_hash == NULL)
2917 Error(ERR_WARN, "ignoring level directory '%s'", directory_path);
2919 free(directory_path);
2925 leveldir_new = newTreeInfo();
2928 setTreeInfoToDefaultsFromParent(leveldir_new, node_parent);
2930 setTreeInfoToDefaults(leveldir_new, TREE_TYPE_LEVEL_DIR);
2932 leveldir_new->subdir = getStringCopy(directory_name);
2934 checkSetupFileHashIdentifier(setup_file_hash, filename,
2935 getCookie("LEVELINFO"));
2937 /* set all structure fields according to the token/value pairs */
2938 ldi = *leveldir_new;
2939 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
2940 setSetupInfo(levelinfo_tokens, i,
2941 getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
2942 *leveldir_new = ldi;
2944 if (strEqual(leveldir_new->name, ANONYMOUS_NAME))
2945 setString(&leveldir_new->name, leveldir_new->subdir);
2947 if (leveldir_new->identifier == NULL)
2948 leveldir_new->identifier = getStringCopy(leveldir_new->subdir);
2950 if (leveldir_new->name_sorting == NULL)
2951 leveldir_new->name_sorting = getStringCopy(leveldir_new->name);
2953 if (node_parent == NULL) /* top level group */
2955 leveldir_new->basepath = getStringCopy(level_directory);
2956 leveldir_new->fullpath = getStringCopy(leveldir_new->subdir);
2958 else /* sub level group */
2960 leveldir_new->basepath = getStringCopy(node_parent->basepath);
2961 leveldir_new->fullpath = getPath2(node_parent->fullpath, directory_name);
2965 if (leveldir_new->levels < 1)
2966 leveldir_new->levels = 1;
2969 leveldir_new->last_level =
2970 leveldir_new->first_level + leveldir_new->levels - 1;
2972 leveldir_new->in_user_dir =
2973 (!strEqual(leveldir_new->basepath, options.level_directory));
2975 /* adjust some settings if user's private level directory was detected */
2976 if (leveldir_new->sort_priority == LEVELCLASS_UNDEFINED &&
2977 leveldir_new->in_user_dir &&
2978 (strEqual(leveldir_new->subdir, getLoginName()) ||
2979 strEqual(leveldir_new->name, getLoginName()) ||
2980 strEqual(leveldir_new->author, getRealName())))
2982 leveldir_new->sort_priority = LEVELCLASS_PRIVATE_START;
2983 leveldir_new->readonly = FALSE;
2986 leveldir_new->user_defined =
2987 (leveldir_new->in_user_dir && IS_LEVELCLASS_PRIVATE(leveldir_new));
2989 leveldir_new->color = LEVELCOLOR(leveldir_new);
2991 setString(&leveldir_new->class_desc, getLevelClassDescription(leveldir_new));
2993 leveldir_new->handicap_level = /* set handicap to default value */
2994 (leveldir_new->user_defined || !leveldir_new->handicap ?
2995 leveldir_new->last_level : leveldir_new->first_level);
2998 if (leveldir_new->level_group ||
2999 DelayReached(&progress_delay, progress_delay_value))
3000 DrawInitText(leveldir_new->name, 150, FC_YELLOW);
3002 DrawInitText(leveldir_new->name, 150, FC_YELLOW);
3006 /* !!! don't skip sets without levels (else artwork base sets are missing) */
3008 if (leveldir_new->levels < 1 && !leveldir_new->level_group)
3010 /* skip level sets without levels (which are probably artwork base sets) */
3012 freeSetupFileHash(setup_file_hash);
3013 free(directory_path);
3021 pushTreeInfo(node_first, leveldir_new);
3023 freeSetupFileHash(setup_file_hash);
3025 if (leveldir_new->level_group)
3027 /* create node to link back to current level directory */
3028 createParentTreeInfoNode(leveldir_new);
3030 /* recursively step into sub-directory and look for more level series */
3031 LoadLevelInfoFromLevelDir(&leveldir_new->node_group,
3032 leveldir_new, directory_path);
3035 free(directory_path);
3041 static void LoadLevelInfoFromLevelDir(TreeInfo **node_first,
3042 TreeInfo *node_parent,
3043 char *level_directory)
3046 struct dirent *dir_entry;
3047 boolean valid_entry_found = FALSE;
3049 if ((dir = opendir(level_directory)) == NULL)
3051 Error(ERR_WARN, "cannot read level directory '%s'", level_directory);
3055 while ((dir_entry = readdir(dir)) != NULL) /* loop until last dir entry */
3057 struct stat file_status;
3058 char *directory_name = dir_entry->d_name;
3059 char *directory_path = getPath2(level_directory, directory_name);
3061 /* skip entries for current and parent directory */
3062 if (strEqual(directory_name, ".") ||
3063 strEqual(directory_name, ".."))
3065 free(directory_path);
3069 /* find out if directory entry is itself a directory */
3070 if (stat(directory_path, &file_status) != 0 || /* cannot stat file */
3071 (file_status.st_mode & S_IFMT) != S_IFDIR) /* not a directory */
3073 free(directory_path);
3077 free(directory_path);
3079 if (strEqual(directory_name, GRAPHICS_DIRECTORY) ||
3080 strEqual(directory_name, SOUNDS_DIRECTORY) ||
3081 strEqual(directory_name, MUSIC_DIRECTORY))
3084 valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
3091 /* special case: top level directory may directly contain "levelinfo.conf" */
3092 if (node_parent == NULL && !valid_entry_found)
3094 /* check if this directory directly contains a file "levelinfo.conf" */
3095 valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
3096 level_directory, ".");
3099 if (!valid_entry_found)
3100 Error(ERR_WARN, "cannot find any valid level series in directory '%s'",
3104 boolean AdjustGraphicsForEMC()
3106 boolean settings_changed = FALSE;
3108 settings_changed |= adjustTreeGraphicsForEMC(leveldir_first_all);
3109 settings_changed |= adjustTreeGraphicsForEMC(leveldir_first);
3111 return settings_changed;
3114 void LoadLevelInfo()
3116 InitUserLevelDirectory(getLoginName());
3118 DrawInitText("Loading level series", 120, FC_GREEN);
3120 LoadLevelInfoFromLevelDir(&leveldir_first, NULL, options.level_directory);
3121 LoadLevelInfoFromLevelDir(&leveldir_first, NULL, getUserLevelDir(NULL));
3123 /* after loading all level set information, clone the level directory tree
3124 and remove all level sets without levels (these may still contain artwork
3125 to be offered in the setup menu as "custom artwork", and are therefore
3126 checked for existing artwork in the function "LoadLevelArtworkInfo()") */
3127 leveldir_first_all = leveldir_first;
3128 cloneTree(&leveldir_first, leveldir_first_all, TRUE);
3130 AdjustGraphicsForEMC();
3132 /* before sorting, the first entries will be from the user directory */
3133 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
3135 if (leveldir_first == NULL)
3136 Error(ERR_EXIT, "cannot find any valid level series in any directory");
3138 sortTreeInfo(&leveldir_first);
3141 dumpTreeInfo(leveldir_first, 0);
3145 static boolean LoadArtworkInfoFromArtworkConf(TreeInfo **node_first,
3146 TreeInfo *node_parent,
3147 char *base_directory,
3148 char *directory_name, int type)
3150 char *directory_path = getPath2(base_directory, directory_name);
3151 char *filename = getPath2(directory_path, ARTWORKINFO_FILENAME(type));
3152 SetupFileHash *setup_file_hash = NULL;
3153 TreeInfo *artwork_new = NULL;
3156 if (fileExists(filename))
3157 setup_file_hash = loadSetupFileHash(filename);
3159 if (setup_file_hash == NULL) /* no config file -- look for artwork files */
3162 struct dirent *dir_entry;
3163 boolean valid_file_found = FALSE;
3165 if ((dir = opendir(directory_path)) != NULL)
3167 while ((dir_entry = readdir(dir)) != NULL)
3169 char *entry_name = dir_entry->d_name;
3171 if (FileIsArtworkType(entry_name, type))
3173 valid_file_found = TRUE;
3181 if (!valid_file_found)
3183 if (!strEqual(directory_name, "."))
3184 Error(ERR_WARN, "ignoring artwork directory '%s'", directory_path);
3186 free(directory_path);
3193 artwork_new = newTreeInfo();
3196 setTreeInfoToDefaultsFromParent(artwork_new, node_parent);
3198 setTreeInfoToDefaults(artwork_new, type);
3200 artwork_new->subdir = getStringCopy(directory_name);
3202 if (setup_file_hash) /* (before defining ".color" and ".class_desc") */
3205 checkSetupFileHashIdentifier(setup_file_hash, filename, getCookie("..."));
3208 /* set all structure fields according to the token/value pairs */
3210 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
3211 setSetupInfo(levelinfo_tokens, i,
3212 getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
3215 if (strEqual(artwork_new->name, ANONYMOUS_NAME))
3216 setString(&artwork_new->name, artwork_new->subdir);
3218 if (artwork_new->identifier == NULL)
3219 artwork_new->identifier = getStringCopy(artwork_new->subdir);
3221 if (artwork_new->name_sorting == NULL)
3222 artwork_new->name_sorting = getStringCopy(artwork_new->name);
3225 if (node_parent == NULL) /* top level group */
3227 artwork_new->basepath = getStringCopy(base_directory);
3228 artwork_new->fullpath = getStringCopy(artwork_new->subdir);
3230 else /* sub level group */
3232 artwork_new->basepath = getStringCopy(node_parent->basepath);
3233 artwork_new->fullpath = getPath2(node_parent->fullpath, directory_name);
3236 artwork_new->in_user_dir =
3237 (!strEqual(artwork_new->basepath, OPTIONS_ARTWORK_DIRECTORY(type)));
3239 /* (may use ".sort_priority" from "setup_file_hash" above) */
3240 artwork_new->color = ARTWORKCOLOR(artwork_new);
3242 setString(&artwork_new->class_desc, getLevelClassDescription(artwork_new));
3244 if (setup_file_hash == NULL) /* (after determining ".user_defined") */
3246 if (strEqual(artwork_new->subdir, "."))
3248 if (artwork_new->user_defined)
3250 setString(&artwork_new->identifier, "private");
3251 artwork_new->sort_priority = ARTWORKCLASS_PRIVATE;
3255 setString(&artwork_new->identifier, "classic");
3256 artwork_new->sort_priority = ARTWORKCLASS_CLASSICS;
3259 /* set to new values after changing ".sort_priority" */
3260 artwork_new->color = ARTWORKCOLOR(artwork_new);
3262 setString(&artwork_new->class_desc,
3263 getLevelClassDescription(artwork_new));
3267 setString(&artwork_new->identifier, artwork_new->subdir);
3270 setString(&artwork_new->name, artwork_new->identifier);
3271 setString(&artwork_new->name_sorting, artwork_new->name);
3275 DrawInitText(artwork_new->name, 150, FC_YELLOW);
3278 pushTreeInfo(node_first, artwork_new);
3280 freeSetupFileHash(setup_file_hash);
3282 free(directory_path);
3288 static void LoadArtworkInfoFromArtworkDir(TreeInfo **node_first,
3289 TreeInfo *node_parent,
3290 char *base_directory, int type)
3293 struct dirent *dir_entry;
3294 boolean valid_entry_found = FALSE;
3296 if ((dir = opendir(base_directory)) == NULL)
3298 /* display error if directory is main "options.graphics_directory" etc. */
3299 if (base_directory == OPTIONS_ARTWORK_DIRECTORY(type))
3300 Error(ERR_WARN, "cannot read directory '%s'", base_directory);
3305 while ((dir_entry = readdir(dir)) != NULL) /* loop until last dir entry */
3307 struct stat file_status;
3308 char *directory_name = dir_entry->d_name;
3309 char *directory_path = getPath2(base_directory, directory_name);
3311 /* skip directory entries for current and parent directory */
3312 if (strEqual(directory_name, ".") ||
3313 strEqual(directory_name, ".."))
3315 free(directory_path);
3319 /* skip directory entries which are not a directory or are not accessible */
3320 if (stat(directory_path, &file_status) != 0 || /* cannot stat file */
3321 (file_status.st_mode & S_IFMT) != S_IFDIR) /* not a directory */
3323 free(directory_path);
3327 free(directory_path);
3329 /* check if this directory contains artwork with or without config file */
3330 valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
3332 directory_name, type);
3337 /* check if this directory directly contains artwork itself */
3338 valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
3339 base_directory, ".",
3341 if (!valid_entry_found)
3342 Error(ERR_WARN, "cannot find any valid artwork in directory '%s'",
3346 static TreeInfo *getDummyArtworkInfo(int type)
3348 /* this is only needed when there is completely no artwork available */
3349 TreeInfo *artwork_new = newTreeInfo();
3351 setTreeInfoToDefaults(artwork_new, type);
3353 setString(&artwork_new->subdir, UNDEFINED_FILENAME);
3354 setString(&artwork_new->fullpath, UNDEFINED_FILENAME);
3355 setString(&artwork_new->basepath, UNDEFINED_FILENAME);
3357 setString(&artwork_new->identifier, UNDEFINED_FILENAME);
3358 setString(&artwork_new->name, UNDEFINED_FILENAME);
3359 setString(&artwork_new->name_sorting, UNDEFINED_FILENAME);
3364 void LoadArtworkInfo()
3366 LoadArtworkInfoCache();
3368 DrawInitText("Looking for custom artwork", 120, FC_GREEN);
3370 LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
3371 options.graphics_directory,
3372 TREE_TYPE_GRAPHICS_DIR);
3373 LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
3374 getUserGraphicsDir(),
3375 TREE_TYPE_GRAPHICS_DIR);
3377 LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
3378 options.sounds_directory,
3379 TREE_TYPE_SOUNDS_DIR);
3380 LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
3382 TREE_TYPE_SOUNDS_DIR);
3384 LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
3385 options.music_directory,
3386 TREE_TYPE_MUSIC_DIR);
3387 LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
3389 TREE_TYPE_MUSIC_DIR);
3391 if (artwork.gfx_first == NULL)
3392 artwork.gfx_first = getDummyArtworkInfo(TREE_TYPE_GRAPHICS_DIR);
3393 if (artwork.snd_first == NULL)
3394 artwork.snd_first = getDummyArtworkInfo(TREE_TYPE_SOUNDS_DIR);
3395 if (artwork.mus_first == NULL)
3396 artwork.mus_first = getDummyArtworkInfo(TREE_TYPE_MUSIC_DIR);
3398 /* before sorting, the first entries will be from the user directory */
3399 artwork.gfx_current =
3400 getTreeInfoFromIdentifier(artwork.gfx_first, setup.graphics_set);
3401 if (artwork.gfx_current == NULL)
3402 artwork.gfx_current =
3403 getTreeInfoFromIdentifier(artwork.gfx_first, GFX_CLASSIC_SUBDIR);
3404 if (artwork.gfx_current == NULL)
3405 artwork.gfx_current = getFirstValidTreeInfoEntry(artwork.gfx_first);
3407 artwork.snd_current =
3408 getTreeInfoFromIdentifier(artwork.snd_first, setup.sounds_set);
3409 if (artwork.snd_current == NULL)
3410 artwork.snd_current =
3411 getTreeInfoFromIdentifier(artwork.snd_first, SND_CLASSIC_SUBDIR);
3412 if (artwork.snd_current == NULL)
3413 artwork.snd_current = getFirstValidTreeInfoEntry(artwork.snd_first);
3415 artwork.mus_current =
3416 getTreeInfoFromIdentifier(artwork.mus_first, setup.music_set);
3417 if (artwork.mus_current == NULL)
3418 artwork.mus_current =
3419 getTreeInfoFromIdentifier(artwork.mus_first, MUS_CLASSIC_SUBDIR);
3420 if (artwork.mus_current == NULL)
3421 artwork.mus_current = getFirstValidTreeInfoEntry(artwork.mus_first);
3423 artwork.gfx_current_identifier = artwork.gfx_current->identifier;
3424 artwork.snd_current_identifier = artwork.snd_current->identifier;
3425 artwork.mus_current_identifier = artwork.mus_current->identifier;
3428 printf("graphics set == %s\n\n", artwork.gfx_current_identifier);
3429 printf("sounds set == %s\n\n", artwork.snd_current_identifier);
3430 printf("music set == %s\n\n", artwork.mus_current_identifier);
3433 sortTreeInfo(&artwork.gfx_first);
3434 sortTreeInfo(&artwork.snd_first);
3435 sortTreeInfo(&artwork.mus_first);
3438 dumpTreeInfo(artwork.gfx_first, 0);
3439 dumpTreeInfo(artwork.snd_first, 0);
3440 dumpTreeInfo(artwork.mus_first, 0);
3444 void LoadArtworkInfoFromLevelInfo(ArtworkDirTree **artwork_node,
3445 LevelDirTree *level_node)
3447 static unsigned long progress_delay = 0;
3448 unsigned long progress_delay_value = 100; /* (in milliseconds) */
3449 int type = (*artwork_node)->type;
3451 /* recursively check all level directories for artwork sub-directories */
3455 /* check all tree entries for artwork, but skip parent link entries */
3456 if (!level_node->parent_link)
3458 TreeInfo *artwork_new = getArtworkInfoCacheEntry(level_node, type);
3459 boolean cached = (artwork_new != NULL);
3463 pushTreeInfo(artwork_node, artwork_new);
3467 TreeInfo *topnode_last = *artwork_node;
3468 char *path = getPath2(getLevelDirFromTreeInfo(level_node),
3469 ARTWORK_DIRECTORY(type));
3471 LoadArtworkInfoFromArtworkDir(artwork_node, NULL, path, type);
3473 if (topnode_last != *artwork_node) /* check for newly added node */
3475 artwork_new = *artwork_node;
3477 setString(&artwork_new->identifier, level_node->subdir);
3478 setString(&artwork_new->name, level_node->name);
3479 setString(&artwork_new->name_sorting, level_node->name_sorting);
3481 artwork_new->sort_priority = level_node->sort_priority;
3482 artwork_new->color = LEVELCOLOR(artwork_new);
3488 /* insert artwork info (from old cache or filesystem) into new cache */
3489 if (artwork_new != NULL)
3490 setArtworkInfoCacheEntry(artwork_new, level_node, type);
3494 if (level_node->level_group ||
3495 DelayReached(&progress_delay, progress_delay_value))
3496 DrawInitText(level_node->name, 150, FC_YELLOW);
3499 if (level_node->node_group != NULL)
3500 LoadArtworkInfoFromLevelInfo(artwork_node, level_node->node_group);
3502 level_node = level_node->next;
3506 void LoadLevelArtworkInfo()
3508 DrawInitText("Looking for custom level artwork", 120, FC_GREEN);
3510 LoadArtworkInfoFromLevelInfo(&artwork.gfx_first, leveldir_first_all);
3511 LoadArtworkInfoFromLevelInfo(&artwork.snd_first, leveldir_first_all);
3512 LoadArtworkInfoFromLevelInfo(&artwork.mus_first, leveldir_first_all);
3514 SaveArtworkInfoCache();
3516 /* needed for reloading level artwork not known at ealier stage */
3518 if (!strEqual(artwork.gfx_current_identifier, setup.graphics_set))
3520 artwork.gfx_current =
3521 getTreeInfoFromIdentifier(artwork.gfx_first, setup.graphics_set);
3522 if (artwork.gfx_current == NULL)
3523 artwork.gfx_current =
3524 getTreeInfoFromIdentifier(artwork.gfx_first, GFX_CLASSIC_SUBDIR);
3525 if (artwork.gfx_current == NULL)
3526 artwork.gfx_current = getFirstValidTreeInfoEntry(artwork.gfx_first);
3529 if (!strEqual(artwork.snd_current_identifier, setup.sounds_set))
3531 artwork.snd_current =
3532 getTreeInfoFromIdentifier(artwork.snd_first, setup.sounds_set);
3533 if (artwork.snd_current == NULL)
3534 artwork.snd_current =
3535 getTreeInfoFromIdentifier(artwork.snd_first, SND_CLASSIC_SUBDIR);
3536 if (artwork.snd_current == NULL)
3537 artwork.snd_current = getFirstValidTreeInfoEntry(artwork.snd_first);
3540 if (!strEqual(artwork.mus_current_identifier, setup.music_set))
3542 artwork.mus_current =
3543 getTreeInfoFromIdentifier(artwork.mus_first, setup.music_set);
3544 if (artwork.mus_current == NULL)
3545 artwork.mus_current =
3546 getTreeInfoFromIdentifier(artwork.mus_first, MUS_CLASSIC_SUBDIR);
3547 if (artwork.mus_current == NULL)
3548 artwork.mus_current = getFirstValidTreeInfoEntry(artwork.mus_first);
3551 sortTreeInfo(&artwork.gfx_first);
3552 sortTreeInfo(&artwork.snd_first);
3553 sortTreeInfo(&artwork.mus_first);
3556 dumpTreeInfo(artwork.gfx_first, 0);
3557 dumpTreeInfo(artwork.snd_first, 0);
3558 dumpTreeInfo(artwork.mus_first, 0);
3562 static void SaveUserLevelInfo()
3564 LevelDirTree *level_info;
3569 filename = getPath2(getUserLevelDir(getLoginName()), LEVELINFO_FILENAME);
3571 if (!(file = fopen(filename, MODE_WRITE)))
3573 Error(ERR_WARN, "cannot write level info file '%s'", filename);
3578 level_info = newTreeInfo();
3580 /* always start with reliable default values */
3581 setTreeInfoToDefaults(level_info, TREE_TYPE_LEVEL_DIR);
3583 setString(&level_info->name, getLoginName());
3584 setString(&level_info->author, getRealName());
3585 level_info->levels = 100;
3586 level_info->first_level = 1;
3588 token_value_position = TOKEN_VALUE_POSITION_SHORT;
3590 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER,
3591 getCookie("LEVELINFO")));
3594 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
3596 if (i == LEVELINFO_TOKEN_NAME ||
3597 i == LEVELINFO_TOKEN_AUTHOR ||
3598 i == LEVELINFO_TOKEN_LEVELS ||
3599 i == LEVELINFO_TOKEN_FIRST_LEVEL)
3600 fprintf(file, "%s\n", getSetupLine(levelinfo_tokens, "", i));
3602 /* just to make things nicer :) */
3603 if (i == LEVELINFO_TOKEN_AUTHOR)
3604 fprintf(file, "\n");
3607 token_value_position = TOKEN_VALUE_POSITION_DEFAULT;
3611 SetFilePermissions(filename, PERMS_PRIVATE);
3613 freeTreeInfo(level_info);
3617 char *getSetupValue(int type, void *value)
3619 static char value_string[MAX_LINE_LEN];
3627 strcpy(value_string, (*(boolean *)value ? "true" : "false"));
3631 strcpy(value_string, (*(boolean *)value ? "on" : "off"));
3635 strcpy(value_string, (*(boolean *)value ? "yes" : "no"));
3639 strcpy(value_string, (*(boolean *)value ? "AGA" : "ECS"));
3643 strcpy(value_string, getKeyNameFromKey(*(Key *)value));
3647 strcpy(value_string, getX11KeyNameFromKey(*(Key *)value));
3651 sprintf(value_string, "%d", *(int *)value);
3655 if (*(char **)value == NULL)
3658 strcpy(value_string, *(char **)value);
3662 value_string[0] = '\0';
3666 if (type & TYPE_GHOSTED)
3667 strcpy(value_string, "n/a");
3669 return value_string;
3672 char *getSetupLine(struct TokenInfo *token_info, char *prefix, int token_nr)
3676 static char token_string[MAX_LINE_LEN];
3677 int token_type = token_info[token_nr].type;
3678 void *setup_value = token_info[token_nr].value;
3679 char *token_text = token_info[token_nr].text;
3680 char *value_string = getSetupValue(token_type, setup_value);
3682 /* build complete token string */
3683 sprintf(token_string, "%s%s", prefix, token_text);
3685 /* build setup entry line */
3686 line = getFormattedSetupEntry(token_string, value_string);
3688 if (token_type == TYPE_KEY_X11)
3690 Key key = *(Key *)setup_value;
3691 char *keyname = getKeyNameFromKey(key);
3693 /* add comment, if useful */
3694 if (!strEqual(keyname, "(undefined)") &&
3695 !strEqual(keyname, "(unknown)"))
3697 /* add at least one whitespace */
3699 for (i = strlen(line); i < token_comment_position; i++)
3703 strcat(line, keyname);
3710 void LoadLevelSetup_LastSeries()
3712 /* ----------------------------------------------------------------------- */
3713 /* ~/.<program>/levelsetup.conf */
3714 /* ----------------------------------------------------------------------- */
3716 char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
3717 SetupFileHash *level_setup_hash = NULL;
3719 /* always start with reliable default values */
3720 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
3722 if ((level_setup_hash = loadSetupFileHash(filename)))
3724 char *last_level_series =
3725 getHashEntry(level_setup_hash, TOKEN_STR_LAST_LEVEL_SERIES);
3727 leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
3729 if (leveldir_current == NULL)
3730 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
3732 checkSetupFileHashIdentifier(level_setup_hash, filename,
3733 getCookie("LEVELSETUP"));
3735 freeSetupFileHash(level_setup_hash);
3738 Error(ERR_WARN, "using default setup values");
3743 void SaveLevelSetup_LastSeries()
3745 /* ----------------------------------------------------------------------- */
3746 /* ~/.<program>/levelsetup.conf */
3747 /* ----------------------------------------------------------------------- */
3749 char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
3750 char *level_subdir = leveldir_current->subdir;
3753 InitUserDataDirectory();
3755 if (!(file = fopen(filename, MODE_WRITE)))
3757 Error(ERR_WARN, "cannot write setup file '%s'", filename);
3762 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER,
3763 getCookie("LEVELSETUP")));
3764 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_LEVEL_SERIES,
3769 SetFilePermissions(filename, PERMS_PRIVATE);
3774 static void checkSeriesInfo()
3776 static char *level_directory = NULL;
3778 struct dirent *dir_entry;
3780 /* check for more levels besides the 'levels' field of 'levelinfo.conf' */
3782 level_directory = getPath2((leveldir_current->in_user_dir ?
3783 getUserLevelDir(NULL) :
3784 options.level_directory),
3785 leveldir_current->fullpath);
3787 if ((dir = opendir(level_directory)) == NULL)
3789 Error(ERR_WARN, "cannot read level directory '%s'", level_directory);
3793 while ((dir_entry = readdir(dir)) != NULL) /* last directory entry */
3795 if (strlen(dir_entry->d_name) > 4 &&
3796 dir_entry->d_name[3] == '.' &&
3797 strEqual(&dir_entry->d_name[4], LEVELFILE_EXTENSION))
3799 char levelnum_str[4];
3802 strncpy(levelnum_str, dir_entry->d_name, 3);
3803 levelnum_str[3] = '\0';
3805 levelnum_value = atoi(levelnum_str);
3808 if (levelnum_value < leveldir_current->first_level)
3810 Error(ERR_WARN, "additional level %d found", levelnum_value);
3811 leveldir_current->first_level = levelnum_value;
3813 else if (levelnum_value > leveldir_current->last_level)
3815 Error(ERR_WARN, "additional level %d found", levelnum_value);
3816 leveldir_current->last_level = levelnum_value;
3825 void LoadLevelSetup_SeriesInfo()
3828 SetupFileHash *level_setup_hash = NULL;
3829 char *level_subdir = leveldir_current->subdir;
3831 /* always start with reliable default values */
3832 level_nr = leveldir_current->first_level;
3834 checkSeriesInfo(leveldir_current);
3836 /* ----------------------------------------------------------------------- */
3837 /* ~/.<program>/levelsetup/<level series>/levelsetup.conf */
3838 /* ----------------------------------------------------------------------- */
3840 level_subdir = leveldir_current->subdir;
3842 filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
3844 if ((level_setup_hash = loadSetupFileHash(filename)))
3848 token_value = getHashEntry(level_setup_hash, TOKEN_STR_LAST_PLAYED_LEVEL);
3852 level_nr = atoi(token_value);
3854 if (level_nr < leveldir_current->first_level)
3855 level_nr = leveldir_current->first_level;
3856 if (level_nr > leveldir_current->last_level)
3857 level_nr = leveldir_current->last_level;
3860 token_value = getHashEntry(level_setup_hash, TOKEN_STR_HANDICAP_LEVEL);
3864 int level_nr = atoi(token_value);
3866 if (level_nr < leveldir_current->first_level)
3867 level_nr = leveldir_current->first_level;
3868 if (level_nr > leveldir_current->last_level + 1)
3869 level_nr = leveldir_current->last_level;
3871 if (leveldir_current->user_defined || !leveldir_current->handicap)
3872 level_nr = leveldir_current->last_level;
3874 leveldir_current->handicap_level = level_nr;
3877 checkSetupFileHashIdentifier(level_setup_hash, filename,
3878 getCookie("LEVELSETUP"));
3880 freeSetupFileHash(level_setup_hash);
3883 Error(ERR_WARN, "using default setup values");
3888 void SaveLevelSetup_SeriesInfo()
3891 char *level_subdir = leveldir_current->subdir;
3892 char *level_nr_str = int2str(level_nr, 0);
3893 char *handicap_level_str = int2str(leveldir_current->handicap_level, 0);
3896 /* ----------------------------------------------------------------------- */
3897 /* ~/.<program>/levelsetup/<level series>/levelsetup.conf */
3898 /* ----------------------------------------------------------------------- */
3900 InitLevelSetupDirectory(level_subdir);
3902 filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
3904 if (!(file = fopen(filename, MODE_WRITE)))
3906 Error(ERR_WARN, "cannot write setup file '%s'", filename);
3911 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER,
3912 getCookie("LEVELSETUP")));
3913 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_PLAYED_LEVEL,
3915 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_HANDICAP_LEVEL,
3916 handicap_level_str));
3920 SetFilePermissions(filename, PERMS_PRIVATE);