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 */
34 #define DEBUG_NO_CONFIG_FILE FALSE /* for extra-verbose debug output */
36 #define NUM_LEVELCLASS_DESC 8
38 static char *levelclass_desc[NUM_LEVELCLASS_DESC] =
51 #define LEVELCOLOR(n) (IS_LEVELCLASS_TUTORIAL(n) ? FC_BLUE : \
52 IS_LEVELCLASS_CLASSICS(n) ? FC_RED : \
53 IS_LEVELCLASS_BD(n) ? FC_YELLOW : \
54 IS_LEVELCLASS_EM(n) ? FC_YELLOW : \
55 IS_LEVELCLASS_SP(n) ? FC_YELLOW : \
56 IS_LEVELCLASS_DX(n) ? FC_YELLOW : \
57 IS_LEVELCLASS_SB(n) ? FC_YELLOW : \
58 IS_LEVELCLASS_CONTRIB(n) ? FC_GREEN : \
59 IS_LEVELCLASS_PRIVATE(n) ? FC_RED : \
62 #define LEVELSORTING(n) (IS_LEVELCLASS_TUTORIAL(n) ? 0 : \
63 IS_LEVELCLASS_CLASSICS(n) ? 1 : \
64 IS_LEVELCLASS_BD(n) ? 2 : \
65 IS_LEVELCLASS_EM(n) ? 3 : \
66 IS_LEVELCLASS_SP(n) ? 4 : \
67 IS_LEVELCLASS_DX(n) ? 5 : \
68 IS_LEVELCLASS_SB(n) ? 6 : \
69 IS_LEVELCLASS_CONTRIB(n) ? 7 : \
70 IS_LEVELCLASS_PRIVATE(n) ? 8 : \
73 #define ARTWORKCOLOR(n) (IS_ARTWORKCLASS_CLASSICS(n) ? FC_RED : \
74 IS_ARTWORKCLASS_CONTRIB(n) ? FC_GREEN : \
75 IS_ARTWORKCLASS_PRIVATE(n) ? FC_RED : \
76 IS_ARTWORKCLASS_LEVEL(n) ? FC_YELLOW : \
79 #define ARTWORKSORTING(n) (IS_ARTWORKCLASS_CLASSICS(n) ? 0 : \
80 IS_ARTWORKCLASS_LEVEL(n) ? 1 : \
81 IS_ARTWORKCLASS_CONTRIB(n) ? 2 : \
82 IS_ARTWORKCLASS_PRIVATE(n) ? 3 : \
85 #define TOKEN_VALUE_POSITION_SHORT 32
86 #define TOKEN_VALUE_POSITION_DEFAULT 40
87 #define TOKEN_COMMENT_POSITION_DEFAULT 60
89 #define MAX_COOKIE_LEN 256
92 static void setTreeInfoToDefaults(TreeInfo *, int);
93 static TreeInfo *getTreeInfoCopy(TreeInfo *ti);
94 static int compareTreeInfoEntries(const void *, const void *);
96 static int token_value_position = TOKEN_VALUE_POSITION_DEFAULT;
97 static int token_comment_position = TOKEN_COMMENT_POSITION_DEFAULT;
99 static SetupFileHash *artworkinfo_cache_old = NULL;
100 static SetupFileHash *artworkinfo_cache_new = NULL;
101 static boolean use_artworkinfo_cache = TRUE;
104 /* ------------------------------------------------------------------------- */
106 /* ------------------------------------------------------------------------- */
108 static char *getLevelClassDescription(TreeInfo *ti)
110 int position = ti->sort_priority / 100;
112 if (position >= 0 && position < NUM_LEVELCLASS_DESC)
113 return levelclass_desc[position];
115 return "Unknown Level Class";
118 static char *getScoreDir(char *level_subdir)
120 static char *score_dir = NULL;
121 static char *score_level_dir = NULL;
122 char *score_subdir = SCORES_DIRECTORY;
124 if (score_dir == NULL)
126 if (program.global_scores)
127 score_dir = getPath2(getCommonDataDir(), score_subdir);
129 score_dir = getPath2(getUserGameDataDir(), score_subdir);
132 if (level_subdir != NULL)
134 checked_free(score_level_dir);
136 score_level_dir = getPath2(score_dir, level_subdir);
138 return score_level_dir;
144 static char *getLevelSetupDir(char *level_subdir)
146 static char *levelsetup_dir = NULL;
147 char *data_dir = getUserGameDataDir();
148 char *levelsetup_subdir = LEVELSETUP_DIRECTORY;
150 checked_free(levelsetup_dir);
152 if (level_subdir != NULL)
153 levelsetup_dir = getPath3(data_dir, levelsetup_subdir, level_subdir);
155 levelsetup_dir = getPath2(data_dir, levelsetup_subdir);
157 return levelsetup_dir;
160 static char *getCacheDir()
162 static char *cache_dir = NULL;
164 if (cache_dir == NULL)
165 cache_dir = getPath2(getUserGameDataDir(), CACHE_DIRECTORY);
170 static char *getLevelDirFromTreeInfo(TreeInfo *node)
172 static char *level_dir = NULL;
175 return options.level_directory;
177 checked_free(level_dir);
179 level_dir = getPath2((node->in_user_dir ? getUserLevelDir(NULL) :
180 options.level_directory), node->fullpath);
185 char *getUserLevelDir(char *level_subdir)
187 static char *userlevel_dir = NULL;
188 char *data_dir = getUserGameDataDir();
189 char *userlevel_subdir = LEVELS_DIRECTORY;
191 checked_free(userlevel_dir);
193 if (level_subdir != NULL)
194 userlevel_dir = getPath3(data_dir, userlevel_subdir, level_subdir);
196 userlevel_dir = getPath2(data_dir, userlevel_subdir);
198 return userlevel_dir;
201 char *getCurrentLevelDir()
203 return getLevelDirFromTreeInfo(leveldir_current);
206 char *getNewUserLevelSubdir()
208 static char *new_level_subdir = NULL;
209 char *subdir_prefix = getLoginName();
210 char subdir_suffix[10];
211 int max_suffix_number = 1000;
214 while (++i < max_suffix_number)
216 sprintf(subdir_suffix, "_%d", i);
218 checked_free(new_level_subdir);
219 new_level_subdir = getStringCat2(subdir_prefix, subdir_suffix);
221 if (!directoryExists(getUserLevelDir(new_level_subdir)))
225 return new_level_subdir;
228 static char *getTapeDir(char *level_subdir)
230 static char *tape_dir = NULL;
231 char *data_dir = getUserGameDataDir();
232 char *tape_subdir = TAPES_DIRECTORY;
234 checked_free(tape_dir);
236 if (level_subdir != NULL)
237 tape_dir = getPath3(data_dir, tape_subdir, level_subdir);
239 tape_dir = getPath2(data_dir, tape_subdir);
244 static char *getSolutionTapeDir()
246 static char *tape_dir = NULL;
247 char *data_dir = getCurrentLevelDir();
248 char *tape_subdir = TAPES_DIRECTORY;
250 checked_free(tape_dir);
252 tape_dir = getPath2(data_dir, tape_subdir);
257 static char *getDefaultGraphicsDir(char *graphics_subdir)
259 static char *graphics_dir = NULL;
261 if (graphics_subdir == NULL)
262 return options.graphics_directory;
264 checked_free(graphics_dir);
266 graphics_dir = getPath2(options.graphics_directory, graphics_subdir);
271 static char *getDefaultSoundsDir(char *sounds_subdir)
273 static char *sounds_dir = NULL;
275 if (sounds_subdir == NULL)
276 return options.sounds_directory;
278 checked_free(sounds_dir);
280 sounds_dir = getPath2(options.sounds_directory, sounds_subdir);
285 static char *getDefaultMusicDir(char *music_subdir)
287 static char *music_dir = NULL;
289 if (music_subdir == NULL)
290 return options.music_directory;
292 checked_free(music_dir);
294 music_dir = getPath2(options.music_directory, music_subdir);
299 static char *getClassicArtworkSet(int type)
301 return (type == TREE_TYPE_GRAPHICS_DIR ? GFX_CLASSIC_SUBDIR :
302 type == TREE_TYPE_SOUNDS_DIR ? SND_CLASSIC_SUBDIR :
303 type == TREE_TYPE_MUSIC_DIR ? MUS_CLASSIC_SUBDIR : "");
306 static char *getClassicArtworkDir(int type)
308 return (type == TREE_TYPE_GRAPHICS_DIR ?
309 getDefaultGraphicsDir(GFX_CLASSIC_SUBDIR) :
310 type == TREE_TYPE_SOUNDS_DIR ?
311 getDefaultSoundsDir(SND_CLASSIC_SUBDIR) :
312 type == TREE_TYPE_MUSIC_DIR ?
313 getDefaultMusicDir(MUS_CLASSIC_SUBDIR) : "");
316 static char *getUserGraphicsDir()
318 static char *usergraphics_dir = NULL;
320 if (usergraphics_dir == NULL)
321 usergraphics_dir = getPath2(getUserGameDataDir(), GRAPHICS_DIRECTORY);
323 return usergraphics_dir;
326 static char *getUserSoundsDir()
328 static char *usersounds_dir = NULL;
330 if (usersounds_dir == NULL)
331 usersounds_dir = getPath2(getUserGameDataDir(), SOUNDS_DIRECTORY);
333 return usersounds_dir;
336 static char *getUserMusicDir()
338 static char *usermusic_dir = NULL;
340 if (usermusic_dir == NULL)
341 usermusic_dir = getPath2(getUserGameDataDir(), MUSIC_DIRECTORY);
343 return usermusic_dir;
346 static char *getSetupArtworkDir(TreeInfo *ti)
348 static char *artwork_dir = NULL;
353 checked_free(artwork_dir);
355 artwork_dir = getPath2(ti->basepath, ti->fullpath);
360 char *setLevelArtworkDir(TreeInfo *ti)
362 char **artwork_path_ptr, **artwork_set_ptr;
363 TreeInfo *level_artwork;
365 if (ti == NULL || leveldir_current == NULL)
368 artwork_path_ptr = LEVELDIR_ARTWORK_PATH_PTR(leveldir_current, ti->type);
369 artwork_set_ptr = LEVELDIR_ARTWORK_SET_PTR( leveldir_current, ti->type);
371 checked_free(*artwork_path_ptr);
373 if ((level_artwork = getTreeInfoFromIdentifier(ti, *artwork_set_ptr)))
375 *artwork_path_ptr = getStringCopy(getSetupArtworkDir(level_artwork));
380 No (or non-existing) artwork configured in "levelinfo.conf". This would
381 normally result in using the artwork configured in the setup menu. But
382 if an artwork subdirectory exists (which might contain custom artwork
383 or an artwork configuration file), this level artwork must be treated
384 as relative to the default "classic" artwork, not to the artwork that
385 is currently configured in the setup menu.
387 Update: For "special" versions of R'n'D (like "R'n'D jue"), do not use
388 the "default" artwork (which would be "jue0" for "R'n'D jue"), but use
389 the real "classic" artwork from the original R'n'D (like "gfx_classic").
392 char *dir = getPath2(getCurrentLevelDir(), ARTWORK_DIRECTORY(ti->type));
394 checked_free(*artwork_set_ptr);
396 if (directoryExists(dir))
398 *artwork_path_ptr = getStringCopy(getClassicArtworkDir(ti->type));
399 *artwork_set_ptr = getStringCopy(getClassicArtworkSet(ti->type));
403 *artwork_path_ptr = getStringCopy(UNDEFINED_FILENAME);
404 *artwork_set_ptr = NULL;
410 return *artwork_set_ptr;
413 inline static char *getLevelArtworkSet(int type)
415 if (leveldir_current == NULL)
418 return LEVELDIR_ARTWORK_SET(leveldir_current, type);
421 inline static char *getLevelArtworkDir(int type)
423 if (leveldir_current == NULL)
424 return UNDEFINED_FILENAME;
426 return LEVELDIR_ARTWORK_PATH(leveldir_current, type);
429 char *getProgramMainDataPath(char *command_filename, char *base_path)
431 /* check if the program's main data base directory is configured */
432 if (!strEqual(base_path, "."))
435 /* if the program is configured to start from current directory (default),
436 determine program package directory from program binary (some versions
437 of KDE/Konqueror and Mac OS X (especially "Mavericks") apparently do not
438 set the current working directory to the program package directory) */
439 char *main_data_path = getBasePath(command_filename);
441 #if defined(PLATFORM_MACOSX)
442 if (strSuffix(main_data_path, MAC_APP_BINARY_SUBDIR))
444 char *main_data_path_old = main_data_path;
446 // cut relative path to Mac OS X application binary directory from path
447 main_data_path[strlen(main_data_path) -
448 strlen(MAC_APP_BINARY_SUBDIR)] = '\0';
450 // cut trailing path separator from path (but not if path is root directory)
451 if (strSuffix(main_data_path, "/") && !strEqual(main_data_path, "/"))
452 main_data_path[strlen(main_data_path) - 1] = '\0';
454 // replace empty path with current directory
455 if (strEqual(main_data_path, ""))
456 main_data_path = ".";
458 // add relative path to Mac OS X application resources directory to path
459 main_data_path = getPath2(main_data_path, MAC_APP_FILES_SUBDIR);
461 free(main_data_path_old);
465 return main_data_path;
468 char *getProgramConfigFilename(char *command_filename)
470 char *command_filename_1 = getStringCopy(command_filename);
472 // strip trailing executable suffix from command filename
473 if (strSuffix(command_filename_1, ".exe"))
474 command_filename_1[strlen(command_filename_1) - 4] = '\0';
476 char *ro_base_path = getProgramMainDataPath(command_filename, RO_BASE_PATH);
477 char *conf_directory = getPath2(ro_base_path, CONF_DIRECTORY);
479 char *command_basepath = getBasePath(command_filename);
480 char *command_basename = getBaseNameNoSuffix(command_filename);
481 char *command_filename_2 = getPath2(command_basepath, command_basename);
483 char *config_filename_1 = getStringCat2(command_filename_1, ".conf");
484 char *config_filename_2 = getStringCat2(command_filename_2, ".conf");
485 char *config_filename_3 = getPath2(conf_directory, SETUP_FILENAME);
487 // 1st try: look for config file that exactly matches the binary filename
488 if (fileExists(config_filename_1))
489 return config_filename_1;
491 // 2nd try: look for config file that matches binary filename without suffix
492 if (fileExists(config_filename_2))
493 return config_filename_2;
495 // 3rd try: return setup config filename in global program config directory
496 return config_filename_3;
499 char *getTapeFilename(int nr)
501 static char *filename = NULL;
502 char basename[MAX_FILENAME_LEN];
504 checked_free(filename);
506 sprintf(basename, "%03d.%s", nr, TAPEFILE_EXTENSION);
507 filename = getPath2(getTapeDir(leveldir_current->subdir), basename);
512 char *getSolutionTapeFilename(int nr)
514 static char *filename = NULL;
515 char basename[MAX_FILENAME_LEN];
517 checked_free(filename);
519 sprintf(basename, "%03d.%s", nr, TAPEFILE_EXTENSION);
520 filename = getPath2(getSolutionTapeDir(), basename);
522 if (!fileExists(filename))
524 static char *filename_sln = NULL;
526 checked_free(filename_sln);
528 sprintf(basename, "%03d.sln", nr);
529 filename_sln = getPath2(getSolutionTapeDir(), basename);
531 if (fileExists(filename_sln))
538 char *getScoreFilename(int nr)
540 static char *filename = NULL;
541 char basename[MAX_FILENAME_LEN];
543 checked_free(filename);
545 sprintf(basename, "%03d.%s", nr, SCOREFILE_EXTENSION);
546 filename = getPath2(getScoreDir(leveldir_current->subdir), basename);
551 char *getSetupFilename()
553 static char *filename = NULL;
555 checked_free(filename);
557 filename = getPath2(getSetupDir(), SETUP_FILENAME);
562 char *getDefaultSetupFilename()
564 return program.config_filename;
567 char *getEditorSetupFilename()
569 static char *filename = NULL;
571 checked_free(filename);
572 filename = getPath2(getCurrentLevelDir(), EDITORSETUP_FILENAME);
574 if (fileExists(filename))
577 checked_free(filename);
578 filename = getPath2(getSetupDir(), EDITORSETUP_FILENAME);
583 char *getHelpAnimFilename()
585 static char *filename = NULL;
587 checked_free(filename);
589 filename = getPath2(getCurrentLevelDir(), HELPANIM_FILENAME);
594 char *getHelpTextFilename()
596 static char *filename = NULL;
598 checked_free(filename);
600 filename = getPath2(getCurrentLevelDir(), HELPTEXT_FILENAME);
605 char *getLevelSetInfoFilename()
607 static char *filename = NULL;
622 for (i = 0; basenames[i] != NULL; i++)
624 checked_free(filename);
625 filename = getPath2(getCurrentLevelDir(), basenames[i]);
627 if (fileExists(filename))
634 char *getLevelSetTitleMessageBasename(int nr, boolean initial)
636 static char basename[32];
638 sprintf(basename, "%s_%d.txt",
639 (initial ? "titlemessage_initial" : "titlemessage"), nr + 1);
644 char *getLevelSetTitleMessageFilename(int nr, boolean initial)
646 static char *filename = NULL;
648 boolean skip_setup_artwork = FALSE;
650 checked_free(filename);
652 basename = getLevelSetTitleMessageBasename(nr, initial);
654 if (!gfx.override_level_graphics)
656 /* 1st try: look for special artwork in current level series directory */
657 filename = getPath3(getCurrentLevelDir(), GRAPHICS_DIRECTORY, basename);
658 if (fileExists(filename))
663 /* 2nd try: look for message file in current level set directory */
664 filename = getPath2(getCurrentLevelDir(), basename);
665 if (fileExists(filename))
670 /* check if there is special artwork configured in level series config */
671 if (getLevelArtworkSet(ARTWORK_TYPE_GRAPHICS) != NULL)
673 /* 3rd try: look for special artwork configured in level series config */
674 filename = getPath2(getLevelArtworkDir(ARTWORK_TYPE_GRAPHICS), basename);
675 if (fileExists(filename))
680 /* take missing artwork configured in level set config from default */
681 skip_setup_artwork = TRUE;
685 if (!skip_setup_artwork)
687 /* 4th try: look for special artwork in configured artwork directory */
688 filename = getPath2(getSetupArtworkDir(artwork.gfx_current), basename);
689 if (fileExists(filename))
695 /* 5th try: look for default artwork in new default artwork directory */
696 filename = getPath2(getDefaultGraphicsDir(GFX_DEFAULT_SUBDIR), basename);
697 if (fileExists(filename))
702 /* 6th try: look for default artwork in old default artwork directory */
703 filename = getPath2(options.graphics_directory, basename);
704 if (fileExists(filename))
707 return NULL; /* cannot find specified artwork file anywhere */
710 static char *getCorrectedArtworkBasename(char *basename)
715 char *getCustomImageFilename(char *basename)
717 static char *filename = NULL;
718 boolean skip_setup_artwork = FALSE;
720 checked_free(filename);
722 basename = getCorrectedArtworkBasename(basename);
724 if (!gfx.override_level_graphics)
726 /* 1st try: look for special artwork in current level series directory */
727 filename = getImg3(getCurrentLevelDir(), GRAPHICS_DIRECTORY, basename);
728 if (fileExists(filename))
733 /* check if there is special artwork configured in level series config */
734 if (getLevelArtworkSet(ARTWORK_TYPE_GRAPHICS) != NULL)
736 /* 2nd try: look for special artwork configured in level series config */
737 filename = getImg2(getLevelArtworkDir(ARTWORK_TYPE_GRAPHICS), basename);
738 if (fileExists(filename))
743 /* take missing artwork configured in level set config from default */
744 skip_setup_artwork = TRUE;
748 if (!skip_setup_artwork)
750 /* 3rd try: look for special artwork in configured artwork directory */
751 filename = getImg2(getSetupArtworkDir(artwork.gfx_current), basename);
752 if (fileExists(filename))
758 /* 4th try: look for default artwork in new default artwork directory */
759 filename = getImg2(getDefaultGraphicsDir(GFX_DEFAULT_SUBDIR), basename);
760 if (fileExists(filename))
765 /* 5th try: look for default artwork in old default artwork directory */
766 filename = getImg2(options.graphics_directory, basename);
767 if (fileExists(filename))
770 if (!strEqual(GFX_FALLBACK_FILENAME, UNDEFINED_FILENAME))
775 Error(ERR_WARN, "cannot find artwork file '%s' (using fallback)",
778 /* 6th try: look for fallback artwork in old default artwork directory */
779 /* (needed to prevent errors when trying to access unused artwork files) */
780 filename = getImg2(options.graphics_directory, GFX_FALLBACK_FILENAME);
781 if (fileExists(filename))
785 return NULL; /* cannot find specified artwork file anywhere */
788 char *getCustomSoundFilename(char *basename)
790 static char *filename = NULL;
791 boolean skip_setup_artwork = FALSE;
793 checked_free(filename);
795 basename = getCorrectedArtworkBasename(basename);
797 if (!gfx.override_level_sounds)
799 /* 1st try: look for special artwork in current level series directory */
800 filename = getPath3(getCurrentLevelDir(), SOUNDS_DIRECTORY, basename);
801 if (fileExists(filename))
806 /* check if there is special artwork configured in level series config */
807 if (getLevelArtworkSet(ARTWORK_TYPE_SOUNDS) != NULL)
809 /* 2nd try: look for special artwork configured in level series config */
810 filename = getPath2(getLevelArtworkDir(TREE_TYPE_SOUNDS_DIR), basename);
811 if (fileExists(filename))
816 /* take missing artwork configured in level set config from default */
817 skip_setup_artwork = TRUE;
821 if (!skip_setup_artwork)
823 /* 3rd try: look for special artwork in configured artwork directory */
824 filename = getPath2(getSetupArtworkDir(artwork.snd_current), basename);
825 if (fileExists(filename))
831 /* 4th try: look for default artwork in new default artwork directory */
832 filename = getPath2(getDefaultSoundsDir(SND_DEFAULT_SUBDIR), basename);
833 if (fileExists(filename))
838 /* 5th try: look for default artwork in old default artwork directory */
839 filename = getPath2(options.sounds_directory, basename);
840 if (fileExists(filename))
843 if (!strEqual(SND_FALLBACK_FILENAME, UNDEFINED_FILENAME))
848 Error(ERR_WARN, "cannot find artwork file '%s' (using fallback)",
851 /* 6th try: look for fallback artwork in old default artwork directory */
852 /* (needed to prevent errors when trying to access unused artwork files) */
853 filename = getPath2(options.sounds_directory, SND_FALLBACK_FILENAME);
854 if (fileExists(filename))
858 return NULL; /* cannot find specified artwork file anywhere */
861 char *getCustomMusicFilename(char *basename)
863 static char *filename = NULL;
864 boolean skip_setup_artwork = FALSE;
866 checked_free(filename);
868 basename = getCorrectedArtworkBasename(basename);
870 if (!gfx.override_level_music)
872 /* 1st try: look for special artwork in current level series directory */
873 filename = getPath3(getCurrentLevelDir(), MUSIC_DIRECTORY, basename);
874 if (fileExists(filename))
879 /* check if there is special artwork configured in level series config */
880 if (getLevelArtworkSet(ARTWORK_TYPE_MUSIC) != NULL)
882 /* 2nd try: look for special artwork configured in level series config */
883 filename = getPath2(getLevelArtworkDir(TREE_TYPE_MUSIC_DIR), basename);
884 if (fileExists(filename))
889 /* take missing artwork configured in level set config from default */
890 skip_setup_artwork = TRUE;
894 if (!skip_setup_artwork)
896 /* 3rd try: look for special artwork in configured artwork directory */
897 filename = getPath2(getSetupArtworkDir(artwork.mus_current), basename);
898 if (fileExists(filename))
904 /* 4th try: look for default artwork in new default artwork directory */
905 filename = getPath2(getDefaultMusicDir(MUS_DEFAULT_SUBDIR), basename);
906 if (fileExists(filename))
911 /* 5th try: look for default artwork in old default artwork directory */
912 filename = getPath2(options.music_directory, basename);
913 if (fileExists(filename))
916 if (!strEqual(MUS_FALLBACK_FILENAME, UNDEFINED_FILENAME))
921 Error(ERR_WARN, "cannot find artwork file '%s' (using fallback)",
924 /* 6th try: look for fallback artwork in old default artwork directory */
925 /* (needed to prevent errors when trying to access unused artwork files) */
926 filename = getPath2(options.music_directory, MUS_FALLBACK_FILENAME);
927 if (fileExists(filename))
931 return NULL; /* cannot find specified artwork file anywhere */
934 char *getCustomArtworkFilename(char *basename, int type)
936 if (type == ARTWORK_TYPE_GRAPHICS)
937 return getCustomImageFilename(basename);
938 else if (type == ARTWORK_TYPE_SOUNDS)
939 return getCustomSoundFilename(basename);
940 else if (type == ARTWORK_TYPE_MUSIC)
941 return getCustomMusicFilename(basename);
943 return UNDEFINED_FILENAME;
946 char *getCustomArtworkConfigFilename(int type)
948 return getCustomArtworkFilename(ARTWORKINFO_FILENAME(type), type);
951 char *getCustomArtworkLevelConfigFilename(int type)
953 static char *filename = NULL;
955 checked_free(filename);
957 filename = getPath2(getLevelArtworkDir(type), ARTWORKINFO_FILENAME(type));
962 char *getCustomMusicDirectory(void)
964 static char *directory = NULL;
965 boolean skip_setup_artwork = FALSE;
967 checked_free(directory);
969 if (!gfx.override_level_music)
971 /* 1st try: look for special artwork in current level series directory */
972 directory = getPath2(getCurrentLevelDir(), MUSIC_DIRECTORY);
973 if (directoryExists(directory))
978 /* check if there is special artwork configured in level series config */
979 if (getLevelArtworkSet(ARTWORK_TYPE_MUSIC) != NULL)
981 /* 2nd try: look for special artwork configured in level series config */
982 directory = getStringCopy(getLevelArtworkDir(TREE_TYPE_MUSIC_DIR));
983 if (directoryExists(directory))
988 /* take missing artwork configured in level set config from default */
989 skip_setup_artwork = TRUE;
993 if (!skip_setup_artwork)
995 /* 3rd try: look for special artwork in configured artwork directory */
996 directory = getStringCopy(getSetupArtworkDir(artwork.mus_current));
997 if (directoryExists(directory))
1003 /* 4th try: look for default artwork in new default artwork directory */
1004 directory = getStringCopy(getDefaultMusicDir(MUS_DEFAULT_SUBDIR));
1005 if (directoryExists(directory))
1010 /* 5th try: look for default artwork in old default artwork directory */
1011 directory = getStringCopy(options.music_directory);
1012 if (directoryExists(directory))
1015 return NULL; /* cannot find specified artwork file anywhere */
1018 void InitTapeDirectory(char *level_subdir)
1020 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
1021 createDirectory(getTapeDir(NULL), "main tape", PERMS_PRIVATE);
1022 createDirectory(getTapeDir(level_subdir), "level tape", PERMS_PRIVATE);
1025 void InitScoreDirectory(char *level_subdir)
1027 int permissions = (program.global_scores ? PERMS_PUBLIC : PERMS_PRIVATE);
1029 if (program.global_scores)
1030 createDirectory(getCommonDataDir(), "common data", permissions);
1032 createDirectory(getUserGameDataDir(), "user data", permissions);
1034 createDirectory(getScoreDir(NULL), "main score", permissions);
1035 createDirectory(getScoreDir(level_subdir), "level score", permissions);
1038 static void SaveUserLevelInfo();
1040 void InitUserLevelDirectory(char *level_subdir)
1042 if (!directoryExists(getUserLevelDir(level_subdir)))
1044 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
1045 createDirectory(getUserLevelDir(NULL), "main user level", PERMS_PRIVATE);
1046 createDirectory(getUserLevelDir(level_subdir), "user level", PERMS_PRIVATE);
1048 SaveUserLevelInfo();
1052 void InitLevelSetupDirectory(char *level_subdir)
1054 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
1055 createDirectory(getLevelSetupDir(NULL), "main level setup", PERMS_PRIVATE);
1056 createDirectory(getLevelSetupDir(level_subdir), "level setup", PERMS_PRIVATE);
1059 void InitCacheDirectory()
1061 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
1062 createDirectory(getCacheDir(), "cache data", PERMS_PRIVATE);
1066 /* ------------------------------------------------------------------------- */
1067 /* some functions to handle lists of level and artwork directories */
1068 /* ------------------------------------------------------------------------- */
1070 TreeInfo *newTreeInfo()
1072 return checked_calloc(sizeof(TreeInfo));
1075 TreeInfo *newTreeInfo_setDefaults(int type)
1077 TreeInfo *ti = newTreeInfo();
1079 setTreeInfoToDefaults(ti, type);
1084 void pushTreeInfo(TreeInfo **node_first, TreeInfo *node_new)
1086 node_new->next = *node_first;
1087 *node_first = node_new;
1090 int numTreeInfo(TreeInfo *node)
1103 boolean validLevelSeries(TreeInfo *node)
1105 return (node != NULL && !node->node_group && !node->parent_link);
1108 TreeInfo *getFirstValidTreeInfoEntry(TreeInfo *node)
1113 if (node->node_group) /* enter level group (step down into tree) */
1114 return getFirstValidTreeInfoEntry(node->node_group);
1115 else if (node->parent_link) /* skip start entry of level group */
1117 if (node->next) /* get first real level series entry */
1118 return getFirstValidTreeInfoEntry(node->next);
1119 else /* leave empty level group and go on */
1120 return getFirstValidTreeInfoEntry(node->node_parent->next);
1122 else /* this seems to be a regular level series */
1126 TreeInfo *getTreeInfoFirstGroupEntry(TreeInfo *node)
1131 if (node->node_parent == NULL) /* top level group */
1132 return *node->node_top;
1133 else /* sub level group */
1134 return node->node_parent->node_group;
1137 int numTreeInfoInGroup(TreeInfo *node)
1139 return numTreeInfo(getTreeInfoFirstGroupEntry(node));
1142 int posTreeInfo(TreeInfo *node)
1144 TreeInfo *node_cmp = getTreeInfoFirstGroupEntry(node);
1149 if (node_cmp == node)
1153 node_cmp = node_cmp->next;
1159 TreeInfo *getTreeInfoFromPos(TreeInfo *node, int pos)
1161 TreeInfo *node_default = node;
1173 return node_default;
1176 TreeInfo *getTreeInfoFromIdentifier(TreeInfo *node, char *identifier)
1178 if (identifier == NULL)
1183 if (node->node_group)
1185 TreeInfo *node_group;
1187 node_group = getTreeInfoFromIdentifier(node->node_group, identifier);
1192 else if (!node->parent_link)
1194 if (strEqual(identifier, node->identifier))
1204 TreeInfo *cloneTreeNode(TreeInfo **node_top, TreeInfo *node_parent,
1205 TreeInfo *node, boolean skip_sets_without_levels)
1212 if (!node->parent_link && !node->level_group &&
1213 skip_sets_without_levels && node->levels == 0)
1214 return cloneTreeNode(node_top, node_parent, node->next,
1215 skip_sets_without_levels);
1217 node_new = getTreeInfoCopy(node); /* copy complete node */
1219 node_new->node_top = node_top; /* correct top node link */
1220 node_new->node_parent = node_parent; /* correct parent node link */
1222 if (node->level_group)
1223 node_new->node_group = cloneTreeNode(node_top, node_new, node->node_group,
1224 skip_sets_without_levels);
1226 node_new->next = cloneTreeNode(node_top, node_parent, node->next,
1227 skip_sets_without_levels);
1232 void cloneTree(TreeInfo **ti_new, TreeInfo *ti, boolean skip_empty_sets)
1234 TreeInfo *ti_cloned = cloneTreeNode(ti_new, NULL, ti, skip_empty_sets);
1236 *ti_new = ti_cloned;
1239 static boolean adjustTreeGraphicsForEMC(TreeInfo *node)
1241 boolean settings_changed = FALSE;
1245 if (node->graphics_set_ecs && !setup.prefer_aga_graphics &&
1246 !strEqual(node->graphics_set, node->graphics_set_ecs))
1248 setString(&node->graphics_set, node->graphics_set_ecs);
1249 settings_changed = TRUE;
1251 else if (node->graphics_set_aga && setup.prefer_aga_graphics &&
1252 !strEqual(node->graphics_set, node->graphics_set_aga))
1254 setString(&node->graphics_set, node->graphics_set_aga);
1255 settings_changed = TRUE;
1258 if (node->node_group != NULL)
1259 settings_changed |= adjustTreeGraphicsForEMC(node->node_group);
1264 return settings_changed;
1267 void dumpTreeInfo(TreeInfo *node, int depth)
1271 printf("Dumping TreeInfo:\n");
1275 for (i = 0; i < (depth + 1) * 3; i++)
1278 printf("'%s' / '%s'\n", node->identifier, node->name);
1281 // use for dumping artwork info tree
1282 printf("subdir == '%s' ['%s', '%s'] [%d])\n",
1283 node->subdir, node->fullpath, node->basepath, node->in_user_dir);
1286 if (node->node_group != NULL)
1287 dumpTreeInfo(node->node_group, depth + 1);
1293 void sortTreeInfoBySortFunction(TreeInfo **node_first,
1294 int (*compare_function)(const void *,
1297 int num_nodes = numTreeInfo(*node_first);
1298 TreeInfo **sort_array;
1299 TreeInfo *node = *node_first;
1305 /* allocate array for sorting structure pointers */
1306 sort_array = checked_calloc(num_nodes * sizeof(TreeInfo *));
1308 /* writing structure pointers to sorting array */
1309 while (i < num_nodes && node) /* double boundary check... */
1311 sort_array[i] = node;
1317 /* sorting the structure pointers in the sorting array */
1318 qsort(sort_array, num_nodes, sizeof(TreeInfo *),
1321 /* update the linkage of list elements with the sorted node array */
1322 for (i = 0; i < num_nodes - 1; i++)
1323 sort_array[i]->next = sort_array[i + 1];
1324 sort_array[num_nodes - 1]->next = NULL;
1326 /* update the linkage of the main list anchor pointer */
1327 *node_first = sort_array[0];
1331 /* now recursively sort the level group structures */
1335 if (node->node_group != NULL)
1336 sortTreeInfoBySortFunction(&node->node_group, compare_function);
1342 void sortTreeInfo(TreeInfo **node_first)
1344 sortTreeInfoBySortFunction(node_first, compareTreeInfoEntries);
1348 /* ========================================================================= */
1349 /* some stuff from "files.c" */
1350 /* ========================================================================= */
1352 #if defined(PLATFORM_WIN32)
1354 #define S_IRGRP S_IRUSR
1357 #define S_IROTH S_IRUSR
1360 #define S_IWGRP S_IWUSR
1363 #define S_IWOTH S_IWUSR
1366 #define S_IXGRP S_IXUSR
1369 #define S_IXOTH S_IXUSR
1372 #define S_IRWXG (S_IRGRP | S_IWGRP | S_IXGRP)
1377 #endif /* PLATFORM_WIN32 */
1379 /* file permissions for newly written files */
1380 #define MODE_R_ALL (S_IRUSR | S_IRGRP | S_IROTH)
1381 #define MODE_W_ALL (S_IWUSR | S_IWGRP | S_IWOTH)
1382 #define MODE_X_ALL (S_IXUSR | S_IXGRP | S_IXOTH)
1384 #define MODE_W_PRIVATE (S_IWUSR)
1385 #define MODE_W_PUBLIC_FILE (S_IWUSR | S_IWGRP)
1386 #define MODE_W_PUBLIC_DIR (S_IWUSR | S_IWGRP | S_ISGID)
1388 #define DIR_PERMS_PRIVATE (MODE_R_ALL | MODE_X_ALL | MODE_W_PRIVATE)
1389 #define DIR_PERMS_PUBLIC (MODE_R_ALL | MODE_X_ALL | MODE_W_PUBLIC_DIR)
1390 #define DIR_PERMS_PUBLIC_ALL (MODE_R_ALL | MODE_X_ALL | MODE_W_ALL)
1392 #define FILE_PERMS_PRIVATE (MODE_R_ALL | MODE_W_PRIVATE)
1393 #define FILE_PERMS_PUBLIC (MODE_R_ALL | MODE_W_PUBLIC_FILE)
1394 #define FILE_PERMS_PUBLIC_ALL (MODE_R_ALL | MODE_W_ALL)
1399 static char *dir = NULL;
1401 #if defined(PLATFORM_WIN32)
1404 dir = checked_malloc(MAX_PATH + 1);
1406 if (!SUCCEEDED(SHGetFolderPath(NULL, CSIDL_PERSONAL, NULL, 0, dir)))
1409 #elif defined(PLATFORM_UNIX)
1412 if ((dir = getenv("HOME")) == NULL)
1416 if ((pwd = getpwuid(getuid())) != NULL)
1417 dir = getStringCopy(pwd->pw_dir);
1429 char *getCommonDataDir(void)
1431 static char *common_data_dir = NULL;
1433 #if defined(PLATFORM_WIN32)
1434 if (common_data_dir == NULL)
1436 char *dir = checked_malloc(MAX_PATH + 1);
1438 if (SUCCEEDED(SHGetFolderPath(NULL, CSIDL_COMMON_DOCUMENTS, NULL, 0, dir))
1439 && !strEqual(dir, "")) /* empty for Windows 95/98 */
1440 common_data_dir = getPath2(dir, program.userdata_subdir);
1442 common_data_dir = options.rw_base_directory;
1445 if (common_data_dir == NULL)
1446 common_data_dir = options.rw_base_directory;
1449 return common_data_dir;
1452 char *getPersonalDataDir(void)
1454 static char *personal_data_dir = NULL;
1456 #if defined(PLATFORM_MACOSX)
1457 if (personal_data_dir == NULL)
1458 personal_data_dir = getPath2(getHomeDir(), "Documents");
1460 if (personal_data_dir == NULL)
1461 personal_data_dir = getHomeDir();
1464 return personal_data_dir;
1467 char *getUserGameDataDir(void)
1469 static char *user_game_data_dir = NULL;
1471 #if defined(PLATFORM_ANDROID)
1472 if (user_game_data_dir == NULL)
1473 user_game_data_dir = (char *)(SDL_AndroidGetExternalStorageState() &
1474 SDL_ANDROID_EXTERNAL_STORAGE_WRITE ?
1475 SDL_AndroidGetExternalStoragePath() :
1476 SDL_AndroidGetInternalStoragePath());
1478 if (user_game_data_dir == NULL)
1479 user_game_data_dir = getPath2(getPersonalDataDir(),
1480 program.userdata_subdir);
1483 return user_game_data_dir;
1488 return getUserGameDataDir();
1491 static mode_t posix_umask(mode_t mask)
1493 #if defined(PLATFORM_UNIX)
1500 static int posix_mkdir(const char *pathname, mode_t mode)
1502 #if defined(PLATFORM_WIN32)
1503 return mkdir(pathname);
1505 return mkdir(pathname, mode);
1509 static boolean posix_process_running_setgid()
1511 #if defined(PLATFORM_UNIX)
1512 return (getgid() != getegid());
1518 void createDirectory(char *dir, char *text, int permission_class)
1520 if (directoryExists(dir))
1523 /* leave "other" permissions in umask untouched, but ensure group parts
1524 of USERDATA_DIR_MODE are not masked */
1525 mode_t dir_mode = (permission_class == PERMS_PRIVATE ?
1526 DIR_PERMS_PRIVATE : DIR_PERMS_PUBLIC);
1527 mode_t last_umask = posix_umask(0);
1528 mode_t group_umask = ~(dir_mode & S_IRWXG);
1529 int running_setgid = posix_process_running_setgid();
1531 if (permission_class == PERMS_PUBLIC)
1533 /* if we're setgid, protect files against "other" */
1534 /* else keep umask(0) to make the dir world-writable */
1537 posix_umask(last_umask & group_umask);
1539 dir_mode = DIR_PERMS_PUBLIC_ALL;
1542 if (posix_mkdir(dir, dir_mode) != 0)
1543 Error(ERR_WARN, "cannot create %s directory '%s': %s",
1544 text, dir, strerror(errno));
1546 if (permission_class == PERMS_PUBLIC && !running_setgid)
1547 chmod(dir, dir_mode);
1549 posix_umask(last_umask); /* restore previous umask */
1552 void InitUserDataDirectory()
1554 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
1557 void SetFilePermissions(char *filename, int permission_class)
1559 int running_setgid = posix_process_running_setgid();
1560 int perms = (permission_class == PERMS_PRIVATE ?
1561 FILE_PERMS_PRIVATE : FILE_PERMS_PUBLIC);
1563 if (permission_class == PERMS_PUBLIC && !running_setgid)
1564 perms = FILE_PERMS_PUBLIC_ALL;
1566 chmod(filename, perms);
1569 char *getCookie(char *file_type)
1571 static char cookie[MAX_COOKIE_LEN + 1];
1573 if (strlen(program.cookie_prefix) + 1 +
1574 strlen(file_type) + strlen("_FILE_VERSION_x.x") > MAX_COOKIE_LEN)
1575 return "[COOKIE ERROR]"; /* should never happen */
1577 sprintf(cookie, "%s_%s_FILE_VERSION_%d.%d",
1578 program.cookie_prefix, file_type,
1579 program.version_super, program.version_major);
1584 void fprintFileHeader(FILE *file, char *basename)
1586 char *prefix = "# ";
1589 fprintf_line_with_prefix(file, prefix, sep1, 77);
1590 fprintf(file, "%s%s\n", prefix, basename);
1591 fprintf_line_with_prefix(file, prefix, sep1, 77);
1592 fprintf(file, "\n");
1595 int getFileVersionFromCookieString(const char *cookie)
1597 const char *ptr_cookie1, *ptr_cookie2;
1598 const char *pattern1 = "_FILE_VERSION_";
1599 const char *pattern2 = "?.?";
1600 const int len_cookie = strlen(cookie);
1601 const int len_pattern1 = strlen(pattern1);
1602 const int len_pattern2 = strlen(pattern2);
1603 const int len_pattern = len_pattern1 + len_pattern2;
1604 int version_super, version_major;
1606 if (len_cookie <= len_pattern)
1609 ptr_cookie1 = &cookie[len_cookie - len_pattern];
1610 ptr_cookie2 = &cookie[len_cookie - len_pattern2];
1612 if (strncmp(ptr_cookie1, pattern1, len_pattern1) != 0)
1615 if (ptr_cookie2[0] < '0' || ptr_cookie2[0] > '9' ||
1616 ptr_cookie2[1] != '.' ||
1617 ptr_cookie2[2] < '0' || ptr_cookie2[2] > '9')
1620 version_super = ptr_cookie2[0] - '0';
1621 version_major = ptr_cookie2[2] - '0';
1623 return VERSION_IDENT(version_super, version_major, 0, 0);
1626 boolean checkCookieString(const char *cookie, const char *template)
1628 const char *pattern = "_FILE_VERSION_?.?";
1629 const int len_cookie = strlen(cookie);
1630 const int len_template = strlen(template);
1631 const int len_pattern = strlen(pattern);
1633 if (len_cookie != len_template)
1636 if (strncmp(cookie, template, len_cookie - len_pattern) != 0)
1643 /* ------------------------------------------------------------------------- */
1644 /* setup file list and hash handling functions */
1645 /* ------------------------------------------------------------------------- */
1647 char *getFormattedSetupEntry(char *token, char *value)
1650 static char entry[MAX_LINE_LEN];
1652 /* if value is an empty string, just return token without value */
1656 /* start with the token and some spaces to format output line */
1657 sprintf(entry, "%s:", token);
1658 for (i = strlen(entry); i < token_value_position; i++)
1661 /* continue with the token's value */
1662 strcat(entry, value);
1667 SetupFileList *newSetupFileList(char *token, char *value)
1669 SetupFileList *new = checked_malloc(sizeof(SetupFileList));
1671 new->token = getStringCopy(token);
1672 new->value = getStringCopy(value);
1679 void freeSetupFileList(SetupFileList *list)
1684 checked_free(list->token);
1685 checked_free(list->value);
1688 freeSetupFileList(list->next);
1693 char *getListEntry(SetupFileList *list, char *token)
1698 if (strEqual(list->token, token))
1701 return getListEntry(list->next, token);
1704 SetupFileList *setListEntry(SetupFileList *list, char *token, char *value)
1709 if (strEqual(list->token, token))
1711 checked_free(list->value);
1713 list->value = getStringCopy(value);
1717 else if (list->next == NULL)
1718 return (list->next = newSetupFileList(token, value));
1720 return setListEntry(list->next, token, value);
1723 SetupFileList *addListEntry(SetupFileList *list, char *token, char *value)
1728 if (list->next == NULL)
1729 return (list->next = newSetupFileList(token, value));
1731 return addListEntry(list->next, token, value);
1734 #if ENABLE_UNUSED_CODE
1736 static void printSetupFileList(SetupFileList *list)
1741 printf("token: '%s'\n", list->token);
1742 printf("value: '%s'\n", list->value);
1744 printSetupFileList(list->next);
1750 DEFINE_HASHTABLE_INSERT(insert_hash_entry, char, char);
1751 DEFINE_HASHTABLE_SEARCH(search_hash_entry, char, char);
1752 DEFINE_HASHTABLE_CHANGE(change_hash_entry, char, char);
1753 DEFINE_HASHTABLE_REMOVE(remove_hash_entry, char, char);
1755 #define insert_hash_entry hashtable_insert
1756 #define search_hash_entry hashtable_search
1757 #define change_hash_entry hashtable_change
1758 #define remove_hash_entry hashtable_remove
1761 unsigned int get_hash_from_key(void *key)
1766 This algorithm (k=33) was first reported by Dan Bernstein many years ago in
1767 'comp.lang.c'. Another version of this algorithm (now favored by Bernstein)
1768 uses XOR: hash(i) = hash(i - 1) * 33 ^ str[i]; the magic of number 33 (why
1769 it works better than many other constants, prime or not) has never been
1770 adequately explained.
1772 If you just want to have a good hash function, and cannot wait, djb2
1773 is one of the best string hash functions i know. It has excellent
1774 distribution and speed on many different sets of keys and table sizes.
1775 You are not likely to do better with one of the "well known" functions
1776 such as PJW, K&R, etc.
1778 Ozan (oz) Yigit [http://www.cs.yorku.ca/~oz/hash.html]
1781 char *str = (char *)key;
1782 unsigned int hash = 5381;
1785 while ((c = *str++))
1786 hash = ((hash << 5) + hash) + c; /* hash * 33 + c */
1791 static int keys_are_equal(void *key1, void *key2)
1793 return (strEqual((char *)key1, (char *)key2));
1796 SetupFileHash *newSetupFileHash()
1798 SetupFileHash *new_hash =
1799 create_hashtable(16, 0.75, get_hash_from_key, keys_are_equal);
1801 if (new_hash == NULL)
1802 Error(ERR_EXIT, "create_hashtable() failed -- out of memory");
1807 void freeSetupFileHash(SetupFileHash *hash)
1812 hashtable_destroy(hash, 1); /* 1 == also free values stored in hash */
1815 char *getHashEntry(SetupFileHash *hash, char *token)
1820 return search_hash_entry(hash, token);
1823 void setHashEntry(SetupFileHash *hash, char *token, char *value)
1830 value_copy = getStringCopy(value);
1832 /* change value; if it does not exist, insert it as new */
1833 if (!change_hash_entry(hash, token, value_copy))
1834 if (!insert_hash_entry(hash, getStringCopy(token), value_copy))
1835 Error(ERR_EXIT, "cannot insert into hash -- aborting");
1838 char *removeHashEntry(SetupFileHash *hash, char *token)
1843 return remove_hash_entry(hash, token);
1846 #if ENABLE_UNUSED_CODE
1848 static void printSetupFileHash(SetupFileHash *hash)
1850 BEGIN_HASH_ITERATION(hash, itr)
1852 printf("token: '%s'\n", HASH_ITERATION_TOKEN(itr));
1853 printf("value: '%s'\n", HASH_ITERATION_VALUE(itr));
1855 END_HASH_ITERATION(hash, itr)
1860 #define ALLOW_TOKEN_VALUE_SEPARATOR_BEING_WHITESPACE 1
1861 #define CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING 0
1862 #define CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH 0
1864 static boolean token_value_separator_found = FALSE;
1865 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
1866 static boolean token_value_separator_warning = FALSE;
1868 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
1869 static boolean token_already_exists_warning = FALSE;
1872 static boolean getTokenValueFromSetupLineExt(char *line,
1873 char **token_ptr, char **value_ptr,
1874 char *filename, char *line_raw,
1876 boolean separator_required)
1878 static char line_copy[MAX_LINE_LEN + 1], line_raw_copy[MAX_LINE_LEN + 1];
1879 char *token, *value, *line_ptr;
1881 /* when externally invoked via ReadTokenValueFromLine(), copy line buffers */
1882 if (line_raw == NULL)
1884 strncpy(line_copy, line, MAX_LINE_LEN);
1885 line_copy[MAX_LINE_LEN] = '\0';
1888 strcpy(line_raw_copy, line_copy);
1889 line_raw = line_raw_copy;
1892 /* cut trailing comment from input line */
1893 for (line_ptr = line; *line_ptr; line_ptr++)
1895 if (*line_ptr == '#')
1902 /* cut trailing whitespaces from input line */
1903 for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
1904 if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
1907 /* ignore empty lines */
1911 /* cut leading whitespaces from token */
1912 for (token = line; *token; token++)
1913 if (*token != ' ' && *token != '\t')
1916 /* start with empty value as reliable default */
1919 token_value_separator_found = FALSE;
1921 /* find end of token to determine start of value */
1922 for (line_ptr = token; *line_ptr; line_ptr++)
1924 /* first look for an explicit token/value separator, like ':' or '=' */
1925 if (*line_ptr == ':' || *line_ptr == '=')
1927 *line_ptr = '\0'; /* terminate token string */
1928 value = line_ptr + 1; /* set beginning of value */
1930 token_value_separator_found = TRUE;
1936 #if ALLOW_TOKEN_VALUE_SEPARATOR_BEING_WHITESPACE
1937 /* fallback: if no token/value separator found, also allow whitespaces */
1938 if (!token_value_separator_found && !separator_required)
1940 for (line_ptr = token; *line_ptr; line_ptr++)
1942 if (*line_ptr == ' ' || *line_ptr == '\t')
1944 *line_ptr = '\0'; /* terminate token string */
1945 value = line_ptr + 1; /* set beginning of value */
1947 token_value_separator_found = TRUE;
1953 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
1954 if (token_value_separator_found)
1956 if (!token_value_separator_warning)
1958 Error(ERR_INFO_LINE, "-");
1960 if (filename != NULL)
1962 Error(ERR_WARN, "missing token/value separator(s) in config file:");
1963 Error(ERR_INFO, "- config file: '%s'", filename);
1967 Error(ERR_WARN, "missing token/value separator(s):");
1970 token_value_separator_warning = TRUE;
1973 if (filename != NULL)
1974 Error(ERR_INFO, "- line %d: '%s'", line_nr, line_raw);
1976 Error(ERR_INFO, "- line: '%s'", line_raw);
1982 /* cut trailing whitespaces from token */
1983 for (line_ptr = &token[strlen(token)]; line_ptr >= token; line_ptr--)
1984 if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
1987 /* cut leading whitespaces from value */
1988 for (; *value; value++)
1989 if (*value != ' ' && *value != '\t')
1998 boolean getTokenValueFromSetupLine(char *line, char **token, char **value)
2000 /* while the internal (old) interface does not require a token/value
2001 separator (for downwards compatibility with existing files which
2002 don't use them), it is mandatory for the external (new) interface */
2004 return getTokenValueFromSetupLineExt(line, token, value, NULL, NULL, 0, TRUE);
2007 static boolean loadSetupFileData(void *setup_file_data, char *filename,
2008 boolean top_recursion_level, boolean is_hash)
2010 static SetupFileHash *include_filename_hash = NULL;
2011 char line[MAX_LINE_LEN], line_raw[MAX_LINE_LEN], previous_line[MAX_LINE_LEN];
2012 char *token, *value, *line_ptr;
2013 void *insert_ptr = NULL;
2014 boolean read_continued_line = FALSE;
2016 int line_nr = 0, token_count = 0, include_count = 0;
2018 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2019 token_value_separator_warning = FALSE;
2022 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2023 token_already_exists_warning = FALSE;
2026 if (!(file = openFile(filename, MODE_READ)))
2028 #if DEBUG_NO_CONFIG_FILE
2029 Error(ERR_DEBUG, "cannot open configuration file '%s'", filename);
2035 /* use "insert pointer" to store list end for constant insertion complexity */
2037 insert_ptr = setup_file_data;
2039 /* on top invocation, create hash to mark included files (to prevent loops) */
2040 if (top_recursion_level)
2041 include_filename_hash = newSetupFileHash();
2043 /* mark this file as already included (to prevent including it again) */
2044 setHashEntry(include_filename_hash, getBaseNamePtr(filename), "true");
2046 while (!checkEndOfFile(file))
2048 /* read next line of input file */
2049 if (!getStringFromFile(file, line, MAX_LINE_LEN))
2052 /* check if line was completely read and is terminated by line break */
2053 if (strlen(line) > 0 && line[strlen(line) - 1] == '\n')
2056 /* cut trailing line break (this can be newline and/or carriage return) */
2057 for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
2058 if ((*line_ptr == '\n' || *line_ptr == '\r') && *(line_ptr + 1) == '\0')
2061 /* copy raw input line for later use (mainly debugging output) */
2062 strcpy(line_raw, line);
2064 if (read_continued_line)
2066 /* append new line to existing line, if there is enough space */
2067 if (strlen(previous_line) + strlen(line_ptr) < MAX_LINE_LEN)
2068 strcat(previous_line, line_ptr);
2070 strcpy(line, previous_line); /* copy storage buffer to line */
2072 read_continued_line = FALSE;
2075 /* if the last character is '\', continue at next line */
2076 if (strlen(line) > 0 && line[strlen(line) - 1] == '\\')
2078 line[strlen(line) - 1] = '\0'; /* cut off trailing backslash */
2079 strcpy(previous_line, line); /* copy line to storage buffer */
2081 read_continued_line = TRUE;
2086 if (!getTokenValueFromSetupLineExt(line, &token, &value, filename,
2087 line_raw, line_nr, FALSE))
2092 if (strEqual(token, "include"))
2094 if (getHashEntry(include_filename_hash, value) == NULL)
2096 char *basepath = getBasePath(filename);
2097 char *basename = getBaseName(value);
2098 char *filename_include = getPath2(basepath, basename);
2100 loadSetupFileData(setup_file_data, filename_include, FALSE, is_hash);
2104 free(filename_include);
2110 Error(ERR_WARN, "ignoring already processed file '%s'", value);
2117 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2119 getHashEntry((SetupFileHash *)setup_file_data, token);
2121 if (old_value != NULL)
2123 if (!token_already_exists_warning)
2125 Error(ERR_INFO_LINE, "-");
2126 Error(ERR_WARN, "duplicate token(s) found in config file:");
2127 Error(ERR_INFO, "- config file: '%s'", filename);
2129 token_already_exists_warning = TRUE;
2132 Error(ERR_INFO, "- token: '%s' (in line %d)", token, line_nr);
2133 Error(ERR_INFO, " old value: '%s'", old_value);
2134 Error(ERR_INFO, " new value: '%s'", value);
2138 setHashEntry((SetupFileHash *)setup_file_data, token, value);
2142 insert_ptr = addListEntry((SetupFileList *)insert_ptr, token, value);
2152 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2153 if (token_value_separator_warning)
2154 Error(ERR_INFO_LINE, "-");
2157 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2158 if (token_already_exists_warning)
2159 Error(ERR_INFO_LINE, "-");
2162 if (token_count == 0 && include_count == 0)
2163 Error(ERR_WARN, "configuration file '%s' is empty", filename);
2165 if (top_recursion_level)
2166 freeSetupFileHash(include_filename_hash);
2171 void saveSetupFileHash(SetupFileHash *hash, char *filename)
2175 if (!(file = fopen(filename, MODE_WRITE)))
2177 Error(ERR_WARN, "cannot write configuration file '%s'", filename);
2182 BEGIN_HASH_ITERATION(hash, itr)
2184 fprintf(file, "%s\n", getFormattedSetupEntry(HASH_ITERATION_TOKEN(itr),
2185 HASH_ITERATION_VALUE(itr)));
2187 END_HASH_ITERATION(hash, itr)
2192 SetupFileList *loadSetupFileList(char *filename)
2194 SetupFileList *setup_file_list = newSetupFileList("", "");
2195 SetupFileList *first_valid_list_entry;
2197 if (!loadSetupFileData(setup_file_list, filename, TRUE, FALSE))
2199 freeSetupFileList(setup_file_list);
2204 first_valid_list_entry = setup_file_list->next;
2206 /* free empty list header */
2207 setup_file_list->next = NULL;
2208 freeSetupFileList(setup_file_list);
2210 return first_valid_list_entry;
2213 SetupFileHash *loadSetupFileHash(char *filename)
2215 SetupFileHash *setup_file_hash = newSetupFileHash();
2217 if (!loadSetupFileData(setup_file_hash, filename, TRUE, TRUE))
2219 freeSetupFileHash(setup_file_hash);
2224 return setup_file_hash;
2228 /* ========================================================================= */
2229 /* setup file stuff */
2230 /* ========================================================================= */
2232 #define TOKEN_STR_LAST_LEVEL_SERIES "last_level_series"
2233 #define TOKEN_STR_LAST_PLAYED_LEVEL "last_played_level"
2234 #define TOKEN_STR_HANDICAP_LEVEL "handicap_level"
2236 /* level directory info */
2237 #define LEVELINFO_TOKEN_IDENTIFIER 0
2238 #define LEVELINFO_TOKEN_NAME 1
2239 #define LEVELINFO_TOKEN_NAME_SORTING 2
2240 #define LEVELINFO_TOKEN_AUTHOR 3
2241 #define LEVELINFO_TOKEN_YEAR 4
2242 #define LEVELINFO_TOKEN_PROGRAM_TITLE 5
2243 #define LEVELINFO_TOKEN_PROGRAM_COPYRIGHT 6
2244 #define LEVELINFO_TOKEN_PROGRAM_COMPANY 7
2245 #define LEVELINFO_TOKEN_IMPORTED_FROM 8
2246 #define LEVELINFO_TOKEN_IMPORTED_BY 9
2247 #define LEVELINFO_TOKEN_TESTED_BY 10
2248 #define LEVELINFO_TOKEN_LEVELS 11
2249 #define LEVELINFO_TOKEN_FIRST_LEVEL 12
2250 #define LEVELINFO_TOKEN_SORT_PRIORITY 13
2251 #define LEVELINFO_TOKEN_LATEST_ENGINE 14
2252 #define LEVELINFO_TOKEN_LEVEL_GROUP 15
2253 #define LEVELINFO_TOKEN_READONLY 16
2254 #define LEVELINFO_TOKEN_GRAPHICS_SET_ECS 17
2255 #define LEVELINFO_TOKEN_GRAPHICS_SET_AGA 18
2256 #define LEVELINFO_TOKEN_GRAPHICS_SET 19
2257 #define LEVELINFO_TOKEN_SOUNDS_SET 20
2258 #define LEVELINFO_TOKEN_MUSIC_SET 21
2259 #define LEVELINFO_TOKEN_FILENAME 22
2260 #define LEVELINFO_TOKEN_FILETYPE 23
2261 #define LEVELINFO_TOKEN_SPECIAL_FLAGS 24
2262 #define LEVELINFO_TOKEN_HANDICAP 25
2263 #define LEVELINFO_TOKEN_SKIP_LEVELS 26
2265 #define NUM_LEVELINFO_TOKENS 27
2267 static LevelDirTree ldi;
2269 static struct TokenInfo levelinfo_tokens[] =
2271 /* level directory info */
2272 { TYPE_STRING, &ldi.identifier, "identifier" },
2273 { TYPE_STRING, &ldi.name, "name" },
2274 { TYPE_STRING, &ldi.name_sorting, "name_sorting" },
2275 { TYPE_STRING, &ldi.author, "author" },
2276 { TYPE_STRING, &ldi.year, "year" },
2277 { TYPE_STRING, &ldi.program_title, "program_title" },
2278 { TYPE_STRING, &ldi.program_copyright, "program_copyright" },
2279 { TYPE_STRING, &ldi.program_company, "program_company" },
2280 { TYPE_STRING, &ldi.imported_from, "imported_from" },
2281 { TYPE_STRING, &ldi.imported_by, "imported_by" },
2282 { TYPE_STRING, &ldi.tested_by, "tested_by" },
2283 { TYPE_INTEGER, &ldi.levels, "levels" },
2284 { TYPE_INTEGER, &ldi.first_level, "first_level" },
2285 { TYPE_INTEGER, &ldi.sort_priority, "sort_priority" },
2286 { TYPE_BOOLEAN, &ldi.latest_engine, "latest_engine" },
2287 { TYPE_BOOLEAN, &ldi.level_group, "level_group" },
2288 { TYPE_BOOLEAN, &ldi.readonly, "readonly" },
2289 { TYPE_STRING, &ldi.graphics_set_ecs, "graphics_set.ecs" },
2290 { TYPE_STRING, &ldi.graphics_set_aga, "graphics_set.aga" },
2291 { TYPE_STRING, &ldi.graphics_set, "graphics_set" },
2292 { TYPE_STRING, &ldi.sounds_set, "sounds_set" },
2293 { TYPE_STRING, &ldi.music_set, "music_set" },
2294 { TYPE_STRING, &ldi.level_filename, "filename" },
2295 { TYPE_STRING, &ldi.level_filetype, "filetype" },
2296 { TYPE_STRING, &ldi.special_flags, "special_flags" },
2297 { TYPE_BOOLEAN, &ldi.handicap, "handicap" },
2298 { TYPE_BOOLEAN, &ldi.skip_levels, "skip_levels" }
2301 static struct TokenInfo artworkinfo_tokens[] =
2303 /* artwork directory info */
2304 { TYPE_STRING, &ldi.identifier, "identifier" },
2305 { TYPE_STRING, &ldi.subdir, "subdir" },
2306 { TYPE_STRING, &ldi.name, "name" },
2307 { TYPE_STRING, &ldi.name_sorting, "name_sorting" },
2308 { TYPE_STRING, &ldi.author, "author" },
2309 { TYPE_STRING, &ldi.program_title, "program_title" },
2310 { TYPE_STRING, &ldi.program_copyright, "program_copyright" },
2311 { TYPE_STRING, &ldi.program_company, "program_company" },
2312 { TYPE_INTEGER, &ldi.sort_priority, "sort_priority" },
2313 { TYPE_STRING, &ldi.basepath, "basepath" },
2314 { TYPE_STRING, &ldi.fullpath, "fullpath" },
2315 { TYPE_BOOLEAN, &ldi.in_user_dir, "in_user_dir" },
2316 { TYPE_INTEGER, &ldi.color, "color" },
2317 { TYPE_STRING, &ldi.class_desc, "class_desc" },
2322 static void setTreeInfoToDefaults(TreeInfo *ti, int type)
2326 ti->node_top = (ti->type == TREE_TYPE_LEVEL_DIR ? &leveldir_first :
2327 ti->type == TREE_TYPE_GRAPHICS_DIR ? &artwork.gfx_first :
2328 ti->type == TREE_TYPE_SOUNDS_DIR ? &artwork.snd_first :
2329 ti->type == TREE_TYPE_MUSIC_DIR ? &artwork.mus_first :
2332 ti->node_parent = NULL;
2333 ti->node_group = NULL;
2340 ti->fullpath = NULL;
2341 ti->basepath = NULL;
2342 ti->identifier = NULL;
2343 ti->name = getStringCopy(ANONYMOUS_NAME);
2344 ti->name_sorting = NULL;
2345 ti->author = getStringCopy(ANONYMOUS_NAME);
2348 ti->program_title = NULL;
2349 ti->program_copyright = NULL;
2350 ti->program_company = NULL;
2352 ti->sort_priority = LEVELCLASS_UNDEFINED; /* default: least priority */
2353 ti->latest_engine = FALSE; /* default: get from level */
2354 ti->parent_link = FALSE;
2355 ti->in_user_dir = FALSE;
2356 ti->user_defined = FALSE;
2358 ti->class_desc = NULL;
2360 ti->infotext = getStringCopy(TREE_INFOTEXT(ti->type));
2362 if (ti->type == TREE_TYPE_LEVEL_DIR)
2364 ti->imported_from = NULL;
2365 ti->imported_by = NULL;
2366 ti->tested_by = NULL;
2368 ti->graphics_set_ecs = NULL;
2369 ti->graphics_set_aga = NULL;
2370 ti->graphics_set = NULL;
2371 ti->sounds_set = NULL;
2372 ti->music_set = NULL;
2373 ti->graphics_path = getStringCopy(UNDEFINED_FILENAME);
2374 ti->sounds_path = getStringCopy(UNDEFINED_FILENAME);
2375 ti->music_path = getStringCopy(UNDEFINED_FILENAME);
2377 ti->level_filename = NULL;
2378 ti->level_filetype = NULL;
2380 ti->special_flags = NULL;
2383 ti->first_level = 0;
2385 ti->level_group = FALSE;
2386 ti->handicap_level = 0;
2387 ti->readonly = TRUE;
2388 ti->handicap = TRUE;
2389 ti->skip_levels = FALSE;
2393 static void setTreeInfoToDefaultsFromParent(TreeInfo *ti, TreeInfo *parent)
2397 Error(ERR_WARN, "setTreeInfoToDefaultsFromParent(): parent == NULL");
2399 setTreeInfoToDefaults(ti, TREE_TYPE_UNDEFINED);
2404 /* copy all values from the parent structure */
2406 ti->type = parent->type;
2408 ti->node_top = parent->node_top;
2409 ti->node_parent = parent;
2410 ti->node_group = NULL;
2417 ti->fullpath = NULL;
2418 ti->basepath = NULL;
2419 ti->identifier = NULL;
2420 ti->name = getStringCopy(ANONYMOUS_NAME);
2421 ti->name_sorting = NULL;
2422 ti->author = getStringCopy(parent->author);
2423 ti->year = getStringCopy(parent->year);
2425 ti->program_title = getStringCopy(parent->program_title);
2426 ti->program_copyright = getStringCopy(parent->program_copyright);
2427 ti->program_company = getStringCopy(parent->program_company);
2429 ti->sort_priority = parent->sort_priority;
2430 ti->latest_engine = parent->latest_engine;
2431 ti->parent_link = FALSE;
2432 ti->in_user_dir = parent->in_user_dir;
2433 ti->user_defined = parent->user_defined;
2434 ti->color = parent->color;
2435 ti->class_desc = getStringCopy(parent->class_desc);
2437 ti->infotext = getStringCopy(parent->infotext);
2439 if (ti->type == TREE_TYPE_LEVEL_DIR)
2441 ti->imported_from = getStringCopy(parent->imported_from);
2442 ti->imported_by = getStringCopy(parent->imported_by);
2443 ti->tested_by = getStringCopy(parent->tested_by);
2445 ti->graphics_set_ecs = getStringCopy(parent->graphics_set_ecs);
2446 ti->graphics_set_aga = getStringCopy(parent->graphics_set_aga);
2447 ti->graphics_set = getStringCopy(parent->graphics_set);
2448 ti->sounds_set = getStringCopy(parent->sounds_set);
2449 ti->music_set = getStringCopy(parent->music_set);
2450 ti->graphics_path = getStringCopy(UNDEFINED_FILENAME);
2451 ti->sounds_path = getStringCopy(UNDEFINED_FILENAME);
2452 ti->music_path = getStringCopy(UNDEFINED_FILENAME);
2454 ti->level_filename = getStringCopy(parent->level_filename);
2455 ti->level_filetype = getStringCopy(parent->level_filetype);
2457 ti->special_flags = getStringCopy(parent->special_flags);
2459 ti->levels = parent->levels;
2460 ti->first_level = parent->first_level;
2461 ti->last_level = parent->last_level;
2462 ti->level_group = FALSE;
2463 ti->handicap_level = parent->handicap_level;
2464 ti->readonly = parent->readonly;
2465 ti->handicap = parent->handicap;
2466 ti->skip_levels = parent->skip_levels;
2470 static TreeInfo *getTreeInfoCopy(TreeInfo *ti)
2472 TreeInfo *ti_copy = newTreeInfo();
2474 /* copy all values from the original structure */
2476 ti_copy->type = ti->type;
2478 ti_copy->node_top = ti->node_top;
2479 ti_copy->node_parent = ti->node_parent;
2480 ti_copy->node_group = ti->node_group;
2481 ti_copy->next = ti->next;
2483 ti_copy->cl_first = ti->cl_first;
2484 ti_copy->cl_cursor = ti->cl_cursor;
2486 ti_copy->subdir = getStringCopy(ti->subdir);
2487 ti_copy->fullpath = getStringCopy(ti->fullpath);
2488 ti_copy->basepath = getStringCopy(ti->basepath);
2489 ti_copy->identifier = getStringCopy(ti->identifier);
2490 ti_copy->name = getStringCopy(ti->name);
2491 ti_copy->name_sorting = getStringCopy(ti->name_sorting);
2492 ti_copy->author = getStringCopy(ti->author);
2493 ti_copy->year = getStringCopy(ti->year);
2495 ti_copy->program_title = getStringCopy(ti->program_title);
2496 ti_copy->program_copyright = getStringCopy(ti->program_copyright);
2497 ti_copy->program_company = getStringCopy(ti->program_company);
2499 ti_copy->imported_from = getStringCopy(ti->imported_from);
2500 ti_copy->imported_by = getStringCopy(ti->imported_by);
2501 ti_copy->tested_by = getStringCopy(ti->tested_by);
2503 ti_copy->graphics_set_ecs = getStringCopy(ti->graphics_set_ecs);
2504 ti_copy->graphics_set_aga = getStringCopy(ti->graphics_set_aga);
2505 ti_copy->graphics_set = getStringCopy(ti->graphics_set);
2506 ti_copy->sounds_set = getStringCopy(ti->sounds_set);
2507 ti_copy->music_set = getStringCopy(ti->music_set);
2508 ti_copy->graphics_path = getStringCopy(ti->graphics_path);
2509 ti_copy->sounds_path = getStringCopy(ti->sounds_path);
2510 ti_copy->music_path = getStringCopy(ti->music_path);
2512 ti_copy->level_filename = getStringCopy(ti->level_filename);
2513 ti_copy->level_filetype = getStringCopy(ti->level_filetype);
2515 ti_copy->special_flags = getStringCopy(ti->special_flags);
2517 ti_copy->levels = ti->levels;
2518 ti_copy->first_level = ti->first_level;
2519 ti_copy->last_level = ti->last_level;
2520 ti_copy->sort_priority = ti->sort_priority;
2522 ti_copy->latest_engine = ti->latest_engine;
2524 ti_copy->level_group = ti->level_group;
2525 ti_copy->parent_link = ti->parent_link;
2526 ti_copy->in_user_dir = ti->in_user_dir;
2527 ti_copy->user_defined = ti->user_defined;
2528 ti_copy->readonly = ti->readonly;
2529 ti_copy->handicap = ti->handicap;
2530 ti_copy->skip_levels = ti->skip_levels;
2532 ti_copy->color = ti->color;
2533 ti_copy->class_desc = getStringCopy(ti->class_desc);
2534 ti_copy->handicap_level = ti->handicap_level;
2536 ti_copy->infotext = getStringCopy(ti->infotext);
2541 void freeTreeInfo(TreeInfo *ti)
2546 checked_free(ti->subdir);
2547 checked_free(ti->fullpath);
2548 checked_free(ti->basepath);
2549 checked_free(ti->identifier);
2551 checked_free(ti->name);
2552 checked_free(ti->name_sorting);
2553 checked_free(ti->author);
2554 checked_free(ti->year);
2556 checked_free(ti->program_title);
2557 checked_free(ti->program_copyright);
2558 checked_free(ti->program_company);
2560 checked_free(ti->class_desc);
2562 checked_free(ti->infotext);
2564 if (ti->type == TREE_TYPE_LEVEL_DIR)
2566 checked_free(ti->imported_from);
2567 checked_free(ti->imported_by);
2568 checked_free(ti->tested_by);
2570 checked_free(ti->graphics_set_ecs);
2571 checked_free(ti->graphics_set_aga);
2572 checked_free(ti->graphics_set);
2573 checked_free(ti->sounds_set);
2574 checked_free(ti->music_set);
2576 checked_free(ti->graphics_path);
2577 checked_free(ti->sounds_path);
2578 checked_free(ti->music_path);
2580 checked_free(ti->level_filename);
2581 checked_free(ti->level_filetype);
2583 checked_free(ti->special_flags);
2586 // recursively free child node
2588 freeTreeInfo(ti->node_group);
2590 // recursively free next node
2592 freeTreeInfo(ti->next);
2597 void setSetupInfo(struct TokenInfo *token_info,
2598 int token_nr, char *token_value)
2600 int token_type = token_info[token_nr].type;
2601 void *setup_value = token_info[token_nr].value;
2603 if (token_value == NULL)
2606 /* set setup field to corresponding token value */
2611 *(boolean *)setup_value = get_boolean_from_string(token_value);
2615 *(int *)setup_value = get_switch3_from_string(token_value);
2619 *(Key *)setup_value = getKeyFromKeyName(token_value);
2623 *(Key *)setup_value = getKeyFromX11KeyName(token_value);
2627 *(int *)setup_value = get_integer_from_string(token_value);
2631 checked_free(*(char **)setup_value);
2632 *(char **)setup_value = getStringCopy(token_value);
2636 *(int *)setup_value = get_player_nr_from_string(token_value);
2644 static int compareTreeInfoEntries(const void *object1, const void *object2)
2646 const TreeInfo *entry1 = *((TreeInfo **)object1);
2647 const TreeInfo *entry2 = *((TreeInfo **)object2);
2648 int class_sorting1 = 0, class_sorting2 = 0;
2651 if (entry1->type == TREE_TYPE_LEVEL_DIR)
2653 class_sorting1 = LEVELSORTING(entry1);
2654 class_sorting2 = LEVELSORTING(entry2);
2656 else if (entry1->type == TREE_TYPE_GRAPHICS_DIR ||
2657 entry1->type == TREE_TYPE_SOUNDS_DIR ||
2658 entry1->type == TREE_TYPE_MUSIC_DIR)
2660 class_sorting1 = ARTWORKSORTING(entry1);
2661 class_sorting2 = ARTWORKSORTING(entry2);
2664 if (entry1->parent_link || entry2->parent_link)
2665 compare_result = (entry1->parent_link ? -1 : +1);
2666 else if (entry1->sort_priority == entry2->sort_priority)
2668 char *name1 = getStringToLower(entry1->name_sorting);
2669 char *name2 = getStringToLower(entry2->name_sorting);
2671 compare_result = strcmp(name1, name2);
2676 else if (class_sorting1 == class_sorting2)
2677 compare_result = entry1->sort_priority - entry2->sort_priority;
2679 compare_result = class_sorting1 - class_sorting2;
2681 return compare_result;
2684 static TreeInfo *createParentTreeInfoNode(TreeInfo *node_parent)
2688 if (node_parent == NULL)
2691 ti_new = newTreeInfo();
2692 setTreeInfoToDefaults(ti_new, node_parent->type);
2694 ti_new->node_parent = node_parent;
2695 ti_new->parent_link = TRUE;
2697 setString(&ti_new->identifier, node_parent->identifier);
2698 setString(&ti_new->name, ".. (parent directory)");
2699 setString(&ti_new->name_sorting, ti_new->name);
2701 setString(&ti_new->subdir, STRING_PARENT_DIRECTORY);
2702 setString(&ti_new->fullpath, node_parent->fullpath);
2704 ti_new->sort_priority = node_parent->sort_priority;
2705 ti_new->latest_engine = node_parent->latest_engine;
2707 setString(&ti_new->class_desc, getLevelClassDescription(ti_new));
2709 pushTreeInfo(&node_parent->node_group, ti_new);
2714 static TreeInfo *createTopTreeInfoNode(TreeInfo *node_first)
2716 TreeInfo *ti_new, *ti_new2;
2718 if (node_first == NULL)
2721 ti_new = newTreeInfo();
2722 setTreeInfoToDefaults(ti_new, TREE_TYPE_LEVEL_DIR);
2724 ti_new->node_parent = NULL;
2725 ti_new->parent_link = FALSE;
2727 setString(&ti_new->identifier, node_first->identifier);
2728 setString(&ti_new->name, "level sets");
2729 setString(&ti_new->name_sorting, ti_new->name);
2731 setString(&ti_new->subdir, STRING_TOP_DIRECTORY);
2732 setString(&ti_new->fullpath, ".");
2734 ti_new->sort_priority = node_first->sort_priority;;
2735 ti_new->latest_engine = node_first->latest_engine;
2737 setString(&ti_new->class_desc, "level sets");
2739 ti_new->node_group = node_first;
2740 ti_new->level_group = TRUE;
2742 ti_new2 = createParentTreeInfoNode(ti_new);
2744 setString(&ti_new2->name, ".. (main menu)");
2745 setString(&ti_new2->name_sorting, ti_new2->name);
2751 /* -------------------------------------------------------------------------- */
2752 /* functions for handling level and custom artwork info cache */
2753 /* -------------------------------------------------------------------------- */
2755 static void LoadArtworkInfoCache()
2757 InitCacheDirectory();
2759 if (artworkinfo_cache_old == NULL)
2761 char *filename = getPath2(getCacheDir(), ARTWORKINFO_CACHE_FILE);
2763 /* try to load artwork info hash from already existing cache file */
2764 artworkinfo_cache_old = loadSetupFileHash(filename);
2766 /* if no artwork info cache file was found, start with empty hash */
2767 if (artworkinfo_cache_old == NULL)
2768 artworkinfo_cache_old = newSetupFileHash();
2773 if (artworkinfo_cache_new == NULL)
2774 artworkinfo_cache_new = newSetupFileHash();
2777 static void SaveArtworkInfoCache()
2779 char *filename = getPath2(getCacheDir(), ARTWORKINFO_CACHE_FILE);
2781 InitCacheDirectory();
2783 saveSetupFileHash(artworkinfo_cache_new, filename);
2788 static char *getCacheTokenPrefix(char *prefix1, char *prefix2)
2790 static char *prefix = NULL;
2792 checked_free(prefix);
2794 prefix = getStringCat2WithSeparator(prefix1, prefix2, ".");
2799 /* (identical to above function, but separate string buffer needed -- nasty) */
2800 static char *getCacheToken(char *prefix, char *suffix)
2802 static char *token = NULL;
2804 checked_free(token);
2806 token = getStringCat2WithSeparator(prefix, suffix, ".");
2811 static char *getFileTimestampString(char *filename)
2813 return getStringCopy(i_to_a(getFileTimestampEpochSeconds(filename)));
2816 static boolean modifiedFileTimestamp(char *filename, char *timestamp_string)
2818 struct stat file_status;
2820 if (timestamp_string == NULL)
2823 if (stat(filename, &file_status) != 0) /* cannot stat file */
2826 return (file_status.st_mtime != atoi(timestamp_string));
2829 static TreeInfo *getArtworkInfoCacheEntry(LevelDirTree *level_node, int type)
2831 char *identifier = level_node->subdir;
2832 char *type_string = ARTWORK_DIRECTORY(type);
2833 char *token_prefix = getCacheTokenPrefix(type_string, identifier);
2834 char *token_main = getCacheToken(token_prefix, "CACHED");
2835 char *cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
2836 boolean cached = (cache_entry != NULL && strEqual(cache_entry, "true"));
2837 TreeInfo *artwork_info = NULL;
2839 if (!use_artworkinfo_cache)
2846 artwork_info = newTreeInfo();
2847 setTreeInfoToDefaults(artwork_info, type);
2849 /* set all structure fields according to the token/value pairs */
2850 ldi = *artwork_info;
2851 for (i = 0; artworkinfo_tokens[i].type != -1; i++)
2853 char *token = getCacheToken(token_prefix, artworkinfo_tokens[i].text);
2854 char *value = getHashEntry(artworkinfo_cache_old, token);
2856 /* if defined, use value from cache, else keep default value */
2858 setSetupInfo(artworkinfo_tokens, i, value);
2861 *artwork_info = ldi;
2863 char *filename_levelinfo = getPath2(getLevelDirFromTreeInfo(level_node),
2864 LEVELINFO_FILENAME);
2865 char *filename_artworkinfo = getPath2(getSetupArtworkDir(artwork_info),
2866 ARTWORKINFO_FILENAME(type));
2868 /* check if corresponding "levelinfo.conf" file has changed */
2869 token_main = getCacheToken(token_prefix, "TIMESTAMP_LEVELINFO");
2870 cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
2872 if (modifiedFileTimestamp(filename_levelinfo, cache_entry))
2875 /* check if corresponding "<artworkinfo>.conf" file has changed */
2876 token_main = getCacheToken(token_prefix, "TIMESTAMP_ARTWORKINFO");
2877 cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
2879 if (modifiedFileTimestamp(filename_artworkinfo, cache_entry))
2882 checked_free(filename_levelinfo);
2883 checked_free(filename_artworkinfo);
2886 if (!cached && artwork_info != NULL)
2888 freeTreeInfo(artwork_info);
2893 return artwork_info;
2896 static void setArtworkInfoCacheEntry(TreeInfo *artwork_info,
2897 LevelDirTree *level_node, int type)
2899 char *identifier = level_node->subdir;
2900 char *type_string = ARTWORK_DIRECTORY(type);
2901 char *token_prefix = getCacheTokenPrefix(type_string, identifier);
2902 char *token_main = getCacheToken(token_prefix, "CACHED");
2903 boolean set_cache_timestamps = TRUE;
2906 setHashEntry(artworkinfo_cache_new, token_main, "true");
2908 if (set_cache_timestamps)
2910 char *filename_levelinfo = getPath2(getLevelDirFromTreeInfo(level_node),
2911 LEVELINFO_FILENAME);
2912 char *filename_artworkinfo = getPath2(getSetupArtworkDir(artwork_info),
2913 ARTWORKINFO_FILENAME(type));
2914 char *timestamp_levelinfo = getFileTimestampString(filename_levelinfo);
2915 char *timestamp_artworkinfo = getFileTimestampString(filename_artworkinfo);
2917 token_main = getCacheToken(token_prefix, "TIMESTAMP_LEVELINFO");
2918 setHashEntry(artworkinfo_cache_new, token_main, timestamp_levelinfo);
2920 token_main = getCacheToken(token_prefix, "TIMESTAMP_ARTWORKINFO");
2921 setHashEntry(artworkinfo_cache_new, token_main, timestamp_artworkinfo);
2923 checked_free(filename_levelinfo);
2924 checked_free(filename_artworkinfo);
2925 checked_free(timestamp_levelinfo);
2926 checked_free(timestamp_artworkinfo);
2929 ldi = *artwork_info;
2930 for (i = 0; artworkinfo_tokens[i].type != -1; i++)
2932 char *token = getCacheToken(token_prefix, artworkinfo_tokens[i].text);
2933 char *value = getSetupValue(artworkinfo_tokens[i].type,
2934 artworkinfo_tokens[i].value);
2936 setHashEntry(artworkinfo_cache_new, token, value);
2941 /* -------------------------------------------------------------------------- */
2942 /* functions for loading level info and custom artwork info */
2943 /* -------------------------------------------------------------------------- */
2945 /* forward declaration for recursive call by "LoadLevelInfoFromLevelDir()" */
2946 static void LoadLevelInfoFromLevelDir(TreeInfo **, TreeInfo *, char *);
2948 static boolean LoadLevelInfoFromLevelConf(TreeInfo **node_first,
2949 TreeInfo *node_parent,
2950 char *level_directory,
2951 char *directory_name)
2953 char *directory_path = getPath2(level_directory, directory_name);
2954 char *filename = getPath2(directory_path, LEVELINFO_FILENAME);
2955 SetupFileHash *setup_file_hash;
2956 LevelDirTree *leveldir_new = NULL;
2959 /* unless debugging, silently ignore directories without "levelinfo.conf" */
2960 if (!options.debug && !fileExists(filename))
2962 free(directory_path);
2968 setup_file_hash = loadSetupFileHash(filename);
2970 if (setup_file_hash == NULL)
2972 #if DEBUG_NO_CONFIG_FILE
2973 Error(ERR_WARN, "ignoring level directory '%s'", directory_path);
2976 free(directory_path);
2982 leveldir_new = newTreeInfo();
2985 setTreeInfoToDefaultsFromParent(leveldir_new, node_parent);
2987 setTreeInfoToDefaults(leveldir_new, TREE_TYPE_LEVEL_DIR);
2989 leveldir_new->subdir = getStringCopy(directory_name);
2991 /* set all structure fields according to the token/value pairs */
2992 ldi = *leveldir_new;
2993 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
2994 setSetupInfo(levelinfo_tokens, i,
2995 getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
2996 *leveldir_new = ldi;
2998 if (strEqual(leveldir_new->name, ANONYMOUS_NAME))
2999 setString(&leveldir_new->name, leveldir_new->subdir);
3001 if (leveldir_new->identifier == NULL)
3002 leveldir_new->identifier = getStringCopy(leveldir_new->subdir);
3004 if (leveldir_new->name_sorting == NULL)
3005 leveldir_new->name_sorting = getStringCopy(leveldir_new->name);
3007 if (node_parent == NULL) /* top level group */
3009 leveldir_new->basepath = getStringCopy(level_directory);
3010 leveldir_new->fullpath = getStringCopy(leveldir_new->subdir);
3012 else /* sub level group */
3014 leveldir_new->basepath = getStringCopy(node_parent->basepath);
3015 leveldir_new->fullpath = getPath2(node_parent->fullpath, directory_name);
3018 leveldir_new->last_level =
3019 leveldir_new->first_level + leveldir_new->levels - 1;
3021 leveldir_new->in_user_dir =
3022 (!strEqual(leveldir_new->basepath, options.level_directory));
3024 /* adjust some settings if user's private level directory was detected */
3025 if (leveldir_new->sort_priority == LEVELCLASS_UNDEFINED &&
3026 leveldir_new->in_user_dir &&
3027 (strEqual(leveldir_new->subdir, getLoginName()) ||
3028 strEqual(leveldir_new->name, getLoginName()) ||
3029 strEqual(leveldir_new->author, getRealName())))
3031 leveldir_new->sort_priority = LEVELCLASS_PRIVATE_START;
3032 leveldir_new->readonly = FALSE;
3035 leveldir_new->user_defined =
3036 (leveldir_new->in_user_dir && IS_LEVELCLASS_PRIVATE(leveldir_new));
3038 leveldir_new->color = LEVELCOLOR(leveldir_new);
3040 setString(&leveldir_new->class_desc, getLevelClassDescription(leveldir_new));
3042 leveldir_new->handicap_level = /* set handicap to default value */
3043 (leveldir_new->user_defined || !leveldir_new->handicap ?
3044 leveldir_new->last_level : leveldir_new->first_level);
3046 DrawInitText(leveldir_new->name, 150, FC_YELLOW);
3048 pushTreeInfo(node_first, leveldir_new);
3050 freeSetupFileHash(setup_file_hash);
3052 if (leveldir_new->level_group)
3054 /* create node to link back to current level directory */
3055 createParentTreeInfoNode(leveldir_new);
3057 /* recursively step into sub-directory and look for more level series */
3058 LoadLevelInfoFromLevelDir(&leveldir_new->node_group,
3059 leveldir_new, directory_path);
3062 free(directory_path);
3068 static void LoadLevelInfoFromLevelDir(TreeInfo **node_first,
3069 TreeInfo *node_parent,
3070 char *level_directory)
3073 DirectoryEntry *dir_entry;
3074 boolean valid_entry_found = FALSE;
3076 if ((dir = openDirectory(level_directory)) == NULL)
3078 Error(ERR_WARN, "cannot read level directory '%s'", level_directory);
3083 while ((dir_entry = readDirectory(dir)) != NULL) /* loop all entries */
3085 char *directory_name = dir_entry->basename;
3086 char *directory_path = getPath2(level_directory, directory_name);
3088 /* skip entries for current and parent directory */
3089 if (strEqual(directory_name, ".") ||
3090 strEqual(directory_name, ".."))
3092 free(directory_path);
3097 /* find out if directory entry is itself a directory */
3098 if (!dir_entry->is_directory) /* not a directory */
3100 free(directory_path);
3105 free(directory_path);
3107 if (strEqual(directory_name, GRAPHICS_DIRECTORY) ||
3108 strEqual(directory_name, SOUNDS_DIRECTORY) ||
3109 strEqual(directory_name, MUSIC_DIRECTORY))
3112 valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
3117 closeDirectory(dir);
3119 /* special case: top level directory may directly contain "levelinfo.conf" */
3120 if (node_parent == NULL && !valid_entry_found)
3122 /* check if this directory directly contains a file "levelinfo.conf" */
3123 valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
3124 level_directory, ".");
3127 if (!valid_entry_found)
3128 Error(ERR_WARN, "cannot find any valid level series in directory '%s'",
3132 boolean AdjustGraphicsForEMC()
3134 boolean settings_changed = FALSE;
3136 settings_changed |= adjustTreeGraphicsForEMC(leveldir_first_all);
3137 settings_changed |= adjustTreeGraphicsForEMC(leveldir_first);
3139 return settings_changed;
3142 void LoadLevelInfo()
3144 InitUserLevelDirectory(getLoginName());
3146 DrawInitText("Loading level series", 120, FC_GREEN);
3148 LoadLevelInfoFromLevelDir(&leveldir_first, NULL, options.level_directory);
3149 LoadLevelInfoFromLevelDir(&leveldir_first, NULL, getUserLevelDir(NULL));
3151 leveldir_first = createTopTreeInfoNode(leveldir_first);
3153 /* after loading all level set information, clone the level directory tree
3154 and remove all level sets without levels (these may still contain artwork
3155 to be offered in the setup menu as "custom artwork", and are therefore
3156 checked for existing artwork in the function "LoadLevelArtworkInfo()") */
3157 leveldir_first_all = leveldir_first;
3158 cloneTree(&leveldir_first, leveldir_first_all, TRUE);
3160 AdjustGraphicsForEMC();
3162 /* before sorting, the first entries will be from the user directory */
3163 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
3165 if (leveldir_first == NULL)
3166 Error(ERR_EXIT, "cannot find any valid level series in any directory");
3168 sortTreeInfo(&leveldir_first);
3170 #if ENABLE_UNUSED_CODE
3171 dumpTreeInfo(leveldir_first, 0);
3175 static boolean LoadArtworkInfoFromArtworkConf(TreeInfo **node_first,
3176 TreeInfo *node_parent,
3177 char *base_directory,
3178 char *directory_name, int type)
3180 char *directory_path = getPath2(base_directory, directory_name);
3181 char *filename = getPath2(directory_path, ARTWORKINFO_FILENAME(type));
3182 SetupFileHash *setup_file_hash = NULL;
3183 TreeInfo *artwork_new = NULL;
3186 if (fileExists(filename))
3187 setup_file_hash = loadSetupFileHash(filename);
3189 if (setup_file_hash == NULL) /* no config file -- look for artwork files */
3192 DirectoryEntry *dir_entry;
3193 boolean valid_file_found = FALSE;
3195 if ((dir = openDirectory(directory_path)) != NULL)
3197 while ((dir_entry = readDirectory(dir)) != NULL)
3199 if (FileIsArtworkType(dir_entry->filename, type))
3201 valid_file_found = TRUE;
3207 closeDirectory(dir);
3210 if (!valid_file_found)
3212 #if DEBUG_NO_CONFIG_FILE
3213 if (!strEqual(directory_name, "."))
3214 Error(ERR_WARN, "ignoring artwork directory '%s'", directory_path);
3217 free(directory_path);
3224 artwork_new = newTreeInfo();
3227 setTreeInfoToDefaultsFromParent(artwork_new, node_parent);
3229 setTreeInfoToDefaults(artwork_new, type);
3231 artwork_new->subdir = getStringCopy(directory_name);
3233 if (setup_file_hash) /* (before defining ".color" and ".class_desc") */
3235 /* set all structure fields according to the token/value pairs */
3237 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
3238 setSetupInfo(levelinfo_tokens, i,
3239 getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
3242 if (strEqual(artwork_new->name, ANONYMOUS_NAME))
3243 setString(&artwork_new->name, artwork_new->subdir);
3245 if (artwork_new->identifier == NULL)
3246 artwork_new->identifier = getStringCopy(artwork_new->subdir);
3248 if (artwork_new->name_sorting == NULL)
3249 artwork_new->name_sorting = getStringCopy(artwork_new->name);
3252 if (node_parent == NULL) /* top level group */
3254 artwork_new->basepath = getStringCopy(base_directory);
3255 artwork_new->fullpath = getStringCopy(artwork_new->subdir);
3257 else /* sub level group */
3259 artwork_new->basepath = getStringCopy(node_parent->basepath);
3260 artwork_new->fullpath = getPath2(node_parent->fullpath, directory_name);
3263 artwork_new->in_user_dir =
3264 (!strEqual(artwork_new->basepath, OPTIONS_ARTWORK_DIRECTORY(type)));
3266 /* (may use ".sort_priority" from "setup_file_hash" above) */
3267 artwork_new->color = ARTWORKCOLOR(artwork_new);
3269 setString(&artwork_new->class_desc, getLevelClassDescription(artwork_new));
3271 if (setup_file_hash == NULL) /* (after determining ".user_defined") */
3273 if (strEqual(artwork_new->subdir, "."))
3275 if (artwork_new->user_defined)
3277 setString(&artwork_new->identifier, "private");
3278 artwork_new->sort_priority = ARTWORKCLASS_PRIVATE;
3282 setString(&artwork_new->identifier, "classic");
3283 artwork_new->sort_priority = ARTWORKCLASS_CLASSICS;
3286 /* set to new values after changing ".sort_priority" */
3287 artwork_new->color = ARTWORKCOLOR(artwork_new);
3289 setString(&artwork_new->class_desc,
3290 getLevelClassDescription(artwork_new));
3294 setString(&artwork_new->identifier, artwork_new->subdir);
3297 setString(&artwork_new->name, artwork_new->identifier);
3298 setString(&artwork_new->name_sorting, artwork_new->name);
3301 pushTreeInfo(node_first, artwork_new);
3303 freeSetupFileHash(setup_file_hash);
3305 free(directory_path);
3311 static void LoadArtworkInfoFromArtworkDir(TreeInfo **node_first,
3312 TreeInfo *node_parent,
3313 char *base_directory, int type)
3316 DirectoryEntry *dir_entry;
3317 boolean valid_entry_found = FALSE;
3319 if ((dir = openDirectory(base_directory)) == NULL)
3321 /* display error if directory is main "options.graphics_directory" etc. */
3322 if (base_directory == OPTIONS_ARTWORK_DIRECTORY(type))
3323 Error(ERR_WARN, "cannot read directory '%s'", base_directory);
3328 while ((dir_entry = readDirectory(dir)) != NULL) /* loop all entries */
3330 char *directory_name = dir_entry->basename;
3331 char *directory_path = getPath2(base_directory, directory_name);
3333 /* skip directory entries for current and parent directory */
3334 if (strEqual(directory_name, ".") ||
3335 strEqual(directory_name, ".."))
3337 free(directory_path);
3342 /* skip directory entries which are not a directory */
3343 if (!dir_entry->is_directory) /* not a directory */
3345 free(directory_path);
3350 free(directory_path);
3352 /* check if this directory contains artwork with or without config file */
3353 valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
3355 directory_name, type);
3358 closeDirectory(dir);
3360 /* check if this directory directly contains artwork itself */
3361 valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
3362 base_directory, ".",
3364 if (!valid_entry_found)
3365 Error(ERR_WARN, "cannot find any valid artwork in directory '%s'",
3369 static TreeInfo *getDummyArtworkInfo(int type)
3371 /* this is only needed when there is completely no artwork available */
3372 TreeInfo *artwork_new = newTreeInfo();
3374 setTreeInfoToDefaults(artwork_new, type);
3376 setString(&artwork_new->subdir, UNDEFINED_FILENAME);
3377 setString(&artwork_new->fullpath, UNDEFINED_FILENAME);
3378 setString(&artwork_new->basepath, UNDEFINED_FILENAME);
3380 setString(&artwork_new->identifier, UNDEFINED_FILENAME);
3381 setString(&artwork_new->name, UNDEFINED_FILENAME);
3382 setString(&artwork_new->name_sorting, UNDEFINED_FILENAME);
3387 void LoadArtworkInfo()
3389 LoadArtworkInfoCache();
3391 DrawInitText("Looking for custom artwork", 120, FC_GREEN);
3393 LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
3394 options.graphics_directory,
3395 TREE_TYPE_GRAPHICS_DIR);
3396 LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
3397 getUserGraphicsDir(),
3398 TREE_TYPE_GRAPHICS_DIR);
3400 LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
3401 options.sounds_directory,
3402 TREE_TYPE_SOUNDS_DIR);
3403 LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
3405 TREE_TYPE_SOUNDS_DIR);
3407 LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
3408 options.music_directory,
3409 TREE_TYPE_MUSIC_DIR);
3410 LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
3412 TREE_TYPE_MUSIC_DIR);
3414 if (artwork.gfx_first == NULL)
3415 artwork.gfx_first = getDummyArtworkInfo(TREE_TYPE_GRAPHICS_DIR);
3416 if (artwork.snd_first == NULL)
3417 artwork.snd_first = getDummyArtworkInfo(TREE_TYPE_SOUNDS_DIR);
3418 if (artwork.mus_first == NULL)
3419 artwork.mus_first = getDummyArtworkInfo(TREE_TYPE_MUSIC_DIR);
3421 /* before sorting, the first entries will be from the user directory */
3422 artwork.gfx_current =
3423 getTreeInfoFromIdentifier(artwork.gfx_first, setup.graphics_set);
3424 if (artwork.gfx_current == NULL)
3425 artwork.gfx_current =
3426 getTreeInfoFromIdentifier(artwork.gfx_first, GFX_DEFAULT_SUBDIR);
3427 if (artwork.gfx_current == NULL)
3428 artwork.gfx_current = getFirstValidTreeInfoEntry(artwork.gfx_first);
3430 artwork.snd_current =
3431 getTreeInfoFromIdentifier(artwork.snd_first, setup.sounds_set);
3432 if (artwork.snd_current == NULL)
3433 artwork.snd_current =
3434 getTreeInfoFromIdentifier(artwork.snd_first, SND_DEFAULT_SUBDIR);
3435 if (artwork.snd_current == NULL)
3436 artwork.snd_current = getFirstValidTreeInfoEntry(artwork.snd_first);
3438 artwork.mus_current =
3439 getTreeInfoFromIdentifier(artwork.mus_first, setup.music_set);
3440 if (artwork.mus_current == NULL)
3441 artwork.mus_current =
3442 getTreeInfoFromIdentifier(artwork.mus_first, MUS_DEFAULT_SUBDIR);
3443 if (artwork.mus_current == NULL)
3444 artwork.mus_current = getFirstValidTreeInfoEntry(artwork.mus_first);
3446 artwork.gfx_current_identifier = artwork.gfx_current->identifier;
3447 artwork.snd_current_identifier = artwork.snd_current->identifier;
3448 artwork.mus_current_identifier = artwork.mus_current->identifier;
3450 #if ENABLE_UNUSED_CODE
3451 printf("graphics set == %s\n\n", artwork.gfx_current_identifier);
3452 printf("sounds set == %s\n\n", artwork.snd_current_identifier);
3453 printf("music set == %s\n\n", artwork.mus_current_identifier);
3456 sortTreeInfo(&artwork.gfx_first);
3457 sortTreeInfo(&artwork.snd_first);
3458 sortTreeInfo(&artwork.mus_first);
3460 #if ENABLE_UNUSED_CODE
3461 dumpTreeInfo(artwork.gfx_first, 0);
3462 dumpTreeInfo(artwork.snd_first, 0);
3463 dumpTreeInfo(artwork.mus_first, 0);
3467 void LoadArtworkInfoFromLevelInfo(ArtworkDirTree **artwork_node,
3468 LevelDirTree *level_node)
3470 int type = (*artwork_node)->type;
3472 /* recursively check all level directories for artwork sub-directories */
3476 /* check all tree entries for artwork, but skip parent link entries */
3477 if (!level_node->parent_link)
3479 TreeInfo *artwork_new = getArtworkInfoCacheEntry(level_node, type);
3480 boolean cached = (artwork_new != NULL);
3484 pushTreeInfo(artwork_node, artwork_new);
3488 TreeInfo *topnode_last = *artwork_node;
3489 char *path = getPath2(getLevelDirFromTreeInfo(level_node),
3490 ARTWORK_DIRECTORY(type));
3492 LoadArtworkInfoFromArtworkDir(artwork_node, NULL, path, type);
3494 if (topnode_last != *artwork_node) /* check for newly added node */
3496 artwork_new = *artwork_node;
3498 setString(&artwork_new->identifier, level_node->subdir);
3499 setString(&artwork_new->name, level_node->name);
3500 setString(&artwork_new->name_sorting, level_node->name_sorting);
3502 artwork_new->sort_priority = level_node->sort_priority;
3503 artwork_new->color = LEVELCOLOR(artwork_new);
3509 /* insert artwork info (from old cache or filesystem) into new cache */
3510 if (artwork_new != NULL)
3511 setArtworkInfoCacheEntry(artwork_new, level_node, type);
3514 DrawInitText(level_node->name, 150, FC_YELLOW);
3516 if (level_node->node_group != NULL)
3517 LoadArtworkInfoFromLevelInfo(artwork_node, level_node->node_group);
3519 level_node = level_node->next;
3523 void LoadLevelArtworkInfo()
3525 print_timestamp_init("LoadLevelArtworkInfo");
3527 DrawInitText("Looking for custom level artwork", 120, FC_GREEN);
3529 print_timestamp_time("DrawTimeText");
3531 LoadArtworkInfoFromLevelInfo(&artwork.gfx_first, leveldir_first_all);
3532 print_timestamp_time("LoadArtworkInfoFromLevelInfo (gfx)");
3533 LoadArtworkInfoFromLevelInfo(&artwork.snd_first, leveldir_first_all);
3534 print_timestamp_time("LoadArtworkInfoFromLevelInfo (snd)");
3535 LoadArtworkInfoFromLevelInfo(&artwork.mus_first, leveldir_first_all);
3536 print_timestamp_time("LoadArtworkInfoFromLevelInfo (mus)");
3538 SaveArtworkInfoCache();
3540 print_timestamp_time("SaveArtworkInfoCache");
3542 /* needed for reloading level artwork not known at ealier stage */
3544 if (!strEqual(artwork.gfx_current_identifier, setup.graphics_set))
3546 artwork.gfx_current =
3547 getTreeInfoFromIdentifier(artwork.gfx_first, setup.graphics_set);
3548 if (artwork.gfx_current == NULL)
3549 artwork.gfx_current =
3550 getTreeInfoFromIdentifier(artwork.gfx_first, GFX_DEFAULT_SUBDIR);
3551 if (artwork.gfx_current == NULL)
3552 artwork.gfx_current = getFirstValidTreeInfoEntry(artwork.gfx_first);
3555 if (!strEqual(artwork.snd_current_identifier, setup.sounds_set))
3557 artwork.snd_current =
3558 getTreeInfoFromIdentifier(artwork.snd_first, setup.sounds_set);
3559 if (artwork.snd_current == NULL)
3560 artwork.snd_current =
3561 getTreeInfoFromIdentifier(artwork.snd_first, SND_DEFAULT_SUBDIR);
3562 if (artwork.snd_current == NULL)
3563 artwork.snd_current = getFirstValidTreeInfoEntry(artwork.snd_first);
3566 if (!strEqual(artwork.mus_current_identifier, setup.music_set))
3568 artwork.mus_current =
3569 getTreeInfoFromIdentifier(artwork.mus_first, setup.music_set);
3570 if (artwork.mus_current == NULL)
3571 artwork.mus_current =
3572 getTreeInfoFromIdentifier(artwork.mus_first, MUS_DEFAULT_SUBDIR);
3573 if (artwork.mus_current == NULL)
3574 artwork.mus_current = getFirstValidTreeInfoEntry(artwork.mus_first);
3577 print_timestamp_time("getTreeInfoFromIdentifier");
3579 sortTreeInfo(&artwork.gfx_first);
3580 sortTreeInfo(&artwork.snd_first);
3581 sortTreeInfo(&artwork.mus_first);
3583 print_timestamp_time("sortTreeInfo");
3585 #if ENABLE_UNUSED_CODE
3586 dumpTreeInfo(artwork.gfx_first, 0);
3587 dumpTreeInfo(artwork.snd_first, 0);
3588 dumpTreeInfo(artwork.mus_first, 0);
3591 print_timestamp_done("LoadLevelArtworkInfo");
3594 static boolean AddUserLevelSetToLevelInfoExt(char *level_subdir_new)
3596 // get level info tree node of first (original) user level set
3597 char *level_subdir_old = getLoginName();
3598 LevelDirTree *leveldir_old = getTreeInfoFromIdentifier(leveldir_first,
3600 if (leveldir_old == NULL) // should not happen
3603 int draw_deactivation_mask = GetDrawDeactivationMask();
3605 // override draw deactivation mask (temporarily disable drawing)
3606 SetDrawDeactivationMask(REDRAW_ALL);
3608 // load new level set config and add it next to first user level set
3609 LoadLevelInfoFromLevelConf(&leveldir_old->next, NULL,
3610 leveldir_old->basepath, level_subdir_new);
3612 // set draw deactivation mask to previous value
3613 SetDrawDeactivationMask(draw_deactivation_mask);
3615 // get level info tree node of newly added user level set
3616 LevelDirTree *leveldir_new = getTreeInfoFromIdentifier(leveldir_first,
3618 if (leveldir_new == NULL) // should not happen
3621 // correct top link and parent node link of newly created tree node
3622 leveldir_new->node_top = leveldir_old->node_top;
3623 leveldir_new->node_parent = leveldir_old->node_parent;
3625 // sort level info tree to adjust position of newly added level set
3626 sortTreeInfo(&leveldir_first);
3631 void AddUserLevelSetToLevelInfo(char *level_subdir_new)
3633 if (!AddUserLevelSetToLevelInfoExt(level_subdir_new))
3634 Error(ERR_EXIT, "internal level set structure corrupted -- aborting");
3637 char *getArtworkIdentifierForUserLevelSet(int type)
3639 char *classic_artwork_set = getClassicArtworkSet(type);
3641 /* check for custom artwork configured in "levelinfo.conf" */
3642 char *leveldir_artwork_set =
3643 *LEVELDIR_ARTWORK_SET_PTR(leveldir_current, type);
3644 boolean has_leveldir_artwork_set =
3645 (leveldir_artwork_set != NULL && !strEqual(leveldir_artwork_set,
3646 classic_artwork_set));
3648 /* check for custom artwork in sub-directory "graphics" etc. */
3649 TreeInfo *artwork_first_node = ARTWORK_FIRST_NODE(artwork, type);
3650 char *leveldir_identifier = leveldir_current->identifier;
3651 boolean has_artwork_subdir =
3652 (getTreeInfoFromIdentifier(artwork_first_node,
3653 leveldir_identifier) != NULL);
3655 return (has_leveldir_artwork_set ? leveldir_artwork_set :
3656 has_artwork_subdir ? leveldir_identifier :
3657 classic_artwork_set);
3660 TreeInfo *getArtworkTreeInfoForUserLevelSet(int type)
3662 char *artwork_set = getArtworkIdentifierForUserLevelSet(type);
3663 TreeInfo *artwork_first_node = ARTWORK_FIRST_NODE(artwork, type);
3665 return getTreeInfoFromIdentifier(artwork_first_node, artwork_set);
3668 boolean checkIfCustomArtworkExistsForCurrentLevelSet()
3670 char *graphics_set =
3671 getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_GRAPHICS);
3673 getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_SOUNDS);
3675 getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_MUSIC);
3677 return (!strEqual(graphics_set, GFX_CLASSIC_SUBDIR) ||
3678 !strEqual(sounds_set, SND_CLASSIC_SUBDIR) ||
3679 !strEqual(music_set, MUS_CLASSIC_SUBDIR));
3682 boolean UpdateUserLevelSet(char *level_subdir, char *level_name,
3683 char *level_author, int num_levels)
3685 char *filename = getPath2(getUserLevelDir(level_subdir), LEVELINFO_FILENAME);
3686 char *filename_tmp = getStringCat2(filename, ".tmp");
3688 FILE *file_tmp = NULL;
3689 char line[MAX_LINE_LEN];
3690 boolean success = FALSE;
3691 LevelDirTree *leveldir = getTreeInfoFromIdentifier(leveldir_first,
3693 // update values in level directory tree
3695 if (level_name != NULL)
3696 setString(&leveldir->name, level_name);
3698 if (level_author != NULL)
3699 setString(&leveldir->author, level_author);
3701 if (num_levels != -1)
3702 leveldir->levels = num_levels;
3704 // update values that depend on other values
3706 setString(&leveldir->name_sorting, leveldir->name);
3708 leveldir->last_level = leveldir->first_level + leveldir->levels - 1;
3710 // sort order of level sets may have changed
3711 sortTreeInfo(&leveldir_first);
3713 if ((file = fopen(filename, MODE_READ)) &&
3714 (file_tmp = fopen(filename_tmp, MODE_WRITE)))
3716 while (fgets(line, MAX_LINE_LEN, file))
3718 if (strPrefix(line, "name:") && level_name != NULL)
3719 fprintf(file_tmp, "%-32s%s\n", "name:", level_name);
3720 else if (strPrefix(line, "author:") && level_author != NULL)
3721 fprintf(file_tmp, "%-32s%s\n", "author:", level_author);
3722 else if (strPrefix(line, "levels:") && num_levels != -1)
3723 fprintf(file_tmp, "%-32s%d\n", "levels:", num_levels);
3725 fputs(line, file_tmp);
3738 success = (rename(filename_tmp, filename) == 0);
3746 boolean CreateUserLevelSet(char *level_subdir, char *level_name,
3747 char *level_author, int num_levels,
3748 boolean use_artwork_set)
3750 LevelDirTree *level_info;
3755 // create user level sub-directory, if needed
3756 createDirectory(getUserLevelDir(level_subdir), "user level", PERMS_PRIVATE);
3758 filename = getPath2(getUserLevelDir(level_subdir), LEVELINFO_FILENAME);
3760 if (!(file = fopen(filename, MODE_WRITE)))
3762 Error(ERR_WARN, "cannot write level info file '%s'", filename);
3768 level_info = newTreeInfo();
3770 /* always start with reliable default values */
3771 setTreeInfoToDefaults(level_info, TREE_TYPE_LEVEL_DIR);
3773 setString(&level_info->name, level_name);
3774 setString(&level_info->author, level_author);
3775 level_info->levels = num_levels;
3776 level_info->first_level = 1;
3777 level_info->sort_priority = LEVELCLASS_PRIVATE_START;
3778 level_info->readonly = FALSE;
3780 if (use_artwork_set)
3782 level_info->graphics_set =
3783 getStringCopy(getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_GRAPHICS));
3784 level_info->sounds_set =
3785 getStringCopy(getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_SOUNDS));
3786 level_info->music_set =
3787 getStringCopy(getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_MUSIC));
3790 token_value_position = TOKEN_VALUE_POSITION_SHORT;
3792 fprintFileHeader(file, LEVELINFO_FILENAME);
3795 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
3797 if (i == LEVELINFO_TOKEN_NAME ||
3798 i == LEVELINFO_TOKEN_AUTHOR ||
3799 i == LEVELINFO_TOKEN_LEVELS ||
3800 i == LEVELINFO_TOKEN_FIRST_LEVEL ||
3801 i == LEVELINFO_TOKEN_SORT_PRIORITY ||
3802 i == LEVELINFO_TOKEN_READONLY ||
3803 (use_artwork_set && (i == LEVELINFO_TOKEN_GRAPHICS_SET ||
3804 i == LEVELINFO_TOKEN_SOUNDS_SET ||
3805 i == LEVELINFO_TOKEN_MUSIC_SET)))
3806 fprintf(file, "%s\n", getSetupLine(levelinfo_tokens, "", i));
3808 /* just to make things nicer :) */
3809 if (i == LEVELINFO_TOKEN_AUTHOR ||
3810 i == LEVELINFO_TOKEN_FIRST_LEVEL ||
3811 (use_artwork_set && i == LEVELINFO_TOKEN_READONLY))
3812 fprintf(file, "\n");
3815 token_value_position = TOKEN_VALUE_POSITION_DEFAULT;
3819 SetFilePermissions(filename, PERMS_PRIVATE);
3821 freeTreeInfo(level_info);
3827 static void SaveUserLevelInfo()
3829 CreateUserLevelSet(getLoginName(), getLoginName(), getRealName(), 100, FALSE);
3832 char *getSetupValue(int type, void *value)
3834 static char value_string[MAX_LINE_LEN];
3842 strcpy(value_string, (*(boolean *)value ? "true" : "false"));
3846 strcpy(value_string, (*(boolean *)value ? "on" : "off"));
3850 strcpy(value_string, (*(int *)value == AUTO ? "auto" :
3851 *(int *)value == FALSE ? "off" : "on"));
3855 strcpy(value_string, (*(boolean *)value ? "yes" : "no"));
3858 case TYPE_YES_NO_AUTO:
3859 strcpy(value_string, (*(int *)value == AUTO ? "auto" :
3860 *(int *)value == FALSE ? "no" : "yes"));
3864 strcpy(value_string, (*(boolean *)value ? "AGA" : "ECS"));
3868 strcpy(value_string, getKeyNameFromKey(*(Key *)value));
3872 strcpy(value_string, getX11KeyNameFromKey(*(Key *)value));
3876 sprintf(value_string, "%d", *(int *)value);
3880 if (*(char **)value == NULL)
3883 strcpy(value_string, *(char **)value);
3887 sprintf(value_string, "player_%d", *(int *)value + 1);
3891 value_string[0] = '\0';
3895 if (type & TYPE_GHOSTED)
3896 strcpy(value_string, "n/a");
3898 return value_string;
3901 char *getSetupLine(struct TokenInfo *token_info, char *prefix, int token_nr)
3905 static char token_string[MAX_LINE_LEN];
3906 int token_type = token_info[token_nr].type;
3907 void *setup_value = token_info[token_nr].value;
3908 char *token_text = token_info[token_nr].text;
3909 char *value_string = getSetupValue(token_type, setup_value);
3911 /* build complete token string */
3912 sprintf(token_string, "%s%s", prefix, token_text);
3914 /* build setup entry line */
3915 line = getFormattedSetupEntry(token_string, value_string);
3917 if (token_type == TYPE_KEY_X11)
3919 Key key = *(Key *)setup_value;
3920 char *keyname = getKeyNameFromKey(key);
3922 /* add comment, if useful */
3923 if (!strEqual(keyname, "(undefined)") &&
3924 !strEqual(keyname, "(unknown)"))
3926 /* add at least one whitespace */
3928 for (i = strlen(line); i < token_comment_position; i++)
3932 strcat(line, keyname);
3939 void LoadLevelSetup_LastSeries()
3941 /* ----------------------------------------------------------------------- */
3942 /* ~/.<program>/levelsetup.conf */
3943 /* ----------------------------------------------------------------------- */
3945 char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
3946 SetupFileHash *level_setup_hash = NULL;
3948 /* always start with reliable default values */
3949 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
3951 if (!strEqual(DEFAULT_LEVELSET, UNDEFINED_LEVELSET))
3953 leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
3955 if (leveldir_current == NULL)
3956 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
3959 if ((level_setup_hash = loadSetupFileHash(filename)))
3961 char *last_level_series =
3962 getHashEntry(level_setup_hash, TOKEN_STR_LAST_LEVEL_SERIES);
3964 leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
3966 if (leveldir_current == NULL)
3967 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
3969 freeSetupFileHash(level_setup_hash);
3973 Error(ERR_DEBUG, "using default setup values");
3979 static void SaveLevelSetup_LastSeries_Ext(boolean deactivate_last_level_series)
3981 /* ----------------------------------------------------------------------- */
3982 /* ~/.<program>/levelsetup.conf */
3983 /* ----------------------------------------------------------------------- */
3985 // check if the current level directory structure is available at this point
3986 if (leveldir_current == NULL)
3989 char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
3990 char *level_subdir = leveldir_current->subdir;
3993 InitUserDataDirectory();
3995 if (!(file = fopen(filename, MODE_WRITE)))
3997 Error(ERR_WARN, "cannot write setup file '%s'", filename);
4004 fprintFileHeader(file, LEVELSETUP_FILENAME);
4006 if (deactivate_last_level_series)
4007 fprintf(file, "# %s\n# ", "the following level set may have caused a problem and was deactivated");
4009 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_LEVEL_SERIES,
4014 SetFilePermissions(filename, PERMS_PRIVATE);
4019 void SaveLevelSetup_LastSeries()
4021 SaveLevelSetup_LastSeries_Ext(FALSE);
4024 void SaveLevelSetup_LastSeries_Deactivate()
4026 SaveLevelSetup_LastSeries_Ext(TRUE);
4029 static void checkSeriesInfo()
4031 static char *level_directory = NULL;
4034 /* check for more levels besides the 'levels' field of 'levelinfo.conf' */
4036 level_directory = getPath2((leveldir_current->in_user_dir ?
4037 getUserLevelDir(NULL) :
4038 options.level_directory),
4039 leveldir_current->fullpath);
4041 if ((dir = openDirectory(level_directory)) == NULL)
4043 Error(ERR_WARN, "cannot read level directory '%s'", level_directory);
4048 closeDirectory(dir);
4051 void LoadLevelSetup_SeriesInfo()
4054 SetupFileHash *level_setup_hash = NULL;
4055 char *level_subdir = leveldir_current->subdir;
4058 /* always start with reliable default values */
4059 level_nr = leveldir_current->first_level;
4061 for (i = 0; i < MAX_LEVELS; i++)
4063 LevelStats_setPlayed(i, 0);
4064 LevelStats_setSolved(i, 0);
4069 /* ----------------------------------------------------------------------- */
4070 /* ~/.<program>/levelsetup/<level series>/levelsetup.conf */
4071 /* ----------------------------------------------------------------------- */
4073 level_subdir = leveldir_current->subdir;
4075 filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
4077 if ((level_setup_hash = loadSetupFileHash(filename)))
4081 /* get last played level in this level set */
4083 token_value = getHashEntry(level_setup_hash, TOKEN_STR_LAST_PLAYED_LEVEL);
4087 level_nr = atoi(token_value);
4089 if (level_nr < leveldir_current->first_level)
4090 level_nr = leveldir_current->first_level;
4091 if (level_nr > leveldir_current->last_level)
4092 level_nr = leveldir_current->last_level;
4095 /* get handicap level in this level set */
4097 token_value = getHashEntry(level_setup_hash, TOKEN_STR_HANDICAP_LEVEL);
4101 int level_nr = atoi(token_value);
4103 if (level_nr < leveldir_current->first_level)
4104 level_nr = leveldir_current->first_level;
4105 if (level_nr > leveldir_current->last_level + 1)
4106 level_nr = leveldir_current->last_level;
4108 if (leveldir_current->user_defined || !leveldir_current->handicap)
4109 level_nr = leveldir_current->last_level;
4111 leveldir_current->handicap_level = level_nr;
4114 /* get number of played and solved levels in this level set */
4116 BEGIN_HASH_ITERATION(level_setup_hash, itr)
4118 char *token = HASH_ITERATION_TOKEN(itr);
4119 char *value = HASH_ITERATION_VALUE(itr);
4121 if (strlen(token) == 3 &&
4122 token[0] >= '0' && token[0] <= '9' &&
4123 token[1] >= '0' && token[1] <= '9' &&
4124 token[2] >= '0' && token[2] <= '9')
4126 int level_nr = atoi(token);
4129 LevelStats_setPlayed(level_nr, atoi(value)); /* read 1st column */
4131 value = strchr(value, ' ');
4134 LevelStats_setSolved(level_nr, atoi(value)); /* read 2nd column */
4137 END_HASH_ITERATION(hash, itr)
4139 freeSetupFileHash(level_setup_hash);
4143 Error(ERR_DEBUG, "using default setup values");
4149 void SaveLevelSetup_SeriesInfo()
4152 char *level_subdir = leveldir_current->subdir;
4153 char *level_nr_str = int2str(level_nr, 0);
4154 char *handicap_level_str = int2str(leveldir_current->handicap_level, 0);
4158 /* ----------------------------------------------------------------------- */
4159 /* ~/.<program>/levelsetup/<level series>/levelsetup.conf */
4160 /* ----------------------------------------------------------------------- */
4162 InitLevelSetupDirectory(level_subdir);
4164 filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
4166 if (!(file = fopen(filename, MODE_WRITE)))
4168 Error(ERR_WARN, "cannot write setup file '%s'", filename);
4173 fprintFileHeader(file, LEVELSETUP_FILENAME);
4175 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_PLAYED_LEVEL,
4177 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_HANDICAP_LEVEL,
4178 handicap_level_str));
4180 for (i = leveldir_current->first_level; i <= leveldir_current->last_level;
4183 if (LevelStats_getPlayed(i) > 0 ||
4184 LevelStats_getSolved(i) > 0)
4189 sprintf(token, "%03d", i);
4190 sprintf(value, "%d %d", LevelStats_getPlayed(i), LevelStats_getSolved(i));
4192 fprintf(file, "%s\n", getFormattedSetupEntry(token, value));
4198 SetFilePermissions(filename, PERMS_PRIVATE);
4203 int LevelStats_getPlayed(int nr)
4205 return (nr >= 0 && nr < MAX_LEVELS ? level_stats[nr].played : 0);
4208 int LevelStats_getSolved(int nr)
4210 return (nr >= 0 && nr < MAX_LEVELS ? level_stats[nr].solved : 0);
4213 void LevelStats_setPlayed(int nr, int value)
4215 if (nr >= 0 && nr < MAX_LEVELS)
4216 level_stats[nr].played = value;
4219 void LevelStats_setSolved(int nr, int value)
4221 if (nr >= 0 && nr < MAX_LEVELS)
4222 level_stats[nr].solved = value;
4225 void LevelStats_incPlayed(int nr)
4227 if (nr >= 0 && nr < MAX_LEVELS)
4228 level_stats[nr].played++;
4231 void LevelStats_incSolved(int nr)
4233 if (nr >= 0 && nr < MAX_LEVELS)
4234 level_stats[nr].solved++;