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;
505 char *filename_from_artwork;
509 sprintf(basename, "%s_%d.txt",
510 (initial ? "titlemessage_initial" : "titlemessage"), nr + 1);
513 /* 1st try: look for message file in all relevant graphics directories */
514 if ((filename_from_artwork = getCustomImageFilename(basename)) != NULL)
515 return filename_from_artwork;
519 /* forced custom graphics also override messages in level set directory */
520 if (!setup.override_level_graphics)
523 checked_free(filename);
525 /* 2nd try: look for message file in current level set directory */
526 filename = getPath2(getCurrentLevelDir(), basename);
528 if (fileExists(filename))
535 static char *getCorrectedArtworkBasename(char *basename)
537 char *basename_corrected = basename;
539 #if defined(PLATFORM_MSDOS)
540 if (program.filename_prefix != NULL)
542 int prefix_len = strlen(program.filename_prefix);
544 if (strncmp(basename, program.filename_prefix, prefix_len) == 0)
545 basename_corrected = &basename[prefix_len];
547 /* if corrected filename is still longer than standard MS-DOS filename
548 size (8 characters + 1 dot + 3 characters file extension), shorten
549 filename by writing file extension after 8th basename character */
550 if (strlen(basename_corrected) > 8 + 1 + 3)
552 static char *msdos_filename = NULL;
554 checked_free(msdos_filename);
556 msdos_filename = getStringCopy(basename_corrected);
557 strncpy(&msdos_filename[8], &basename[strlen(basename) - (1+3)], 1+3 +1);
559 basename_corrected = msdos_filename;
564 return basename_corrected;
567 char *getCustomImageFilename(char *basename)
569 static char *filename = NULL;
570 boolean skip_setup_artwork = FALSE;
572 checked_free(filename);
574 basename = getCorrectedArtworkBasename(basename);
576 if (!setup.override_level_graphics)
578 /* 1st try: look for special artwork in current level series directory */
579 filename = getPath3(getCurrentLevelDir(), GRAPHICS_DIRECTORY, basename);
580 if (fileExists(filename))
585 /* check if there is special artwork configured in level series config */
586 if (getLevelArtworkSet(ARTWORK_TYPE_GRAPHICS) != NULL)
588 /* 2nd try: look for special artwork configured in level series config */
589 filename = getPath2(getLevelArtworkDir(ARTWORK_TYPE_GRAPHICS), basename);
590 if (fileExists(filename))
595 /* take missing artwork configured in level set config from default */
596 skip_setup_artwork = TRUE;
600 if (!skip_setup_artwork)
602 /* 3rd try: look for special artwork in configured artwork directory */
603 filename = getPath2(getSetupArtworkDir(artwork.gfx_current), basename);
604 if (fileExists(filename))
610 /* 4th try: look for default artwork in new default artwork directory */
611 filename = getPath2(getDefaultGraphicsDir(GFX_CLASSIC_SUBDIR), basename);
612 if (fileExists(filename))
617 /* 5th try: look for default artwork in old default artwork directory */
618 filename = getPath2(options.graphics_directory, basename);
619 if (fileExists(filename))
622 #if CREATE_SPECIAL_EDITION
625 /* 6th try: look for fallback artwork in old default artwork directory */
626 filename = getPath2(options.graphics_directory, GFX_FALLBACK_FILENAME);
627 if (fileExists(filename))
631 return NULL; /* cannot find specified artwork file anywhere */
634 char *getCustomSoundFilename(char *basename)
636 static char *filename = NULL;
637 boolean skip_setup_artwork = FALSE;
639 checked_free(filename);
641 basename = getCorrectedArtworkBasename(basename);
643 if (!setup.override_level_sounds)
645 /* 1st try: look for special artwork in current level series directory */
646 filename = getPath3(getCurrentLevelDir(), SOUNDS_DIRECTORY, basename);
647 if (fileExists(filename))
652 /* check if there is special artwork configured in level series config */
653 if (getLevelArtworkSet(ARTWORK_TYPE_SOUNDS) != NULL)
655 /* 2nd try: look for special artwork configured in level series config */
656 filename = getPath2(getLevelArtworkDir(TREE_TYPE_SOUNDS_DIR), basename);
657 if (fileExists(filename))
662 /* take missing artwork configured in level set config from default */
663 skip_setup_artwork = TRUE;
667 if (!skip_setup_artwork)
669 /* 3rd try: look for special artwork in configured artwork directory */
670 filename = getPath2(getSetupArtworkDir(artwork.snd_current), basename);
671 if (fileExists(filename))
677 /* 4th try: look for default artwork in new default artwork directory */
678 filename = getPath2(getDefaultSoundsDir(SND_CLASSIC_SUBDIR), basename);
679 if (fileExists(filename))
684 /* 5th try: look for default artwork in old default artwork directory */
685 filename = getPath2(options.sounds_directory, basename);
686 if (fileExists(filename))
689 #if CREATE_SPECIAL_EDITION
692 /* 6th try: look for fallback artwork in old default artwork directory */
693 filename = getPath2(options.sounds_directory, SND_FALLBACK_FILENAME);
694 if (fileExists(filename))
698 return NULL; /* cannot find specified artwork file anywhere */
701 char *getCustomMusicFilename(char *basename)
703 static char *filename = NULL;
704 boolean skip_setup_artwork = FALSE;
706 checked_free(filename);
708 basename = getCorrectedArtworkBasename(basename);
710 if (!setup.override_level_music)
712 /* 1st try: look for special artwork in current level series directory */
713 filename = getPath3(getCurrentLevelDir(), MUSIC_DIRECTORY, basename);
714 if (fileExists(filename))
719 /* check if there is special artwork configured in level series config */
720 if (getLevelArtworkSet(ARTWORK_TYPE_MUSIC) != NULL)
722 /* 2nd try: look for special artwork configured in level series config */
723 filename = getPath2(getLevelArtworkDir(TREE_TYPE_MUSIC_DIR), basename);
724 if (fileExists(filename))
729 /* take missing artwork configured in level set config from default */
730 skip_setup_artwork = TRUE;
734 if (!skip_setup_artwork)
736 /* 3rd try: look for special artwork in configured artwork directory */
737 filename = getPath2(getSetupArtworkDir(artwork.mus_current), basename);
738 if (fileExists(filename))
744 /* 4th try: look for default artwork in new default artwork directory */
745 filename = getPath2(getDefaultMusicDir(MUS_CLASSIC_SUBDIR), basename);
746 if (fileExists(filename))
751 /* 5th try: look for default artwork in old default artwork directory */
752 filename = getPath2(options.music_directory, basename);
753 if (fileExists(filename))
756 #if CREATE_SPECIAL_EDITION
759 /* 6th try: look for fallback artwork in old default artwork directory */
760 filename = getPath2(options.music_directory, MUS_FALLBACK_FILENAME);
761 if (fileExists(filename))
765 return NULL; /* cannot find specified artwork file anywhere */
768 char *getCustomArtworkFilename(char *basename, int type)
770 if (type == ARTWORK_TYPE_GRAPHICS)
771 return getCustomImageFilename(basename);
772 else if (type == ARTWORK_TYPE_SOUNDS)
773 return getCustomSoundFilename(basename);
774 else if (type == ARTWORK_TYPE_MUSIC)
775 return getCustomMusicFilename(basename);
777 return UNDEFINED_FILENAME;
780 char *getCustomArtworkConfigFilename(int type)
782 return getCustomArtworkFilename(ARTWORKINFO_FILENAME(type), type);
785 char *getCustomArtworkLevelConfigFilename(int type)
787 static char *filename = NULL;
789 checked_free(filename);
791 filename = getPath2(getLevelArtworkDir(type), ARTWORKINFO_FILENAME(type));
796 char *getCustomMusicDirectory(void)
798 static char *directory = NULL;
799 boolean skip_setup_artwork = FALSE;
801 checked_free(directory);
803 if (!setup.override_level_music)
805 /* 1st try: look for special artwork in current level series directory */
806 directory = getPath2(getCurrentLevelDir(), MUSIC_DIRECTORY);
807 if (fileExists(directory))
812 /* check if there is special artwork configured in level series config */
813 if (getLevelArtworkSet(ARTWORK_TYPE_MUSIC) != NULL)
815 /* 2nd try: look for special artwork configured in level series config */
816 directory = getStringCopy(getLevelArtworkDir(TREE_TYPE_MUSIC_DIR));
817 if (fileExists(directory))
822 /* take missing artwork configured in level set config from default */
823 skip_setup_artwork = TRUE;
827 if (!skip_setup_artwork)
829 /* 3rd try: look for special artwork in configured artwork directory */
830 directory = getStringCopy(getSetupArtworkDir(artwork.mus_current));
831 if (fileExists(directory))
837 /* 4th try: look for default artwork in new default artwork directory */
838 directory = getStringCopy(getDefaultMusicDir(MUS_CLASSIC_SUBDIR));
839 if (fileExists(directory))
844 /* 5th try: look for default artwork in old default artwork directory */
845 directory = getStringCopy(options.music_directory);
846 if (fileExists(directory))
849 return NULL; /* cannot find specified artwork file anywhere */
852 void InitTapeDirectory(char *level_subdir)
854 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
855 createDirectory(getTapeDir(NULL), "main tape", PERMS_PRIVATE);
856 createDirectory(getTapeDir(level_subdir), "level tape", PERMS_PRIVATE);
859 void InitScoreDirectory(char *level_subdir)
861 createDirectory(getCommonDataDir(), "common data", PERMS_PUBLIC);
862 createDirectory(getScoreDir(NULL), "main score", PERMS_PUBLIC);
863 createDirectory(getScoreDir(level_subdir), "level score", PERMS_PUBLIC);
866 static void SaveUserLevelInfo();
868 void InitUserLevelDirectory(char *level_subdir)
870 if (!fileExists(getUserLevelDir(level_subdir)))
872 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
873 createDirectory(getUserLevelDir(NULL), "main user level", PERMS_PRIVATE);
874 createDirectory(getUserLevelDir(level_subdir), "user level", PERMS_PRIVATE);
880 void InitLevelSetupDirectory(char *level_subdir)
882 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
883 createDirectory(getLevelSetupDir(NULL), "main level setup", PERMS_PRIVATE);
884 createDirectory(getLevelSetupDir(level_subdir), "level setup", PERMS_PRIVATE);
887 void InitCacheDirectory()
889 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
890 createDirectory(getCacheDir(), "cache data", PERMS_PRIVATE);
894 /* ------------------------------------------------------------------------- */
895 /* some functions to handle lists of level and artwork directories */
896 /* ------------------------------------------------------------------------- */
898 TreeInfo *newTreeInfo()
900 return checked_calloc(sizeof(TreeInfo));
903 TreeInfo *newTreeInfo_setDefaults(int type)
905 TreeInfo *ti = newTreeInfo();
907 setTreeInfoToDefaults(ti, type);
912 void pushTreeInfo(TreeInfo **node_first, TreeInfo *node_new)
914 node_new->next = *node_first;
915 *node_first = node_new;
918 int numTreeInfo(TreeInfo *node)
931 boolean validLevelSeries(TreeInfo *node)
933 return (node != NULL && !node->node_group && !node->parent_link);
936 TreeInfo *getFirstValidTreeInfoEntry(TreeInfo *node)
941 if (node->node_group) /* enter level group (step down into tree) */
942 return getFirstValidTreeInfoEntry(node->node_group);
943 else if (node->parent_link) /* skip start entry of level group */
945 if (node->next) /* get first real level series entry */
946 return getFirstValidTreeInfoEntry(node->next);
947 else /* leave empty level group and go on */
948 return getFirstValidTreeInfoEntry(node->node_parent->next);
950 else /* this seems to be a regular level series */
954 TreeInfo *getTreeInfoFirstGroupEntry(TreeInfo *node)
959 if (node->node_parent == NULL) /* top level group */
960 return *node->node_top;
961 else /* sub level group */
962 return node->node_parent->node_group;
965 int numTreeInfoInGroup(TreeInfo *node)
967 return numTreeInfo(getTreeInfoFirstGroupEntry(node));
970 int posTreeInfo(TreeInfo *node)
972 TreeInfo *node_cmp = getTreeInfoFirstGroupEntry(node);
977 if (node_cmp == node)
981 node_cmp = node_cmp->next;
987 TreeInfo *getTreeInfoFromPos(TreeInfo *node, int pos)
989 TreeInfo *node_default = node;
1001 return node_default;
1004 TreeInfo *getTreeInfoFromIdentifier(TreeInfo *node, char *identifier)
1006 if (identifier == NULL)
1011 if (node->node_group)
1013 TreeInfo *node_group;
1015 node_group = getTreeInfoFromIdentifier(node->node_group, identifier);
1020 else if (!node->parent_link)
1022 if (strEqual(identifier, node->identifier))
1032 TreeInfo *cloneTreeNode(TreeInfo **node_top, TreeInfo *node_parent,
1033 TreeInfo *node, boolean skip_sets_without_levels)
1040 if (!node->parent_link && !node->level_group &&
1041 skip_sets_without_levels && node->levels == 0)
1042 return cloneTreeNode(node_top, node_parent, node->next,
1043 skip_sets_without_levels);
1046 node_new = getTreeInfoCopy(node); /* copy complete node */
1048 node_new = newTreeInfo();
1050 *node_new = *node; /* copy complete node */
1053 node_new->node_top = node_top; /* correct top node link */
1054 node_new->node_parent = node_parent; /* correct parent node link */
1056 if (node->level_group)
1057 node_new->node_group = cloneTreeNode(node_top, node_new, node->node_group,
1058 skip_sets_without_levels);
1060 node_new->next = cloneTreeNode(node_top, node_parent, node->next,
1061 skip_sets_without_levels);
1066 void cloneTree(TreeInfo **ti_new, TreeInfo *ti, boolean skip_empty_sets)
1068 TreeInfo *ti_cloned = cloneTreeNode(ti_new, NULL, ti, skip_empty_sets);
1070 *ti_new = ti_cloned;
1073 static boolean adjustTreeGraphicsForEMC(TreeInfo *node)
1075 boolean settings_changed = FALSE;
1079 if (node->graphics_set_ecs && !setup.prefer_aga_graphics &&
1080 !strEqual(node->graphics_set, node->graphics_set_ecs))
1082 setString(&node->graphics_set, node->graphics_set_ecs);
1083 settings_changed = TRUE;
1085 else if (node->graphics_set_aga && setup.prefer_aga_graphics &&
1086 !strEqual(node->graphics_set, node->graphics_set_aga))
1088 setString(&node->graphics_set, node->graphics_set_aga);
1089 settings_changed = TRUE;
1092 if (node->node_group != NULL)
1093 settings_changed |= adjustTreeGraphicsForEMC(node->node_group);
1098 return settings_changed;
1101 void dumpTreeInfo(TreeInfo *node, int depth)
1105 printf("Dumping TreeInfo:\n");
1109 for (i = 0; i < (depth + 1) * 3; i++)
1112 printf("subdir == '%s' ['%s', '%s'] [%d])\n",
1113 node->subdir, node->fullpath, node->basepath, node->in_user_dir);
1115 if (node->node_group != NULL)
1116 dumpTreeInfo(node->node_group, depth + 1);
1122 void sortTreeInfoBySortFunction(TreeInfo **node_first,
1123 int (*compare_function)(const void *,
1126 int num_nodes = numTreeInfo(*node_first);
1127 TreeInfo **sort_array;
1128 TreeInfo *node = *node_first;
1134 /* allocate array for sorting structure pointers */
1135 sort_array = checked_calloc(num_nodes * sizeof(TreeInfo *));
1137 /* writing structure pointers to sorting array */
1138 while (i < num_nodes && node) /* double boundary check... */
1140 sort_array[i] = node;
1146 /* sorting the structure pointers in the sorting array */
1147 qsort(sort_array, num_nodes, sizeof(TreeInfo *),
1150 /* update the linkage of list elements with the sorted node array */
1151 for (i = 0; i < num_nodes - 1; i++)
1152 sort_array[i]->next = sort_array[i + 1];
1153 sort_array[num_nodes - 1]->next = NULL;
1155 /* update the linkage of the main list anchor pointer */
1156 *node_first = sort_array[0];
1160 /* now recursively sort the level group structures */
1164 if (node->node_group != NULL)
1165 sortTreeInfoBySortFunction(&node->node_group, compare_function);
1171 void sortTreeInfo(TreeInfo **node_first)
1173 sortTreeInfoBySortFunction(node_first, compareTreeInfoEntries);
1177 /* ========================================================================= */
1178 /* some stuff from "files.c" */
1179 /* ========================================================================= */
1181 #if defined(PLATFORM_WIN32)
1183 #define S_IRGRP S_IRUSR
1186 #define S_IROTH S_IRUSR
1189 #define S_IWGRP S_IWUSR
1192 #define S_IWOTH S_IWUSR
1195 #define S_IXGRP S_IXUSR
1198 #define S_IXOTH S_IXUSR
1201 #define S_IRWXG (S_IRGRP | S_IWGRP | S_IXGRP)
1206 #endif /* PLATFORM_WIN32 */
1208 /* file permissions for newly written files */
1209 #define MODE_R_ALL (S_IRUSR | S_IRGRP | S_IROTH)
1210 #define MODE_W_ALL (S_IWUSR | S_IWGRP | S_IWOTH)
1211 #define MODE_X_ALL (S_IXUSR | S_IXGRP | S_IXOTH)
1213 #define MODE_W_PRIVATE (S_IWUSR)
1214 #define MODE_W_PUBLIC (S_IWUSR | S_IWGRP)
1215 #define MODE_W_PUBLIC_DIR (S_IWUSR | S_IWGRP | S_ISGID)
1217 #define DIR_PERMS_PRIVATE (MODE_R_ALL | MODE_X_ALL | MODE_W_PRIVATE)
1218 #define DIR_PERMS_PUBLIC (MODE_R_ALL | MODE_X_ALL | MODE_W_PUBLIC_DIR)
1220 #define FILE_PERMS_PRIVATE (MODE_R_ALL | MODE_W_PRIVATE)
1221 #define FILE_PERMS_PUBLIC (MODE_R_ALL | MODE_W_PUBLIC)
1225 static char *dir = NULL;
1227 #if defined(PLATFORM_WIN32)
1230 dir = checked_malloc(MAX_PATH + 1);
1232 if (!SUCCEEDED(SHGetFolderPath(NULL, CSIDL_PERSONAL, NULL, 0, dir)))
1235 #elif defined(PLATFORM_UNIX)
1238 if ((dir = getenv("HOME")) == NULL)
1242 if ((pwd = getpwuid(getuid())) != NULL)
1243 dir = getStringCopy(pwd->pw_dir);
1255 char *getCommonDataDir(void)
1257 static char *common_data_dir = NULL;
1259 #if defined(PLATFORM_WIN32)
1260 if (common_data_dir == NULL)
1262 char *dir = checked_malloc(MAX_PATH + 1);
1264 if (SUCCEEDED(SHGetFolderPath(NULL, CSIDL_COMMON_DOCUMENTS, NULL, 0, dir))
1265 && !strEqual(dir, "")) /* empty for Windows 95/98 */
1266 common_data_dir = getPath2(dir, program.userdata_subdir);
1268 common_data_dir = options.rw_base_directory;
1271 if (common_data_dir == NULL)
1272 common_data_dir = options.rw_base_directory;
1275 return common_data_dir;
1278 char *getPersonalDataDir(void)
1280 static char *personal_data_dir = NULL;
1282 #if defined(PLATFORM_MACOSX)
1283 if (personal_data_dir == NULL)
1284 personal_data_dir = getPath2(getHomeDir(), "Documents");
1286 if (personal_data_dir == NULL)
1287 personal_data_dir = getHomeDir();
1290 return personal_data_dir;
1293 char *getUserGameDataDir(void)
1295 static char *user_game_data_dir = NULL;
1297 if (user_game_data_dir == NULL)
1298 user_game_data_dir = getPath2(getPersonalDataDir(),
1299 program.userdata_subdir);
1301 return user_game_data_dir;
1304 void updateUserGameDataDir()
1306 #if defined(PLATFORM_MACOSX)
1307 char *userdata_dir_old = getPath2(getHomeDir(), program.userdata_subdir_unix);
1308 char *userdata_dir_new = getUserGameDataDir(); /* do not free() this */
1310 /* convert old Unix style game data directory to Mac OS X style, if needed */
1311 if (fileExists(userdata_dir_old) && !fileExists(userdata_dir_new))
1313 if (rename(userdata_dir_old, userdata_dir_new) != 0)
1315 Error(ERR_WARN, "cannot move game data directory '%s' to '%s'",
1316 userdata_dir_old, userdata_dir_new);
1318 /* continue using Unix style data directory -- this should not happen */
1319 program.userdata_path = getPath2(getPersonalDataDir(),
1320 program.userdata_subdir_unix);
1324 free(userdata_dir_old);
1330 return getUserGameDataDir();
1333 static mode_t posix_umask(mode_t mask)
1335 #if defined(PLATFORM_UNIX)
1342 static int posix_mkdir(const char *pathname, mode_t mode)
1344 #if defined(PLATFORM_WIN32)
1345 return mkdir(pathname);
1347 return mkdir(pathname, mode);
1351 void createDirectory(char *dir, char *text, int permission_class)
1353 /* leave "other" permissions in umask untouched, but ensure group parts
1354 of USERDATA_DIR_MODE are not masked */
1355 mode_t dir_mode = (permission_class == PERMS_PRIVATE ?
1356 DIR_PERMS_PRIVATE : DIR_PERMS_PUBLIC);
1357 mode_t normal_umask = posix_umask(0);
1358 mode_t group_umask = ~(dir_mode & S_IRWXG);
1359 posix_umask(normal_umask & group_umask);
1361 if (!fileExists(dir))
1362 if (posix_mkdir(dir, dir_mode) != 0)
1363 Error(ERR_WARN, "cannot create %s directory '%s'", text, dir);
1365 posix_umask(normal_umask); /* reset normal umask */
1368 void InitUserDataDirectory()
1370 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
1373 void SetFilePermissions(char *filename, int permission_class)
1375 chmod(filename, (permission_class == PERMS_PRIVATE ?
1376 FILE_PERMS_PRIVATE : FILE_PERMS_PUBLIC));
1379 char *getCookie(char *file_type)
1381 static char cookie[MAX_COOKIE_LEN + 1];
1383 if (strlen(program.cookie_prefix) + 1 +
1384 strlen(file_type) + strlen("_FILE_VERSION_x.x") > MAX_COOKIE_LEN)
1385 return "[COOKIE ERROR]"; /* should never happen */
1387 sprintf(cookie, "%s_%s_FILE_VERSION_%d.%d",
1388 program.cookie_prefix, file_type,
1389 program.version_major, program.version_minor);
1394 int getFileVersionFromCookieString(const char *cookie)
1396 const char *ptr_cookie1, *ptr_cookie2;
1397 const char *pattern1 = "_FILE_VERSION_";
1398 const char *pattern2 = "?.?";
1399 const int len_cookie = strlen(cookie);
1400 const int len_pattern1 = strlen(pattern1);
1401 const int len_pattern2 = strlen(pattern2);
1402 const int len_pattern = len_pattern1 + len_pattern2;
1403 int version_major, version_minor;
1405 if (len_cookie <= len_pattern)
1408 ptr_cookie1 = &cookie[len_cookie - len_pattern];
1409 ptr_cookie2 = &cookie[len_cookie - len_pattern2];
1411 if (strncmp(ptr_cookie1, pattern1, len_pattern1) != 0)
1414 if (ptr_cookie2[0] < '0' || ptr_cookie2[0] > '9' ||
1415 ptr_cookie2[1] != '.' ||
1416 ptr_cookie2[2] < '0' || ptr_cookie2[2] > '9')
1419 version_major = ptr_cookie2[0] - '0';
1420 version_minor = ptr_cookie2[2] - '0';
1422 return VERSION_IDENT(version_major, version_minor, 0, 0);
1425 boolean checkCookieString(const char *cookie, const char *template)
1427 const char *pattern = "_FILE_VERSION_?.?";
1428 const int len_cookie = strlen(cookie);
1429 const int len_template = strlen(template);
1430 const int len_pattern = strlen(pattern);
1432 if (len_cookie != len_template)
1435 if (strncmp(cookie, template, len_cookie - len_pattern) != 0)
1441 /* ------------------------------------------------------------------------- */
1442 /* setup file list and hash handling functions */
1443 /* ------------------------------------------------------------------------- */
1445 char *getFormattedSetupEntry(char *token, char *value)
1448 static char entry[MAX_LINE_LEN];
1450 /* if value is an empty string, just return token without value */
1454 /* start with the token and some spaces to format output line */
1455 sprintf(entry, "%s:", token);
1456 for (i = strlen(entry); i < token_value_position; i++)
1459 /* continue with the token's value */
1460 strcat(entry, value);
1465 SetupFileList *newSetupFileList(char *token, char *value)
1467 SetupFileList *new = checked_malloc(sizeof(SetupFileList));
1469 new->token = getStringCopy(token);
1470 new->value = getStringCopy(value);
1477 void freeSetupFileList(SetupFileList *list)
1482 checked_free(list->token);
1483 checked_free(list->value);
1486 freeSetupFileList(list->next);
1491 char *getListEntry(SetupFileList *list, char *token)
1496 if (strEqual(list->token, token))
1499 return getListEntry(list->next, token);
1502 SetupFileList *setListEntry(SetupFileList *list, char *token, char *value)
1507 if (strEqual(list->token, token))
1509 checked_free(list->value);
1511 list->value = getStringCopy(value);
1515 else if (list->next == NULL)
1516 return (list->next = newSetupFileList(token, value));
1518 return setListEntry(list->next, token, value);
1521 SetupFileList *addListEntry(SetupFileList *list, char *token, char *value)
1526 if (list->next == NULL)
1527 return (list->next = newSetupFileList(token, value));
1529 return addListEntry(list->next, token, value);
1533 static void printSetupFileList(SetupFileList *list)
1538 printf("token: '%s'\n", list->token);
1539 printf("value: '%s'\n", list->value);
1541 printSetupFileList(list->next);
1546 DEFINE_HASHTABLE_INSERT(insert_hash_entry, char, char);
1547 DEFINE_HASHTABLE_SEARCH(search_hash_entry, char, char);
1548 DEFINE_HASHTABLE_CHANGE(change_hash_entry, char, char);
1549 DEFINE_HASHTABLE_REMOVE(remove_hash_entry, char, char);
1551 #define insert_hash_entry hashtable_insert
1552 #define search_hash_entry hashtable_search
1553 #define change_hash_entry hashtable_change
1554 #define remove_hash_entry hashtable_remove
1557 static unsigned int get_hash_from_key(void *key)
1562 This algorithm (k=33) was first reported by Dan Bernstein many years ago in
1563 'comp.lang.c'. Another version of this algorithm (now favored by Bernstein)
1564 uses XOR: hash(i) = hash(i - 1) * 33 ^ str[i]; the magic of number 33 (why
1565 it works better than many other constants, prime or not) has never been
1566 adequately explained.
1568 If you just want to have a good hash function, and cannot wait, djb2
1569 is one of the best string hash functions i know. It has excellent
1570 distribution and speed on many different sets of keys and table sizes.
1571 You are not likely to do better with one of the "well known" functions
1572 such as PJW, K&R, etc.
1574 Ozan (oz) Yigit [http://www.cs.yorku.ca/~oz/hash.html]
1577 char *str = (char *)key;
1578 unsigned int hash = 5381;
1581 while ((c = *str++))
1582 hash = ((hash << 5) + hash) + c; /* hash * 33 + c */
1587 static int keys_are_equal(void *key1, void *key2)
1589 return (strEqual((char *)key1, (char *)key2));
1592 SetupFileHash *newSetupFileHash()
1594 SetupFileHash *new_hash =
1595 create_hashtable(16, 0.75, get_hash_from_key, keys_are_equal);
1597 if (new_hash == NULL)
1598 Error(ERR_EXIT, "create_hashtable() failed -- out of memory");
1603 void freeSetupFileHash(SetupFileHash *hash)
1608 hashtable_destroy(hash, 1); /* 1 == also free values stored in hash */
1611 char *getHashEntry(SetupFileHash *hash, char *token)
1616 return search_hash_entry(hash, token);
1619 void setHashEntry(SetupFileHash *hash, char *token, char *value)
1626 value_copy = getStringCopy(value);
1628 /* change value; if it does not exist, insert it as new */
1629 if (!change_hash_entry(hash, token, value_copy))
1630 if (!insert_hash_entry(hash, getStringCopy(token), value_copy))
1631 Error(ERR_EXIT, "cannot insert into hash -- aborting");
1634 char *removeHashEntry(SetupFileHash *hash, char *token)
1639 return remove_hash_entry(hash, token);
1643 static void printSetupFileHash(SetupFileHash *hash)
1645 BEGIN_HASH_ITERATION(hash, itr)
1647 printf("token: '%s'\n", HASH_ITERATION_TOKEN(itr));
1648 printf("value: '%s'\n", HASH_ITERATION_VALUE(itr));
1650 END_HASH_ITERATION(hash, itr)
1654 #define ALLOW_TOKEN_VALUE_SEPARATOR_BEING_WHITESPACE 1
1655 #define CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING 0
1656 #define CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH 0
1658 static boolean token_value_separator_found = FALSE;
1659 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
1660 static boolean token_value_separator_warning = FALSE;
1662 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
1663 static boolean token_already_exists_warning = FALSE;
1666 static boolean getTokenValueFromSetupLineExt(char *line,
1667 char **token_ptr, char **value_ptr,
1668 char *filename, char *line_raw,
1670 boolean separator_required)
1672 static char line_copy[MAX_LINE_LEN + 1], line_raw_copy[MAX_LINE_LEN + 1];
1673 char *token, *value, *line_ptr;
1675 /* when externally invoked via ReadTokenValueFromLine(), copy line buffers */
1676 if (line_raw == NULL)
1678 strncpy(line_copy, line, MAX_LINE_LEN);
1679 line_copy[MAX_LINE_LEN] = '\0';
1682 strcpy(line_raw_copy, line_copy);
1683 line_raw = line_raw_copy;
1686 /* cut trailing comment from input line */
1687 for (line_ptr = line; *line_ptr; line_ptr++)
1689 if (*line_ptr == '#')
1696 /* cut trailing whitespaces from input line */
1697 for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
1698 if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
1701 /* ignore empty lines */
1705 /* cut leading whitespaces from token */
1706 for (token = line; *token; token++)
1707 if (*token != ' ' && *token != '\t')
1710 /* start with empty value as reliable default */
1713 token_value_separator_found = FALSE;
1715 /* find end of token to determine start of value */
1716 for (line_ptr = token; *line_ptr; line_ptr++)
1719 /* first look for an explicit token/value separator, like ':' or '=' */
1720 if (*line_ptr == ':' || *line_ptr == '=')
1722 if (*line_ptr == ' ' || *line_ptr == '\t' || *line_ptr == ':')
1725 *line_ptr = '\0'; /* terminate token string */
1726 value = line_ptr + 1; /* set beginning of value */
1728 token_value_separator_found = TRUE;
1734 #if ALLOW_TOKEN_VALUE_SEPARATOR_BEING_WHITESPACE
1735 /* fallback: if no token/value separator found, also allow whitespaces */
1736 if (!token_value_separator_found && !separator_required)
1738 for (line_ptr = token; *line_ptr; line_ptr++)
1740 if (*line_ptr == ' ' || *line_ptr == '\t')
1742 *line_ptr = '\0'; /* terminate token string */
1743 value = line_ptr + 1; /* set beginning of value */
1745 token_value_separator_found = TRUE;
1751 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
1752 if (token_value_separator_found)
1754 if (!token_value_separator_warning)
1756 Error(ERR_INFO_LINE, "-");
1758 if (filename != NULL)
1760 Error(ERR_WARN, "missing token/value separator(s) in config file:");
1761 Error(ERR_INFO, "- config file: '%s'", filename);
1765 Error(ERR_WARN, "missing token/value separator(s):");
1768 token_value_separator_warning = TRUE;
1771 if (filename != NULL)
1772 Error(ERR_INFO, "- line %d: '%s'", line_nr, line_raw);
1774 Error(ERR_INFO, "- line: '%s'", line_raw);
1780 /* cut trailing whitespaces from token */
1781 for (line_ptr = &token[strlen(token)]; line_ptr >= token; line_ptr--)
1782 if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
1785 /* cut leading whitespaces from value */
1786 for (; *value; value++)
1787 if (*value != ' ' && *value != '\t')
1792 value = "true"; /* treat tokens without value as "true" */
1801 boolean getTokenValueFromSetupLine(char *line, char **token, char **value)
1803 /* while the internal (old) interface does not require a token/value
1804 separator (for downwards compatibility with existing files which
1805 don't use them), it is mandatory for the external (new) interface */
1807 return getTokenValueFromSetupLineExt(line, token, value, NULL, NULL, 0, TRUE);
1811 static boolean loadSetupFileData(void *setup_file_data, char *filename,
1812 boolean top_recursion_level, boolean is_hash)
1814 static SetupFileHash *include_filename_hash = NULL;
1815 char line[MAX_LINE_LEN], line_raw[MAX_LINE_LEN], previous_line[MAX_LINE_LEN];
1816 char *token, *value, *line_ptr;
1817 void *insert_ptr = NULL;
1818 boolean read_continued_line = FALSE;
1820 int line_nr = 0, token_count = 0, include_count = 0;
1822 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
1823 token_value_separator_warning = FALSE;
1826 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
1827 token_already_exists_warning = FALSE;
1830 if (!(file = fopen(filename, MODE_READ)))
1832 Error(ERR_WARN, "cannot open configuration file '%s'", filename);
1837 /* use "insert pointer" to store list end for constant insertion complexity */
1839 insert_ptr = setup_file_data;
1841 /* on top invocation, create hash to mark included files (to prevent loops) */
1842 if (top_recursion_level)
1843 include_filename_hash = newSetupFileHash();
1845 /* mark this file as already included (to prevent including it again) */
1846 setHashEntry(include_filename_hash, getBaseNamePtr(filename), "true");
1850 /* read next line of input file */
1851 if (!fgets(line, MAX_LINE_LEN, file))
1854 /* check if line was completely read and is terminated by line break */
1855 if (strlen(line) > 0 && line[strlen(line) - 1] == '\n')
1858 /* cut trailing line break (this can be newline and/or carriage return) */
1859 for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
1860 if ((*line_ptr == '\n' || *line_ptr == '\r') && *(line_ptr + 1) == '\0')
1863 /* copy raw input line for later use (mainly debugging output) */
1864 strcpy(line_raw, line);
1866 if (read_continued_line)
1869 /* !!! ??? WHY ??? !!! */
1870 /* cut leading whitespaces from input line */
1871 for (line_ptr = line; *line_ptr; line_ptr++)
1872 if (*line_ptr != ' ' && *line_ptr != '\t')
1876 /* append new line to existing line, if there is enough space */
1877 if (strlen(previous_line) + strlen(line_ptr) < MAX_LINE_LEN)
1878 strcat(previous_line, line_ptr);
1880 strcpy(line, previous_line); /* copy storage buffer to line */
1882 read_continued_line = FALSE;
1885 /* if the last character is '\', continue at next line */
1886 if (strlen(line) > 0 && line[strlen(line) - 1] == '\\')
1888 line[strlen(line) - 1] = '\0'; /* cut off trailing backslash */
1889 strcpy(previous_line, line); /* copy line to storage buffer */
1891 read_continued_line = TRUE;
1896 if (!getTokenValueFromSetupLineExt(line, &token, &value, filename,
1897 line_raw, line_nr, FALSE))
1902 if (strEqual(token, "include"))
1904 if (getHashEntry(include_filename_hash, value) == NULL)
1906 char *basepath = getBasePath(filename);
1907 char *basename = getBaseName(value);
1908 char *filename_include = getPath2(basepath, basename);
1911 Error(ERR_INFO, "[including file '%s']", filename_include);
1914 loadSetupFileData(setup_file_data, filename_include, FALSE, is_hash);
1918 free(filename_include);
1924 Error(ERR_WARN, "ignoring already processed file '%s'", value);
1931 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
1933 getHashEntry((SetupFileHash *)setup_file_data, token);
1935 if (old_value != NULL)
1937 if (!token_already_exists_warning)
1939 Error(ERR_INFO_LINE, "-");
1940 Error(ERR_WARN, "duplicate token(s) found in config file:");
1941 Error(ERR_INFO, "- config file: '%s'", filename);
1943 token_already_exists_warning = TRUE;
1946 Error(ERR_INFO, "- token: '%s' (in line %d)", token, line_nr);
1947 Error(ERR_INFO, " old value: '%s'", old_value);
1948 Error(ERR_INFO, " new value: '%s'", value);
1952 setHashEntry((SetupFileHash *)setup_file_data, token, value);
1956 insert_ptr = addListEntry((SetupFileList *)insert_ptr, token, value);
1966 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
1967 if (token_value_separator_warning)
1968 Error(ERR_INFO_LINE, "-");
1971 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
1972 if (token_already_exists_warning)
1973 Error(ERR_INFO_LINE, "-");
1976 if (token_count == 0 && include_count == 0)
1977 Error(ERR_WARN, "configuration file '%s' is empty", filename);
1979 if (top_recursion_level)
1980 freeSetupFileHash(include_filename_hash);
1987 static boolean loadSetupFileData(void *setup_file_data, char *filename,
1988 boolean top_recursion_level, boolean is_hash)
1990 static SetupFileHash *include_filename_hash = NULL;
1991 char line[MAX_LINE_LEN], line_raw[MAX_LINE_LEN], previous_line[MAX_LINE_LEN];
1992 char *token, *value, *line_ptr;
1993 void *insert_ptr = NULL;
1994 boolean read_continued_line = FALSE;
1997 int token_count = 0;
1999 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2000 token_value_separator_warning = FALSE;
2003 if (!(file = fopen(filename, MODE_READ)))
2005 Error(ERR_WARN, "cannot open configuration file '%s'", filename);
2010 /* use "insert pointer" to store list end for constant insertion complexity */
2012 insert_ptr = setup_file_data;
2014 /* on top invocation, create hash to mark included files (to prevent loops) */
2015 if (top_recursion_level)
2016 include_filename_hash = newSetupFileHash();
2018 /* mark this file as already included (to prevent including it again) */
2019 setHashEntry(include_filename_hash, getBaseNamePtr(filename), "true");
2023 /* read next line of input file */
2024 if (!fgets(line, MAX_LINE_LEN, file))
2027 /* check if line was completely read and is terminated by line break */
2028 if (strlen(line) > 0 && line[strlen(line) - 1] == '\n')
2031 /* cut trailing line break (this can be newline and/or carriage return) */
2032 for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
2033 if ((*line_ptr == '\n' || *line_ptr == '\r') && *(line_ptr + 1) == '\0')
2036 /* copy raw input line for later use (mainly debugging output) */
2037 strcpy(line_raw, line);
2039 if (read_continued_line)
2041 /* cut leading whitespaces from input line */
2042 for (line_ptr = line; *line_ptr; line_ptr++)
2043 if (*line_ptr != ' ' && *line_ptr != '\t')
2046 /* append new line to existing line, if there is enough space */
2047 if (strlen(previous_line) + strlen(line_ptr) < MAX_LINE_LEN)
2048 strcat(previous_line, line_ptr);
2050 strcpy(line, previous_line); /* copy storage buffer to line */
2052 read_continued_line = FALSE;
2055 /* if the last character is '\', continue at next line */
2056 if (strlen(line) > 0 && line[strlen(line) - 1] == '\\')
2058 line[strlen(line) - 1] = '\0'; /* cut off trailing backslash */
2059 strcpy(previous_line, line); /* copy line to storage buffer */
2061 read_continued_line = TRUE;
2066 /* cut trailing comment from input line */
2067 for (line_ptr = line; *line_ptr; line_ptr++)
2069 if (*line_ptr == '#')
2076 /* cut trailing whitespaces from input line */
2077 for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
2078 if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
2081 /* ignore empty lines */
2085 /* cut leading whitespaces from token */
2086 for (token = line; *token; token++)
2087 if (*token != ' ' && *token != '\t')
2090 /* start with empty value as reliable default */
2093 token_value_separator_found = FALSE;
2095 /* find end of token to determine start of value */
2096 for (line_ptr = token; *line_ptr; line_ptr++)
2099 /* first look for an explicit token/value separator, like ':' or '=' */
2100 if (*line_ptr == ':' || *line_ptr == '=')
2102 if (*line_ptr == ' ' || *line_ptr == '\t' || *line_ptr == ':')
2105 *line_ptr = '\0'; /* terminate token string */
2106 value = line_ptr + 1; /* set beginning of value */
2108 token_value_separator_found = TRUE;
2114 #if ALLOW_TOKEN_VALUE_SEPARATOR_BEING_WHITESPACE
2115 /* fallback: if no token/value separator found, also allow whitespaces */
2116 if (!token_value_separator_found)
2118 for (line_ptr = token; *line_ptr; line_ptr++)
2120 if (*line_ptr == ' ' || *line_ptr == '\t')
2122 *line_ptr = '\0'; /* terminate token string */
2123 value = line_ptr + 1; /* set beginning of value */
2125 token_value_separator_found = TRUE;
2131 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2132 if (token_value_separator_found)
2134 if (!token_value_separator_warning)
2136 Error(ERR_INFO_LINE, "-");
2137 Error(ERR_WARN, "missing token/value separator(s) in config file:");
2138 Error(ERR_INFO, "- config file: '%s'", filename);
2140 token_value_separator_warning = TRUE;
2143 Error(ERR_INFO, "- line %d: '%s'", line_nr, line_raw);
2149 /* cut trailing whitespaces from token */
2150 for (line_ptr = &token[strlen(token)]; line_ptr >= token; line_ptr--)
2151 if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
2154 /* cut leading whitespaces from value */
2155 for (; *value; value++)
2156 if (*value != ' ' && *value != '\t')
2161 value = "true"; /* treat tokens without value as "true" */
2166 if (strEqual(token, "include"))
2168 if (getHashEntry(include_filename_hash, value) == NULL)
2170 char *basepath = getBasePath(filename);
2171 char *basename = getBaseName(value);
2172 char *filename_include = getPath2(basepath, basename);
2175 Error(ERR_INFO, "[including file '%s']", filename_include);
2178 loadSetupFileData(setup_file_data, filename_include, FALSE, is_hash);
2182 free(filename_include);
2186 Error(ERR_WARN, "ignoring already processed file '%s'", value);
2192 setHashEntry((SetupFileHash *)setup_file_data, token, value);
2194 insert_ptr = addListEntry((SetupFileList *)insert_ptr, token, value);
2203 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2204 if (token_value_separator_warning)
2205 Error(ERR_INFO_LINE, "-");
2208 if (token_count == 0)
2209 Error(ERR_WARN, "configuration file '%s' is empty", filename);
2211 if (top_recursion_level)
2212 freeSetupFileHash(include_filename_hash);
2218 void saveSetupFileHash(SetupFileHash *hash, char *filename)
2222 if (!(file = fopen(filename, MODE_WRITE)))
2224 Error(ERR_WARN, "cannot write configuration file '%s'", filename);
2229 BEGIN_HASH_ITERATION(hash, itr)
2231 fprintf(file, "%s\n", getFormattedSetupEntry(HASH_ITERATION_TOKEN(itr),
2232 HASH_ITERATION_VALUE(itr)));
2234 END_HASH_ITERATION(hash, itr)
2239 SetupFileList *loadSetupFileList(char *filename)
2241 SetupFileList *setup_file_list = newSetupFileList("", "");
2242 SetupFileList *first_valid_list_entry;
2244 if (!loadSetupFileData(setup_file_list, filename, TRUE, FALSE))
2246 freeSetupFileList(setup_file_list);
2251 first_valid_list_entry = setup_file_list->next;
2253 /* free empty list header */
2254 setup_file_list->next = NULL;
2255 freeSetupFileList(setup_file_list);
2257 return first_valid_list_entry;
2260 SetupFileHash *loadSetupFileHash(char *filename)
2262 SetupFileHash *setup_file_hash = newSetupFileHash();
2264 if (!loadSetupFileData(setup_file_hash, filename, TRUE, TRUE))
2266 freeSetupFileHash(setup_file_hash);
2271 return setup_file_hash;
2274 void checkSetupFileHashIdentifier(SetupFileHash *setup_file_hash,
2275 char *filename, char *identifier)
2277 char *value = getHashEntry(setup_file_hash, TOKEN_STR_FILE_IDENTIFIER);
2280 Error(ERR_WARN, "config file '%s' has no file identifier", filename);
2281 else if (!checkCookieString(value, identifier))
2282 Error(ERR_WARN, "config file '%s' has wrong file identifier", filename);
2286 /* ========================================================================= */
2287 /* setup file stuff */
2288 /* ========================================================================= */
2290 #define TOKEN_STR_LAST_LEVEL_SERIES "last_level_series"
2291 #define TOKEN_STR_LAST_PLAYED_LEVEL "last_played_level"
2292 #define TOKEN_STR_HANDICAP_LEVEL "handicap_level"
2294 /* level directory info */
2295 #define LEVELINFO_TOKEN_IDENTIFIER 0
2296 #define LEVELINFO_TOKEN_NAME 1
2297 #define LEVELINFO_TOKEN_NAME_SORTING 2
2298 #define LEVELINFO_TOKEN_AUTHOR 3
2299 #define LEVELINFO_TOKEN_YEAR 4
2300 #define LEVELINFO_TOKEN_IMPORTED_FROM 5
2301 #define LEVELINFO_TOKEN_IMPORTED_BY 6
2302 #define LEVELINFO_TOKEN_TESTED_BY 7
2303 #define LEVELINFO_TOKEN_LEVELS 8
2304 #define LEVELINFO_TOKEN_FIRST_LEVEL 9
2305 #define LEVELINFO_TOKEN_SORT_PRIORITY 10
2306 #define LEVELINFO_TOKEN_LATEST_ENGINE 11
2307 #define LEVELINFO_TOKEN_LEVEL_GROUP 12
2308 #define LEVELINFO_TOKEN_READONLY 13
2309 #define LEVELINFO_TOKEN_GRAPHICS_SET_ECS 14
2310 #define LEVELINFO_TOKEN_GRAPHICS_SET_AGA 15
2311 #define LEVELINFO_TOKEN_GRAPHICS_SET 16
2312 #define LEVELINFO_TOKEN_SOUNDS_SET 17
2313 #define LEVELINFO_TOKEN_MUSIC_SET 18
2314 #define LEVELINFO_TOKEN_FILENAME 19
2315 #define LEVELINFO_TOKEN_FILETYPE 20
2316 #define LEVELINFO_TOKEN_HANDICAP 21
2317 #define LEVELINFO_TOKEN_SKIP_LEVELS 22
2319 #define NUM_LEVELINFO_TOKENS 23
2321 static LevelDirTree ldi;
2323 static struct TokenInfo levelinfo_tokens[] =
2325 /* level directory info */
2326 { TYPE_STRING, &ldi.identifier, "identifier" },
2327 { TYPE_STRING, &ldi.name, "name" },
2328 { TYPE_STRING, &ldi.name_sorting, "name_sorting" },
2329 { TYPE_STRING, &ldi.author, "author" },
2330 { TYPE_STRING, &ldi.year, "year" },
2331 { TYPE_STRING, &ldi.imported_from, "imported_from" },
2332 { TYPE_STRING, &ldi.imported_by, "imported_by" },
2333 { TYPE_STRING, &ldi.tested_by, "tested_by" },
2334 { TYPE_INTEGER, &ldi.levels, "levels" },
2335 { TYPE_INTEGER, &ldi.first_level, "first_level" },
2336 { TYPE_INTEGER, &ldi.sort_priority, "sort_priority" },
2337 { TYPE_BOOLEAN, &ldi.latest_engine, "latest_engine" },
2338 { TYPE_BOOLEAN, &ldi.level_group, "level_group" },
2339 { TYPE_BOOLEAN, &ldi.readonly, "readonly" },
2340 { TYPE_STRING, &ldi.graphics_set_ecs, "graphics_set.ecs" },
2341 { TYPE_STRING, &ldi.graphics_set_aga, "graphics_set.aga" },
2342 { TYPE_STRING, &ldi.graphics_set, "graphics_set" },
2343 { TYPE_STRING, &ldi.sounds_set, "sounds_set" },
2344 { TYPE_STRING, &ldi.music_set, "music_set" },
2345 { TYPE_STRING, &ldi.level_filename, "filename" },
2346 { TYPE_STRING, &ldi.level_filetype, "filetype" },
2347 { TYPE_BOOLEAN, &ldi.handicap, "handicap" },
2348 { TYPE_BOOLEAN, &ldi.skip_levels, "skip_levels" }
2351 static struct TokenInfo artworkinfo_tokens[] =
2353 /* artwork directory info */
2354 { TYPE_STRING, &ldi.identifier, "identifier" },
2355 { TYPE_STRING, &ldi.subdir, "subdir" },
2356 { TYPE_STRING, &ldi.name, "name" },
2357 { TYPE_STRING, &ldi.name_sorting, "name_sorting" },
2358 { TYPE_STRING, &ldi.author, "author" },
2359 { TYPE_INTEGER, &ldi.sort_priority, "sort_priority" },
2360 { TYPE_STRING, &ldi.basepath, "basepath" },
2361 { TYPE_STRING, &ldi.fullpath, "fullpath" },
2362 { TYPE_BOOLEAN, &ldi.in_user_dir, "in_user_dir" },
2363 { TYPE_INTEGER, &ldi.color, "color" },
2364 { TYPE_STRING, &ldi.class_desc, "class_desc" },
2369 static void setTreeInfoToDefaults(TreeInfo *ti, int type)
2373 ti->node_top = (ti->type == TREE_TYPE_LEVEL_DIR ? &leveldir_first :
2374 ti->type == TREE_TYPE_GRAPHICS_DIR ? &artwork.gfx_first :
2375 ti->type == TREE_TYPE_SOUNDS_DIR ? &artwork.snd_first :
2376 ti->type == TREE_TYPE_MUSIC_DIR ? &artwork.mus_first :
2379 ti->node_parent = NULL;
2380 ti->node_group = NULL;
2387 ti->fullpath = NULL;
2388 ti->basepath = NULL;
2389 ti->identifier = NULL;
2390 ti->name = getStringCopy(ANONYMOUS_NAME);
2391 ti->name_sorting = NULL;
2392 ti->author = getStringCopy(ANONYMOUS_NAME);
2395 ti->sort_priority = LEVELCLASS_UNDEFINED; /* default: least priority */
2396 ti->latest_engine = FALSE; /* default: get from level */
2397 ti->parent_link = FALSE;
2398 ti->in_user_dir = FALSE;
2399 ti->user_defined = FALSE;
2401 ti->class_desc = NULL;
2403 ti->infotext = getStringCopy(TREE_INFOTEXT(ti->type));
2405 if (ti->type == TREE_TYPE_LEVEL_DIR)
2407 ti->imported_from = NULL;
2408 ti->imported_by = NULL;
2409 ti->tested_by = NULL;
2411 ti->graphics_set_ecs = NULL;
2412 ti->graphics_set_aga = NULL;
2413 ti->graphics_set = NULL;
2414 ti->sounds_set = NULL;
2415 ti->music_set = NULL;
2416 ti->graphics_path = getStringCopy(UNDEFINED_FILENAME);
2417 ti->sounds_path = getStringCopy(UNDEFINED_FILENAME);
2418 ti->music_path = getStringCopy(UNDEFINED_FILENAME);
2420 ti->level_filename = NULL;
2421 ti->level_filetype = NULL;
2424 ti->first_level = 0;
2426 ti->level_group = FALSE;
2427 ti->handicap_level = 0;
2428 ti->readonly = TRUE;
2429 ti->handicap = TRUE;
2430 ti->skip_levels = FALSE;
2434 static void setTreeInfoToDefaultsFromParent(TreeInfo *ti, TreeInfo *parent)
2438 Error(ERR_WARN, "setTreeInfoToDefaultsFromParent(): parent == NULL");
2440 setTreeInfoToDefaults(ti, TREE_TYPE_UNDEFINED);
2445 /* copy all values from the parent structure */
2447 ti->type = parent->type;
2449 ti->node_top = parent->node_top;
2450 ti->node_parent = parent;
2451 ti->node_group = NULL;
2458 ti->fullpath = NULL;
2459 ti->basepath = NULL;
2460 ti->identifier = NULL;
2461 ti->name = getStringCopy(ANONYMOUS_NAME);
2462 ti->name_sorting = NULL;
2463 ti->author = getStringCopy(parent->author);
2464 ti->year = getStringCopy(parent->year);
2466 ti->sort_priority = parent->sort_priority;
2467 ti->latest_engine = parent->latest_engine;
2468 ti->parent_link = FALSE;
2469 ti->in_user_dir = parent->in_user_dir;
2470 ti->user_defined = parent->user_defined;
2471 ti->color = parent->color;
2472 ti->class_desc = getStringCopy(parent->class_desc);
2474 ti->infotext = getStringCopy(parent->infotext);
2476 if (ti->type == TREE_TYPE_LEVEL_DIR)
2478 ti->imported_from = getStringCopy(parent->imported_from);
2479 ti->imported_by = getStringCopy(parent->imported_by);
2480 ti->tested_by = getStringCopy(parent->tested_by);
2482 ti->graphics_set_ecs = NULL;
2483 ti->graphics_set_aga = NULL;
2484 ti->graphics_set = NULL;
2485 ti->sounds_set = NULL;
2486 ti->music_set = NULL;
2487 ti->graphics_path = getStringCopy(UNDEFINED_FILENAME);
2488 ti->sounds_path = getStringCopy(UNDEFINED_FILENAME);
2489 ti->music_path = getStringCopy(UNDEFINED_FILENAME);
2491 ti->level_filename = NULL;
2492 ti->level_filetype = NULL;
2495 ti->first_level = 0;
2497 ti->level_group = FALSE;
2498 ti->handicap_level = 0;
2499 ti->readonly = TRUE;
2500 ti->handicap = TRUE;
2501 ti->skip_levels = FALSE;
2505 static TreeInfo *getTreeInfoCopy(TreeInfo *ti)
2507 TreeInfo *ti_copy = newTreeInfo();
2509 /* copy all values from the original structure */
2511 ti_copy->type = ti->type;
2513 ti_copy->node_top = ti->node_top;
2514 ti_copy->node_parent = ti->node_parent;
2515 ti_copy->node_group = ti->node_group;
2516 ti_copy->next = ti->next;
2518 ti_copy->cl_first = ti->cl_first;
2519 ti_copy->cl_cursor = ti->cl_cursor;
2521 ti_copy->subdir = getStringCopy(ti->subdir);
2522 ti_copy->fullpath = getStringCopy(ti->fullpath);
2523 ti_copy->basepath = getStringCopy(ti->basepath);
2524 ti_copy->identifier = getStringCopy(ti->identifier);
2525 ti_copy->name = getStringCopy(ti->name);
2526 ti_copy->name_sorting = getStringCopy(ti->name_sorting);
2527 ti_copy->author = getStringCopy(ti->author);
2528 ti_copy->year = getStringCopy(ti->year);
2529 ti_copy->imported_from = getStringCopy(ti->imported_from);
2530 ti_copy->imported_by = getStringCopy(ti->imported_by);
2531 ti_copy->tested_by = getStringCopy(ti->tested_by);
2533 ti_copy->graphics_set_ecs = getStringCopy(ti->graphics_set_ecs);
2534 ti_copy->graphics_set_aga = getStringCopy(ti->graphics_set_aga);
2535 ti_copy->graphics_set = getStringCopy(ti->graphics_set);
2536 ti_copy->sounds_set = getStringCopy(ti->sounds_set);
2537 ti_copy->music_set = getStringCopy(ti->music_set);
2538 ti_copy->graphics_path = getStringCopy(ti->graphics_path);
2539 ti_copy->sounds_path = getStringCopy(ti->sounds_path);
2540 ti_copy->music_path = getStringCopy(ti->music_path);
2542 ti_copy->level_filename = getStringCopy(ti->level_filename);
2543 ti_copy->level_filetype = getStringCopy(ti->level_filetype);
2545 ti_copy->levels = ti->levels;
2546 ti_copy->first_level = ti->first_level;
2547 ti_copy->last_level = ti->last_level;
2548 ti_copy->sort_priority = ti->sort_priority;
2550 ti_copy->latest_engine = ti->latest_engine;
2552 ti_copy->level_group = ti->level_group;
2553 ti_copy->parent_link = ti->parent_link;
2554 ti_copy->in_user_dir = ti->in_user_dir;
2555 ti_copy->user_defined = ti->user_defined;
2556 ti_copy->readonly = ti->readonly;
2557 ti_copy->handicap = ti->handicap;
2558 ti_copy->skip_levels = ti->skip_levels;
2560 ti_copy->color = ti->color;
2561 ti_copy->class_desc = getStringCopy(ti->class_desc);
2562 ti_copy->handicap_level = ti->handicap_level;
2564 ti_copy->infotext = getStringCopy(ti->infotext);
2569 static void freeTreeInfo(TreeInfo *ti)
2574 checked_free(ti->subdir);
2575 checked_free(ti->fullpath);
2576 checked_free(ti->basepath);
2577 checked_free(ti->identifier);
2579 checked_free(ti->name);
2580 checked_free(ti->name_sorting);
2581 checked_free(ti->author);
2582 checked_free(ti->year);
2584 checked_free(ti->class_desc);
2586 checked_free(ti->infotext);
2588 if (ti->type == TREE_TYPE_LEVEL_DIR)
2590 checked_free(ti->imported_from);
2591 checked_free(ti->imported_by);
2592 checked_free(ti->tested_by);
2594 checked_free(ti->graphics_set_ecs);
2595 checked_free(ti->graphics_set_aga);
2596 checked_free(ti->graphics_set);
2597 checked_free(ti->sounds_set);
2598 checked_free(ti->music_set);
2600 checked_free(ti->graphics_path);
2601 checked_free(ti->sounds_path);
2602 checked_free(ti->music_path);
2604 checked_free(ti->level_filename);
2605 checked_free(ti->level_filetype);
2611 void setSetupInfo(struct TokenInfo *token_info,
2612 int token_nr, char *token_value)
2614 int token_type = token_info[token_nr].type;
2615 void *setup_value = token_info[token_nr].value;
2617 if (token_value == NULL)
2620 /* set setup field to corresponding token value */
2625 *(boolean *)setup_value = get_boolean_from_string(token_value);
2629 *(Key *)setup_value = getKeyFromKeyName(token_value);
2633 *(Key *)setup_value = getKeyFromX11KeyName(token_value);
2637 *(int *)setup_value = get_integer_from_string(token_value);
2641 checked_free(*(char **)setup_value);
2642 *(char **)setup_value = getStringCopy(token_value);
2650 static int compareTreeInfoEntries(const void *object1, const void *object2)
2652 const TreeInfo *entry1 = *((TreeInfo **)object1);
2653 const TreeInfo *entry2 = *((TreeInfo **)object2);
2654 int class_sorting1, class_sorting2;
2657 if (entry1->type == TREE_TYPE_LEVEL_DIR)
2659 class_sorting1 = LEVELSORTING(entry1);
2660 class_sorting2 = LEVELSORTING(entry2);
2664 class_sorting1 = ARTWORKSORTING(entry1);
2665 class_sorting2 = ARTWORKSORTING(entry2);
2668 if (entry1->parent_link || entry2->parent_link)
2669 compare_result = (entry1->parent_link ? -1 : +1);
2670 else if (entry1->sort_priority == entry2->sort_priority)
2672 char *name1 = getStringToLower(entry1->name_sorting);
2673 char *name2 = getStringToLower(entry2->name_sorting);
2675 compare_result = strcmp(name1, name2);
2680 else if (class_sorting1 == class_sorting2)
2681 compare_result = entry1->sort_priority - entry2->sort_priority;
2683 compare_result = class_sorting1 - class_sorting2;
2685 return compare_result;
2688 static void createParentTreeInfoNode(TreeInfo *node_parent)
2692 if (node_parent == NULL)
2695 ti_new = newTreeInfo();
2696 setTreeInfoToDefaults(ti_new, node_parent->type);
2698 ti_new->node_parent = node_parent;
2699 ti_new->parent_link = TRUE;
2701 setString(&ti_new->identifier, node_parent->identifier);
2702 setString(&ti_new->name, ".. (parent directory)");
2703 setString(&ti_new->name_sorting, ti_new->name);
2705 setString(&ti_new->subdir, "..");
2706 setString(&ti_new->fullpath, node_parent->fullpath);
2708 ti_new->sort_priority = node_parent->sort_priority;
2709 ti_new->latest_engine = node_parent->latest_engine;
2711 setString(&ti_new->class_desc, getLevelClassDescription(ti_new));
2713 pushTreeInfo(&node_parent->node_group, ti_new);
2717 /* -------------------------------------------------------------------------- */
2718 /* functions for handling level and custom artwork info cache */
2719 /* -------------------------------------------------------------------------- */
2721 static void LoadArtworkInfoCache()
2723 InitCacheDirectory();
2725 if (artworkinfo_cache_old == NULL)
2727 char *filename = getPath2(getCacheDir(), ARTWORKINFO_CACHE_FILE);
2729 /* try to load artwork info hash from already existing cache file */
2730 artworkinfo_cache_old = loadSetupFileHash(filename);
2732 /* if no artwork info cache file was found, start with empty hash */
2733 if (artworkinfo_cache_old == NULL)
2734 artworkinfo_cache_old = newSetupFileHash();
2739 if (artworkinfo_cache_new == NULL)
2740 artworkinfo_cache_new = newSetupFileHash();
2743 static void SaveArtworkInfoCache()
2745 char *filename = getPath2(getCacheDir(), ARTWORKINFO_CACHE_FILE);
2747 InitCacheDirectory();
2749 saveSetupFileHash(artworkinfo_cache_new, filename);
2754 static char *getCacheTokenPrefix(char *prefix1, char *prefix2)
2756 static char *prefix = NULL;
2758 checked_free(prefix);
2760 prefix = getStringCat2WithSeparator(prefix1, prefix2, ".");
2765 /* (identical to above function, but separate string buffer needed -- nasty) */
2766 static char *getCacheToken(char *prefix, char *suffix)
2768 static char *token = NULL;
2770 checked_free(token);
2772 token = getStringCat2WithSeparator(prefix, suffix, ".");
2777 static char *getFileTimestamp(char *filename)
2779 struct stat file_status;
2781 if (stat(filename, &file_status) != 0) /* cannot stat file */
2782 return getStringCopy(i_to_a(0));
2784 return getStringCopy(i_to_a(file_status.st_mtime));
2787 static boolean modifiedFileTimestamp(char *filename, char *timestamp_string)
2789 struct stat file_status;
2791 if (timestamp_string == NULL)
2794 if (stat(filename, &file_status) != 0) /* cannot stat file */
2797 return (file_status.st_mtime != atoi(timestamp_string));
2800 static TreeInfo *getArtworkInfoCacheEntry(LevelDirTree *level_node, int type)
2802 char *identifier = level_node->subdir;
2803 char *type_string = ARTWORK_DIRECTORY(type);
2804 char *token_prefix = getCacheTokenPrefix(type_string, identifier);
2805 char *token_main = getCacheToken(token_prefix, "CACHED");
2806 char *cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
2807 boolean cached = (cache_entry != NULL && strEqual(cache_entry, "true"));
2808 TreeInfo *artwork_info = NULL;
2810 if (!use_artworkinfo_cache)
2817 artwork_info = newTreeInfo();
2818 setTreeInfoToDefaults(artwork_info, type);
2820 /* set all structure fields according to the token/value pairs */
2821 ldi = *artwork_info;
2822 for (i = 0; artworkinfo_tokens[i].type != -1; i++)
2824 char *token = getCacheToken(token_prefix, artworkinfo_tokens[i].text);
2825 char *value = getHashEntry(artworkinfo_cache_old, token);
2827 setSetupInfo(artworkinfo_tokens, i, value);
2829 /* check if cache entry for this item is invalid or incomplete */
2833 Error(ERR_WARN, "cache entry '%s' invalid", token);
2840 *artwork_info = ldi;
2845 char *filename_levelinfo = getPath2(getLevelDirFromTreeInfo(level_node),
2846 LEVELINFO_FILENAME);
2847 char *filename_artworkinfo = getPath2(getSetupArtworkDir(artwork_info),
2848 ARTWORKINFO_FILENAME(type));
2850 /* check if corresponding "levelinfo.conf" file has changed */
2851 token_main = getCacheToken(token_prefix, "TIMESTAMP_LEVELINFO");
2852 cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
2854 if (modifiedFileTimestamp(filename_levelinfo, cache_entry))
2857 /* check if corresponding "<artworkinfo>.conf" file has changed */
2858 token_main = getCacheToken(token_prefix, "TIMESTAMP_ARTWORKINFO");
2859 cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
2861 if (modifiedFileTimestamp(filename_artworkinfo, cache_entry))
2866 printf("::: '%s': INVALIDATED FROM CACHE BY TIMESTAMP\n", identifier);
2869 checked_free(filename_levelinfo);
2870 checked_free(filename_artworkinfo);
2873 if (!cached && artwork_info != NULL)
2875 freeTreeInfo(artwork_info);
2880 return artwork_info;
2883 static void setArtworkInfoCacheEntry(TreeInfo *artwork_info,
2884 LevelDirTree *level_node, int type)
2886 char *identifier = level_node->subdir;
2887 char *type_string = ARTWORK_DIRECTORY(type);
2888 char *token_prefix = getCacheTokenPrefix(type_string, identifier);
2889 char *token_main = getCacheToken(token_prefix, "CACHED");
2890 boolean set_cache_timestamps = TRUE;
2893 setHashEntry(artworkinfo_cache_new, token_main, "true");
2895 if (set_cache_timestamps)
2897 char *filename_levelinfo = getPath2(getLevelDirFromTreeInfo(level_node),
2898 LEVELINFO_FILENAME);
2899 char *filename_artworkinfo = getPath2(getSetupArtworkDir(artwork_info),
2900 ARTWORKINFO_FILENAME(type));
2901 char *timestamp_levelinfo = getFileTimestamp(filename_levelinfo);
2902 char *timestamp_artworkinfo = getFileTimestamp(filename_artworkinfo);
2904 token_main = getCacheToken(token_prefix, "TIMESTAMP_LEVELINFO");
2905 setHashEntry(artworkinfo_cache_new, token_main, timestamp_levelinfo);
2907 token_main = getCacheToken(token_prefix, "TIMESTAMP_ARTWORKINFO");
2908 setHashEntry(artworkinfo_cache_new, token_main, timestamp_artworkinfo);
2910 checked_free(filename_levelinfo);
2911 checked_free(filename_artworkinfo);
2912 checked_free(timestamp_levelinfo);
2913 checked_free(timestamp_artworkinfo);
2916 ldi = *artwork_info;
2917 for (i = 0; artworkinfo_tokens[i].type != -1; i++)
2919 char *token = getCacheToken(token_prefix, artworkinfo_tokens[i].text);
2920 char *value = getSetupValue(artworkinfo_tokens[i].type,
2921 artworkinfo_tokens[i].value);
2923 setHashEntry(artworkinfo_cache_new, token, value);
2928 /* -------------------------------------------------------------------------- */
2929 /* functions for loading level info and custom artwork info */
2930 /* -------------------------------------------------------------------------- */
2932 /* forward declaration for recursive call by "LoadLevelInfoFromLevelDir()" */
2933 static void LoadLevelInfoFromLevelDir(TreeInfo **, TreeInfo *, char *);
2935 static boolean LoadLevelInfoFromLevelConf(TreeInfo **node_first,
2936 TreeInfo *node_parent,
2937 char *level_directory,
2938 char *directory_name)
2941 static unsigned long progress_delay = 0;
2942 unsigned long progress_delay_value = 100; /* (in milliseconds) */
2944 char *directory_path = getPath2(level_directory, directory_name);
2945 char *filename = getPath2(directory_path, LEVELINFO_FILENAME);
2946 SetupFileHash *setup_file_hash;
2947 LevelDirTree *leveldir_new = NULL;
2950 /* unless debugging, silently ignore directories without "levelinfo.conf" */
2951 if (!options.debug && !fileExists(filename))
2953 free(directory_path);
2959 setup_file_hash = loadSetupFileHash(filename);
2961 if (setup_file_hash == NULL)
2963 Error(ERR_WARN, "ignoring level directory '%s'", directory_path);
2965 free(directory_path);
2971 leveldir_new = newTreeInfo();
2974 setTreeInfoToDefaultsFromParent(leveldir_new, node_parent);
2976 setTreeInfoToDefaults(leveldir_new, TREE_TYPE_LEVEL_DIR);
2978 leveldir_new->subdir = getStringCopy(directory_name);
2980 checkSetupFileHashIdentifier(setup_file_hash, filename,
2981 getCookie("LEVELINFO"));
2983 /* set all structure fields according to the token/value pairs */
2984 ldi = *leveldir_new;
2985 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
2986 setSetupInfo(levelinfo_tokens, i,
2987 getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
2988 *leveldir_new = ldi;
2990 if (strEqual(leveldir_new->name, ANONYMOUS_NAME))
2991 setString(&leveldir_new->name, leveldir_new->subdir);
2993 if (leveldir_new->identifier == NULL)
2994 leveldir_new->identifier = getStringCopy(leveldir_new->subdir);
2996 if (leveldir_new->name_sorting == NULL)
2997 leveldir_new->name_sorting = getStringCopy(leveldir_new->name);
2999 if (node_parent == NULL) /* top level group */
3001 leveldir_new->basepath = getStringCopy(level_directory);
3002 leveldir_new->fullpath = getStringCopy(leveldir_new->subdir);
3004 else /* sub level group */
3006 leveldir_new->basepath = getStringCopy(node_parent->basepath);
3007 leveldir_new->fullpath = getPath2(node_parent->fullpath, directory_name);
3011 if (leveldir_new->levels < 1)
3012 leveldir_new->levels = 1;
3015 leveldir_new->last_level =
3016 leveldir_new->first_level + leveldir_new->levels - 1;
3018 leveldir_new->in_user_dir =
3019 (!strEqual(leveldir_new->basepath, options.level_directory));
3021 /* adjust some settings if user's private level directory was detected */
3022 if (leveldir_new->sort_priority == LEVELCLASS_UNDEFINED &&
3023 leveldir_new->in_user_dir &&
3024 (strEqual(leveldir_new->subdir, getLoginName()) ||
3025 strEqual(leveldir_new->name, getLoginName()) ||
3026 strEqual(leveldir_new->author, getRealName())))
3028 leveldir_new->sort_priority = LEVELCLASS_PRIVATE_START;
3029 leveldir_new->readonly = FALSE;
3032 leveldir_new->user_defined =
3033 (leveldir_new->in_user_dir && IS_LEVELCLASS_PRIVATE(leveldir_new));
3035 leveldir_new->color = LEVELCOLOR(leveldir_new);
3037 setString(&leveldir_new->class_desc, getLevelClassDescription(leveldir_new));
3039 leveldir_new->handicap_level = /* set handicap to default value */
3040 (leveldir_new->user_defined || !leveldir_new->handicap ?
3041 leveldir_new->last_level : leveldir_new->first_level);
3045 DrawInitTextExt(leveldir_new->name, 150, FC_YELLOW,
3046 leveldir_new->level_group);
3048 if (leveldir_new->level_group ||
3049 DelayReached(&progress_delay, progress_delay_value))
3050 DrawInitText(leveldir_new->name, 150, FC_YELLOW);
3053 DrawInitText(leveldir_new->name, 150, FC_YELLOW);
3057 /* !!! don't skip sets without levels (else artwork base sets are missing) */
3059 if (leveldir_new->levels < 1 && !leveldir_new->level_group)
3061 /* skip level sets without levels (which are probably artwork base sets) */
3063 freeSetupFileHash(setup_file_hash);
3064 free(directory_path);
3072 pushTreeInfo(node_first, leveldir_new);
3074 freeSetupFileHash(setup_file_hash);
3076 if (leveldir_new->level_group)
3078 /* create node to link back to current level directory */
3079 createParentTreeInfoNode(leveldir_new);
3081 /* recursively step into sub-directory and look for more level series */
3082 LoadLevelInfoFromLevelDir(&leveldir_new->node_group,
3083 leveldir_new, directory_path);
3086 free(directory_path);
3092 static void LoadLevelInfoFromLevelDir(TreeInfo **node_first,
3093 TreeInfo *node_parent,
3094 char *level_directory)
3097 struct dirent *dir_entry;
3098 boolean valid_entry_found = FALSE;
3100 if ((dir = opendir(level_directory)) == NULL)
3102 Error(ERR_WARN, "cannot read level directory '%s'", level_directory);
3106 while ((dir_entry = readdir(dir)) != NULL) /* loop until last dir entry */
3108 struct stat file_status;
3109 char *directory_name = dir_entry->d_name;
3110 char *directory_path = getPath2(level_directory, directory_name);
3112 /* skip entries for current and parent directory */
3113 if (strEqual(directory_name, ".") ||
3114 strEqual(directory_name, ".."))
3116 free(directory_path);
3120 /* find out if directory entry is itself a directory */
3121 if (stat(directory_path, &file_status) != 0 || /* cannot stat file */
3122 (file_status.st_mode & S_IFMT) != S_IFDIR) /* not a directory */
3124 free(directory_path);
3128 free(directory_path);
3130 if (strEqual(directory_name, GRAPHICS_DIRECTORY) ||
3131 strEqual(directory_name, SOUNDS_DIRECTORY) ||
3132 strEqual(directory_name, MUSIC_DIRECTORY))
3135 valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
3142 /* special case: top level directory may directly contain "levelinfo.conf" */
3143 if (node_parent == NULL && !valid_entry_found)
3145 /* check if this directory directly contains a file "levelinfo.conf" */
3146 valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
3147 level_directory, ".");
3150 if (!valid_entry_found)
3151 Error(ERR_WARN, "cannot find any valid level series in directory '%s'",
3155 boolean AdjustGraphicsForEMC()
3157 boolean settings_changed = FALSE;
3159 settings_changed |= adjustTreeGraphicsForEMC(leveldir_first_all);
3160 settings_changed |= adjustTreeGraphicsForEMC(leveldir_first);
3162 return settings_changed;
3165 void LoadLevelInfo()
3167 InitUserLevelDirectory(getLoginName());
3169 DrawInitText("Loading level series", 120, FC_GREEN);
3171 LoadLevelInfoFromLevelDir(&leveldir_first, NULL, options.level_directory);
3172 LoadLevelInfoFromLevelDir(&leveldir_first, NULL, getUserLevelDir(NULL));
3174 /* after loading all level set information, clone the level directory tree
3175 and remove all level sets without levels (these may still contain artwork
3176 to be offered in the setup menu as "custom artwork", and are therefore
3177 checked for existing artwork in the function "LoadLevelArtworkInfo()") */
3178 leveldir_first_all = leveldir_first;
3179 cloneTree(&leveldir_first, leveldir_first_all, TRUE);
3181 AdjustGraphicsForEMC();
3183 /* before sorting, the first entries will be from the user directory */
3184 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
3186 if (leveldir_first == NULL)
3187 Error(ERR_EXIT, "cannot find any valid level series in any directory");
3189 sortTreeInfo(&leveldir_first);
3192 dumpTreeInfo(leveldir_first, 0);
3196 static boolean LoadArtworkInfoFromArtworkConf(TreeInfo **node_first,
3197 TreeInfo *node_parent,
3198 char *base_directory,
3199 char *directory_name, int type)
3201 char *directory_path = getPath2(base_directory, directory_name);
3202 char *filename = getPath2(directory_path, ARTWORKINFO_FILENAME(type));
3203 SetupFileHash *setup_file_hash = NULL;
3204 TreeInfo *artwork_new = NULL;
3207 if (fileExists(filename))
3208 setup_file_hash = loadSetupFileHash(filename);
3210 if (setup_file_hash == NULL) /* no config file -- look for artwork files */
3213 struct dirent *dir_entry;
3214 boolean valid_file_found = FALSE;
3216 if ((dir = opendir(directory_path)) != NULL)
3218 while ((dir_entry = readdir(dir)) != NULL)
3220 char *entry_name = dir_entry->d_name;
3222 if (FileIsArtworkType(entry_name, type))
3224 valid_file_found = TRUE;
3232 if (!valid_file_found)
3234 if (!strEqual(directory_name, "."))
3235 Error(ERR_WARN, "ignoring artwork directory '%s'", directory_path);
3237 free(directory_path);
3244 artwork_new = newTreeInfo();
3247 setTreeInfoToDefaultsFromParent(artwork_new, node_parent);
3249 setTreeInfoToDefaults(artwork_new, type);
3251 artwork_new->subdir = getStringCopy(directory_name);
3253 if (setup_file_hash) /* (before defining ".color" and ".class_desc") */
3256 checkSetupFileHashIdentifier(setup_file_hash, filename, getCookie("..."));
3259 /* set all structure fields according to the token/value pairs */
3261 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
3262 setSetupInfo(levelinfo_tokens, i,
3263 getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
3266 if (strEqual(artwork_new->name, ANONYMOUS_NAME))
3267 setString(&artwork_new->name, artwork_new->subdir);
3269 if (artwork_new->identifier == NULL)
3270 artwork_new->identifier = getStringCopy(artwork_new->subdir);
3272 if (artwork_new->name_sorting == NULL)
3273 artwork_new->name_sorting = getStringCopy(artwork_new->name);
3276 if (node_parent == NULL) /* top level group */
3278 artwork_new->basepath = getStringCopy(base_directory);
3279 artwork_new->fullpath = getStringCopy(artwork_new->subdir);
3281 else /* sub level group */
3283 artwork_new->basepath = getStringCopy(node_parent->basepath);
3284 artwork_new->fullpath = getPath2(node_parent->fullpath, directory_name);
3287 artwork_new->in_user_dir =
3288 (!strEqual(artwork_new->basepath, OPTIONS_ARTWORK_DIRECTORY(type)));
3290 /* (may use ".sort_priority" from "setup_file_hash" above) */
3291 artwork_new->color = ARTWORKCOLOR(artwork_new);
3293 setString(&artwork_new->class_desc, getLevelClassDescription(artwork_new));
3295 if (setup_file_hash == NULL) /* (after determining ".user_defined") */
3297 if (strEqual(artwork_new->subdir, "."))
3299 if (artwork_new->user_defined)
3301 setString(&artwork_new->identifier, "private");
3302 artwork_new->sort_priority = ARTWORKCLASS_PRIVATE;
3306 setString(&artwork_new->identifier, "classic");
3307 artwork_new->sort_priority = ARTWORKCLASS_CLASSICS;
3310 /* set to new values after changing ".sort_priority" */
3311 artwork_new->color = ARTWORKCOLOR(artwork_new);
3313 setString(&artwork_new->class_desc,
3314 getLevelClassDescription(artwork_new));
3318 setString(&artwork_new->identifier, artwork_new->subdir);
3321 setString(&artwork_new->name, artwork_new->identifier);
3322 setString(&artwork_new->name_sorting, artwork_new->name);
3326 DrawInitText(artwork_new->name, 150, FC_YELLOW);
3329 pushTreeInfo(node_first, artwork_new);
3331 freeSetupFileHash(setup_file_hash);
3333 free(directory_path);
3339 static void LoadArtworkInfoFromArtworkDir(TreeInfo **node_first,
3340 TreeInfo *node_parent,
3341 char *base_directory, int type)
3344 struct dirent *dir_entry;
3345 boolean valid_entry_found = FALSE;
3347 if ((dir = opendir(base_directory)) == NULL)
3349 /* display error if directory is main "options.graphics_directory" etc. */
3350 if (base_directory == OPTIONS_ARTWORK_DIRECTORY(type))
3351 Error(ERR_WARN, "cannot read directory '%s'", base_directory);
3356 while ((dir_entry = readdir(dir)) != NULL) /* loop until last dir entry */
3358 struct stat file_status;
3359 char *directory_name = dir_entry->d_name;
3360 char *directory_path = getPath2(base_directory, directory_name);
3362 /* skip directory entries for current and parent directory */
3363 if (strEqual(directory_name, ".") ||
3364 strEqual(directory_name, ".."))
3366 free(directory_path);
3370 /* skip directory entries which are not a directory or are not accessible */
3371 if (stat(directory_path, &file_status) != 0 || /* cannot stat file */
3372 (file_status.st_mode & S_IFMT) != S_IFDIR) /* not a directory */
3374 free(directory_path);
3378 free(directory_path);
3380 /* check if this directory contains artwork with or without config file */
3381 valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
3383 directory_name, type);
3388 /* check if this directory directly contains artwork itself */
3389 valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
3390 base_directory, ".",
3392 if (!valid_entry_found)
3393 Error(ERR_WARN, "cannot find any valid artwork in directory '%s'",
3397 static TreeInfo *getDummyArtworkInfo(int type)
3399 /* this is only needed when there is completely no artwork available */
3400 TreeInfo *artwork_new = newTreeInfo();
3402 setTreeInfoToDefaults(artwork_new, type);
3404 setString(&artwork_new->subdir, UNDEFINED_FILENAME);
3405 setString(&artwork_new->fullpath, UNDEFINED_FILENAME);
3406 setString(&artwork_new->basepath, UNDEFINED_FILENAME);
3408 setString(&artwork_new->identifier, UNDEFINED_FILENAME);
3409 setString(&artwork_new->name, UNDEFINED_FILENAME);
3410 setString(&artwork_new->name_sorting, UNDEFINED_FILENAME);
3415 void LoadArtworkInfo()
3417 LoadArtworkInfoCache();
3419 DrawInitText("Looking for custom artwork", 120, FC_GREEN);
3421 LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
3422 options.graphics_directory,
3423 TREE_TYPE_GRAPHICS_DIR);
3424 LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
3425 getUserGraphicsDir(),
3426 TREE_TYPE_GRAPHICS_DIR);
3428 LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
3429 options.sounds_directory,
3430 TREE_TYPE_SOUNDS_DIR);
3431 LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
3433 TREE_TYPE_SOUNDS_DIR);
3435 LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
3436 options.music_directory,
3437 TREE_TYPE_MUSIC_DIR);
3438 LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
3440 TREE_TYPE_MUSIC_DIR);
3442 if (artwork.gfx_first == NULL)
3443 artwork.gfx_first = getDummyArtworkInfo(TREE_TYPE_GRAPHICS_DIR);
3444 if (artwork.snd_first == NULL)
3445 artwork.snd_first = getDummyArtworkInfo(TREE_TYPE_SOUNDS_DIR);
3446 if (artwork.mus_first == NULL)
3447 artwork.mus_first = getDummyArtworkInfo(TREE_TYPE_MUSIC_DIR);
3449 /* before sorting, the first entries will be from the user directory */
3450 artwork.gfx_current =
3451 getTreeInfoFromIdentifier(artwork.gfx_first, setup.graphics_set);
3452 if (artwork.gfx_current == NULL)
3453 artwork.gfx_current =
3454 getTreeInfoFromIdentifier(artwork.gfx_first, GFX_CLASSIC_SUBDIR);
3455 if (artwork.gfx_current == NULL)
3456 artwork.gfx_current = getFirstValidTreeInfoEntry(artwork.gfx_first);
3458 artwork.snd_current =
3459 getTreeInfoFromIdentifier(artwork.snd_first, setup.sounds_set);
3460 if (artwork.snd_current == NULL)
3461 artwork.snd_current =
3462 getTreeInfoFromIdentifier(artwork.snd_first, SND_CLASSIC_SUBDIR);
3463 if (artwork.snd_current == NULL)
3464 artwork.snd_current = getFirstValidTreeInfoEntry(artwork.snd_first);
3466 artwork.mus_current =
3467 getTreeInfoFromIdentifier(artwork.mus_first, setup.music_set);
3468 if (artwork.mus_current == NULL)
3469 artwork.mus_current =
3470 getTreeInfoFromIdentifier(artwork.mus_first, MUS_CLASSIC_SUBDIR);
3471 if (artwork.mus_current == NULL)
3472 artwork.mus_current = getFirstValidTreeInfoEntry(artwork.mus_first);
3474 artwork.gfx_current_identifier = artwork.gfx_current->identifier;
3475 artwork.snd_current_identifier = artwork.snd_current->identifier;
3476 artwork.mus_current_identifier = artwork.mus_current->identifier;
3479 printf("graphics set == %s\n\n", artwork.gfx_current_identifier);
3480 printf("sounds set == %s\n\n", artwork.snd_current_identifier);
3481 printf("music set == %s\n\n", artwork.mus_current_identifier);
3484 sortTreeInfo(&artwork.gfx_first);
3485 sortTreeInfo(&artwork.snd_first);
3486 sortTreeInfo(&artwork.mus_first);
3489 dumpTreeInfo(artwork.gfx_first, 0);
3490 dumpTreeInfo(artwork.snd_first, 0);
3491 dumpTreeInfo(artwork.mus_first, 0);
3495 void LoadArtworkInfoFromLevelInfo(ArtworkDirTree **artwork_node,
3496 LevelDirTree *level_node)
3499 static unsigned long progress_delay = 0;
3500 unsigned long progress_delay_value = 100; /* (in milliseconds) */
3502 int type = (*artwork_node)->type;
3504 /* recursively check all level directories for artwork sub-directories */
3508 /* check all tree entries for artwork, but skip parent link entries */
3509 if (!level_node->parent_link)
3511 TreeInfo *artwork_new = getArtworkInfoCacheEntry(level_node, type);
3512 boolean cached = (artwork_new != NULL);
3516 pushTreeInfo(artwork_node, artwork_new);
3520 TreeInfo *topnode_last = *artwork_node;
3521 char *path = getPath2(getLevelDirFromTreeInfo(level_node),
3522 ARTWORK_DIRECTORY(type));
3524 LoadArtworkInfoFromArtworkDir(artwork_node, NULL, path, type);
3526 if (topnode_last != *artwork_node) /* check for newly added node */
3528 artwork_new = *artwork_node;
3530 setString(&artwork_new->identifier, level_node->subdir);
3531 setString(&artwork_new->name, level_node->name);
3532 setString(&artwork_new->name_sorting, level_node->name_sorting);
3534 artwork_new->sort_priority = level_node->sort_priority;
3535 artwork_new->color = LEVELCOLOR(artwork_new);
3541 /* insert artwork info (from old cache or filesystem) into new cache */
3542 if (artwork_new != NULL)
3543 setArtworkInfoCacheEntry(artwork_new, level_node, type);
3547 DrawInitTextExt(level_node->name, 150, FC_YELLOW,
3548 level_node->level_group);
3550 if (level_node->level_group ||
3551 DelayReached(&progress_delay, progress_delay_value))
3552 DrawInitText(level_node->name, 150, FC_YELLOW);
3555 if (level_node->node_group != NULL)
3556 LoadArtworkInfoFromLevelInfo(artwork_node, level_node->node_group);
3558 level_node = level_node->next;
3562 void LoadLevelArtworkInfo()
3564 DrawInitText("Looking for custom level artwork", 120, FC_GREEN);
3566 LoadArtworkInfoFromLevelInfo(&artwork.gfx_first, leveldir_first_all);
3567 LoadArtworkInfoFromLevelInfo(&artwork.snd_first, leveldir_first_all);
3568 LoadArtworkInfoFromLevelInfo(&artwork.mus_first, leveldir_first_all);
3570 SaveArtworkInfoCache();
3572 /* needed for reloading level artwork not known at ealier stage */
3574 if (!strEqual(artwork.gfx_current_identifier, setup.graphics_set))
3576 artwork.gfx_current =
3577 getTreeInfoFromIdentifier(artwork.gfx_first, setup.graphics_set);
3578 if (artwork.gfx_current == NULL)
3579 artwork.gfx_current =
3580 getTreeInfoFromIdentifier(artwork.gfx_first, GFX_CLASSIC_SUBDIR);
3581 if (artwork.gfx_current == NULL)
3582 artwork.gfx_current = getFirstValidTreeInfoEntry(artwork.gfx_first);
3585 if (!strEqual(artwork.snd_current_identifier, setup.sounds_set))
3587 artwork.snd_current =
3588 getTreeInfoFromIdentifier(artwork.snd_first, setup.sounds_set);
3589 if (artwork.snd_current == NULL)
3590 artwork.snd_current =
3591 getTreeInfoFromIdentifier(artwork.snd_first, SND_CLASSIC_SUBDIR);
3592 if (artwork.snd_current == NULL)
3593 artwork.snd_current = getFirstValidTreeInfoEntry(artwork.snd_first);
3596 if (!strEqual(artwork.mus_current_identifier, setup.music_set))
3598 artwork.mus_current =
3599 getTreeInfoFromIdentifier(artwork.mus_first, setup.music_set);
3600 if (artwork.mus_current == NULL)
3601 artwork.mus_current =
3602 getTreeInfoFromIdentifier(artwork.mus_first, MUS_CLASSIC_SUBDIR);
3603 if (artwork.mus_current == NULL)
3604 artwork.mus_current = getFirstValidTreeInfoEntry(artwork.mus_first);
3607 sortTreeInfo(&artwork.gfx_first);
3608 sortTreeInfo(&artwork.snd_first);
3609 sortTreeInfo(&artwork.mus_first);
3612 dumpTreeInfo(artwork.gfx_first, 0);
3613 dumpTreeInfo(artwork.snd_first, 0);
3614 dumpTreeInfo(artwork.mus_first, 0);
3618 static void SaveUserLevelInfo()
3620 LevelDirTree *level_info;
3625 filename = getPath2(getUserLevelDir(getLoginName()), LEVELINFO_FILENAME);
3627 if (!(file = fopen(filename, MODE_WRITE)))
3629 Error(ERR_WARN, "cannot write level info file '%s'", filename);
3634 level_info = newTreeInfo();
3636 /* always start with reliable default values */
3637 setTreeInfoToDefaults(level_info, TREE_TYPE_LEVEL_DIR);
3639 setString(&level_info->name, getLoginName());
3640 setString(&level_info->author, getRealName());
3641 level_info->levels = 100;
3642 level_info->first_level = 1;
3644 token_value_position = TOKEN_VALUE_POSITION_SHORT;
3646 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER,
3647 getCookie("LEVELINFO")));
3650 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
3652 if (i == LEVELINFO_TOKEN_NAME ||
3653 i == LEVELINFO_TOKEN_AUTHOR ||
3654 i == LEVELINFO_TOKEN_LEVELS ||
3655 i == LEVELINFO_TOKEN_FIRST_LEVEL)
3656 fprintf(file, "%s\n", getSetupLine(levelinfo_tokens, "", i));
3658 /* just to make things nicer :) */
3659 if (i == LEVELINFO_TOKEN_AUTHOR)
3660 fprintf(file, "\n");
3663 token_value_position = TOKEN_VALUE_POSITION_DEFAULT;
3667 SetFilePermissions(filename, PERMS_PRIVATE);
3669 freeTreeInfo(level_info);
3673 char *getSetupValue(int type, void *value)
3675 static char value_string[MAX_LINE_LEN];
3683 strcpy(value_string, (*(boolean *)value ? "true" : "false"));
3687 strcpy(value_string, (*(boolean *)value ? "on" : "off"));
3691 strcpy(value_string, (*(boolean *)value ? "yes" : "no"));
3695 strcpy(value_string, (*(boolean *)value ? "AGA" : "ECS"));
3699 strcpy(value_string, getKeyNameFromKey(*(Key *)value));
3703 strcpy(value_string, getX11KeyNameFromKey(*(Key *)value));
3707 sprintf(value_string, "%d", *(int *)value);
3711 if (*(char **)value == NULL)
3714 strcpy(value_string, *(char **)value);
3718 value_string[0] = '\0';
3722 if (type & TYPE_GHOSTED)
3723 strcpy(value_string, "n/a");
3725 return value_string;
3728 char *getSetupLine(struct TokenInfo *token_info, char *prefix, int token_nr)
3732 static char token_string[MAX_LINE_LEN];
3733 int token_type = token_info[token_nr].type;
3734 void *setup_value = token_info[token_nr].value;
3735 char *token_text = token_info[token_nr].text;
3736 char *value_string = getSetupValue(token_type, setup_value);
3738 /* build complete token string */
3739 sprintf(token_string, "%s%s", prefix, token_text);
3741 /* build setup entry line */
3742 line = getFormattedSetupEntry(token_string, value_string);
3744 if (token_type == TYPE_KEY_X11)
3746 Key key = *(Key *)setup_value;
3747 char *keyname = getKeyNameFromKey(key);
3749 /* add comment, if useful */
3750 if (!strEqual(keyname, "(undefined)") &&
3751 !strEqual(keyname, "(unknown)"))
3753 /* add at least one whitespace */
3755 for (i = strlen(line); i < token_comment_position; i++)
3759 strcat(line, keyname);
3766 void LoadLevelSetup_LastSeries()
3768 /* ----------------------------------------------------------------------- */
3769 /* ~/.<program>/levelsetup.conf */
3770 /* ----------------------------------------------------------------------- */
3772 char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
3773 SetupFileHash *level_setup_hash = NULL;
3775 /* always start with reliable default values */
3776 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
3778 if ((level_setup_hash = loadSetupFileHash(filename)))
3780 char *last_level_series =
3781 getHashEntry(level_setup_hash, TOKEN_STR_LAST_LEVEL_SERIES);
3783 leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
3785 if (leveldir_current == NULL)
3786 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
3788 checkSetupFileHashIdentifier(level_setup_hash, filename,
3789 getCookie("LEVELSETUP"));
3791 freeSetupFileHash(level_setup_hash);
3794 Error(ERR_WARN, "using default setup values");
3799 void SaveLevelSetup_LastSeries()
3801 /* ----------------------------------------------------------------------- */
3802 /* ~/.<program>/levelsetup.conf */
3803 /* ----------------------------------------------------------------------- */
3805 char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
3806 char *level_subdir = leveldir_current->subdir;
3809 InitUserDataDirectory();
3811 if (!(file = fopen(filename, MODE_WRITE)))
3813 Error(ERR_WARN, "cannot write setup file '%s'", filename);
3818 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER,
3819 getCookie("LEVELSETUP")));
3820 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_LEVEL_SERIES,
3825 SetFilePermissions(filename, PERMS_PRIVATE);
3830 static void checkSeriesInfo()
3832 static char *level_directory = NULL;
3834 struct dirent *dir_entry;
3836 /* check for more levels besides the 'levels' field of 'levelinfo.conf' */
3838 level_directory = getPath2((leveldir_current->in_user_dir ?
3839 getUserLevelDir(NULL) :
3840 options.level_directory),
3841 leveldir_current->fullpath);
3843 if ((dir = opendir(level_directory)) == NULL)
3845 Error(ERR_WARN, "cannot read level directory '%s'", level_directory);
3849 while ((dir_entry = readdir(dir)) != NULL) /* last directory entry */
3851 if (strlen(dir_entry->d_name) > 4 &&
3852 dir_entry->d_name[3] == '.' &&
3853 strEqual(&dir_entry->d_name[4], LEVELFILE_EXTENSION))
3855 char levelnum_str[4];
3858 strncpy(levelnum_str, dir_entry->d_name, 3);
3859 levelnum_str[3] = '\0';
3861 levelnum_value = atoi(levelnum_str);
3864 if (levelnum_value < leveldir_current->first_level)
3866 Error(ERR_WARN, "additional level %d found", levelnum_value);
3867 leveldir_current->first_level = levelnum_value;
3869 else if (levelnum_value > leveldir_current->last_level)
3871 Error(ERR_WARN, "additional level %d found", levelnum_value);
3872 leveldir_current->last_level = levelnum_value;
3881 void LoadLevelSetup_SeriesInfo()
3884 SetupFileHash *level_setup_hash = NULL;
3885 char *level_subdir = leveldir_current->subdir;
3887 /* always start with reliable default values */
3888 level_nr = leveldir_current->first_level;
3890 checkSeriesInfo(leveldir_current);
3892 /* ----------------------------------------------------------------------- */
3893 /* ~/.<program>/levelsetup/<level series>/levelsetup.conf */
3894 /* ----------------------------------------------------------------------- */
3896 level_subdir = leveldir_current->subdir;
3898 filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
3900 if ((level_setup_hash = loadSetupFileHash(filename)))
3904 token_value = getHashEntry(level_setup_hash, TOKEN_STR_LAST_PLAYED_LEVEL);
3908 level_nr = atoi(token_value);
3910 if (level_nr < leveldir_current->first_level)
3911 level_nr = leveldir_current->first_level;
3912 if (level_nr > leveldir_current->last_level)
3913 level_nr = leveldir_current->last_level;
3916 token_value = getHashEntry(level_setup_hash, TOKEN_STR_HANDICAP_LEVEL);
3920 int level_nr = atoi(token_value);
3922 if (level_nr < leveldir_current->first_level)
3923 level_nr = leveldir_current->first_level;
3924 if (level_nr > leveldir_current->last_level + 1)
3925 level_nr = leveldir_current->last_level;
3927 if (leveldir_current->user_defined || !leveldir_current->handicap)
3928 level_nr = leveldir_current->last_level;
3930 leveldir_current->handicap_level = level_nr;
3933 checkSetupFileHashIdentifier(level_setup_hash, filename,
3934 getCookie("LEVELSETUP"));
3936 freeSetupFileHash(level_setup_hash);
3939 Error(ERR_WARN, "using default setup values");
3944 void SaveLevelSetup_SeriesInfo()
3947 char *level_subdir = leveldir_current->subdir;
3948 char *level_nr_str = int2str(level_nr, 0);
3949 char *handicap_level_str = int2str(leveldir_current->handicap_level, 0);
3952 /* ----------------------------------------------------------------------- */
3953 /* ~/.<program>/levelsetup/<level series>/levelsetup.conf */
3954 /* ----------------------------------------------------------------------- */
3956 InitLevelSetupDirectory(level_subdir);
3958 filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
3960 if (!(file = fopen(filename, MODE_WRITE)))
3962 Error(ERR_WARN, "cannot write setup file '%s'", filename);
3967 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER,
3968 getCookie("LEVELSETUP")));
3969 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_PLAYED_LEVEL,
3971 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_HANDICAP_LEVEL,
3972 handicap_level_str));
3976 SetFilePermissions(filename, PERMS_PRIVATE);