1 // ============================================================================
2 // Artsoft Retro-Game Library
3 // ----------------------------------------------------------------------------
4 // (c) 1995-2014 by Artsoft Entertainment
7 // http://www.artsoft.org/
8 // ----------------------------------------------------------------------------
10 // ============================================================================
12 #include <sys/types.h>
21 #if !defined(PLATFORM_WIN32)
23 #include <sys/param.h>
33 #define ENABLE_UNUSED_CODE FALSE /* for currently unused functions */
35 #define NUM_LEVELCLASS_DESC 8
37 static char *levelclass_desc[NUM_LEVELCLASS_DESC] =
50 #define LEVELCOLOR(n) (IS_LEVELCLASS_TUTORIAL(n) ? FC_BLUE : \
51 IS_LEVELCLASS_CLASSICS(n) ? FC_RED : \
52 IS_LEVELCLASS_BD(n) ? FC_YELLOW : \
53 IS_LEVELCLASS_EM(n) ? FC_YELLOW : \
54 IS_LEVELCLASS_SP(n) ? FC_YELLOW : \
55 IS_LEVELCLASS_DX(n) ? FC_YELLOW : \
56 IS_LEVELCLASS_SB(n) ? FC_YELLOW : \
57 IS_LEVELCLASS_CONTRIB(n) ? FC_GREEN : \
58 IS_LEVELCLASS_PRIVATE(n) ? FC_RED : \
61 #define LEVELSORTING(n) (IS_LEVELCLASS_TUTORIAL(n) ? 0 : \
62 IS_LEVELCLASS_CLASSICS(n) ? 1 : \
63 IS_LEVELCLASS_BD(n) ? 2 : \
64 IS_LEVELCLASS_EM(n) ? 3 : \
65 IS_LEVELCLASS_SP(n) ? 4 : \
66 IS_LEVELCLASS_DX(n) ? 5 : \
67 IS_LEVELCLASS_SB(n) ? 6 : \
68 IS_LEVELCLASS_CONTRIB(n) ? 7 : \
69 IS_LEVELCLASS_PRIVATE(n) ? 8 : \
72 #define ARTWORKCOLOR(n) (IS_ARTWORKCLASS_CLASSICS(n) ? FC_RED : \
73 IS_ARTWORKCLASS_CONTRIB(n) ? FC_GREEN : \
74 IS_ARTWORKCLASS_PRIVATE(n) ? FC_RED : \
75 IS_ARTWORKCLASS_LEVEL(n) ? FC_YELLOW : \
78 #define ARTWORKSORTING(n) (IS_ARTWORKCLASS_CLASSICS(n) ? 0 : \
79 IS_ARTWORKCLASS_LEVEL(n) ? 1 : \
80 IS_ARTWORKCLASS_CONTRIB(n) ? 2 : \
81 IS_ARTWORKCLASS_PRIVATE(n) ? 3 : \
84 #define TOKEN_VALUE_POSITION_SHORT 32
85 #define TOKEN_VALUE_POSITION_DEFAULT 40
86 #define TOKEN_COMMENT_POSITION_DEFAULT 60
88 #define MAX_COOKIE_LEN 256
91 static void setTreeInfoToDefaults(TreeInfo *, int);
92 static TreeInfo *getTreeInfoCopy(TreeInfo *ti);
93 static int compareTreeInfoEntries(const void *, const void *);
95 static int token_value_position = TOKEN_VALUE_POSITION_DEFAULT;
96 static int token_comment_position = TOKEN_COMMENT_POSITION_DEFAULT;
98 static SetupFileHash *artworkinfo_cache_old = NULL;
99 static SetupFileHash *artworkinfo_cache_new = NULL;
100 static boolean use_artworkinfo_cache = TRUE;
103 /* ------------------------------------------------------------------------- */
105 /* ------------------------------------------------------------------------- */
107 static char *getLevelClassDescription(TreeInfo *ti)
109 int position = ti->sort_priority / 100;
111 if (position >= 0 && position < NUM_LEVELCLASS_DESC)
112 return levelclass_desc[position];
114 return "Unknown Level Class";
117 static char *getUserLevelDir(char *level_subdir)
119 static char *userlevel_dir = NULL;
120 char *data_dir = getUserGameDataDir();
121 char *userlevel_subdir = LEVELS_DIRECTORY;
123 checked_free(userlevel_dir);
125 if (level_subdir != NULL)
126 userlevel_dir = getPath3(data_dir, userlevel_subdir, level_subdir);
128 userlevel_dir = getPath2(data_dir, userlevel_subdir);
130 return userlevel_dir;
133 static char *getScoreDir(char *level_subdir)
135 static char *score_dir = NULL;
136 static char *score_level_dir = NULL;
137 char *score_subdir = SCORES_DIRECTORY;
139 if (score_dir == NULL)
141 if (program.global_scores)
142 score_dir = getPath2(getCommonDataDir(), score_subdir);
144 score_dir = getPath2(getUserGameDataDir(), score_subdir);
147 if (level_subdir != NULL)
149 checked_free(score_level_dir);
151 score_level_dir = getPath2(score_dir, level_subdir);
153 return score_level_dir;
159 static char *getLevelSetupDir(char *level_subdir)
161 static char *levelsetup_dir = NULL;
162 char *data_dir = getUserGameDataDir();
163 char *levelsetup_subdir = LEVELSETUP_DIRECTORY;
165 checked_free(levelsetup_dir);
167 if (level_subdir != NULL)
168 levelsetup_dir = getPath3(data_dir, levelsetup_subdir, level_subdir);
170 levelsetup_dir = getPath2(data_dir, levelsetup_subdir);
172 return levelsetup_dir;
175 static char *getCacheDir()
177 static char *cache_dir = NULL;
179 if (cache_dir == NULL)
180 cache_dir = getPath2(getUserGameDataDir(), CACHE_DIRECTORY);
185 static char *getLevelDirFromTreeInfo(TreeInfo *node)
187 static char *level_dir = NULL;
190 return options.level_directory;
192 checked_free(level_dir);
194 level_dir = getPath2((node->in_user_dir ? getUserLevelDir(NULL) :
195 options.level_directory), node->fullpath);
200 char *getCurrentLevelDir()
202 return getLevelDirFromTreeInfo(leveldir_current);
205 char *getNewUserLevelSubdir()
207 static char *new_level_subdir = NULL;
208 char *subdir_prefix = getLoginName();
209 char subdir_suffix[10];
210 int max_suffix_number = 1000;
213 while (++i < max_suffix_number)
215 sprintf(subdir_suffix, "_%d", i);
217 checked_free(new_level_subdir);
218 new_level_subdir = getStringCat2(subdir_prefix, subdir_suffix);
220 if (!directoryExists(getUserLevelDir(new_level_subdir)))
224 return new_level_subdir;
227 static char *getTapeDir(char *level_subdir)
229 static char *tape_dir = NULL;
230 char *data_dir = getUserGameDataDir();
231 char *tape_subdir = TAPES_DIRECTORY;
233 checked_free(tape_dir);
235 if (level_subdir != NULL)
236 tape_dir = getPath3(data_dir, tape_subdir, level_subdir);
238 tape_dir = getPath2(data_dir, tape_subdir);
243 static char *getSolutionTapeDir()
245 static char *tape_dir = NULL;
246 char *data_dir = getCurrentLevelDir();
247 char *tape_subdir = TAPES_DIRECTORY;
249 checked_free(tape_dir);
251 tape_dir = getPath2(data_dir, tape_subdir);
256 static char *getDefaultGraphicsDir(char *graphics_subdir)
258 static char *graphics_dir = NULL;
260 if (graphics_subdir == NULL)
261 return options.graphics_directory;
263 checked_free(graphics_dir);
265 graphics_dir = getPath2(options.graphics_directory, graphics_subdir);
270 static char *getDefaultSoundsDir(char *sounds_subdir)
272 static char *sounds_dir = NULL;
274 if (sounds_subdir == NULL)
275 return options.sounds_directory;
277 checked_free(sounds_dir);
279 sounds_dir = getPath2(options.sounds_directory, sounds_subdir);
284 static char *getDefaultMusicDir(char *music_subdir)
286 static char *music_dir = NULL;
288 if (music_subdir == NULL)
289 return options.music_directory;
291 checked_free(music_dir);
293 music_dir = getPath2(options.music_directory, music_subdir);
298 static char *getClassicArtworkSet(int type)
300 return (type == TREE_TYPE_GRAPHICS_DIR ? GFX_CLASSIC_SUBDIR :
301 type == TREE_TYPE_SOUNDS_DIR ? SND_CLASSIC_SUBDIR :
302 type == TREE_TYPE_MUSIC_DIR ? MUS_CLASSIC_SUBDIR : "");
305 static char *getClassicArtworkDir(int type)
307 return (type == TREE_TYPE_GRAPHICS_DIR ?
308 getDefaultGraphicsDir(GFX_CLASSIC_SUBDIR) :
309 type == TREE_TYPE_SOUNDS_DIR ?
310 getDefaultSoundsDir(SND_CLASSIC_SUBDIR) :
311 type == TREE_TYPE_MUSIC_DIR ?
312 getDefaultMusicDir(MUS_CLASSIC_SUBDIR) : "");
315 static char *getUserGraphicsDir()
317 static char *usergraphics_dir = NULL;
319 if (usergraphics_dir == NULL)
320 usergraphics_dir = getPath2(getUserGameDataDir(), GRAPHICS_DIRECTORY);
322 return usergraphics_dir;
325 static char *getUserSoundsDir()
327 static char *usersounds_dir = NULL;
329 if (usersounds_dir == NULL)
330 usersounds_dir = getPath2(getUserGameDataDir(), SOUNDS_DIRECTORY);
332 return usersounds_dir;
335 static char *getUserMusicDir()
337 static char *usermusic_dir = NULL;
339 if (usermusic_dir == NULL)
340 usermusic_dir = getPath2(getUserGameDataDir(), MUSIC_DIRECTORY);
342 return usermusic_dir;
345 static char *getSetupArtworkDir(TreeInfo *ti)
347 static char *artwork_dir = NULL;
352 checked_free(artwork_dir);
354 artwork_dir = getPath2(ti->basepath, ti->fullpath);
359 char *setLevelArtworkDir(TreeInfo *ti)
361 char **artwork_path_ptr, **artwork_set_ptr;
362 TreeInfo *level_artwork;
364 if (ti == NULL || leveldir_current == NULL)
367 artwork_path_ptr = LEVELDIR_ARTWORK_PATH_PTR(leveldir_current, ti->type);
368 artwork_set_ptr = LEVELDIR_ARTWORK_SET_PTR( leveldir_current, ti->type);
370 checked_free(*artwork_path_ptr);
372 if ((level_artwork = getTreeInfoFromIdentifier(ti, *artwork_set_ptr)))
374 *artwork_path_ptr = getStringCopy(getSetupArtworkDir(level_artwork));
379 No (or non-existing) artwork configured in "levelinfo.conf". This would
380 normally result in using the artwork configured in the setup menu. But
381 if an artwork subdirectory exists (which might contain custom artwork
382 or an artwork configuration file), this level artwork must be treated
383 as relative to the default "classic" artwork, not to the artwork that
384 is currently configured in the setup menu.
386 Update: For "special" versions of R'n'D (like "R'n'D jue"), do not use
387 the "default" artwork (which would be "jue0" for "R'n'D jue"), but use
388 the real "classic" artwork from the original R'n'D (like "gfx_classic").
391 char *dir = getPath2(getCurrentLevelDir(), ARTWORK_DIRECTORY(ti->type));
393 checked_free(*artwork_set_ptr);
395 if (directoryExists(dir))
397 *artwork_path_ptr = getStringCopy(getClassicArtworkDir(ti->type));
398 *artwork_set_ptr = getStringCopy(getClassicArtworkSet(ti->type));
402 *artwork_path_ptr = getStringCopy(UNDEFINED_FILENAME);
403 *artwork_set_ptr = NULL;
409 return *artwork_set_ptr;
412 inline static char *getLevelArtworkSet(int type)
414 if (leveldir_current == NULL)
417 return LEVELDIR_ARTWORK_SET(leveldir_current, type);
420 inline static char *getLevelArtworkDir(int type)
422 if (leveldir_current == NULL)
423 return UNDEFINED_FILENAME;
425 return LEVELDIR_ARTWORK_PATH(leveldir_current, type);
428 char *getProgramConfigFilename(char *command_filename_ptr)
430 char *command_filename_1 = getStringCopy(command_filename_ptr);
432 // strip trailing executable suffix from command filename
433 if (strSuffix(command_filename_1, ".exe"))
434 command_filename_1[strlen(command_filename_1) - 4] = '\0';
436 char *command_basepath = getBasePath(command_filename_ptr);
437 char *command_basename = getBaseNameNoSuffix(command_filename_ptr);
438 char *command_filename_2 = getPath2(command_basepath, command_basename);
440 char *config_filename_1 = getStringCat2(command_filename_1, ".conf");
441 char *config_filename_2 = getStringCat2(command_filename_2, ".conf");
443 // 1st try: look for config file that exactly matches the binary filename
444 if (fileExists(config_filename_1))
445 return config_filename_1;
447 // 2nd try: return config filename that matches binary filename without suffix
448 return config_filename_2;
451 char *getTapeFilename(int nr)
453 static char *filename = NULL;
454 char basename[MAX_FILENAME_LEN];
456 checked_free(filename);
458 sprintf(basename, "%03d.%s", nr, TAPEFILE_EXTENSION);
459 filename = getPath2(getTapeDir(leveldir_current->subdir), basename);
464 char *getSolutionTapeFilename(int nr)
466 static char *filename = NULL;
467 char basename[MAX_FILENAME_LEN];
469 checked_free(filename);
471 sprintf(basename, "%03d.%s", nr, TAPEFILE_EXTENSION);
472 filename = getPath2(getSolutionTapeDir(), basename);
474 if (!fileExists(filename))
476 static char *filename_sln = NULL;
478 checked_free(filename_sln);
480 sprintf(basename, "%03d.sln", nr);
481 filename_sln = getPath2(getSolutionTapeDir(), basename);
483 if (fileExists(filename_sln))
490 char *getScoreFilename(int nr)
492 static char *filename = NULL;
493 char basename[MAX_FILENAME_LEN];
495 checked_free(filename);
497 sprintf(basename, "%03d.%s", nr, SCOREFILE_EXTENSION);
498 filename = getPath2(getScoreDir(leveldir_current->subdir), basename);
503 char *getSetupFilename()
505 static char *filename = NULL;
507 checked_free(filename);
509 filename = getPath2(getSetupDir(), SETUP_FILENAME);
514 char *getDefaultSetupFilename()
516 return program.config_filename;
519 char *getEditorSetupFilename()
521 static char *filename = NULL;
523 checked_free(filename);
524 filename = getPath2(getCurrentLevelDir(), EDITORSETUP_FILENAME);
526 if (fileExists(filename))
529 checked_free(filename);
530 filename = getPath2(getSetupDir(), EDITORSETUP_FILENAME);
535 char *getHelpAnimFilename()
537 static char *filename = NULL;
539 checked_free(filename);
541 filename = getPath2(getCurrentLevelDir(), HELPANIM_FILENAME);
546 char *getHelpTextFilename()
548 static char *filename = NULL;
550 checked_free(filename);
552 filename = getPath2(getCurrentLevelDir(), HELPTEXT_FILENAME);
557 char *getLevelSetInfoFilename()
559 static char *filename = NULL;
574 for (i = 0; basenames[i] != NULL; i++)
576 checked_free(filename);
577 filename = getPath2(getCurrentLevelDir(), basenames[i]);
579 if (fileExists(filename))
586 char *getLevelSetTitleMessageBasename(int nr, boolean initial)
588 static char basename[32];
590 sprintf(basename, "%s_%d.txt",
591 (initial ? "titlemessage_initial" : "titlemessage"), nr + 1);
596 char *getLevelSetTitleMessageFilename(int nr, boolean initial)
598 static char *filename = NULL;
600 boolean skip_setup_artwork = FALSE;
602 checked_free(filename);
604 basename = getLevelSetTitleMessageBasename(nr, initial);
606 if (!gfx.override_level_graphics)
608 /* 1st try: look for special artwork in current level series directory */
609 filename = getPath3(getCurrentLevelDir(), GRAPHICS_DIRECTORY, basename);
610 if (fileExists(filename))
615 /* 2nd try: look for message file in current level set directory */
616 filename = getPath2(getCurrentLevelDir(), basename);
617 if (fileExists(filename))
622 /* check if there is special artwork configured in level series config */
623 if (getLevelArtworkSet(ARTWORK_TYPE_GRAPHICS) != NULL)
625 /* 3rd try: look for special artwork configured in level series config */
626 filename = getPath2(getLevelArtworkDir(ARTWORK_TYPE_GRAPHICS), basename);
627 if (fileExists(filename))
632 /* take missing artwork configured in level set config from default */
633 skip_setup_artwork = TRUE;
637 if (!skip_setup_artwork)
639 /* 4th try: look for special artwork in configured artwork directory */
640 filename = getPath2(getSetupArtworkDir(artwork.gfx_current), basename);
641 if (fileExists(filename))
647 /* 5th try: look for default artwork in new default artwork directory */
648 filename = getPath2(getDefaultGraphicsDir(GFX_DEFAULT_SUBDIR), basename);
649 if (fileExists(filename))
654 /* 6th try: look for default artwork in old default artwork directory */
655 filename = getPath2(options.graphics_directory, basename);
656 if (fileExists(filename))
659 return NULL; /* cannot find specified artwork file anywhere */
662 static char *getCorrectedArtworkBasename(char *basename)
667 char *getCustomImageFilename(char *basename)
669 static char *filename = NULL;
670 boolean skip_setup_artwork = FALSE;
672 checked_free(filename);
674 basename = getCorrectedArtworkBasename(basename);
676 if (!gfx.override_level_graphics)
678 /* 1st try: look for special artwork in current level series directory */
679 filename = getImg3(getCurrentLevelDir(), GRAPHICS_DIRECTORY, basename);
680 if (fileExists(filename))
685 /* check if there is special artwork configured in level series config */
686 if (getLevelArtworkSet(ARTWORK_TYPE_GRAPHICS) != NULL)
688 /* 2nd try: look for special artwork configured in level series config */
689 filename = getImg2(getLevelArtworkDir(ARTWORK_TYPE_GRAPHICS), basename);
690 if (fileExists(filename))
695 /* take missing artwork configured in level set config from default */
696 skip_setup_artwork = TRUE;
700 if (!skip_setup_artwork)
702 /* 3rd try: look for special artwork in configured artwork directory */
703 filename = getImg2(getSetupArtworkDir(artwork.gfx_current), basename);
704 if (fileExists(filename))
710 /* 4th try: look for default artwork in new default artwork directory */
711 filename = getImg2(getDefaultGraphicsDir(GFX_DEFAULT_SUBDIR), basename);
712 if (fileExists(filename))
717 /* 5th try: look for default artwork in old default artwork directory */
718 filename = getImg2(options.graphics_directory, basename);
719 if (fileExists(filename))
722 if (!strEqual(GFX_FALLBACK_FILENAME, UNDEFINED_FILENAME))
727 Error(ERR_WARN, "cannot find artwork file '%s' (using fallback)",
730 /* 6th try: look for fallback artwork in old default artwork directory */
731 /* (needed to prevent errors when trying to access unused artwork files) */
732 filename = getImg2(options.graphics_directory, GFX_FALLBACK_FILENAME);
733 if (fileExists(filename))
737 return NULL; /* cannot find specified artwork file anywhere */
740 char *getCustomSoundFilename(char *basename)
742 static char *filename = NULL;
743 boolean skip_setup_artwork = FALSE;
745 checked_free(filename);
747 basename = getCorrectedArtworkBasename(basename);
749 if (!gfx.override_level_sounds)
751 /* 1st try: look for special artwork in current level series directory */
752 filename = getPath3(getCurrentLevelDir(), SOUNDS_DIRECTORY, basename);
753 if (fileExists(filename))
758 /* check if there is special artwork configured in level series config */
759 if (getLevelArtworkSet(ARTWORK_TYPE_SOUNDS) != NULL)
761 /* 2nd try: look for special artwork configured in level series config */
762 filename = getPath2(getLevelArtworkDir(TREE_TYPE_SOUNDS_DIR), basename);
763 if (fileExists(filename))
768 /* take missing artwork configured in level set config from default */
769 skip_setup_artwork = TRUE;
773 if (!skip_setup_artwork)
775 /* 3rd try: look for special artwork in configured artwork directory */
776 filename = getPath2(getSetupArtworkDir(artwork.snd_current), basename);
777 if (fileExists(filename))
783 /* 4th try: look for default artwork in new default artwork directory */
784 filename = getPath2(getDefaultSoundsDir(SND_DEFAULT_SUBDIR), basename);
785 if (fileExists(filename))
790 /* 5th try: look for default artwork in old default artwork directory */
791 filename = getPath2(options.sounds_directory, basename);
792 if (fileExists(filename))
795 if (!strEqual(SND_FALLBACK_FILENAME, UNDEFINED_FILENAME))
800 Error(ERR_WARN, "cannot find artwork file '%s' (using fallback)",
803 /* 6th try: look for fallback artwork in old default artwork directory */
804 /* (needed to prevent errors when trying to access unused artwork files) */
805 filename = getPath2(options.sounds_directory, SND_FALLBACK_FILENAME);
806 if (fileExists(filename))
810 return NULL; /* cannot find specified artwork file anywhere */
813 char *getCustomMusicFilename(char *basename)
815 static char *filename = NULL;
816 boolean skip_setup_artwork = FALSE;
818 checked_free(filename);
820 basename = getCorrectedArtworkBasename(basename);
822 if (!gfx.override_level_music)
824 /* 1st try: look for special artwork in current level series directory */
825 filename = getPath3(getCurrentLevelDir(), MUSIC_DIRECTORY, basename);
826 if (fileExists(filename))
831 /* check if there is special artwork configured in level series config */
832 if (getLevelArtworkSet(ARTWORK_TYPE_MUSIC) != NULL)
834 /* 2nd try: look for special artwork configured in level series config */
835 filename = getPath2(getLevelArtworkDir(TREE_TYPE_MUSIC_DIR), basename);
836 if (fileExists(filename))
841 /* take missing artwork configured in level set config from default */
842 skip_setup_artwork = TRUE;
846 if (!skip_setup_artwork)
848 /* 3rd try: look for special artwork in configured artwork directory */
849 filename = getPath2(getSetupArtworkDir(artwork.mus_current), basename);
850 if (fileExists(filename))
856 /* 4th try: look for default artwork in new default artwork directory */
857 filename = getPath2(getDefaultMusicDir(MUS_DEFAULT_SUBDIR), basename);
858 if (fileExists(filename))
863 /* 5th try: look for default artwork in old default artwork directory */
864 filename = getPath2(options.music_directory, basename);
865 if (fileExists(filename))
868 if (!strEqual(MUS_FALLBACK_FILENAME, UNDEFINED_FILENAME))
873 Error(ERR_WARN, "cannot find artwork file '%s' (using fallback)",
876 /* 6th try: look for fallback artwork in old default artwork directory */
877 /* (needed to prevent errors when trying to access unused artwork files) */
878 filename = getPath2(options.music_directory, MUS_FALLBACK_FILENAME);
879 if (fileExists(filename))
883 return NULL; /* cannot find specified artwork file anywhere */
886 char *getCustomArtworkFilename(char *basename, int type)
888 if (type == ARTWORK_TYPE_GRAPHICS)
889 return getCustomImageFilename(basename);
890 else if (type == ARTWORK_TYPE_SOUNDS)
891 return getCustomSoundFilename(basename);
892 else if (type == ARTWORK_TYPE_MUSIC)
893 return getCustomMusicFilename(basename);
895 return UNDEFINED_FILENAME;
898 char *getCustomArtworkConfigFilename(int type)
900 return getCustomArtworkFilename(ARTWORKINFO_FILENAME(type), type);
903 char *getCustomArtworkLevelConfigFilename(int type)
905 static char *filename = NULL;
907 checked_free(filename);
909 filename = getPath2(getLevelArtworkDir(type), ARTWORKINFO_FILENAME(type));
914 char *getCustomMusicDirectory(void)
916 static char *directory = NULL;
917 boolean skip_setup_artwork = FALSE;
919 checked_free(directory);
921 if (!gfx.override_level_music)
923 /* 1st try: look for special artwork in current level series directory */
924 directory = getPath2(getCurrentLevelDir(), MUSIC_DIRECTORY);
925 if (directoryExists(directory))
930 /* check if there is special artwork configured in level series config */
931 if (getLevelArtworkSet(ARTWORK_TYPE_MUSIC) != NULL)
933 /* 2nd try: look for special artwork configured in level series config */
934 directory = getStringCopy(getLevelArtworkDir(TREE_TYPE_MUSIC_DIR));
935 if (directoryExists(directory))
940 /* take missing artwork configured in level set config from default */
941 skip_setup_artwork = TRUE;
945 if (!skip_setup_artwork)
947 /* 3rd try: look for special artwork in configured artwork directory */
948 directory = getStringCopy(getSetupArtworkDir(artwork.mus_current));
949 if (directoryExists(directory))
955 /* 4th try: look for default artwork in new default artwork directory */
956 directory = getStringCopy(getDefaultMusicDir(MUS_DEFAULT_SUBDIR));
957 if (directoryExists(directory))
962 /* 5th try: look for default artwork in old default artwork directory */
963 directory = getStringCopy(options.music_directory);
964 if (directoryExists(directory))
967 return NULL; /* cannot find specified artwork file anywhere */
970 void InitTapeDirectory(char *level_subdir)
972 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
973 createDirectory(getTapeDir(NULL), "main tape", PERMS_PRIVATE);
974 createDirectory(getTapeDir(level_subdir), "level tape", PERMS_PRIVATE);
977 void InitScoreDirectory(char *level_subdir)
979 int permissions = (program.global_scores ? PERMS_PUBLIC : PERMS_PRIVATE);
981 if (program.global_scores)
982 createDirectory(getCommonDataDir(), "common data", permissions);
984 createDirectory(getUserGameDataDir(), "user data", permissions);
986 createDirectory(getScoreDir(NULL), "main score", permissions);
987 createDirectory(getScoreDir(level_subdir), "level score", permissions);
990 static void SaveUserLevelInfo();
992 void InitUserLevelDirectory(char *level_subdir)
994 if (!directoryExists(getUserLevelDir(level_subdir)))
996 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
997 createDirectory(getUserLevelDir(NULL), "main user level", PERMS_PRIVATE);
998 createDirectory(getUserLevelDir(level_subdir), "user level", PERMS_PRIVATE);
1000 SaveUserLevelInfo();
1004 void InitLevelSetupDirectory(char *level_subdir)
1006 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
1007 createDirectory(getLevelSetupDir(NULL), "main level setup", PERMS_PRIVATE);
1008 createDirectory(getLevelSetupDir(level_subdir), "level setup", PERMS_PRIVATE);
1011 void InitCacheDirectory()
1013 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
1014 createDirectory(getCacheDir(), "cache data", PERMS_PRIVATE);
1018 /* ------------------------------------------------------------------------- */
1019 /* some functions to handle lists of level and artwork directories */
1020 /* ------------------------------------------------------------------------- */
1022 TreeInfo *newTreeInfo()
1024 return checked_calloc(sizeof(TreeInfo));
1027 TreeInfo *newTreeInfo_setDefaults(int type)
1029 TreeInfo *ti = newTreeInfo();
1031 setTreeInfoToDefaults(ti, type);
1036 void pushTreeInfo(TreeInfo **node_first, TreeInfo *node_new)
1038 node_new->next = *node_first;
1039 *node_first = node_new;
1042 int numTreeInfo(TreeInfo *node)
1055 boolean validLevelSeries(TreeInfo *node)
1057 return (node != NULL && !node->node_group && !node->parent_link);
1060 TreeInfo *getFirstValidTreeInfoEntry(TreeInfo *node)
1065 if (node->node_group) /* enter level group (step down into tree) */
1066 return getFirstValidTreeInfoEntry(node->node_group);
1067 else if (node->parent_link) /* skip start entry of level group */
1069 if (node->next) /* get first real level series entry */
1070 return getFirstValidTreeInfoEntry(node->next);
1071 else /* leave empty level group and go on */
1072 return getFirstValidTreeInfoEntry(node->node_parent->next);
1074 else /* this seems to be a regular level series */
1078 TreeInfo *getTreeInfoFirstGroupEntry(TreeInfo *node)
1083 if (node->node_parent == NULL) /* top level group */
1084 return *node->node_top;
1085 else /* sub level group */
1086 return node->node_parent->node_group;
1089 int numTreeInfoInGroup(TreeInfo *node)
1091 return numTreeInfo(getTreeInfoFirstGroupEntry(node));
1094 int posTreeInfo(TreeInfo *node)
1096 TreeInfo *node_cmp = getTreeInfoFirstGroupEntry(node);
1101 if (node_cmp == node)
1105 node_cmp = node_cmp->next;
1111 TreeInfo *getTreeInfoFromPos(TreeInfo *node, int pos)
1113 TreeInfo *node_default = node;
1125 return node_default;
1128 TreeInfo *getTreeInfoFromIdentifier(TreeInfo *node, char *identifier)
1130 if (identifier == NULL)
1135 if (node->node_group)
1137 TreeInfo *node_group;
1139 node_group = getTreeInfoFromIdentifier(node->node_group, identifier);
1144 else if (!node->parent_link)
1146 if (strEqual(identifier, node->identifier))
1156 TreeInfo *cloneTreeNode(TreeInfo **node_top, TreeInfo *node_parent,
1157 TreeInfo *node, boolean skip_sets_without_levels)
1164 if (!node->parent_link && !node->level_group &&
1165 skip_sets_without_levels && node->levels == 0)
1166 return cloneTreeNode(node_top, node_parent, node->next,
1167 skip_sets_without_levels);
1169 node_new = getTreeInfoCopy(node); /* copy complete node */
1171 node_new->node_top = node_top; /* correct top node link */
1172 node_new->node_parent = node_parent; /* correct parent node link */
1174 if (node->level_group)
1175 node_new->node_group = cloneTreeNode(node_top, node_new, node->node_group,
1176 skip_sets_without_levels);
1178 node_new->next = cloneTreeNode(node_top, node_parent, node->next,
1179 skip_sets_without_levels);
1184 void cloneTree(TreeInfo **ti_new, TreeInfo *ti, boolean skip_empty_sets)
1186 TreeInfo *ti_cloned = cloneTreeNode(ti_new, NULL, ti, skip_empty_sets);
1188 *ti_new = ti_cloned;
1191 static boolean adjustTreeGraphicsForEMC(TreeInfo *node)
1193 boolean settings_changed = FALSE;
1197 if (node->graphics_set_ecs && !setup.prefer_aga_graphics &&
1198 !strEqual(node->graphics_set, node->graphics_set_ecs))
1200 setString(&node->graphics_set, node->graphics_set_ecs);
1201 settings_changed = TRUE;
1203 else if (node->graphics_set_aga && setup.prefer_aga_graphics &&
1204 !strEqual(node->graphics_set, node->graphics_set_aga))
1206 setString(&node->graphics_set, node->graphics_set_aga);
1207 settings_changed = TRUE;
1210 if (node->node_group != NULL)
1211 settings_changed |= adjustTreeGraphicsForEMC(node->node_group);
1216 return settings_changed;
1219 void dumpTreeInfo(TreeInfo *node, int depth)
1223 printf("Dumping TreeInfo:\n");
1227 for (i = 0; i < (depth + 1) * 3; i++)
1230 printf("'%s' / '%s'\n", node->identifier, node->name);
1233 // use for dumping artwork info tree
1234 printf("subdir == '%s' ['%s', '%s'] [%d])\n",
1235 node->subdir, node->fullpath, node->basepath, node->in_user_dir);
1238 if (node->node_group != NULL)
1239 dumpTreeInfo(node->node_group, depth + 1);
1245 void sortTreeInfoBySortFunction(TreeInfo **node_first,
1246 int (*compare_function)(const void *,
1249 int num_nodes = numTreeInfo(*node_first);
1250 TreeInfo **sort_array;
1251 TreeInfo *node = *node_first;
1257 /* allocate array for sorting structure pointers */
1258 sort_array = checked_calloc(num_nodes * sizeof(TreeInfo *));
1260 /* writing structure pointers to sorting array */
1261 while (i < num_nodes && node) /* double boundary check... */
1263 sort_array[i] = node;
1269 /* sorting the structure pointers in the sorting array */
1270 qsort(sort_array, num_nodes, sizeof(TreeInfo *),
1273 /* update the linkage of list elements with the sorted node array */
1274 for (i = 0; i < num_nodes - 1; i++)
1275 sort_array[i]->next = sort_array[i + 1];
1276 sort_array[num_nodes - 1]->next = NULL;
1278 /* update the linkage of the main list anchor pointer */
1279 *node_first = sort_array[0];
1283 /* now recursively sort the level group structures */
1287 if (node->node_group != NULL)
1288 sortTreeInfoBySortFunction(&node->node_group, compare_function);
1294 void sortTreeInfo(TreeInfo **node_first)
1296 sortTreeInfoBySortFunction(node_first, compareTreeInfoEntries);
1300 /* ========================================================================= */
1301 /* some stuff from "files.c" */
1302 /* ========================================================================= */
1304 #if defined(PLATFORM_WIN32)
1306 #define S_IRGRP S_IRUSR
1309 #define S_IROTH S_IRUSR
1312 #define S_IWGRP S_IWUSR
1315 #define S_IWOTH S_IWUSR
1318 #define S_IXGRP S_IXUSR
1321 #define S_IXOTH S_IXUSR
1324 #define S_IRWXG (S_IRGRP | S_IWGRP | S_IXGRP)
1329 #endif /* PLATFORM_WIN32 */
1331 /* file permissions for newly written files */
1332 #define MODE_R_ALL (S_IRUSR | S_IRGRP | S_IROTH)
1333 #define MODE_W_ALL (S_IWUSR | S_IWGRP | S_IWOTH)
1334 #define MODE_X_ALL (S_IXUSR | S_IXGRP | S_IXOTH)
1336 #define MODE_W_PRIVATE (S_IWUSR)
1337 #define MODE_W_PUBLIC_FILE (S_IWUSR | S_IWGRP)
1338 #define MODE_W_PUBLIC_DIR (S_IWUSR | S_IWGRP | S_ISGID)
1340 #define DIR_PERMS_PRIVATE (MODE_R_ALL | MODE_X_ALL | MODE_W_PRIVATE)
1341 #define DIR_PERMS_PUBLIC (MODE_R_ALL | MODE_X_ALL | MODE_W_PUBLIC_DIR)
1342 #define DIR_PERMS_PUBLIC_ALL (MODE_R_ALL | MODE_X_ALL | MODE_W_ALL)
1344 #define FILE_PERMS_PRIVATE (MODE_R_ALL | MODE_W_PRIVATE)
1345 #define FILE_PERMS_PUBLIC (MODE_R_ALL | MODE_W_PUBLIC_FILE)
1346 #define FILE_PERMS_PUBLIC_ALL (MODE_R_ALL | MODE_W_ALL)
1351 static char *dir = NULL;
1353 #if defined(PLATFORM_WIN32)
1356 dir = checked_malloc(MAX_PATH + 1);
1358 if (!SUCCEEDED(SHGetFolderPath(NULL, CSIDL_PERSONAL, NULL, 0, dir)))
1361 #elif defined(PLATFORM_UNIX)
1364 if ((dir = getenv("HOME")) == NULL)
1368 if ((pwd = getpwuid(getuid())) != NULL)
1369 dir = getStringCopy(pwd->pw_dir);
1381 char *getCommonDataDir(void)
1383 static char *common_data_dir = NULL;
1385 #if defined(PLATFORM_WIN32)
1386 if (common_data_dir == NULL)
1388 char *dir = checked_malloc(MAX_PATH + 1);
1390 if (SUCCEEDED(SHGetFolderPath(NULL, CSIDL_COMMON_DOCUMENTS, NULL, 0, dir))
1391 && !strEqual(dir, "")) /* empty for Windows 95/98 */
1392 common_data_dir = getPath2(dir, program.userdata_subdir);
1394 common_data_dir = options.rw_base_directory;
1397 if (common_data_dir == NULL)
1398 common_data_dir = options.rw_base_directory;
1401 return common_data_dir;
1404 char *getPersonalDataDir(void)
1406 static char *personal_data_dir = NULL;
1408 #if defined(PLATFORM_MACOSX)
1409 if (personal_data_dir == NULL)
1410 personal_data_dir = getPath2(getHomeDir(), "Documents");
1412 if (personal_data_dir == NULL)
1413 personal_data_dir = getHomeDir();
1416 return personal_data_dir;
1419 char *getUserGameDataDir(void)
1421 static char *user_game_data_dir = NULL;
1423 #if defined(PLATFORM_ANDROID)
1424 if (user_game_data_dir == NULL)
1425 user_game_data_dir = (char *)(SDL_AndroidGetExternalStorageState() &
1426 SDL_ANDROID_EXTERNAL_STORAGE_WRITE ?
1427 SDL_AndroidGetExternalStoragePath() :
1428 SDL_AndroidGetInternalStoragePath());
1430 if (user_game_data_dir == NULL)
1431 user_game_data_dir = getPath2(getPersonalDataDir(),
1432 program.userdata_subdir);
1435 return user_game_data_dir;
1440 return getUserGameDataDir();
1443 static mode_t posix_umask(mode_t mask)
1445 #if defined(PLATFORM_UNIX)
1452 static int posix_mkdir(const char *pathname, mode_t mode)
1454 #if defined(PLATFORM_WIN32)
1455 return mkdir(pathname);
1457 return mkdir(pathname, mode);
1461 static boolean posix_process_running_setgid()
1463 #if defined(PLATFORM_UNIX)
1464 return (getgid() != getegid());
1470 void createDirectory(char *dir, char *text, int permission_class)
1472 if (directoryExists(dir))
1475 /* leave "other" permissions in umask untouched, but ensure group parts
1476 of USERDATA_DIR_MODE are not masked */
1477 mode_t dir_mode = (permission_class == PERMS_PRIVATE ?
1478 DIR_PERMS_PRIVATE : DIR_PERMS_PUBLIC);
1479 mode_t last_umask = posix_umask(0);
1480 mode_t group_umask = ~(dir_mode & S_IRWXG);
1481 int running_setgid = posix_process_running_setgid();
1483 if (permission_class == PERMS_PUBLIC)
1485 /* if we're setgid, protect files against "other" */
1486 /* else keep umask(0) to make the dir world-writable */
1489 posix_umask(last_umask & group_umask);
1491 dir_mode = DIR_PERMS_PUBLIC_ALL;
1494 if (posix_mkdir(dir, dir_mode) != 0)
1495 Error(ERR_WARN, "cannot create %s directory '%s': %s",
1496 text, dir, strerror(errno));
1498 if (permission_class == PERMS_PUBLIC && !running_setgid)
1499 chmod(dir, dir_mode);
1501 posix_umask(last_umask); /* restore previous umask */
1504 void InitUserDataDirectory()
1506 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
1509 void SetFilePermissions(char *filename, int permission_class)
1511 int running_setgid = posix_process_running_setgid();
1512 int perms = (permission_class == PERMS_PRIVATE ?
1513 FILE_PERMS_PRIVATE : FILE_PERMS_PUBLIC);
1515 if (permission_class == PERMS_PUBLIC && !running_setgid)
1516 perms = FILE_PERMS_PUBLIC_ALL;
1518 chmod(filename, perms);
1521 char *getCookie(char *file_type)
1523 static char cookie[MAX_COOKIE_LEN + 1];
1525 if (strlen(program.cookie_prefix) + 1 +
1526 strlen(file_type) + strlen("_FILE_VERSION_x.x") > MAX_COOKIE_LEN)
1527 return "[COOKIE ERROR]"; /* should never happen */
1529 sprintf(cookie, "%s_%s_FILE_VERSION_%d.%d",
1530 program.cookie_prefix, file_type,
1531 program.version_major, program.version_minor);
1536 void fprintFileHeader(FILE *file, char *basename)
1538 char *prefix = "# ";
1541 fprintf_line_with_prefix(file, prefix, sep1, 77);
1542 fprintf(file, "%s%s\n", prefix, basename);
1543 fprintf_line_with_prefix(file, prefix, sep1, 77);
1544 fprintf(file, "\n");
1547 int getFileVersionFromCookieString(const char *cookie)
1549 const char *ptr_cookie1, *ptr_cookie2;
1550 const char *pattern1 = "_FILE_VERSION_";
1551 const char *pattern2 = "?.?";
1552 const int len_cookie = strlen(cookie);
1553 const int len_pattern1 = strlen(pattern1);
1554 const int len_pattern2 = strlen(pattern2);
1555 const int len_pattern = len_pattern1 + len_pattern2;
1556 int version_major, version_minor;
1558 if (len_cookie <= len_pattern)
1561 ptr_cookie1 = &cookie[len_cookie - len_pattern];
1562 ptr_cookie2 = &cookie[len_cookie - len_pattern2];
1564 if (strncmp(ptr_cookie1, pattern1, len_pattern1) != 0)
1567 if (ptr_cookie2[0] < '0' || ptr_cookie2[0] > '9' ||
1568 ptr_cookie2[1] != '.' ||
1569 ptr_cookie2[2] < '0' || ptr_cookie2[2] > '9')
1572 version_major = ptr_cookie2[0] - '0';
1573 version_minor = ptr_cookie2[2] - '0';
1575 return VERSION_IDENT(version_major, version_minor, 0, 0);
1578 boolean checkCookieString(const char *cookie, const char *template)
1580 const char *pattern = "_FILE_VERSION_?.?";
1581 const int len_cookie = strlen(cookie);
1582 const int len_template = strlen(template);
1583 const int len_pattern = strlen(pattern);
1585 if (len_cookie != len_template)
1588 if (strncmp(cookie, template, len_cookie - len_pattern) != 0)
1595 /* ------------------------------------------------------------------------- */
1596 /* setup file list and hash handling functions */
1597 /* ------------------------------------------------------------------------- */
1599 char *getFormattedSetupEntry(char *token, char *value)
1602 static char entry[MAX_LINE_LEN];
1604 /* if value is an empty string, just return token without value */
1608 /* start with the token and some spaces to format output line */
1609 sprintf(entry, "%s:", token);
1610 for (i = strlen(entry); i < token_value_position; i++)
1613 /* continue with the token's value */
1614 strcat(entry, value);
1619 SetupFileList *newSetupFileList(char *token, char *value)
1621 SetupFileList *new = checked_malloc(sizeof(SetupFileList));
1623 new->token = getStringCopy(token);
1624 new->value = getStringCopy(value);
1631 void freeSetupFileList(SetupFileList *list)
1636 checked_free(list->token);
1637 checked_free(list->value);
1640 freeSetupFileList(list->next);
1645 char *getListEntry(SetupFileList *list, char *token)
1650 if (strEqual(list->token, token))
1653 return getListEntry(list->next, token);
1656 SetupFileList *setListEntry(SetupFileList *list, char *token, char *value)
1661 if (strEqual(list->token, token))
1663 checked_free(list->value);
1665 list->value = getStringCopy(value);
1669 else if (list->next == NULL)
1670 return (list->next = newSetupFileList(token, value));
1672 return setListEntry(list->next, token, value);
1675 SetupFileList *addListEntry(SetupFileList *list, char *token, char *value)
1680 if (list->next == NULL)
1681 return (list->next = newSetupFileList(token, value));
1683 return addListEntry(list->next, token, value);
1686 #if ENABLE_UNUSED_CODE
1688 static void printSetupFileList(SetupFileList *list)
1693 printf("token: '%s'\n", list->token);
1694 printf("value: '%s'\n", list->value);
1696 printSetupFileList(list->next);
1702 DEFINE_HASHTABLE_INSERT(insert_hash_entry, char, char);
1703 DEFINE_HASHTABLE_SEARCH(search_hash_entry, char, char);
1704 DEFINE_HASHTABLE_CHANGE(change_hash_entry, char, char);
1705 DEFINE_HASHTABLE_REMOVE(remove_hash_entry, char, char);
1707 #define insert_hash_entry hashtable_insert
1708 #define search_hash_entry hashtable_search
1709 #define change_hash_entry hashtable_change
1710 #define remove_hash_entry hashtable_remove
1713 unsigned int get_hash_from_key(void *key)
1718 This algorithm (k=33) was first reported by Dan Bernstein many years ago in
1719 'comp.lang.c'. Another version of this algorithm (now favored by Bernstein)
1720 uses XOR: hash(i) = hash(i - 1) * 33 ^ str[i]; the magic of number 33 (why
1721 it works better than many other constants, prime or not) has never been
1722 adequately explained.
1724 If you just want to have a good hash function, and cannot wait, djb2
1725 is one of the best string hash functions i know. It has excellent
1726 distribution and speed on many different sets of keys and table sizes.
1727 You are not likely to do better with one of the "well known" functions
1728 such as PJW, K&R, etc.
1730 Ozan (oz) Yigit [http://www.cs.yorku.ca/~oz/hash.html]
1733 char *str = (char *)key;
1734 unsigned int hash = 5381;
1737 while ((c = *str++))
1738 hash = ((hash << 5) + hash) + c; /* hash * 33 + c */
1743 static int keys_are_equal(void *key1, void *key2)
1745 return (strEqual((char *)key1, (char *)key2));
1748 SetupFileHash *newSetupFileHash()
1750 SetupFileHash *new_hash =
1751 create_hashtable(16, 0.75, get_hash_from_key, keys_are_equal);
1753 if (new_hash == NULL)
1754 Error(ERR_EXIT, "create_hashtable() failed -- out of memory");
1759 void freeSetupFileHash(SetupFileHash *hash)
1764 hashtable_destroy(hash, 1); /* 1 == also free values stored in hash */
1767 char *getHashEntry(SetupFileHash *hash, char *token)
1772 return search_hash_entry(hash, token);
1775 void setHashEntry(SetupFileHash *hash, char *token, char *value)
1782 value_copy = getStringCopy(value);
1784 /* change value; if it does not exist, insert it as new */
1785 if (!change_hash_entry(hash, token, value_copy))
1786 if (!insert_hash_entry(hash, getStringCopy(token), value_copy))
1787 Error(ERR_EXIT, "cannot insert into hash -- aborting");
1790 char *removeHashEntry(SetupFileHash *hash, char *token)
1795 return remove_hash_entry(hash, token);
1798 #if ENABLE_UNUSED_CODE
1800 static void printSetupFileHash(SetupFileHash *hash)
1802 BEGIN_HASH_ITERATION(hash, itr)
1804 printf("token: '%s'\n", HASH_ITERATION_TOKEN(itr));
1805 printf("value: '%s'\n", HASH_ITERATION_VALUE(itr));
1807 END_HASH_ITERATION(hash, itr)
1812 #define ALLOW_TOKEN_VALUE_SEPARATOR_BEING_WHITESPACE 1
1813 #define CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING 0
1814 #define CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH 0
1816 static boolean token_value_separator_found = FALSE;
1817 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
1818 static boolean token_value_separator_warning = FALSE;
1820 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
1821 static boolean token_already_exists_warning = FALSE;
1824 static boolean getTokenValueFromSetupLineExt(char *line,
1825 char **token_ptr, char **value_ptr,
1826 char *filename, char *line_raw,
1828 boolean separator_required)
1830 static char line_copy[MAX_LINE_LEN + 1], line_raw_copy[MAX_LINE_LEN + 1];
1831 char *token, *value, *line_ptr;
1833 /* when externally invoked via ReadTokenValueFromLine(), copy line buffers */
1834 if (line_raw == NULL)
1836 strncpy(line_copy, line, MAX_LINE_LEN);
1837 line_copy[MAX_LINE_LEN] = '\0';
1840 strcpy(line_raw_copy, line_copy);
1841 line_raw = line_raw_copy;
1844 /* cut trailing comment from input line */
1845 for (line_ptr = line; *line_ptr; line_ptr++)
1847 if (*line_ptr == '#')
1854 /* cut trailing whitespaces from input line */
1855 for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
1856 if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
1859 /* ignore empty lines */
1863 /* cut leading whitespaces from token */
1864 for (token = line; *token; token++)
1865 if (*token != ' ' && *token != '\t')
1868 /* start with empty value as reliable default */
1871 token_value_separator_found = FALSE;
1873 /* find end of token to determine start of value */
1874 for (line_ptr = token; *line_ptr; line_ptr++)
1876 /* first look for an explicit token/value separator, like ':' or '=' */
1877 if (*line_ptr == ':' || *line_ptr == '=')
1879 *line_ptr = '\0'; /* terminate token string */
1880 value = line_ptr + 1; /* set beginning of value */
1882 token_value_separator_found = TRUE;
1888 #if ALLOW_TOKEN_VALUE_SEPARATOR_BEING_WHITESPACE
1889 /* fallback: if no token/value separator found, also allow whitespaces */
1890 if (!token_value_separator_found && !separator_required)
1892 for (line_ptr = token; *line_ptr; line_ptr++)
1894 if (*line_ptr == ' ' || *line_ptr == '\t')
1896 *line_ptr = '\0'; /* terminate token string */
1897 value = line_ptr + 1; /* set beginning of value */
1899 token_value_separator_found = TRUE;
1905 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
1906 if (token_value_separator_found)
1908 if (!token_value_separator_warning)
1910 Error(ERR_INFO_LINE, "-");
1912 if (filename != NULL)
1914 Error(ERR_WARN, "missing token/value separator(s) in config file:");
1915 Error(ERR_INFO, "- config file: '%s'", filename);
1919 Error(ERR_WARN, "missing token/value separator(s):");
1922 token_value_separator_warning = TRUE;
1925 if (filename != NULL)
1926 Error(ERR_INFO, "- line %d: '%s'", line_nr, line_raw);
1928 Error(ERR_INFO, "- line: '%s'", line_raw);
1934 /* cut trailing whitespaces from token */
1935 for (line_ptr = &token[strlen(token)]; line_ptr >= token; line_ptr--)
1936 if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
1939 /* cut leading whitespaces from value */
1940 for (; *value; value++)
1941 if (*value != ' ' && *value != '\t')
1950 boolean getTokenValueFromSetupLine(char *line, char **token, char **value)
1952 /* while the internal (old) interface does not require a token/value
1953 separator (for downwards compatibility with existing files which
1954 don't use them), it is mandatory for the external (new) interface */
1956 return getTokenValueFromSetupLineExt(line, token, value, NULL, NULL, 0, TRUE);
1959 static boolean loadSetupFileData(void *setup_file_data, char *filename,
1960 boolean top_recursion_level, boolean is_hash)
1962 static SetupFileHash *include_filename_hash = NULL;
1963 char line[MAX_LINE_LEN], line_raw[MAX_LINE_LEN], previous_line[MAX_LINE_LEN];
1964 char *token, *value, *line_ptr;
1965 void *insert_ptr = NULL;
1966 boolean read_continued_line = FALSE;
1968 int line_nr = 0, token_count = 0, include_count = 0;
1970 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
1971 token_value_separator_warning = FALSE;
1974 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
1975 token_already_exists_warning = FALSE;
1978 if (!(file = openFile(filename, MODE_READ)))
1980 Error(ERR_DEBUG, "cannot open configuration file '%s'", filename);
1985 /* use "insert pointer" to store list end for constant insertion complexity */
1987 insert_ptr = setup_file_data;
1989 /* on top invocation, create hash to mark included files (to prevent loops) */
1990 if (top_recursion_level)
1991 include_filename_hash = newSetupFileHash();
1993 /* mark this file as already included (to prevent including it again) */
1994 setHashEntry(include_filename_hash, getBaseNamePtr(filename), "true");
1996 while (!checkEndOfFile(file))
1998 /* read next line of input file */
1999 if (!getStringFromFile(file, line, MAX_LINE_LEN))
2002 /* check if line was completely read and is terminated by line break */
2003 if (strlen(line) > 0 && line[strlen(line) - 1] == '\n')
2006 /* cut trailing line break (this can be newline and/or carriage return) */
2007 for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
2008 if ((*line_ptr == '\n' || *line_ptr == '\r') && *(line_ptr + 1) == '\0')
2011 /* copy raw input line for later use (mainly debugging output) */
2012 strcpy(line_raw, line);
2014 if (read_continued_line)
2016 /* append new line to existing line, if there is enough space */
2017 if (strlen(previous_line) + strlen(line_ptr) < MAX_LINE_LEN)
2018 strcat(previous_line, line_ptr);
2020 strcpy(line, previous_line); /* copy storage buffer to line */
2022 read_continued_line = FALSE;
2025 /* if the last character is '\', continue at next line */
2026 if (strlen(line) > 0 && line[strlen(line) - 1] == '\\')
2028 line[strlen(line) - 1] = '\0'; /* cut off trailing backslash */
2029 strcpy(previous_line, line); /* copy line to storage buffer */
2031 read_continued_line = TRUE;
2036 if (!getTokenValueFromSetupLineExt(line, &token, &value, filename,
2037 line_raw, line_nr, FALSE))
2042 if (strEqual(token, "include"))
2044 if (getHashEntry(include_filename_hash, value) == NULL)
2046 char *basepath = getBasePath(filename);
2047 char *basename = getBaseName(value);
2048 char *filename_include = getPath2(basepath, basename);
2050 loadSetupFileData(setup_file_data, filename_include, FALSE, is_hash);
2054 free(filename_include);
2060 Error(ERR_WARN, "ignoring already processed file '%s'", value);
2067 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2069 getHashEntry((SetupFileHash *)setup_file_data, token);
2071 if (old_value != NULL)
2073 if (!token_already_exists_warning)
2075 Error(ERR_INFO_LINE, "-");
2076 Error(ERR_WARN, "duplicate token(s) found in config file:");
2077 Error(ERR_INFO, "- config file: '%s'", filename);
2079 token_already_exists_warning = TRUE;
2082 Error(ERR_INFO, "- token: '%s' (in line %d)", token, line_nr);
2083 Error(ERR_INFO, " old value: '%s'", old_value);
2084 Error(ERR_INFO, " new value: '%s'", value);
2088 setHashEntry((SetupFileHash *)setup_file_data, token, value);
2092 insert_ptr = addListEntry((SetupFileList *)insert_ptr, token, value);
2102 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2103 if (token_value_separator_warning)
2104 Error(ERR_INFO_LINE, "-");
2107 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2108 if (token_already_exists_warning)
2109 Error(ERR_INFO_LINE, "-");
2112 if (token_count == 0 && include_count == 0)
2113 Error(ERR_WARN, "configuration file '%s' is empty", filename);
2115 if (top_recursion_level)
2116 freeSetupFileHash(include_filename_hash);
2121 void saveSetupFileHash(SetupFileHash *hash, char *filename)
2125 if (!(file = fopen(filename, MODE_WRITE)))
2127 Error(ERR_WARN, "cannot write configuration file '%s'", filename);
2132 BEGIN_HASH_ITERATION(hash, itr)
2134 fprintf(file, "%s\n", getFormattedSetupEntry(HASH_ITERATION_TOKEN(itr),
2135 HASH_ITERATION_VALUE(itr)));
2137 END_HASH_ITERATION(hash, itr)
2142 SetupFileList *loadSetupFileList(char *filename)
2144 SetupFileList *setup_file_list = newSetupFileList("", "");
2145 SetupFileList *first_valid_list_entry;
2147 if (!loadSetupFileData(setup_file_list, filename, TRUE, FALSE))
2149 freeSetupFileList(setup_file_list);
2154 first_valid_list_entry = setup_file_list->next;
2156 /* free empty list header */
2157 setup_file_list->next = NULL;
2158 freeSetupFileList(setup_file_list);
2160 return first_valid_list_entry;
2163 SetupFileHash *loadSetupFileHash(char *filename)
2165 SetupFileHash *setup_file_hash = newSetupFileHash();
2167 if (!loadSetupFileData(setup_file_hash, filename, TRUE, TRUE))
2169 freeSetupFileHash(setup_file_hash);
2174 return setup_file_hash;
2178 /* ========================================================================= */
2179 /* setup file stuff */
2180 /* ========================================================================= */
2182 #define TOKEN_STR_LAST_LEVEL_SERIES "last_level_series"
2183 #define TOKEN_STR_LAST_PLAYED_LEVEL "last_played_level"
2184 #define TOKEN_STR_HANDICAP_LEVEL "handicap_level"
2186 /* level directory info */
2187 #define LEVELINFO_TOKEN_IDENTIFIER 0
2188 #define LEVELINFO_TOKEN_NAME 1
2189 #define LEVELINFO_TOKEN_NAME_SORTING 2
2190 #define LEVELINFO_TOKEN_AUTHOR 3
2191 #define LEVELINFO_TOKEN_YEAR 4
2192 #define LEVELINFO_TOKEN_IMPORTED_FROM 5
2193 #define LEVELINFO_TOKEN_IMPORTED_BY 6
2194 #define LEVELINFO_TOKEN_TESTED_BY 7
2195 #define LEVELINFO_TOKEN_LEVELS 8
2196 #define LEVELINFO_TOKEN_FIRST_LEVEL 9
2197 #define LEVELINFO_TOKEN_SORT_PRIORITY 10
2198 #define LEVELINFO_TOKEN_LATEST_ENGINE 11
2199 #define LEVELINFO_TOKEN_LEVEL_GROUP 12
2200 #define LEVELINFO_TOKEN_READONLY 13
2201 #define LEVELINFO_TOKEN_GRAPHICS_SET_ECS 14
2202 #define LEVELINFO_TOKEN_GRAPHICS_SET_AGA 15
2203 #define LEVELINFO_TOKEN_GRAPHICS_SET 16
2204 #define LEVELINFO_TOKEN_SOUNDS_SET 17
2205 #define LEVELINFO_TOKEN_MUSIC_SET 18
2206 #define LEVELINFO_TOKEN_FILENAME 19
2207 #define LEVELINFO_TOKEN_FILETYPE 20
2208 #define LEVELINFO_TOKEN_SPECIAL_FLAGS 21
2209 #define LEVELINFO_TOKEN_HANDICAP 22
2210 #define LEVELINFO_TOKEN_SKIP_LEVELS 23
2212 #define NUM_LEVELINFO_TOKENS 24
2214 static LevelDirTree ldi;
2216 static struct TokenInfo levelinfo_tokens[] =
2218 /* level directory info */
2219 { TYPE_STRING, &ldi.identifier, "identifier" },
2220 { TYPE_STRING, &ldi.name, "name" },
2221 { TYPE_STRING, &ldi.name_sorting, "name_sorting" },
2222 { TYPE_STRING, &ldi.author, "author" },
2223 { TYPE_STRING, &ldi.year, "year" },
2224 { TYPE_STRING, &ldi.imported_from, "imported_from" },
2225 { TYPE_STRING, &ldi.imported_by, "imported_by" },
2226 { TYPE_STRING, &ldi.tested_by, "tested_by" },
2227 { TYPE_INTEGER, &ldi.levels, "levels" },
2228 { TYPE_INTEGER, &ldi.first_level, "first_level" },
2229 { TYPE_INTEGER, &ldi.sort_priority, "sort_priority" },
2230 { TYPE_BOOLEAN, &ldi.latest_engine, "latest_engine" },
2231 { TYPE_BOOLEAN, &ldi.level_group, "level_group" },
2232 { TYPE_BOOLEAN, &ldi.readonly, "readonly" },
2233 { TYPE_STRING, &ldi.graphics_set_ecs, "graphics_set.ecs" },
2234 { TYPE_STRING, &ldi.graphics_set_aga, "graphics_set.aga" },
2235 { TYPE_STRING, &ldi.graphics_set, "graphics_set" },
2236 { TYPE_STRING, &ldi.sounds_set, "sounds_set" },
2237 { TYPE_STRING, &ldi.music_set, "music_set" },
2238 { TYPE_STRING, &ldi.level_filename, "filename" },
2239 { TYPE_STRING, &ldi.level_filetype, "filetype" },
2240 { TYPE_STRING, &ldi.special_flags, "special_flags" },
2241 { TYPE_BOOLEAN, &ldi.handicap, "handicap" },
2242 { TYPE_BOOLEAN, &ldi.skip_levels, "skip_levels" }
2245 static struct TokenInfo artworkinfo_tokens[] =
2247 /* artwork directory info */
2248 { TYPE_STRING, &ldi.identifier, "identifier" },
2249 { TYPE_STRING, &ldi.subdir, "subdir" },
2250 { TYPE_STRING, &ldi.name, "name" },
2251 { TYPE_STRING, &ldi.name_sorting, "name_sorting" },
2252 { TYPE_STRING, &ldi.author, "author" },
2253 { TYPE_INTEGER, &ldi.sort_priority, "sort_priority" },
2254 { TYPE_STRING, &ldi.basepath, "basepath" },
2255 { TYPE_STRING, &ldi.fullpath, "fullpath" },
2256 { TYPE_BOOLEAN, &ldi.in_user_dir, "in_user_dir" },
2257 { TYPE_INTEGER, &ldi.color, "color" },
2258 { TYPE_STRING, &ldi.class_desc, "class_desc" },
2263 static void setTreeInfoToDefaults(TreeInfo *ti, int type)
2267 ti->node_top = (ti->type == TREE_TYPE_LEVEL_DIR ? &leveldir_first :
2268 ti->type == TREE_TYPE_GRAPHICS_DIR ? &artwork.gfx_first :
2269 ti->type == TREE_TYPE_SOUNDS_DIR ? &artwork.snd_first :
2270 ti->type == TREE_TYPE_MUSIC_DIR ? &artwork.mus_first :
2273 ti->node_parent = NULL;
2274 ti->node_group = NULL;
2281 ti->fullpath = NULL;
2282 ti->basepath = NULL;
2283 ti->identifier = NULL;
2284 ti->name = getStringCopy(ANONYMOUS_NAME);
2285 ti->name_sorting = NULL;
2286 ti->author = getStringCopy(ANONYMOUS_NAME);
2289 ti->sort_priority = LEVELCLASS_UNDEFINED; /* default: least priority */
2290 ti->latest_engine = FALSE; /* default: get from level */
2291 ti->parent_link = FALSE;
2292 ti->in_user_dir = FALSE;
2293 ti->user_defined = FALSE;
2295 ti->class_desc = NULL;
2297 ti->infotext = getStringCopy(TREE_INFOTEXT(ti->type));
2299 if (ti->type == TREE_TYPE_LEVEL_DIR)
2301 ti->imported_from = NULL;
2302 ti->imported_by = NULL;
2303 ti->tested_by = NULL;
2305 ti->graphics_set_ecs = NULL;
2306 ti->graphics_set_aga = NULL;
2307 ti->graphics_set = NULL;
2308 ti->sounds_set = NULL;
2309 ti->music_set = NULL;
2310 ti->graphics_path = getStringCopy(UNDEFINED_FILENAME);
2311 ti->sounds_path = getStringCopy(UNDEFINED_FILENAME);
2312 ti->music_path = getStringCopy(UNDEFINED_FILENAME);
2314 ti->level_filename = NULL;
2315 ti->level_filetype = NULL;
2317 ti->special_flags = NULL;
2320 ti->first_level = 0;
2322 ti->level_group = FALSE;
2323 ti->handicap_level = 0;
2324 ti->readonly = TRUE;
2325 ti->handicap = TRUE;
2326 ti->skip_levels = FALSE;
2330 static void setTreeInfoToDefaultsFromParent(TreeInfo *ti, TreeInfo *parent)
2334 Error(ERR_WARN, "setTreeInfoToDefaultsFromParent(): parent == NULL");
2336 setTreeInfoToDefaults(ti, TREE_TYPE_UNDEFINED);
2341 /* copy all values from the parent structure */
2343 ti->type = parent->type;
2345 ti->node_top = parent->node_top;
2346 ti->node_parent = parent;
2347 ti->node_group = NULL;
2354 ti->fullpath = NULL;
2355 ti->basepath = NULL;
2356 ti->identifier = NULL;
2357 ti->name = getStringCopy(ANONYMOUS_NAME);
2358 ti->name_sorting = NULL;
2359 ti->author = getStringCopy(parent->author);
2360 ti->year = getStringCopy(parent->year);
2362 ti->sort_priority = parent->sort_priority;
2363 ti->latest_engine = parent->latest_engine;
2364 ti->parent_link = FALSE;
2365 ti->in_user_dir = parent->in_user_dir;
2366 ti->user_defined = parent->user_defined;
2367 ti->color = parent->color;
2368 ti->class_desc = getStringCopy(parent->class_desc);
2370 ti->infotext = getStringCopy(parent->infotext);
2372 if (ti->type == TREE_TYPE_LEVEL_DIR)
2374 ti->imported_from = getStringCopy(parent->imported_from);
2375 ti->imported_by = getStringCopy(parent->imported_by);
2376 ti->tested_by = getStringCopy(parent->tested_by);
2378 ti->graphics_set_ecs = getStringCopy(parent->graphics_set_ecs);
2379 ti->graphics_set_aga = getStringCopy(parent->graphics_set_aga);
2380 ti->graphics_set = getStringCopy(parent->graphics_set);
2381 ti->sounds_set = getStringCopy(parent->sounds_set);
2382 ti->music_set = getStringCopy(parent->music_set);
2383 ti->graphics_path = getStringCopy(UNDEFINED_FILENAME);
2384 ti->sounds_path = getStringCopy(UNDEFINED_FILENAME);
2385 ti->music_path = getStringCopy(UNDEFINED_FILENAME);
2387 ti->level_filename = getStringCopy(parent->level_filename);
2388 ti->level_filetype = getStringCopy(parent->level_filetype);
2390 ti->special_flags = getStringCopy(parent->special_flags);
2392 ti->levels = parent->levels;
2393 ti->first_level = parent->first_level;
2394 ti->last_level = parent->last_level;
2395 ti->level_group = FALSE;
2396 ti->handicap_level = parent->handicap_level;
2397 ti->readonly = parent->readonly;
2398 ti->handicap = parent->handicap;
2399 ti->skip_levels = parent->skip_levels;
2403 static TreeInfo *getTreeInfoCopy(TreeInfo *ti)
2405 TreeInfo *ti_copy = newTreeInfo();
2407 /* copy all values from the original structure */
2409 ti_copy->type = ti->type;
2411 ti_copy->node_top = ti->node_top;
2412 ti_copy->node_parent = ti->node_parent;
2413 ti_copy->node_group = ti->node_group;
2414 ti_copy->next = ti->next;
2416 ti_copy->cl_first = ti->cl_first;
2417 ti_copy->cl_cursor = ti->cl_cursor;
2419 ti_copy->subdir = getStringCopy(ti->subdir);
2420 ti_copy->fullpath = getStringCopy(ti->fullpath);
2421 ti_copy->basepath = getStringCopy(ti->basepath);
2422 ti_copy->identifier = getStringCopy(ti->identifier);
2423 ti_copy->name = getStringCopy(ti->name);
2424 ti_copy->name_sorting = getStringCopy(ti->name_sorting);
2425 ti_copy->author = getStringCopy(ti->author);
2426 ti_copy->year = getStringCopy(ti->year);
2427 ti_copy->imported_from = getStringCopy(ti->imported_from);
2428 ti_copy->imported_by = getStringCopy(ti->imported_by);
2429 ti_copy->tested_by = getStringCopy(ti->tested_by);
2431 ti_copy->graphics_set_ecs = getStringCopy(ti->graphics_set_ecs);
2432 ti_copy->graphics_set_aga = getStringCopy(ti->graphics_set_aga);
2433 ti_copy->graphics_set = getStringCopy(ti->graphics_set);
2434 ti_copy->sounds_set = getStringCopy(ti->sounds_set);
2435 ti_copy->music_set = getStringCopy(ti->music_set);
2436 ti_copy->graphics_path = getStringCopy(ti->graphics_path);
2437 ti_copy->sounds_path = getStringCopy(ti->sounds_path);
2438 ti_copy->music_path = getStringCopy(ti->music_path);
2440 ti_copy->level_filename = getStringCopy(ti->level_filename);
2441 ti_copy->level_filetype = getStringCopy(ti->level_filetype);
2443 ti_copy->special_flags = getStringCopy(ti->special_flags);
2445 ti_copy->levels = ti->levels;
2446 ti_copy->first_level = ti->first_level;
2447 ti_copy->last_level = ti->last_level;
2448 ti_copy->sort_priority = ti->sort_priority;
2450 ti_copy->latest_engine = ti->latest_engine;
2452 ti_copy->level_group = ti->level_group;
2453 ti_copy->parent_link = ti->parent_link;
2454 ti_copy->in_user_dir = ti->in_user_dir;
2455 ti_copy->user_defined = ti->user_defined;
2456 ti_copy->readonly = ti->readonly;
2457 ti_copy->handicap = ti->handicap;
2458 ti_copy->skip_levels = ti->skip_levels;
2460 ti_copy->color = ti->color;
2461 ti_copy->class_desc = getStringCopy(ti->class_desc);
2462 ti_copy->handicap_level = ti->handicap_level;
2464 ti_copy->infotext = getStringCopy(ti->infotext);
2469 void freeTreeInfo(TreeInfo *ti)
2474 checked_free(ti->subdir);
2475 checked_free(ti->fullpath);
2476 checked_free(ti->basepath);
2477 checked_free(ti->identifier);
2479 checked_free(ti->name);
2480 checked_free(ti->name_sorting);
2481 checked_free(ti->author);
2482 checked_free(ti->year);
2484 checked_free(ti->class_desc);
2486 checked_free(ti->infotext);
2488 if (ti->type == TREE_TYPE_LEVEL_DIR)
2490 checked_free(ti->imported_from);
2491 checked_free(ti->imported_by);
2492 checked_free(ti->tested_by);
2494 checked_free(ti->graphics_set_ecs);
2495 checked_free(ti->graphics_set_aga);
2496 checked_free(ti->graphics_set);
2497 checked_free(ti->sounds_set);
2498 checked_free(ti->music_set);
2500 checked_free(ti->graphics_path);
2501 checked_free(ti->sounds_path);
2502 checked_free(ti->music_path);
2504 checked_free(ti->level_filename);
2505 checked_free(ti->level_filetype);
2507 checked_free(ti->special_flags);
2510 // recursively free child node
2512 freeTreeInfo(ti->node_group);
2514 // recursively free next node
2516 freeTreeInfo(ti->next);
2521 void setSetupInfo(struct TokenInfo *token_info,
2522 int token_nr, char *token_value)
2524 int token_type = token_info[token_nr].type;
2525 void *setup_value = token_info[token_nr].value;
2527 if (token_value == NULL)
2530 /* set setup field to corresponding token value */
2535 *(boolean *)setup_value = get_boolean_from_string(token_value);
2539 *(int *)setup_value = get_switch3_from_string(token_value);
2543 *(Key *)setup_value = getKeyFromKeyName(token_value);
2547 *(Key *)setup_value = getKeyFromX11KeyName(token_value);
2551 *(int *)setup_value = get_integer_from_string(token_value);
2555 checked_free(*(char **)setup_value);
2556 *(char **)setup_value = getStringCopy(token_value);
2564 static int compareTreeInfoEntries(const void *object1, const void *object2)
2566 const TreeInfo *entry1 = *((TreeInfo **)object1);
2567 const TreeInfo *entry2 = *((TreeInfo **)object2);
2568 int class_sorting1 = 0, class_sorting2 = 0;
2571 if (entry1->type == TREE_TYPE_LEVEL_DIR)
2573 class_sorting1 = LEVELSORTING(entry1);
2574 class_sorting2 = LEVELSORTING(entry2);
2576 else if (entry1->type == TREE_TYPE_GRAPHICS_DIR ||
2577 entry1->type == TREE_TYPE_SOUNDS_DIR ||
2578 entry1->type == TREE_TYPE_MUSIC_DIR)
2580 class_sorting1 = ARTWORKSORTING(entry1);
2581 class_sorting2 = ARTWORKSORTING(entry2);
2584 if (entry1->parent_link || entry2->parent_link)
2585 compare_result = (entry1->parent_link ? -1 : +1);
2586 else if (entry1->sort_priority == entry2->sort_priority)
2588 char *name1 = getStringToLower(entry1->name_sorting);
2589 char *name2 = getStringToLower(entry2->name_sorting);
2591 compare_result = strcmp(name1, name2);
2596 else if (class_sorting1 == class_sorting2)
2597 compare_result = entry1->sort_priority - entry2->sort_priority;
2599 compare_result = class_sorting1 - class_sorting2;
2601 return compare_result;
2604 static TreeInfo *createParentTreeInfoNode(TreeInfo *node_parent)
2608 if (node_parent == NULL)
2611 ti_new = newTreeInfo();
2612 setTreeInfoToDefaults(ti_new, node_parent->type);
2614 ti_new->node_parent = node_parent;
2615 ti_new->parent_link = TRUE;
2617 setString(&ti_new->identifier, node_parent->identifier);
2618 setString(&ti_new->name, ".. (parent directory)");
2619 setString(&ti_new->name_sorting, ti_new->name);
2621 setString(&ti_new->subdir, STRING_PARENT_DIRECTORY);
2622 setString(&ti_new->fullpath, node_parent->fullpath);
2624 ti_new->sort_priority = node_parent->sort_priority;
2625 ti_new->latest_engine = node_parent->latest_engine;
2627 setString(&ti_new->class_desc, getLevelClassDescription(ti_new));
2629 pushTreeInfo(&node_parent->node_group, ti_new);
2634 static TreeInfo *createTopTreeInfoNode(TreeInfo *node_first)
2636 TreeInfo *ti_new, *ti_new2;
2638 if (node_first == NULL)
2641 ti_new = newTreeInfo();
2642 setTreeInfoToDefaults(ti_new, TREE_TYPE_LEVEL_DIR);
2644 ti_new->node_parent = NULL;
2645 ti_new->parent_link = FALSE;
2647 setString(&ti_new->identifier, node_first->identifier);
2648 setString(&ti_new->name, "level sets");
2649 setString(&ti_new->name_sorting, ti_new->name);
2651 setString(&ti_new->subdir, STRING_TOP_DIRECTORY);
2652 setString(&ti_new->fullpath, ".");
2654 ti_new->sort_priority = node_first->sort_priority;;
2655 ti_new->latest_engine = node_first->latest_engine;
2657 setString(&ti_new->class_desc, "level sets");
2659 ti_new->node_group = node_first;
2660 ti_new->level_group = TRUE;
2662 ti_new2 = createParentTreeInfoNode(ti_new);
2664 setString(&ti_new2->name, ".. (main menu)");
2665 setString(&ti_new2->name_sorting, ti_new2->name);
2671 /* -------------------------------------------------------------------------- */
2672 /* functions for handling level and custom artwork info cache */
2673 /* -------------------------------------------------------------------------- */
2675 static void LoadArtworkInfoCache()
2677 InitCacheDirectory();
2679 if (artworkinfo_cache_old == NULL)
2681 char *filename = getPath2(getCacheDir(), ARTWORKINFO_CACHE_FILE);
2683 /* try to load artwork info hash from already existing cache file */
2684 artworkinfo_cache_old = loadSetupFileHash(filename);
2686 /* if no artwork info cache file was found, start with empty hash */
2687 if (artworkinfo_cache_old == NULL)
2688 artworkinfo_cache_old = newSetupFileHash();
2693 if (artworkinfo_cache_new == NULL)
2694 artworkinfo_cache_new = newSetupFileHash();
2697 static void SaveArtworkInfoCache()
2699 char *filename = getPath2(getCacheDir(), ARTWORKINFO_CACHE_FILE);
2701 InitCacheDirectory();
2703 saveSetupFileHash(artworkinfo_cache_new, filename);
2708 static char *getCacheTokenPrefix(char *prefix1, char *prefix2)
2710 static char *prefix = NULL;
2712 checked_free(prefix);
2714 prefix = getStringCat2WithSeparator(prefix1, prefix2, ".");
2719 /* (identical to above function, but separate string buffer needed -- nasty) */
2720 static char *getCacheToken(char *prefix, char *suffix)
2722 static char *token = NULL;
2724 checked_free(token);
2726 token = getStringCat2WithSeparator(prefix, suffix, ".");
2731 static char *getFileTimestampString(char *filename)
2733 return getStringCopy(i_to_a(getFileTimestampEpochSeconds(filename)));
2736 static boolean modifiedFileTimestamp(char *filename, char *timestamp_string)
2738 struct stat file_status;
2740 if (timestamp_string == NULL)
2743 if (stat(filename, &file_status) != 0) /* cannot stat file */
2746 return (file_status.st_mtime != atoi(timestamp_string));
2749 static TreeInfo *getArtworkInfoCacheEntry(LevelDirTree *level_node, int type)
2751 char *identifier = level_node->subdir;
2752 char *type_string = ARTWORK_DIRECTORY(type);
2753 char *token_prefix = getCacheTokenPrefix(type_string, identifier);
2754 char *token_main = getCacheToken(token_prefix, "CACHED");
2755 char *cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
2756 boolean cached = (cache_entry != NULL && strEqual(cache_entry, "true"));
2757 TreeInfo *artwork_info = NULL;
2759 if (!use_artworkinfo_cache)
2766 artwork_info = newTreeInfo();
2767 setTreeInfoToDefaults(artwork_info, type);
2769 /* set all structure fields according to the token/value pairs */
2770 ldi = *artwork_info;
2771 for (i = 0; artworkinfo_tokens[i].type != -1; i++)
2773 char *token = getCacheToken(token_prefix, artworkinfo_tokens[i].text);
2774 char *value = getHashEntry(artworkinfo_cache_old, token);
2776 setSetupInfo(artworkinfo_tokens, i, value);
2778 /* check if cache entry for this item is invalid or incomplete */
2781 Error(ERR_WARN, "cache entry '%s' invalid", token);
2787 *artwork_info = ldi;
2792 char *filename_levelinfo = getPath2(getLevelDirFromTreeInfo(level_node),
2793 LEVELINFO_FILENAME);
2794 char *filename_artworkinfo = getPath2(getSetupArtworkDir(artwork_info),
2795 ARTWORKINFO_FILENAME(type));
2797 /* check if corresponding "levelinfo.conf" file has changed */
2798 token_main = getCacheToken(token_prefix, "TIMESTAMP_LEVELINFO");
2799 cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
2801 if (modifiedFileTimestamp(filename_levelinfo, cache_entry))
2804 /* check if corresponding "<artworkinfo>.conf" file has changed */
2805 token_main = getCacheToken(token_prefix, "TIMESTAMP_ARTWORKINFO");
2806 cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
2808 if (modifiedFileTimestamp(filename_artworkinfo, cache_entry))
2811 checked_free(filename_levelinfo);
2812 checked_free(filename_artworkinfo);
2815 if (!cached && artwork_info != NULL)
2817 freeTreeInfo(artwork_info);
2822 return artwork_info;
2825 static void setArtworkInfoCacheEntry(TreeInfo *artwork_info,
2826 LevelDirTree *level_node, int type)
2828 char *identifier = level_node->subdir;
2829 char *type_string = ARTWORK_DIRECTORY(type);
2830 char *token_prefix = getCacheTokenPrefix(type_string, identifier);
2831 char *token_main = getCacheToken(token_prefix, "CACHED");
2832 boolean set_cache_timestamps = TRUE;
2835 setHashEntry(artworkinfo_cache_new, token_main, "true");
2837 if (set_cache_timestamps)
2839 char *filename_levelinfo = getPath2(getLevelDirFromTreeInfo(level_node),
2840 LEVELINFO_FILENAME);
2841 char *filename_artworkinfo = getPath2(getSetupArtworkDir(artwork_info),
2842 ARTWORKINFO_FILENAME(type));
2843 char *timestamp_levelinfo = getFileTimestampString(filename_levelinfo);
2844 char *timestamp_artworkinfo = getFileTimestampString(filename_artworkinfo);
2846 token_main = getCacheToken(token_prefix, "TIMESTAMP_LEVELINFO");
2847 setHashEntry(artworkinfo_cache_new, token_main, timestamp_levelinfo);
2849 token_main = getCacheToken(token_prefix, "TIMESTAMP_ARTWORKINFO");
2850 setHashEntry(artworkinfo_cache_new, token_main, timestamp_artworkinfo);
2852 checked_free(filename_levelinfo);
2853 checked_free(filename_artworkinfo);
2854 checked_free(timestamp_levelinfo);
2855 checked_free(timestamp_artworkinfo);
2858 ldi = *artwork_info;
2859 for (i = 0; artworkinfo_tokens[i].type != -1; i++)
2861 char *token = getCacheToken(token_prefix, artworkinfo_tokens[i].text);
2862 char *value = getSetupValue(artworkinfo_tokens[i].type,
2863 artworkinfo_tokens[i].value);
2865 setHashEntry(artworkinfo_cache_new, token, value);
2870 /* -------------------------------------------------------------------------- */
2871 /* functions for loading level info and custom artwork info */
2872 /* -------------------------------------------------------------------------- */
2874 /* forward declaration for recursive call by "LoadLevelInfoFromLevelDir()" */
2875 static void LoadLevelInfoFromLevelDir(TreeInfo **, TreeInfo *, char *);
2877 static boolean LoadLevelInfoFromLevelConf(TreeInfo **node_first,
2878 TreeInfo *node_parent,
2879 char *level_directory,
2880 char *directory_name)
2882 char *directory_path = getPath2(level_directory, directory_name);
2883 char *filename = getPath2(directory_path, LEVELINFO_FILENAME);
2884 SetupFileHash *setup_file_hash;
2885 LevelDirTree *leveldir_new = NULL;
2888 /* unless debugging, silently ignore directories without "levelinfo.conf" */
2889 if (!options.debug && !fileExists(filename))
2891 free(directory_path);
2897 setup_file_hash = loadSetupFileHash(filename);
2899 if (setup_file_hash == NULL)
2901 Error(ERR_WARN, "ignoring level directory '%s'", directory_path);
2903 free(directory_path);
2909 leveldir_new = newTreeInfo();
2912 setTreeInfoToDefaultsFromParent(leveldir_new, node_parent);
2914 setTreeInfoToDefaults(leveldir_new, TREE_TYPE_LEVEL_DIR);
2916 leveldir_new->subdir = getStringCopy(directory_name);
2918 /* set all structure fields according to the token/value pairs */
2919 ldi = *leveldir_new;
2920 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
2921 setSetupInfo(levelinfo_tokens, i,
2922 getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
2923 *leveldir_new = ldi;
2925 if (strEqual(leveldir_new->name, ANONYMOUS_NAME))
2926 setString(&leveldir_new->name, leveldir_new->subdir);
2928 if (leveldir_new->identifier == NULL)
2929 leveldir_new->identifier = getStringCopy(leveldir_new->subdir);
2931 if (leveldir_new->name_sorting == NULL)
2932 leveldir_new->name_sorting = getStringCopy(leveldir_new->name);
2934 if (node_parent == NULL) /* top level group */
2936 leveldir_new->basepath = getStringCopy(level_directory);
2937 leveldir_new->fullpath = getStringCopy(leveldir_new->subdir);
2939 else /* sub level group */
2941 leveldir_new->basepath = getStringCopy(node_parent->basepath);
2942 leveldir_new->fullpath = getPath2(node_parent->fullpath, directory_name);
2945 leveldir_new->last_level =
2946 leveldir_new->first_level + leveldir_new->levels - 1;
2948 leveldir_new->in_user_dir =
2949 (!strEqual(leveldir_new->basepath, options.level_directory));
2951 /* adjust some settings if user's private level directory was detected */
2952 if (leveldir_new->sort_priority == LEVELCLASS_UNDEFINED &&
2953 leveldir_new->in_user_dir &&
2954 (strEqual(leveldir_new->subdir, getLoginName()) ||
2955 strEqual(leveldir_new->name, getLoginName()) ||
2956 strEqual(leveldir_new->author, getRealName())))
2958 leveldir_new->sort_priority = LEVELCLASS_PRIVATE_START;
2959 leveldir_new->readonly = FALSE;
2962 leveldir_new->user_defined =
2963 (leveldir_new->in_user_dir && IS_LEVELCLASS_PRIVATE(leveldir_new));
2965 leveldir_new->color = LEVELCOLOR(leveldir_new);
2967 setString(&leveldir_new->class_desc, getLevelClassDescription(leveldir_new));
2969 leveldir_new->handicap_level = /* set handicap to default value */
2970 (leveldir_new->user_defined || !leveldir_new->handicap ?
2971 leveldir_new->last_level : leveldir_new->first_level);
2973 DrawInitText(leveldir_new->name, 150, FC_YELLOW);
2975 pushTreeInfo(node_first, leveldir_new);
2977 freeSetupFileHash(setup_file_hash);
2979 if (leveldir_new->level_group)
2981 /* create node to link back to current level directory */
2982 createParentTreeInfoNode(leveldir_new);
2984 /* recursively step into sub-directory and look for more level series */
2985 LoadLevelInfoFromLevelDir(&leveldir_new->node_group,
2986 leveldir_new, directory_path);
2989 free(directory_path);
2995 static void LoadLevelInfoFromLevelDir(TreeInfo **node_first,
2996 TreeInfo *node_parent,
2997 char *level_directory)
3000 DirectoryEntry *dir_entry;
3001 boolean valid_entry_found = FALSE;
3003 if ((dir = openDirectory(level_directory)) == NULL)
3005 Error(ERR_WARN, "cannot read level directory '%s'", level_directory);
3010 while ((dir_entry = readDirectory(dir)) != NULL) /* loop all entries */
3012 char *directory_name = dir_entry->basename;
3013 char *directory_path = getPath2(level_directory, directory_name);
3015 /* skip entries for current and parent directory */
3016 if (strEqual(directory_name, ".") ||
3017 strEqual(directory_name, ".."))
3019 free(directory_path);
3024 /* find out if directory entry is itself a directory */
3025 if (!dir_entry->is_directory) /* not a directory */
3027 free(directory_path);
3032 free(directory_path);
3034 if (strEqual(directory_name, GRAPHICS_DIRECTORY) ||
3035 strEqual(directory_name, SOUNDS_DIRECTORY) ||
3036 strEqual(directory_name, MUSIC_DIRECTORY))
3039 valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
3044 closeDirectory(dir);
3046 /* special case: top level directory may directly contain "levelinfo.conf" */
3047 if (node_parent == NULL && !valid_entry_found)
3049 /* check if this directory directly contains a file "levelinfo.conf" */
3050 valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
3051 level_directory, ".");
3054 if (!valid_entry_found)
3055 Error(ERR_WARN, "cannot find any valid level series in directory '%s'",
3059 boolean AdjustGraphicsForEMC()
3061 boolean settings_changed = FALSE;
3063 settings_changed |= adjustTreeGraphicsForEMC(leveldir_first_all);
3064 settings_changed |= adjustTreeGraphicsForEMC(leveldir_first);
3066 return settings_changed;
3069 void LoadLevelInfo()
3071 InitUserLevelDirectory(getLoginName());
3073 DrawInitText("Loading level series", 120, FC_GREEN);
3075 LoadLevelInfoFromLevelDir(&leveldir_first, NULL, options.level_directory);
3076 LoadLevelInfoFromLevelDir(&leveldir_first, NULL, getUserLevelDir(NULL));
3078 leveldir_first = createTopTreeInfoNode(leveldir_first);
3080 /* after loading all level set information, clone the level directory tree
3081 and remove all level sets without levels (these may still contain artwork
3082 to be offered in the setup menu as "custom artwork", and are therefore
3083 checked for existing artwork in the function "LoadLevelArtworkInfo()") */
3084 leveldir_first_all = leveldir_first;
3085 cloneTree(&leveldir_first, leveldir_first_all, TRUE);
3087 AdjustGraphicsForEMC();
3089 /* before sorting, the first entries will be from the user directory */
3090 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
3092 if (leveldir_first == NULL)
3093 Error(ERR_EXIT, "cannot find any valid level series in any directory");
3095 sortTreeInfo(&leveldir_first);
3097 #if ENABLE_UNUSED_CODE
3098 dumpTreeInfo(leveldir_first, 0);
3102 static boolean LoadArtworkInfoFromArtworkConf(TreeInfo **node_first,
3103 TreeInfo *node_parent,
3104 char *base_directory,
3105 char *directory_name, int type)
3107 char *directory_path = getPath2(base_directory, directory_name);
3108 char *filename = getPath2(directory_path, ARTWORKINFO_FILENAME(type));
3109 SetupFileHash *setup_file_hash = NULL;
3110 TreeInfo *artwork_new = NULL;
3113 if (fileExists(filename))
3114 setup_file_hash = loadSetupFileHash(filename);
3116 if (setup_file_hash == NULL) /* no config file -- look for artwork files */
3119 DirectoryEntry *dir_entry;
3120 boolean valid_file_found = FALSE;
3122 if ((dir = openDirectory(directory_path)) != NULL)
3124 while ((dir_entry = readDirectory(dir)) != NULL)
3126 if (FileIsArtworkType(dir_entry->filename, type))
3128 valid_file_found = TRUE;
3134 closeDirectory(dir);
3137 if (!valid_file_found)
3139 if (!strEqual(directory_name, "."))
3140 Error(ERR_WARN, "ignoring artwork directory '%s'", directory_path);
3142 free(directory_path);
3149 artwork_new = newTreeInfo();
3152 setTreeInfoToDefaultsFromParent(artwork_new, node_parent);
3154 setTreeInfoToDefaults(artwork_new, type);
3156 artwork_new->subdir = getStringCopy(directory_name);
3158 if (setup_file_hash) /* (before defining ".color" and ".class_desc") */
3160 /* set all structure fields according to the token/value pairs */
3162 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
3163 setSetupInfo(levelinfo_tokens, i,
3164 getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
3167 if (strEqual(artwork_new->name, ANONYMOUS_NAME))
3168 setString(&artwork_new->name, artwork_new->subdir);
3170 if (artwork_new->identifier == NULL)
3171 artwork_new->identifier = getStringCopy(artwork_new->subdir);
3173 if (artwork_new->name_sorting == NULL)
3174 artwork_new->name_sorting = getStringCopy(artwork_new->name);
3177 if (node_parent == NULL) /* top level group */
3179 artwork_new->basepath = getStringCopy(base_directory);
3180 artwork_new->fullpath = getStringCopy(artwork_new->subdir);
3182 else /* sub level group */
3184 artwork_new->basepath = getStringCopy(node_parent->basepath);
3185 artwork_new->fullpath = getPath2(node_parent->fullpath, directory_name);
3188 artwork_new->in_user_dir =
3189 (!strEqual(artwork_new->basepath, OPTIONS_ARTWORK_DIRECTORY(type)));
3191 /* (may use ".sort_priority" from "setup_file_hash" above) */
3192 artwork_new->color = ARTWORKCOLOR(artwork_new);
3194 setString(&artwork_new->class_desc, getLevelClassDescription(artwork_new));
3196 if (setup_file_hash == NULL) /* (after determining ".user_defined") */
3198 if (strEqual(artwork_new->subdir, "."))
3200 if (artwork_new->user_defined)
3202 setString(&artwork_new->identifier, "private");
3203 artwork_new->sort_priority = ARTWORKCLASS_PRIVATE;
3207 setString(&artwork_new->identifier, "classic");
3208 artwork_new->sort_priority = ARTWORKCLASS_CLASSICS;
3211 /* set to new values after changing ".sort_priority" */
3212 artwork_new->color = ARTWORKCOLOR(artwork_new);
3214 setString(&artwork_new->class_desc,
3215 getLevelClassDescription(artwork_new));
3219 setString(&artwork_new->identifier, artwork_new->subdir);
3222 setString(&artwork_new->name, artwork_new->identifier);
3223 setString(&artwork_new->name_sorting, artwork_new->name);
3226 pushTreeInfo(node_first, artwork_new);
3228 freeSetupFileHash(setup_file_hash);
3230 free(directory_path);
3236 static void LoadArtworkInfoFromArtworkDir(TreeInfo **node_first,
3237 TreeInfo *node_parent,
3238 char *base_directory, int type)
3241 DirectoryEntry *dir_entry;
3242 boolean valid_entry_found = FALSE;
3244 if ((dir = openDirectory(base_directory)) == NULL)
3246 /* display error if directory is main "options.graphics_directory" etc. */
3247 if (base_directory == OPTIONS_ARTWORK_DIRECTORY(type))
3248 Error(ERR_WARN, "cannot read directory '%s'", base_directory);
3253 while ((dir_entry = readDirectory(dir)) != NULL) /* loop all entries */
3255 char *directory_name = dir_entry->basename;
3256 char *directory_path = getPath2(base_directory, directory_name);
3258 /* skip directory entries for current and parent directory */
3259 if (strEqual(directory_name, ".") ||
3260 strEqual(directory_name, ".."))
3262 free(directory_path);
3267 /* skip directory entries which are not a directory */
3268 if (!dir_entry->is_directory) /* not a directory */
3270 free(directory_path);
3275 free(directory_path);
3277 /* check if this directory contains artwork with or without config file */
3278 valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
3280 directory_name, type);
3283 closeDirectory(dir);
3285 /* check if this directory directly contains artwork itself */
3286 valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
3287 base_directory, ".",
3289 if (!valid_entry_found)
3290 Error(ERR_WARN, "cannot find any valid artwork in directory '%s'",
3294 static TreeInfo *getDummyArtworkInfo(int type)
3296 /* this is only needed when there is completely no artwork available */
3297 TreeInfo *artwork_new = newTreeInfo();
3299 setTreeInfoToDefaults(artwork_new, type);
3301 setString(&artwork_new->subdir, UNDEFINED_FILENAME);
3302 setString(&artwork_new->fullpath, UNDEFINED_FILENAME);
3303 setString(&artwork_new->basepath, UNDEFINED_FILENAME);
3305 setString(&artwork_new->identifier, UNDEFINED_FILENAME);
3306 setString(&artwork_new->name, UNDEFINED_FILENAME);
3307 setString(&artwork_new->name_sorting, UNDEFINED_FILENAME);
3312 void LoadArtworkInfo()
3314 LoadArtworkInfoCache();
3316 DrawInitText("Looking for custom artwork", 120, FC_GREEN);
3318 LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
3319 options.graphics_directory,
3320 TREE_TYPE_GRAPHICS_DIR);
3321 LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
3322 getUserGraphicsDir(),
3323 TREE_TYPE_GRAPHICS_DIR);
3325 LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
3326 options.sounds_directory,
3327 TREE_TYPE_SOUNDS_DIR);
3328 LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
3330 TREE_TYPE_SOUNDS_DIR);
3332 LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
3333 options.music_directory,
3334 TREE_TYPE_MUSIC_DIR);
3335 LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
3337 TREE_TYPE_MUSIC_DIR);
3339 if (artwork.gfx_first == NULL)
3340 artwork.gfx_first = getDummyArtworkInfo(TREE_TYPE_GRAPHICS_DIR);
3341 if (artwork.snd_first == NULL)
3342 artwork.snd_first = getDummyArtworkInfo(TREE_TYPE_SOUNDS_DIR);
3343 if (artwork.mus_first == NULL)
3344 artwork.mus_first = getDummyArtworkInfo(TREE_TYPE_MUSIC_DIR);
3346 /* before sorting, the first entries will be from the user directory */
3347 artwork.gfx_current =
3348 getTreeInfoFromIdentifier(artwork.gfx_first, setup.graphics_set);
3349 if (artwork.gfx_current == NULL)
3350 artwork.gfx_current =
3351 getTreeInfoFromIdentifier(artwork.gfx_first, GFX_DEFAULT_SUBDIR);
3352 if (artwork.gfx_current == NULL)
3353 artwork.gfx_current = getFirstValidTreeInfoEntry(artwork.gfx_first);
3355 artwork.snd_current =
3356 getTreeInfoFromIdentifier(artwork.snd_first, setup.sounds_set);
3357 if (artwork.snd_current == NULL)
3358 artwork.snd_current =
3359 getTreeInfoFromIdentifier(artwork.snd_first, SND_DEFAULT_SUBDIR);
3360 if (artwork.snd_current == NULL)
3361 artwork.snd_current = getFirstValidTreeInfoEntry(artwork.snd_first);
3363 artwork.mus_current =
3364 getTreeInfoFromIdentifier(artwork.mus_first, setup.music_set);
3365 if (artwork.mus_current == NULL)
3366 artwork.mus_current =
3367 getTreeInfoFromIdentifier(artwork.mus_first, MUS_DEFAULT_SUBDIR);
3368 if (artwork.mus_current == NULL)
3369 artwork.mus_current = getFirstValidTreeInfoEntry(artwork.mus_first);
3371 artwork.gfx_current_identifier = artwork.gfx_current->identifier;
3372 artwork.snd_current_identifier = artwork.snd_current->identifier;
3373 artwork.mus_current_identifier = artwork.mus_current->identifier;
3375 #if ENABLE_UNUSED_CODE
3376 printf("graphics set == %s\n\n", artwork.gfx_current_identifier);
3377 printf("sounds set == %s\n\n", artwork.snd_current_identifier);
3378 printf("music set == %s\n\n", artwork.mus_current_identifier);
3381 sortTreeInfo(&artwork.gfx_first);
3382 sortTreeInfo(&artwork.snd_first);
3383 sortTreeInfo(&artwork.mus_first);
3385 #if ENABLE_UNUSED_CODE
3386 dumpTreeInfo(artwork.gfx_first, 0);
3387 dumpTreeInfo(artwork.snd_first, 0);
3388 dumpTreeInfo(artwork.mus_first, 0);
3392 void LoadArtworkInfoFromLevelInfo(ArtworkDirTree **artwork_node,
3393 LevelDirTree *level_node)
3395 int type = (*artwork_node)->type;
3397 /* recursively check all level directories for artwork sub-directories */
3401 /* check all tree entries for artwork, but skip parent link entries */
3402 if (!level_node->parent_link)
3404 TreeInfo *artwork_new = getArtworkInfoCacheEntry(level_node, type);
3405 boolean cached = (artwork_new != NULL);
3409 pushTreeInfo(artwork_node, artwork_new);
3413 TreeInfo *topnode_last = *artwork_node;
3414 char *path = getPath2(getLevelDirFromTreeInfo(level_node),
3415 ARTWORK_DIRECTORY(type));
3417 LoadArtworkInfoFromArtworkDir(artwork_node, NULL, path, type);
3419 if (topnode_last != *artwork_node) /* check for newly added node */
3421 artwork_new = *artwork_node;
3423 setString(&artwork_new->identifier, level_node->subdir);
3424 setString(&artwork_new->name, level_node->name);
3425 setString(&artwork_new->name_sorting, level_node->name_sorting);
3427 artwork_new->sort_priority = level_node->sort_priority;
3428 artwork_new->color = LEVELCOLOR(artwork_new);
3434 /* insert artwork info (from old cache or filesystem) into new cache */
3435 if (artwork_new != NULL)
3436 setArtworkInfoCacheEntry(artwork_new, level_node, type);
3439 DrawInitText(level_node->name, 150, FC_YELLOW);
3441 if (level_node->node_group != NULL)
3442 LoadArtworkInfoFromLevelInfo(artwork_node, level_node->node_group);
3444 level_node = level_node->next;
3448 void LoadLevelArtworkInfo()
3450 print_timestamp_init("LoadLevelArtworkInfo");
3452 DrawInitText("Looking for custom level artwork", 120, FC_GREEN);
3454 print_timestamp_time("DrawTimeText");
3456 LoadArtworkInfoFromLevelInfo(&artwork.gfx_first, leveldir_first_all);
3457 print_timestamp_time("LoadArtworkInfoFromLevelInfo (gfx)");
3458 LoadArtworkInfoFromLevelInfo(&artwork.snd_first, leveldir_first_all);
3459 print_timestamp_time("LoadArtworkInfoFromLevelInfo (snd)");
3460 LoadArtworkInfoFromLevelInfo(&artwork.mus_first, leveldir_first_all);
3461 print_timestamp_time("LoadArtworkInfoFromLevelInfo (mus)");
3463 SaveArtworkInfoCache();
3465 print_timestamp_time("SaveArtworkInfoCache");
3467 /* needed for reloading level artwork not known at ealier stage */
3469 if (!strEqual(artwork.gfx_current_identifier, setup.graphics_set))
3471 artwork.gfx_current =
3472 getTreeInfoFromIdentifier(artwork.gfx_first, setup.graphics_set);
3473 if (artwork.gfx_current == NULL)
3474 artwork.gfx_current =
3475 getTreeInfoFromIdentifier(artwork.gfx_first, GFX_DEFAULT_SUBDIR);
3476 if (artwork.gfx_current == NULL)
3477 artwork.gfx_current = getFirstValidTreeInfoEntry(artwork.gfx_first);
3480 if (!strEqual(artwork.snd_current_identifier, setup.sounds_set))
3482 artwork.snd_current =
3483 getTreeInfoFromIdentifier(artwork.snd_first, setup.sounds_set);
3484 if (artwork.snd_current == NULL)
3485 artwork.snd_current =
3486 getTreeInfoFromIdentifier(artwork.snd_first, SND_DEFAULT_SUBDIR);
3487 if (artwork.snd_current == NULL)
3488 artwork.snd_current = getFirstValidTreeInfoEntry(artwork.snd_first);
3491 if (!strEqual(artwork.mus_current_identifier, setup.music_set))
3493 artwork.mus_current =
3494 getTreeInfoFromIdentifier(artwork.mus_first, setup.music_set);
3495 if (artwork.mus_current == NULL)
3496 artwork.mus_current =
3497 getTreeInfoFromIdentifier(artwork.mus_first, MUS_DEFAULT_SUBDIR);
3498 if (artwork.mus_current == NULL)
3499 artwork.mus_current = getFirstValidTreeInfoEntry(artwork.mus_first);
3502 print_timestamp_time("getTreeInfoFromIdentifier");
3504 sortTreeInfo(&artwork.gfx_first);
3505 sortTreeInfo(&artwork.snd_first);
3506 sortTreeInfo(&artwork.mus_first);
3508 print_timestamp_time("sortTreeInfo");
3510 #if ENABLE_UNUSED_CODE
3511 dumpTreeInfo(artwork.gfx_first, 0);
3512 dumpTreeInfo(artwork.snd_first, 0);
3513 dumpTreeInfo(artwork.mus_first, 0);
3516 print_timestamp_done("LoadLevelArtworkInfo");
3519 static boolean AddUserLevelSetToLevelInfoExt(char *level_subdir_new)
3521 // get level info tree node of first (original) user level set
3522 char *level_subdir_old = getLoginName();
3523 LevelDirTree *leveldir_old = getTreeInfoFromIdentifier(leveldir_first,
3525 if (leveldir_old == NULL) // should not happen
3528 int draw_deactivation_mask = GetDrawDeactivationMask();
3530 // override draw deactivation mask (temporarily disable drawing)
3531 SetDrawDeactivationMask(REDRAW_ALL);
3533 // load new level set config and add it next to first user level set
3534 LoadLevelInfoFromLevelConf(&leveldir_old->next, NULL,
3535 leveldir_old->basepath, level_subdir_new);
3537 // set draw deactivation mask to previous value
3538 SetDrawDeactivationMask(draw_deactivation_mask);
3540 // get level info tree node of newly added user level set
3541 LevelDirTree *leveldir_new = getTreeInfoFromIdentifier(leveldir_first,
3543 if (leveldir_new == NULL) // should not happen
3546 // correct top link and parent node link of newly created tree node
3547 leveldir_new->node_top = leveldir_old->node_top;
3548 leveldir_new->node_parent = leveldir_old->node_parent;
3550 // sort level info tree to adjust position of newly added level set
3551 sortTreeInfo(&leveldir_first);
3556 void AddUserLevelSetToLevelInfo(char *level_subdir_new)
3558 if (!AddUserLevelSetToLevelInfoExt(level_subdir_new))
3559 Error(ERR_EXIT, "internal level set structure corrupted -- aborting");
3562 boolean UpdateUserLevelSet(char *level_subdir,
3563 char *level_name, char *level_author,
3564 int num_levels, int first_level_nr)
3566 char *filename = getPath2(getUserLevelDir(level_subdir), LEVELINFO_FILENAME);
3567 char *filename_tmp = getStringCat2(filename, ".tmp");
3569 FILE *file_tmp = NULL;
3570 char line[MAX_LINE_LEN];
3571 boolean success = FALSE;
3572 LevelDirTree *leveldir = getTreeInfoFromIdentifier(leveldir_first,
3574 // update values in level directory tree
3576 if (level_name != NULL)
3577 setString(&leveldir->name, level_name);
3579 if (level_author != NULL)
3580 setString(&leveldir->author, level_author);
3582 if (num_levels != -1)
3583 leveldir->levels = num_levels;
3585 if (first_level_nr != -1)
3586 leveldir->first_level = first_level_nr;
3588 // update values that depend on other values
3590 setString(&leveldir->name_sorting, leveldir->name);
3592 leveldir->last_level = leveldir->first_level + leveldir->levels - 1;
3594 // sort order of level sets may have changed
3595 sortTreeInfo(&leveldir_first);
3597 if ((file = fopen(filename, MODE_READ)) &&
3598 (file_tmp = fopen(filename_tmp, MODE_WRITE)))
3600 while (fgets(line, MAX_LINE_LEN, file))
3602 if (strPrefix(line, "name:") && level_name != NULL)
3603 fprintf(file_tmp, "%-32s%s\n", "name:", level_name);
3604 else if (strPrefix(line, "author:") && level_author != NULL)
3605 fprintf(file_tmp, "%-32s%s\n", "author:", level_author);
3606 else if (strPrefix(line, "levels:") && num_levels != -1)
3607 fprintf(file_tmp, "%-32s%d\n", "levels:", num_levels);
3608 else if (strPrefix(line, "first_level:") && first_level_nr != -1)
3609 fprintf(file_tmp, "%-32s%d\n", "first_level:", first_level_nr);
3611 fputs(line, file_tmp);
3624 success = (rename(filename_tmp, filename) == 0);
3632 boolean CreateUserLevelSet(char *level_subdir,
3633 char *level_name, char *level_author,
3634 int num_levels, int first_level_nr)
3636 LevelDirTree *level_info;
3641 // create user level sub-directory, if needed
3642 createDirectory(getUserLevelDir(level_subdir), "user level", PERMS_PRIVATE);
3644 filename = getPath2(getUserLevelDir(level_subdir), LEVELINFO_FILENAME);
3646 if (!(file = fopen(filename, MODE_WRITE)))
3648 Error(ERR_WARN, "cannot write level info file '%s'", filename);
3654 level_info = newTreeInfo();
3656 /* always start with reliable default values */
3657 setTreeInfoToDefaults(level_info, TREE_TYPE_LEVEL_DIR);
3659 setString(&level_info->name, level_name);
3660 setString(&level_info->author, level_author);
3661 level_info->levels = num_levels;
3662 level_info->first_level = first_level_nr;
3663 level_info->sort_priority = LEVELCLASS_PRIVATE_START;
3664 level_info->readonly = FALSE;
3666 token_value_position = TOKEN_VALUE_POSITION_SHORT;
3668 fprintFileHeader(file, LEVELINFO_FILENAME);
3671 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
3673 if (i == LEVELINFO_TOKEN_NAME ||
3674 i == LEVELINFO_TOKEN_AUTHOR ||
3675 i == LEVELINFO_TOKEN_LEVELS ||
3676 i == LEVELINFO_TOKEN_FIRST_LEVEL ||
3677 i == LEVELINFO_TOKEN_SORT_PRIORITY ||
3678 i == LEVELINFO_TOKEN_READONLY)
3679 fprintf(file, "%s\n", getSetupLine(levelinfo_tokens, "", i));
3681 /* just to make things nicer :) */
3682 if (i == LEVELINFO_TOKEN_AUTHOR ||
3683 i == LEVELINFO_TOKEN_FIRST_LEVEL)
3684 fprintf(file, "\n");
3687 token_value_position = TOKEN_VALUE_POSITION_DEFAULT;
3691 SetFilePermissions(filename, PERMS_PRIVATE);
3693 freeTreeInfo(level_info);
3699 static void SaveUserLevelInfo()
3701 CreateUserLevelSet(getLoginName(), getLoginName(), getRealName(), 100, 1);
3704 char *getSetupValue(int type, void *value)
3706 static char value_string[MAX_LINE_LEN];
3714 strcpy(value_string, (*(boolean *)value ? "true" : "false"));
3718 strcpy(value_string, (*(boolean *)value ? "on" : "off"));
3722 strcpy(value_string, (*(int *)value == AUTO ? "auto" :
3723 *(int *)value == FALSE ? "off" : "on"));
3727 strcpy(value_string, (*(boolean *)value ? "yes" : "no"));
3730 case TYPE_YES_NO_AUTO:
3731 strcpy(value_string, (*(int *)value == AUTO ? "auto" :
3732 *(int *)value == FALSE ? "no" : "yes"));
3736 strcpy(value_string, (*(boolean *)value ? "AGA" : "ECS"));
3740 strcpy(value_string, getKeyNameFromKey(*(Key *)value));
3744 strcpy(value_string, getX11KeyNameFromKey(*(Key *)value));
3748 sprintf(value_string, "%d", *(int *)value);
3752 if (*(char **)value == NULL)
3755 strcpy(value_string, *(char **)value);
3759 value_string[0] = '\0';
3763 if (type & TYPE_GHOSTED)
3764 strcpy(value_string, "n/a");
3766 return value_string;
3769 char *getSetupLine(struct TokenInfo *token_info, char *prefix, int token_nr)
3773 static char token_string[MAX_LINE_LEN];
3774 int token_type = token_info[token_nr].type;
3775 void *setup_value = token_info[token_nr].value;
3776 char *token_text = token_info[token_nr].text;
3777 char *value_string = getSetupValue(token_type, setup_value);
3779 /* build complete token string */
3780 sprintf(token_string, "%s%s", prefix, token_text);
3782 /* build setup entry line */
3783 line = getFormattedSetupEntry(token_string, value_string);
3785 if (token_type == TYPE_KEY_X11)
3787 Key key = *(Key *)setup_value;
3788 char *keyname = getKeyNameFromKey(key);
3790 /* add comment, if useful */
3791 if (!strEqual(keyname, "(undefined)") &&
3792 !strEqual(keyname, "(unknown)"))
3794 /* add at least one whitespace */
3796 for (i = strlen(line); i < token_comment_position; i++)
3800 strcat(line, keyname);
3807 void LoadLevelSetup_LastSeries()
3809 /* ----------------------------------------------------------------------- */
3810 /* ~/.<program>/levelsetup.conf */
3811 /* ----------------------------------------------------------------------- */
3813 char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
3814 SetupFileHash *level_setup_hash = NULL;
3816 /* always start with reliable default values */
3817 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
3819 if (!strEqual(DEFAULT_LEVELSET, UNDEFINED_LEVELSET))
3821 leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
3823 if (leveldir_current == NULL)
3824 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
3827 if ((level_setup_hash = loadSetupFileHash(filename)))
3829 char *last_level_series =
3830 getHashEntry(level_setup_hash, TOKEN_STR_LAST_LEVEL_SERIES);
3832 leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
3834 if (leveldir_current == NULL)
3835 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
3837 freeSetupFileHash(level_setup_hash);
3841 Error(ERR_DEBUG, "using default setup values");
3847 static void SaveLevelSetup_LastSeries_Ext(boolean deactivate_last_level_series)
3849 /* ----------------------------------------------------------------------- */
3850 /* ~/.<program>/levelsetup.conf */
3851 /* ----------------------------------------------------------------------- */
3853 // check if the current level directory structure is available at this point
3854 if (leveldir_current == NULL)
3857 char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
3858 char *level_subdir = leveldir_current->subdir;
3861 InitUserDataDirectory();
3863 if (!(file = fopen(filename, MODE_WRITE)))
3865 Error(ERR_WARN, "cannot write setup file '%s'", filename);
3872 fprintFileHeader(file, LEVELSETUP_FILENAME);
3874 if (deactivate_last_level_series)
3875 fprintf(file, "# %s\n# ", "the following level set may have caused a problem and was deactivated");
3877 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_LEVEL_SERIES,
3882 SetFilePermissions(filename, PERMS_PRIVATE);
3887 void SaveLevelSetup_LastSeries()
3889 SaveLevelSetup_LastSeries_Ext(FALSE);
3892 void SaveLevelSetup_LastSeries_Deactivate()
3894 SaveLevelSetup_LastSeries_Ext(TRUE);
3897 static void checkSeriesInfo()
3899 static char *level_directory = NULL;
3902 /* check for more levels besides the 'levels' field of 'levelinfo.conf' */
3904 level_directory = getPath2((leveldir_current->in_user_dir ?
3905 getUserLevelDir(NULL) :
3906 options.level_directory),
3907 leveldir_current->fullpath);
3909 if ((dir = openDirectory(level_directory)) == NULL)
3911 Error(ERR_WARN, "cannot read level directory '%s'", level_directory);
3916 closeDirectory(dir);
3919 void LoadLevelSetup_SeriesInfo()
3922 SetupFileHash *level_setup_hash = NULL;
3923 char *level_subdir = leveldir_current->subdir;
3926 /* always start with reliable default values */
3927 level_nr = leveldir_current->first_level;
3929 for (i = 0; i < MAX_LEVELS; i++)
3931 LevelStats_setPlayed(i, 0);
3932 LevelStats_setSolved(i, 0);
3937 /* ----------------------------------------------------------------------- */
3938 /* ~/.<program>/levelsetup/<level series>/levelsetup.conf */
3939 /* ----------------------------------------------------------------------- */
3941 level_subdir = leveldir_current->subdir;
3943 filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
3945 if ((level_setup_hash = loadSetupFileHash(filename)))
3949 /* get last played level in this level set */
3951 token_value = getHashEntry(level_setup_hash, TOKEN_STR_LAST_PLAYED_LEVEL);
3955 level_nr = atoi(token_value);
3957 if (level_nr < leveldir_current->first_level)
3958 level_nr = leveldir_current->first_level;
3959 if (level_nr > leveldir_current->last_level)
3960 level_nr = leveldir_current->last_level;
3963 /* get handicap level in this level set */
3965 token_value = getHashEntry(level_setup_hash, TOKEN_STR_HANDICAP_LEVEL);
3969 int level_nr = atoi(token_value);
3971 if (level_nr < leveldir_current->first_level)
3972 level_nr = leveldir_current->first_level;
3973 if (level_nr > leveldir_current->last_level + 1)
3974 level_nr = leveldir_current->last_level;
3976 if (leveldir_current->user_defined || !leveldir_current->handicap)
3977 level_nr = leveldir_current->last_level;
3979 leveldir_current->handicap_level = level_nr;
3982 /* get number of played and solved levels in this level set */
3984 BEGIN_HASH_ITERATION(level_setup_hash, itr)
3986 char *token = HASH_ITERATION_TOKEN(itr);
3987 char *value = HASH_ITERATION_VALUE(itr);
3989 if (strlen(token) == 3 &&
3990 token[0] >= '0' && token[0] <= '9' &&
3991 token[1] >= '0' && token[1] <= '9' &&
3992 token[2] >= '0' && token[2] <= '9')
3994 int level_nr = atoi(token);
3997 LevelStats_setPlayed(level_nr, atoi(value)); /* read 1st column */
3999 value = strchr(value, ' ');
4002 LevelStats_setSolved(level_nr, atoi(value)); /* read 2nd column */
4005 END_HASH_ITERATION(hash, itr)
4007 freeSetupFileHash(level_setup_hash);
4011 Error(ERR_DEBUG, "using default setup values");
4017 void SaveLevelSetup_SeriesInfo()
4020 char *level_subdir = leveldir_current->subdir;
4021 char *level_nr_str = int2str(level_nr, 0);
4022 char *handicap_level_str = int2str(leveldir_current->handicap_level, 0);
4026 /* ----------------------------------------------------------------------- */
4027 /* ~/.<program>/levelsetup/<level series>/levelsetup.conf */
4028 /* ----------------------------------------------------------------------- */
4030 InitLevelSetupDirectory(level_subdir);
4032 filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
4034 if (!(file = fopen(filename, MODE_WRITE)))
4036 Error(ERR_WARN, "cannot write setup file '%s'", filename);
4041 fprintFileHeader(file, LEVELSETUP_FILENAME);
4043 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_PLAYED_LEVEL,
4045 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_HANDICAP_LEVEL,
4046 handicap_level_str));
4048 for (i = leveldir_current->first_level; i <= leveldir_current->last_level;
4051 if (LevelStats_getPlayed(i) > 0 ||
4052 LevelStats_getSolved(i) > 0)
4057 sprintf(token, "%03d", i);
4058 sprintf(value, "%d %d", LevelStats_getPlayed(i), LevelStats_getSolved(i));
4060 fprintf(file, "%s\n", getFormattedSetupEntry(token, value));
4066 SetFilePermissions(filename, PERMS_PRIVATE);
4071 int LevelStats_getPlayed(int nr)
4073 return (nr >= 0 && nr < MAX_LEVELS ? level_stats[nr].played : 0);
4076 int LevelStats_getSolved(int nr)
4078 return (nr >= 0 && nr < MAX_LEVELS ? level_stats[nr].solved : 0);
4081 void LevelStats_setPlayed(int nr, int value)
4083 if (nr >= 0 && nr < MAX_LEVELS)
4084 level_stats[nr].played = value;
4087 void LevelStats_setSolved(int nr, int value)
4089 if (nr >= 0 && nr < MAX_LEVELS)
4090 level_stats[nr].solved = value;
4093 void LevelStats_incPlayed(int nr)
4095 if (nr >= 0 && nr < MAX_LEVELS)
4096 level_stats[nr].played++;
4099 void LevelStats_incSolved(int nr)
4101 if (nr >= 0 && nr < MAX_LEVELS)
4102 level_stats[nr].solved++;