1 // ============================================================================
2 // Artsoft Retro-Game Library
3 // ----------------------------------------------------------------------------
4 // (c) 1995-2014 by Artsoft Entertainment
7 // http://www.artsoft.org/
8 // ----------------------------------------------------------------------------
10 // ============================================================================
12 #include <sys/types.h>
21 #if !defined(PLATFORM_WIN32)
23 #include <sys/param.h>
33 #define ENABLE_UNUSED_CODE FALSE /* for currently unused functions */
35 #define NUM_LEVELCLASS_DESC 8
37 static char *levelclass_desc[NUM_LEVELCLASS_DESC] =
50 #define LEVELCOLOR(n) (IS_LEVELCLASS_TUTORIAL(n) ? FC_BLUE : \
51 IS_LEVELCLASS_CLASSICS(n) ? FC_RED : \
52 IS_LEVELCLASS_BD(n) ? FC_YELLOW : \
53 IS_LEVELCLASS_EM(n) ? FC_YELLOW : \
54 IS_LEVELCLASS_SP(n) ? FC_YELLOW : \
55 IS_LEVELCLASS_DX(n) ? FC_YELLOW : \
56 IS_LEVELCLASS_SB(n) ? FC_YELLOW : \
57 IS_LEVELCLASS_CONTRIB(n) ? FC_GREEN : \
58 IS_LEVELCLASS_PRIVATE(n) ? FC_RED : \
61 #define LEVELSORTING(n) (IS_LEVELCLASS_TUTORIAL(n) ? 0 : \
62 IS_LEVELCLASS_CLASSICS(n) ? 1 : \
63 IS_LEVELCLASS_BD(n) ? 2 : \
64 IS_LEVELCLASS_EM(n) ? 3 : \
65 IS_LEVELCLASS_SP(n) ? 4 : \
66 IS_LEVELCLASS_DX(n) ? 5 : \
67 IS_LEVELCLASS_SB(n) ? 6 : \
68 IS_LEVELCLASS_CONTRIB(n) ? 7 : \
69 IS_LEVELCLASS_PRIVATE(n) ? 8 : \
72 #define ARTWORKCOLOR(n) (IS_ARTWORKCLASS_CLASSICS(n) ? FC_RED : \
73 IS_ARTWORKCLASS_CONTRIB(n) ? FC_GREEN : \
74 IS_ARTWORKCLASS_PRIVATE(n) ? FC_RED : \
75 IS_ARTWORKCLASS_LEVEL(n) ? FC_YELLOW : \
78 #define ARTWORKSORTING(n) (IS_ARTWORKCLASS_CLASSICS(n) ? 0 : \
79 IS_ARTWORKCLASS_LEVEL(n) ? 1 : \
80 IS_ARTWORKCLASS_CONTRIB(n) ? 2 : \
81 IS_ARTWORKCLASS_PRIVATE(n) ? 3 : \
84 #define TOKEN_VALUE_POSITION_SHORT 32
85 #define TOKEN_VALUE_POSITION_DEFAULT 40
86 #define TOKEN_COMMENT_POSITION_DEFAULT 60
88 #define MAX_COOKIE_LEN 256
91 static void setTreeInfoToDefaults(TreeInfo *, int);
92 static TreeInfo *getTreeInfoCopy(TreeInfo *ti);
93 static int compareTreeInfoEntries(const void *, const void *);
95 static int token_value_position = TOKEN_VALUE_POSITION_DEFAULT;
96 static int token_comment_position = TOKEN_COMMENT_POSITION_DEFAULT;
98 static SetupFileHash *artworkinfo_cache_old = NULL;
99 static SetupFileHash *artworkinfo_cache_new = NULL;
100 static boolean use_artworkinfo_cache = TRUE;
103 /* ------------------------------------------------------------------------- */
105 /* ------------------------------------------------------------------------- */
107 static char *getLevelClassDescription(TreeInfo *ti)
109 int position = ti->sort_priority / 100;
111 if (position >= 0 && position < NUM_LEVELCLASS_DESC)
112 return levelclass_desc[position];
114 return "Unknown Level Class";
117 static char *getScoreDir(char *level_subdir)
119 static char *score_dir = NULL;
120 static char *score_level_dir = NULL;
121 char *score_subdir = SCORES_DIRECTORY;
123 if (score_dir == NULL)
125 if (program.global_scores)
126 score_dir = getPath2(getCommonDataDir(), score_subdir);
128 score_dir = getPath2(getUserGameDataDir(), score_subdir);
131 if (level_subdir != NULL)
133 checked_free(score_level_dir);
135 score_level_dir = getPath2(score_dir, level_subdir);
137 return score_level_dir;
143 static char *getLevelSetupDir(char *level_subdir)
145 static char *levelsetup_dir = NULL;
146 char *data_dir = getUserGameDataDir();
147 char *levelsetup_subdir = LEVELSETUP_DIRECTORY;
149 checked_free(levelsetup_dir);
151 if (level_subdir != NULL)
152 levelsetup_dir = getPath3(data_dir, levelsetup_subdir, level_subdir);
154 levelsetup_dir = getPath2(data_dir, levelsetup_subdir);
156 return levelsetup_dir;
159 static char *getCacheDir()
161 static char *cache_dir = NULL;
163 if (cache_dir == NULL)
164 cache_dir = getPath2(getUserGameDataDir(), CACHE_DIRECTORY);
169 static char *getLevelDirFromTreeInfo(TreeInfo *node)
171 static char *level_dir = NULL;
174 return options.level_directory;
176 checked_free(level_dir);
178 level_dir = getPath2((node->in_user_dir ? getUserLevelDir(NULL) :
179 options.level_directory), node->fullpath);
184 char *getUserLevelDir(char *level_subdir)
186 static char *userlevel_dir = NULL;
187 char *data_dir = getUserGameDataDir();
188 char *userlevel_subdir = LEVELS_DIRECTORY;
190 checked_free(userlevel_dir);
192 if (level_subdir != NULL)
193 userlevel_dir = getPath3(data_dir, userlevel_subdir, level_subdir);
195 userlevel_dir = getPath2(data_dir, userlevel_subdir);
197 return userlevel_dir;
200 char *getCurrentLevelDir()
202 return getLevelDirFromTreeInfo(leveldir_current);
205 char *getNewUserLevelSubdir()
207 static char *new_level_subdir = NULL;
208 char *subdir_prefix = getLoginName();
209 char subdir_suffix[10];
210 int max_suffix_number = 1000;
213 while (++i < max_suffix_number)
215 sprintf(subdir_suffix, "_%d", i);
217 checked_free(new_level_subdir);
218 new_level_subdir = getStringCat2(subdir_prefix, subdir_suffix);
220 if (!directoryExists(getUserLevelDir(new_level_subdir)))
224 return new_level_subdir;
227 static char *getTapeDir(char *level_subdir)
229 static char *tape_dir = NULL;
230 char *data_dir = getUserGameDataDir();
231 char *tape_subdir = TAPES_DIRECTORY;
233 checked_free(tape_dir);
235 if (level_subdir != NULL)
236 tape_dir = getPath3(data_dir, tape_subdir, level_subdir);
238 tape_dir = getPath2(data_dir, tape_subdir);
243 static char *getSolutionTapeDir()
245 static char *tape_dir = NULL;
246 char *data_dir = getCurrentLevelDir();
247 char *tape_subdir = TAPES_DIRECTORY;
249 checked_free(tape_dir);
251 tape_dir = getPath2(data_dir, tape_subdir);
256 static char *getDefaultGraphicsDir(char *graphics_subdir)
258 static char *graphics_dir = NULL;
260 if (graphics_subdir == NULL)
261 return options.graphics_directory;
263 checked_free(graphics_dir);
265 graphics_dir = getPath2(options.graphics_directory, graphics_subdir);
270 static char *getDefaultSoundsDir(char *sounds_subdir)
272 static char *sounds_dir = NULL;
274 if (sounds_subdir == NULL)
275 return options.sounds_directory;
277 checked_free(sounds_dir);
279 sounds_dir = getPath2(options.sounds_directory, sounds_subdir);
284 static char *getDefaultMusicDir(char *music_subdir)
286 static char *music_dir = NULL;
288 if (music_subdir == NULL)
289 return options.music_directory;
291 checked_free(music_dir);
293 music_dir = getPath2(options.music_directory, music_subdir);
298 static char *getClassicArtworkSet(int type)
300 return (type == TREE_TYPE_GRAPHICS_DIR ? GFX_CLASSIC_SUBDIR :
301 type == TREE_TYPE_SOUNDS_DIR ? SND_CLASSIC_SUBDIR :
302 type == TREE_TYPE_MUSIC_DIR ? MUS_CLASSIC_SUBDIR : "");
305 static char *getClassicArtworkDir(int type)
307 return (type == TREE_TYPE_GRAPHICS_DIR ?
308 getDefaultGraphicsDir(GFX_CLASSIC_SUBDIR) :
309 type == TREE_TYPE_SOUNDS_DIR ?
310 getDefaultSoundsDir(SND_CLASSIC_SUBDIR) :
311 type == TREE_TYPE_MUSIC_DIR ?
312 getDefaultMusicDir(MUS_CLASSIC_SUBDIR) : "");
315 static char *getUserGraphicsDir()
317 static char *usergraphics_dir = NULL;
319 if (usergraphics_dir == NULL)
320 usergraphics_dir = getPath2(getUserGameDataDir(), GRAPHICS_DIRECTORY);
322 return usergraphics_dir;
325 static char *getUserSoundsDir()
327 static char *usersounds_dir = NULL;
329 if (usersounds_dir == NULL)
330 usersounds_dir = getPath2(getUserGameDataDir(), SOUNDS_DIRECTORY);
332 return usersounds_dir;
335 static char *getUserMusicDir()
337 static char *usermusic_dir = NULL;
339 if (usermusic_dir == NULL)
340 usermusic_dir = getPath2(getUserGameDataDir(), MUSIC_DIRECTORY);
342 return usermusic_dir;
345 static char *getSetupArtworkDir(TreeInfo *ti)
347 static char *artwork_dir = NULL;
352 checked_free(artwork_dir);
354 artwork_dir = getPath2(ti->basepath, ti->fullpath);
359 char *setLevelArtworkDir(TreeInfo *ti)
361 char **artwork_path_ptr, **artwork_set_ptr;
362 TreeInfo *level_artwork;
364 if (ti == NULL || leveldir_current == NULL)
367 artwork_path_ptr = LEVELDIR_ARTWORK_PATH_PTR(leveldir_current, ti->type);
368 artwork_set_ptr = LEVELDIR_ARTWORK_SET_PTR( leveldir_current, ti->type);
370 checked_free(*artwork_path_ptr);
372 if ((level_artwork = getTreeInfoFromIdentifier(ti, *artwork_set_ptr)))
374 *artwork_path_ptr = getStringCopy(getSetupArtworkDir(level_artwork));
379 No (or non-existing) artwork configured in "levelinfo.conf". This would
380 normally result in using the artwork configured in the setup menu. But
381 if an artwork subdirectory exists (which might contain custom artwork
382 or an artwork configuration file), this level artwork must be treated
383 as relative to the default "classic" artwork, not to the artwork that
384 is currently configured in the setup menu.
386 Update: For "special" versions of R'n'D (like "R'n'D jue"), do not use
387 the "default" artwork (which would be "jue0" for "R'n'D jue"), but use
388 the real "classic" artwork from the original R'n'D (like "gfx_classic").
391 char *dir = getPath2(getCurrentLevelDir(), ARTWORK_DIRECTORY(ti->type));
393 checked_free(*artwork_set_ptr);
395 if (directoryExists(dir))
397 *artwork_path_ptr = getStringCopy(getClassicArtworkDir(ti->type));
398 *artwork_set_ptr = getStringCopy(getClassicArtworkSet(ti->type));
402 *artwork_path_ptr = getStringCopy(UNDEFINED_FILENAME);
403 *artwork_set_ptr = NULL;
409 return *artwork_set_ptr;
412 inline static char *getLevelArtworkSet(int type)
414 if (leveldir_current == NULL)
417 return LEVELDIR_ARTWORK_SET(leveldir_current, type);
420 inline static char *getLevelArtworkDir(int type)
422 if (leveldir_current == NULL)
423 return UNDEFINED_FILENAME;
425 return LEVELDIR_ARTWORK_PATH(leveldir_current, type);
428 char *getProgramMainDataPath(char *command_filename, char *base_path)
430 /* check if the program's main data base directory is configured */
431 if (!strEqual(base_path, "."))
434 /* if the program is configured to start from current directory (default),
435 determine program package directory from program binary (some versions
436 of KDE/Konqueror and Mac OS X (especially "Mavericks") apparently do not
437 set the current working directory to the program package directory) */
438 char *main_data_path = getBasePath(command_filename);
440 #if defined(PLATFORM_MACOSX)
441 if (strSuffix(main_data_path, MAC_APP_BINARY_SUBDIR))
443 char *main_data_path_old = main_data_path;
445 // cut relative path to Mac OS X application binary directory from path
446 main_data_path[strlen(main_data_path) -
447 strlen(MAC_APP_BINARY_SUBDIR)] = '\0';
449 // cut trailing path separator from path (but not if path is root directory)
450 if (strSuffix(main_data_path, "/") && !strEqual(main_data_path, "/"))
451 main_data_path[strlen(main_data_path) - 1] = '\0';
453 // replace empty path with current directory
454 if (strEqual(main_data_path, ""))
455 main_data_path = ".";
457 // add relative path to Mac OS X application resources directory to path
458 main_data_path = getPath2(main_data_path, MAC_APP_FILES_SUBDIR);
460 free(main_data_path_old);
464 return main_data_path;
467 char *getProgramConfigFilename(char *command_filename)
469 char *command_filename_1 = getStringCopy(command_filename);
471 // strip trailing executable suffix from command filename
472 if (strSuffix(command_filename_1, ".exe"))
473 command_filename_1[strlen(command_filename_1) - 4] = '\0';
475 char *ro_base_path = getProgramMainDataPath(command_filename, RO_BASE_PATH);
476 char *conf_directory = getPath2(ro_base_path, CONF_DIRECTORY);
478 char *command_basepath = getBasePath(command_filename);
479 char *command_basename = getBaseNameNoSuffix(command_filename);
480 char *command_filename_2 = getPath2(command_basepath, command_basename);
482 char *config_filename_1 = getStringCat2(command_filename_1, ".conf");
483 char *config_filename_2 = getStringCat2(command_filename_2, ".conf");
484 char *config_filename_3 = getPath2(conf_directory, SETUP_FILENAME);
486 // 1st try: look for config file that exactly matches the binary filename
487 if (fileExists(config_filename_1))
488 return config_filename_1;
490 // 2nd try: look for config file that matches binary filename without suffix
491 if (fileExists(config_filename_2))
492 return config_filename_2;
494 // 3rd try: return setup config filename in global program config directory
495 return config_filename_3;
498 char *getTapeFilename(int nr)
500 static char *filename = NULL;
501 char basename[MAX_FILENAME_LEN];
503 checked_free(filename);
505 sprintf(basename, "%03d.%s", nr, TAPEFILE_EXTENSION);
506 filename = getPath2(getTapeDir(leveldir_current->subdir), basename);
511 char *getSolutionTapeFilename(int nr)
513 static char *filename = NULL;
514 char basename[MAX_FILENAME_LEN];
516 checked_free(filename);
518 sprintf(basename, "%03d.%s", nr, TAPEFILE_EXTENSION);
519 filename = getPath2(getSolutionTapeDir(), basename);
521 if (!fileExists(filename))
523 static char *filename_sln = NULL;
525 checked_free(filename_sln);
527 sprintf(basename, "%03d.sln", nr);
528 filename_sln = getPath2(getSolutionTapeDir(), basename);
530 if (fileExists(filename_sln))
537 char *getScoreFilename(int nr)
539 static char *filename = NULL;
540 char basename[MAX_FILENAME_LEN];
542 checked_free(filename);
544 sprintf(basename, "%03d.%s", nr, SCOREFILE_EXTENSION);
545 filename = getPath2(getScoreDir(leveldir_current->subdir), basename);
550 char *getSetupFilename()
552 static char *filename = NULL;
554 checked_free(filename);
556 filename = getPath2(getSetupDir(), SETUP_FILENAME);
561 char *getDefaultSetupFilename()
563 return program.config_filename;
566 char *getEditorSetupFilename()
568 static char *filename = NULL;
570 checked_free(filename);
571 filename = getPath2(getCurrentLevelDir(), EDITORSETUP_FILENAME);
573 if (fileExists(filename))
576 checked_free(filename);
577 filename = getPath2(getSetupDir(), EDITORSETUP_FILENAME);
582 char *getHelpAnimFilename()
584 static char *filename = NULL;
586 checked_free(filename);
588 filename = getPath2(getCurrentLevelDir(), HELPANIM_FILENAME);
593 char *getHelpTextFilename()
595 static char *filename = NULL;
597 checked_free(filename);
599 filename = getPath2(getCurrentLevelDir(), HELPTEXT_FILENAME);
604 char *getLevelSetInfoFilename()
606 static char *filename = NULL;
621 for (i = 0; basenames[i] != NULL; i++)
623 checked_free(filename);
624 filename = getPath2(getCurrentLevelDir(), basenames[i]);
626 if (fileExists(filename))
633 char *getLevelSetTitleMessageBasename(int nr, boolean initial)
635 static char basename[32];
637 sprintf(basename, "%s_%d.txt",
638 (initial ? "titlemessage_initial" : "titlemessage"), nr + 1);
643 char *getLevelSetTitleMessageFilename(int nr, boolean initial)
645 static char *filename = NULL;
647 boolean skip_setup_artwork = FALSE;
649 checked_free(filename);
651 basename = getLevelSetTitleMessageBasename(nr, initial);
653 if (!gfx.override_level_graphics)
655 /* 1st try: look for special artwork in current level series directory */
656 filename = getPath3(getCurrentLevelDir(), GRAPHICS_DIRECTORY, basename);
657 if (fileExists(filename))
662 /* 2nd try: look for message file in current level set directory */
663 filename = getPath2(getCurrentLevelDir(), basename);
664 if (fileExists(filename))
669 /* check if there is special artwork configured in level series config */
670 if (getLevelArtworkSet(ARTWORK_TYPE_GRAPHICS) != NULL)
672 /* 3rd try: look for special artwork configured in level series config */
673 filename = getPath2(getLevelArtworkDir(ARTWORK_TYPE_GRAPHICS), basename);
674 if (fileExists(filename))
679 /* take missing artwork configured in level set config from default */
680 skip_setup_artwork = TRUE;
684 if (!skip_setup_artwork)
686 /* 4th try: look for special artwork in configured artwork directory */
687 filename = getPath2(getSetupArtworkDir(artwork.gfx_current), basename);
688 if (fileExists(filename))
694 /* 5th try: look for default artwork in new default artwork directory */
695 filename = getPath2(getDefaultGraphicsDir(GFX_DEFAULT_SUBDIR), basename);
696 if (fileExists(filename))
701 /* 6th try: look for default artwork in old default artwork directory */
702 filename = getPath2(options.graphics_directory, basename);
703 if (fileExists(filename))
706 return NULL; /* cannot find specified artwork file anywhere */
709 static char *getCorrectedArtworkBasename(char *basename)
714 char *getCustomImageFilename(char *basename)
716 static char *filename = NULL;
717 boolean skip_setup_artwork = FALSE;
719 checked_free(filename);
721 basename = getCorrectedArtworkBasename(basename);
723 if (!gfx.override_level_graphics)
725 /* 1st try: look for special artwork in current level series directory */
726 filename = getImg3(getCurrentLevelDir(), GRAPHICS_DIRECTORY, basename);
727 if (fileExists(filename))
732 /* check if there is special artwork configured in level series config */
733 if (getLevelArtworkSet(ARTWORK_TYPE_GRAPHICS) != NULL)
735 /* 2nd try: look for special artwork configured in level series config */
736 filename = getImg2(getLevelArtworkDir(ARTWORK_TYPE_GRAPHICS), basename);
737 if (fileExists(filename))
742 /* take missing artwork configured in level set config from default */
743 skip_setup_artwork = TRUE;
747 if (!skip_setup_artwork)
749 /* 3rd try: look for special artwork in configured artwork directory */
750 filename = getImg2(getSetupArtworkDir(artwork.gfx_current), basename);
751 if (fileExists(filename))
757 /* 4th try: look for default artwork in new default artwork directory */
758 filename = getImg2(getDefaultGraphicsDir(GFX_DEFAULT_SUBDIR), basename);
759 if (fileExists(filename))
764 /* 5th try: look for default artwork in old default artwork directory */
765 filename = getImg2(options.graphics_directory, basename);
766 if (fileExists(filename))
769 if (!strEqual(GFX_FALLBACK_FILENAME, UNDEFINED_FILENAME))
774 Error(ERR_WARN, "cannot find artwork file '%s' (using fallback)",
777 /* 6th try: look for fallback artwork in old default artwork directory */
778 /* (needed to prevent errors when trying to access unused artwork files) */
779 filename = getImg2(options.graphics_directory, GFX_FALLBACK_FILENAME);
780 if (fileExists(filename))
784 return NULL; /* cannot find specified artwork file anywhere */
787 char *getCustomSoundFilename(char *basename)
789 static char *filename = NULL;
790 boolean skip_setup_artwork = FALSE;
792 checked_free(filename);
794 basename = getCorrectedArtworkBasename(basename);
796 if (!gfx.override_level_sounds)
798 /* 1st try: look for special artwork in current level series directory */
799 filename = getPath3(getCurrentLevelDir(), SOUNDS_DIRECTORY, basename);
800 if (fileExists(filename))
805 /* check if there is special artwork configured in level series config */
806 if (getLevelArtworkSet(ARTWORK_TYPE_SOUNDS) != NULL)
808 /* 2nd try: look for special artwork configured in level series config */
809 filename = getPath2(getLevelArtworkDir(TREE_TYPE_SOUNDS_DIR), basename);
810 if (fileExists(filename))
815 /* take missing artwork configured in level set config from default */
816 skip_setup_artwork = TRUE;
820 if (!skip_setup_artwork)
822 /* 3rd try: look for special artwork in configured artwork directory */
823 filename = getPath2(getSetupArtworkDir(artwork.snd_current), basename);
824 if (fileExists(filename))
830 /* 4th try: look for default artwork in new default artwork directory */
831 filename = getPath2(getDefaultSoundsDir(SND_DEFAULT_SUBDIR), basename);
832 if (fileExists(filename))
837 /* 5th try: look for default artwork in old default artwork directory */
838 filename = getPath2(options.sounds_directory, basename);
839 if (fileExists(filename))
842 if (!strEqual(SND_FALLBACK_FILENAME, UNDEFINED_FILENAME))
847 Error(ERR_WARN, "cannot find artwork file '%s' (using fallback)",
850 /* 6th try: look for fallback artwork in old default artwork directory */
851 /* (needed to prevent errors when trying to access unused artwork files) */
852 filename = getPath2(options.sounds_directory, SND_FALLBACK_FILENAME);
853 if (fileExists(filename))
857 return NULL; /* cannot find specified artwork file anywhere */
860 char *getCustomMusicFilename(char *basename)
862 static char *filename = NULL;
863 boolean skip_setup_artwork = FALSE;
865 checked_free(filename);
867 basename = getCorrectedArtworkBasename(basename);
869 if (!gfx.override_level_music)
871 /* 1st try: look for special artwork in current level series directory */
872 filename = getPath3(getCurrentLevelDir(), MUSIC_DIRECTORY, basename);
873 if (fileExists(filename))
878 /* check if there is special artwork configured in level series config */
879 if (getLevelArtworkSet(ARTWORK_TYPE_MUSIC) != NULL)
881 /* 2nd try: look for special artwork configured in level series config */
882 filename = getPath2(getLevelArtworkDir(TREE_TYPE_MUSIC_DIR), basename);
883 if (fileExists(filename))
888 /* take missing artwork configured in level set config from default */
889 skip_setup_artwork = TRUE;
893 if (!skip_setup_artwork)
895 /* 3rd try: look for special artwork in configured artwork directory */
896 filename = getPath2(getSetupArtworkDir(artwork.mus_current), basename);
897 if (fileExists(filename))
903 /* 4th try: look for default artwork in new default artwork directory */
904 filename = getPath2(getDefaultMusicDir(MUS_DEFAULT_SUBDIR), basename);
905 if (fileExists(filename))
910 /* 5th try: look for default artwork in old default artwork directory */
911 filename = getPath2(options.music_directory, basename);
912 if (fileExists(filename))
915 if (!strEqual(MUS_FALLBACK_FILENAME, UNDEFINED_FILENAME))
920 Error(ERR_WARN, "cannot find artwork file '%s' (using fallback)",
923 /* 6th try: look for fallback artwork in old default artwork directory */
924 /* (needed to prevent errors when trying to access unused artwork files) */
925 filename = getPath2(options.music_directory, MUS_FALLBACK_FILENAME);
926 if (fileExists(filename))
930 return NULL; /* cannot find specified artwork file anywhere */
933 char *getCustomArtworkFilename(char *basename, int type)
935 if (type == ARTWORK_TYPE_GRAPHICS)
936 return getCustomImageFilename(basename);
937 else if (type == ARTWORK_TYPE_SOUNDS)
938 return getCustomSoundFilename(basename);
939 else if (type == ARTWORK_TYPE_MUSIC)
940 return getCustomMusicFilename(basename);
942 return UNDEFINED_FILENAME;
945 char *getCustomArtworkConfigFilename(int type)
947 return getCustomArtworkFilename(ARTWORKINFO_FILENAME(type), type);
950 char *getCustomArtworkLevelConfigFilename(int type)
952 static char *filename = NULL;
954 checked_free(filename);
956 filename = getPath2(getLevelArtworkDir(type), ARTWORKINFO_FILENAME(type));
961 char *getCustomMusicDirectory(void)
963 static char *directory = NULL;
964 boolean skip_setup_artwork = FALSE;
966 checked_free(directory);
968 if (!gfx.override_level_music)
970 /* 1st try: look for special artwork in current level series directory */
971 directory = getPath2(getCurrentLevelDir(), MUSIC_DIRECTORY);
972 if (directoryExists(directory))
977 /* check if there is special artwork configured in level series config */
978 if (getLevelArtworkSet(ARTWORK_TYPE_MUSIC) != NULL)
980 /* 2nd try: look for special artwork configured in level series config */
981 directory = getStringCopy(getLevelArtworkDir(TREE_TYPE_MUSIC_DIR));
982 if (directoryExists(directory))
987 /* take missing artwork configured in level set config from default */
988 skip_setup_artwork = TRUE;
992 if (!skip_setup_artwork)
994 /* 3rd try: look for special artwork in configured artwork directory */
995 directory = getStringCopy(getSetupArtworkDir(artwork.mus_current));
996 if (directoryExists(directory))
1002 /* 4th try: look for default artwork in new default artwork directory */
1003 directory = getStringCopy(getDefaultMusicDir(MUS_DEFAULT_SUBDIR));
1004 if (directoryExists(directory))
1009 /* 5th try: look for default artwork in old default artwork directory */
1010 directory = getStringCopy(options.music_directory);
1011 if (directoryExists(directory))
1014 return NULL; /* cannot find specified artwork file anywhere */
1017 void InitTapeDirectory(char *level_subdir)
1019 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
1020 createDirectory(getTapeDir(NULL), "main tape", PERMS_PRIVATE);
1021 createDirectory(getTapeDir(level_subdir), "level tape", PERMS_PRIVATE);
1024 void InitScoreDirectory(char *level_subdir)
1026 int permissions = (program.global_scores ? PERMS_PUBLIC : PERMS_PRIVATE);
1028 if (program.global_scores)
1029 createDirectory(getCommonDataDir(), "common data", permissions);
1031 createDirectory(getUserGameDataDir(), "user data", permissions);
1033 createDirectory(getScoreDir(NULL), "main score", permissions);
1034 createDirectory(getScoreDir(level_subdir), "level score", permissions);
1037 static void SaveUserLevelInfo();
1039 void InitUserLevelDirectory(char *level_subdir)
1041 if (!directoryExists(getUserLevelDir(level_subdir)))
1043 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
1044 createDirectory(getUserLevelDir(NULL), "main user level", PERMS_PRIVATE);
1045 createDirectory(getUserLevelDir(level_subdir), "user level", PERMS_PRIVATE);
1047 SaveUserLevelInfo();
1051 void InitLevelSetupDirectory(char *level_subdir)
1053 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
1054 createDirectory(getLevelSetupDir(NULL), "main level setup", PERMS_PRIVATE);
1055 createDirectory(getLevelSetupDir(level_subdir), "level setup", PERMS_PRIVATE);
1058 void InitCacheDirectory()
1060 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
1061 createDirectory(getCacheDir(), "cache data", PERMS_PRIVATE);
1065 /* ------------------------------------------------------------------------- */
1066 /* some functions to handle lists of level and artwork directories */
1067 /* ------------------------------------------------------------------------- */
1069 TreeInfo *newTreeInfo()
1071 return checked_calloc(sizeof(TreeInfo));
1074 TreeInfo *newTreeInfo_setDefaults(int type)
1076 TreeInfo *ti = newTreeInfo();
1078 setTreeInfoToDefaults(ti, type);
1083 void pushTreeInfo(TreeInfo **node_first, TreeInfo *node_new)
1085 node_new->next = *node_first;
1086 *node_first = node_new;
1089 int numTreeInfo(TreeInfo *node)
1102 boolean validLevelSeries(TreeInfo *node)
1104 return (node != NULL && !node->node_group && !node->parent_link);
1107 TreeInfo *getFirstValidTreeInfoEntry(TreeInfo *node)
1112 if (node->node_group) /* enter level group (step down into tree) */
1113 return getFirstValidTreeInfoEntry(node->node_group);
1114 else if (node->parent_link) /* skip start entry of level group */
1116 if (node->next) /* get first real level series entry */
1117 return getFirstValidTreeInfoEntry(node->next);
1118 else /* leave empty level group and go on */
1119 return getFirstValidTreeInfoEntry(node->node_parent->next);
1121 else /* this seems to be a regular level series */
1125 TreeInfo *getTreeInfoFirstGroupEntry(TreeInfo *node)
1130 if (node->node_parent == NULL) /* top level group */
1131 return *node->node_top;
1132 else /* sub level group */
1133 return node->node_parent->node_group;
1136 int numTreeInfoInGroup(TreeInfo *node)
1138 return numTreeInfo(getTreeInfoFirstGroupEntry(node));
1141 int posTreeInfo(TreeInfo *node)
1143 TreeInfo *node_cmp = getTreeInfoFirstGroupEntry(node);
1148 if (node_cmp == node)
1152 node_cmp = node_cmp->next;
1158 TreeInfo *getTreeInfoFromPos(TreeInfo *node, int pos)
1160 TreeInfo *node_default = node;
1172 return node_default;
1175 TreeInfo *getTreeInfoFromIdentifier(TreeInfo *node, char *identifier)
1177 if (identifier == NULL)
1182 if (node->node_group)
1184 TreeInfo *node_group;
1186 node_group = getTreeInfoFromIdentifier(node->node_group, identifier);
1191 else if (!node->parent_link)
1193 if (strEqual(identifier, node->identifier))
1203 TreeInfo *cloneTreeNode(TreeInfo **node_top, TreeInfo *node_parent,
1204 TreeInfo *node, boolean skip_sets_without_levels)
1211 if (!node->parent_link && !node->level_group &&
1212 skip_sets_without_levels && node->levels == 0)
1213 return cloneTreeNode(node_top, node_parent, node->next,
1214 skip_sets_without_levels);
1216 node_new = getTreeInfoCopy(node); /* copy complete node */
1218 node_new->node_top = node_top; /* correct top node link */
1219 node_new->node_parent = node_parent; /* correct parent node link */
1221 if (node->level_group)
1222 node_new->node_group = cloneTreeNode(node_top, node_new, node->node_group,
1223 skip_sets_without_levels);
1225 node_new->next = cloneTreeNode(node_top, node_parent, node->next,
1226 skip_sets_without_levels);
1231 void cloneTree(TreeInfo **ti_new, TreeInfo *ti, boolean skip_empty_sets)
1233 TreeInfo *ti_cloned = cloneTreeNode(ti_new, NULL, ti, skip_empty_sets);
1235 *ti_new = ti_cloned;
1238 static boolean adjustTreeGraphicsForEMC(TreeInfo *node)
1240 boolean settings_changed = FALSE;
1244 if (node->graphics_set_ecs && !setup.prefer_aga_graphics &&
1245 !strEqual(node->graphics_set, node->graphics_set_ecs))
1247 setString(&node->graphics_set, node->graphics_set_ecs);
1248 settings_changed = TRUE;
1250 else if (node->graphics_set_aga && setup.prefer_aga_graphics &&
1251 !strEqual(node->graphics_set, node->graphics_set_aga))
1253 setString(&node->graphics_set, node->graphics_set_aga);
1254 settings_changed = TRUE;
1257 if (node->node_group != NULL)
1258 settings_changed |= adjustTreeGraphicsForEMC(node->node_group);
1263 return settings_changed;
1266 void dumpTreeInfo(TreeInfo *node, int depth)
1270 printf("Dumping TreeInfo:\n");
1274 for (i = 0; i < (depth + 1) * 3; i++)
1277 printf("'%s' / '%s'\n", node->identifier, node->name);
1280 // use for dumping artwork info tree
1281 printf("subdir == '%s' ['%s', '%s'] [%d])\n",
1282 node->subdir, node->fullpath, node->basepath, node->in_user_dir);
1285 if (node->node_group != NULL)
1286 dumpTreeInfo(node->node_group, depth + 1);
1292 void sortTreeInfoBySortFunction(TreeInfo **node_first,
1293 int (*compare_function)(const void *,
1296 int num_nodes = numTreeInfo(*node_first);
1297 TreeInfo **sort_array;
1298 TreeInfo *node = *node_first;
1304 /* allocate array for sorting structure pointers */
1305 sort_array = checked_calloc(num_nodes * sizeof(TreeInfo *));
1307 /* writing structure pointers to sorting array */
1308 while (i < num_nodes && node) /* double boundary check... */
1310 sort_array[i] = node;
1316 /* sorting the structure pointers in the sorting array */
1317 qsort(sort_array, num_nodes, sizeof(TreeInfo *),
1320 /* update the linkage of list elements with the sorted node array */
1321 for (i = 0; i < num_nodes - 1; i++)
1322 sort_array[i]->next = sort_array[i + 1];
1323 sort_array[num_nodes - 1]->next = NULL;
1325 /* update the linkage of the main list anchor pointer */
1326 *node_first = sort_array[0];
1330 /* now recursively sort the level group structures */
1334 if (node->node_group != NULL)
1335 sortTreeInfoBySortFunction(&node->node_group, compare_function);
1341 void sortTreeInfo(TreeInfo **node_first)
1343 sortTreeInfoBySortFunction(node_first, compareTreeInfoEntries);
1347 /* ========================================================================= */
1348 /* some stuff from "files.c" */
1349 /* ========================================================================= */
1351 #if defined(PLATFORM_WIN32)
1353 #define S_IRGRP S_IRUSR
1356 #define S_IROTH S_IRUSR
1359 #define S_IWGRP S_IWUSR
1362 #define S_IWOTH S_IWUSR
1365 #define S_IXGRP S_IXUSR
1368 #define S_IXOTH S_IXUSR
1371 #define S_IRWXG (S_IRGRP | S_IWGRP | S_IXGRP)
1376 #endif /* PLATFORM_WIN32 */
1378 /* file permissions for newly written files */
1379 #define MODE_R_ALL (S_IRUSR | S_IRGRP | S_IROTH)
1380 #define MODE_W_ALL (S_IWUSR | S_IWGRP | S_IWOTH)
1381 #define MODE_X_ALL (S_IXUSR | S_IXGRP | S_IXOTH)
1383 #define MODE_W_PRIVATE (S_IWUSR)
1384 #define MODE_W_PUBLIC_FILE (S_IWUSR | S_IWGRP)
1385 #define MODE_W_PUBLIC_DIR (S_IWUSR | S_IWGRP | S_ISGID)
1387 #define DIR_PERMS_PRIVATE (MODE_R_ALL | MODE_X_ALL | MODE_W_PRIVATE)
1388 #define DIR_PERMS_PUBLIC (MODE_R_ALL | MODE_X_ALL | MODE_W_PUBLIC_DIR)
1389 #define DIR_PERMS_PUBLIC_ALL (MODE_R_ALL | MODE_X_ALL | MODE_W_ALL)
1391 #define FILE_PERMS_PRIVATE (MODE_R_ALL | MODE_W_PRIVATE)
1392 #define FILE_PERMS_PUBLIC (MODE_R_ALL | MODE_W_PUBLIC_FILE)
1393 #define FILE_PERMS_PUBLIC_ALL (MODE_R_ALL | MODE_W_ALL)
1398 static char *dir = NULL;
1400 #if defined(PLATFORM_WIN32)
1403 dir = checked_malloc(MAX_PATH + 1);
1405 if (!SUCCEEDED(SHGetFolderPath(NULL, CSIDL_PERSONAL, NULL, 0, dir)))
1408 #elif defined(PLATFORM_UNIX)
1411 if ((dir = getenv("HOME")) == NULL)
1415 if ((pwd = getpwuid(getuid())) != NULL)
1416 dir = getStringCopy(pwd->pw_dir);
1428 char *getCommonDataDir(void)
1430 static char *common_data_dir = NULL;
1432 #if defined(PLATFORM_WIN32)
1433 if (common_data_dir == NULL)
1435 char *dir = checked_malloc(MAX_PATH + 1);
1437 if (SUCCEEDED(SHGetFolderPath(NULL, CSIDL_COMMON_DOCUMENTS, NULL, 0, dir))
1438 && !strEqual(dir, "")) /* empty for Windows 95/98 */
1439 common_data_dir = getPath2(dir, program.userdata_subdir);
1441 common_data_dir = options.rw_base_directory;
1444 if (common_data_dir == NULL)
1445 common_data_dir = options.rw_base_directory;
1448 return common_data_dir;
1451 char *getPersonalDataDir(void)
1453 static char *personal_data_dir = NULL;
1455 #if defined(PLATFORM_MACOSX)
1456 if (personal_data_dir == NULL)
1457 personal_data_dir = getPath2(getHomeDir(), "Documents");
1459 if (personal_data_dir == NULL)
1460 personal_data_dir = getHomeDir();
1463 return personal_data_dir;
1466 char *getUserGameDataDir(void)
1468 static char *user_game_data_dir = NULL;
1470 #if defined(PLATFORM_ANDROID)
1471 if (user_game_data_dir == NULL)
1472 user_game_data_dir = (char *)(SDL_AndroidGetExternalStorageState() &
1473 SDL_ANDROID_EXTERNAL_STORAGE_WRITE ?
1474 SDL_AndroidGetExternalStoragePath() :
1475 SDL_AndroidGetInternalStoragePath());
1477 if (user_game_data_dir == NULL)
1478 user_game_data_dir = getPath2(getPersonalDataDir(),
1479 program.userdata_subdir);
1482 return user_game_data_dir;
1487 return getUserGameDataDir();
1490 static mode_t posix_umask(mode_t mask)
1492 #if defined(PLATFORM_UNIX)
1499 static int posix_mkdir(const char *pathname, mode_t mode)
1501 #if defined(PLATFORM_WIN32)
1502 return mkdir(pathname);
1504 return mkdir(pathname, mode);
1508 static boolean posix_process_running_setgid()
1510 #if defined(PLATFORM_UNIX)
1511 return (getgid() != getegid());
1517 void createDirectory(char *dir, char *text, int permission_class)
1519 if (directoryExists(dir))
1522 /* leave "other" permissions in umask untouched, but ensure group parts
1523 of USERDATA_DIR_MODE are not masked */
1524 mode_t dir_mode = (permission_class == PERMS_PRIVATE ?
1525 DIR_PERMS_PRIVATE : DIR_PERMS_PUBLIC);
1526 mode_t last_umask = posix_umask(0);
1527 mode_t group_umask = ~(dir_mode & S_IRWXG);
1528 int running_setgid = posix_process_running_setgid();
1530 if (permission_class == PERMS_PUBLIC)
1532 /* if we're setgid, protect files against "other" */
1533 /* else keep umask(0) to make the dir world-writable */
1536 posix_umask(last_umask & group_umask);
1538 dir_mode = DIR_PERMS_PUBLIC_ALL;
1541 if (posix_mkdir(dir, dir_mode) != 0)
1542 Error(ERR_WARN, "cannot create %s directory '%s': %s",
1543 text, dir, strerror(errno));
1545 if (permission_class == PERMS_PUBLIC && !running_setgid)
1546 chmod(dir, dir_mode);
1548 posix_umask(last_umask); /* restore previous umask */
1551 void InitUserDataDirectory()
1553 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
1556 void SetFilePermissions(char *filename, int permission_class)
1558 int running_setgid = posix_process_running_setgid();
1559 int perms = (permission_class == PERMS_PRIVATE ?
1560 FILE_PERMS_PRIVATE : FILE_PERMS_PUBLIC);
1562 if (permission_class == PERMS_PUBLIC && !running_setgid)
1563 perms = FILE_PERMS_PUBLIC_ALL;
1565 chmod(filename, perms);
1568 char *getCookie(char *file_type)
1570 static char cookie[MAX_COOKIE_LEN + 1];
1572 if (strlen(program.cookie_prefix) + 1 +
1573 strlen(file_type) + strlen("_FILE_VERSION_x.x") > MAX_COOKIE_LEN)
1574 return "[COOKIE ERROR]"; /* should never happen */
1576 sprintf(cookie, "%s_%s_FILE_VERSION_%d.%d",
1577 program.cookie_prefix, file_type,
1578 program.version_super, program.version_major);
1583 void fprintFileHeader(FILE *file, char *basename)
1585 char *prefix = "# ";
1588 fprintf_line_with_prefix(file, prefix, sep1, 77);
1589 fprintf(file, "%s%s\n", prefix, basename);
1590 fprintf_line_with_prefix(file, prefix, sep1, 77);
1591 fprintf(file, "\n");
1594 int getFileVersionFromCookieString(const char *cookie)
1596 const char *ptr_cookie1, *ptr_cookie2;
1597 const char *pattern1 = "_FILE_VERSION_";
1598 const char *pattern2 = "?.?";
1599 const int len_cookie = strlen(cookie);
1600 const int len_pattern1 = strlen(pattern1);
1601 const int len_pattern2 = strlen(pattern2);
1602 const int len_pattern = len_pattern1 + len_pattern2;
1603 int version_super, version_major;
1605 if (len_cookie <= len_pattern)
1608 ptr_cookie1 = &cookie[len_cookie - len_pattern];
1609 ptr_cookie2 = &cookie[len_cookie - len_pattern2];
1611 if (strncmp(ptr_cookie1, pattern1, len_pattern1) != 0)
1614 if (ptr_cookie2[0] < '0' || ptr_cookie2[0] > '9' ||
1615 ptr_cookie2[1] != '.' ||
1616 ptr_cookie2[2] < '0' || ptr_cookie2[2] > '9')
1619 version_super = ptr_cookie2[0] - '0';
1620 version_major = ptr_cookie2[2] - '0';
1622 return VERSION_IDENT(version_super, version_major, 0, 0);
1625 boolean checkCookieString(const char *cookie, const char *template)
1627 const char *pattern = "_FILE_VERSION_?.?";
1628 const int len_cookie = strlen(cookie);
1629 const int len_template = strlen(template);
1630 const int len_pattern = strlen(pattern);
1632 if (len_cookie != len_template)
1635 if (strncmp(cookie, template, len_cookie - len_pattern) != 0)
1642 /* ------------------------------------------------------------------------- */
1643 /* setup file list and hash handling functions */
1644 /* ------------------------------------------------------------------------- */
1646 char *getFormattedSetupEntry(char *token, char *value)
1649 static char entry[MAX_LINE_LEN];
1651 /* if value is an empty string, just return token without value */
1655 /* start with the token and some spaces to format output line */
1656 sprintf(entry, "%s:", token);
1657 for (i = strlen(entry); i < token_value_position; i++)
1660 /* continue with the token's value */
1661 strcat(entry, value);
1666 SetupFileList *newSetupFileList(char *token, char *value)
1668 SetupFileList *new = checked_malloc(sizeof(SetupFileList));
1670 new->token = getStringCopy(token);
1671 new->value = getStringCopy(value);
1678 void freeSetupFileList(SetupFileList *list)
1683 checked_free(list->token);
1684 checked_free(list->value);
1687 freeSetupFileList(list->next);
1692 char *getListEntry(SetupFileList *list, char *token)
1697 if (strEqual(list->token, token))
1700 return getListEntry(list->next, token);
1703 SetupFileList *setListEntry(SetupFileList *list, char *token, char *value)
1708 if (strEqual(list->token, token))
1710 checked_free(list->value);
1712 list->value = getStringCopy(value);
1716 else if (list->next == NULL)
1717 return (list->next = newSetupFileList(token, value));
1719 return setListEntry(list->next, token, value);
1722 SetupFileList *addListEntry(SetupFileList *list, char *token, char *value)
1727 if (list->next == NULL)
1728 return (list->next = newSetupFileList(token, value));
1730 return addListEntry(list->next, token, value);
1733 #if ENABLE_UNUSED_CODE
1735 static void printSetupFileList(SetupFileList *list)
1740 printf("token: '%s'\n", list->token);
1741 printf("value: '%s'\n", list->value);
1743 printSetupFileList(list->next);
1749 DEFINE_HASHTABLE_INSERT(insert_hash_entry, char, char);
1750 DEFINE_HASHTABLE_SEARCH(search_hash_entry, char, char);
1751 DEFINE_HASHTABLE_CHANGE(change_hash_entry, char, char);
1752 DEFINE_HASHTABLE_REMOVE(remove_hash_entry, char, char);
1754 #define insert_hash_entry hashtable_insert
1755 #define search_hash_entry hashtable_search
1756 #define change_hash_entry hashtable_change
1757 #define remove_hash_entry hashtable_remove
1760 unsigned int get_hash_from_key(void *key)
1765 This algorithm (k=33) was first reported by Dan Bernstein many years ago in
1766 'comp.lang.c'. Another version of this algorithm (now favored by Bernstein)
1767 uses XOR: hash(i) = hash(i - 1) * 33 ^ str[i]; the magic of number 33 (why
1768 it works better than many other constants, prime or not) has never been
1769 adequately explained.
1771 If you just want to have a good hash function, and cannot wait, djb2
1772 is one of the best string hash functions i know. It has excellent
1773 distribution and speed on many different sets of keys and table sizes.
1774 You are not likely to do better with one of the "well known" functions
1775 such as PJW, K&R, etc.
1777 Ozan (oz) Yigit [http://www.cs.yorku.ca/~oz/hash.html]
1780 char *str = (char *)key;
1781 unsigned int hash = 5381;
1784 while ((c = *str++))
1785 hash = ((hash << 5) + hash) + c; /* hash * 33 + c */
1790 static int keys_are_equal(void *key1, void *key2)
1792 return (strEqual((char *)key1, (char *)key2));
1795 SetupFileHash *newSetupFileHash()
1797 SetupFileHash *new_hash =
1798 create_hashtable(16, 0.75, get_hash_from_key, keys_are_equal);
1800 if (new_hash == NULL)
1801 Error(ERR_EXIT, "create_hashtable() failed -- out of memory");
1806 void freeSetupFileHash(SetupFileHash *hash)
1811 hashtable_destroy(hash, 1); /* 1 == also free values stored in hash */
1814 char *getHashEntry(SetupFileHash *hash, char *token)
1819 return search_hash_entry(hash, token);
1822 void setHashEntry(SetupFileHash *hash, char *token, char *value)
1829 value_copy = getStringCopy(value);
1831 /* change value; if it does not exist, insert it as new */
1832 if (!change_hash_entry(hash, token, value_copy))
1833 if (!insert_hash_entry(hash, getStringCopy(token), value_copy))
1834 Error(ERR_EXIT, "cannot insert into hash -- aborting");
1837 char *removeHashEntry(SetupFileHash *hash, char *token)
1842 return remove_hash_entry(hash, token);
1845 #if ENABLE_UNUSED_CODE
1847 static void printSetupFileHash(SetupFileHash *hash)
1849 BEGIN_HASH_ITERATION(hash, itr)
1851 printf("token: '%s'\n", HASH_ITERATION_TOKEN(itr));
1852 printf("value: '%s'\n", HASH_ITERATION_VALUE(itr));
1854 END_HASH_ITERATION(hash, itr)
1859 #define ALLOW_TOKEN_VALUE_SEPARATOR_BEING_WHITESPACE 1
1860 #define CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING 0
1861 #define CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH 0
1863 static boolean token_value_separator_found = FALSE;
1864 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
1865 static boolean token_value_separator_warning = FALSE;
1867 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
1868 static boolean token_already_exists_warning = FALSE;
1871 static boolean getTokenValueFromSetupLineExt(char *line,
1872 char **token_ptr, char **value_ptr,
1873 char *filename, char *line_raw,
1875 boolean separator_required)
1877 static char line_copy[MAX_LINE_LEN + 1], line_raw_copy[MAX_LINE_LEN + 1];
1878 char *token, *value, *line_ptr;
1880 /* when externally invoked via ReadTokenValueFromLine(), copy line buffers */
1881 if (line_raw == NULL)
1883 strncpy(line_copy, line, MAX_LINE_LEN);
1884 line_copy[MAX_LINE_LEN] = '\0';
1887 strcpy(line_raw_copy, line_copy);
1888 line_raw = line_raw_copy;
1891 /* cut trailing comment from input line */
1892 for (line_ptr = line; *line_ptr; line_ptr++)
1894 if (*line_ptr == '#')
1901 /* cut trailing whitespaces from input line */
1902 for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
1903 if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
1906 /* ignore empty lines */
1910 /* cut leading whitespaces from token */
1911 for (token = line; *token; token++)
1912 if (*token != ' ' && *token != '\t')
1915 /* start with empty value as reliable default */
1918 token_value_separator_found = FALSE;
1920 /* find end of token to determine start of value */
1921 for (line_ptr = token; *line_ptr; line_ptr++)
1923 /* first look for an explicit token/value separator, like ':' or '=' */
1924 if (*line_ptr == ':' || *line_ptr == '=')
1926 *line_ptr = '\0'; /* terminate token string */
1927 value = line_ptr + 1; /* set beginning of value */
1929 token_value_separator_found = TRUE;
1935 #if ALLOW_TOKEN_VALUE_SEPARATOR_BEING_WHITESPACE
1936 /* fallback: if no token/value separator found, also allow whitespaces */
1937 if (!token_value_separator_found && !separator_required)
1939 for (line_ptr = token; *line_ptr; line_ptr++)
1941 if (*line_ptr == ' ' || *line_ptr == '\t')
1943 *line_ptr = '\0'; /* terminate token string */
1944 value = line_ptr + 1; /* set beginning of value */
1946 token_value_separator_found = TRUE;
1952 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
1953 if (token_value_separator_found)
1955 if (!token_value_separator_warning)
1957 Error(ERR_INFO_LINE, "-");
1959 if (filename != NULL)
1961 Error(ERR_WARN, "missing token/value separator(s) in config file:");
1962 Error(ERR_INFO, "- config file: '%s'", filename);
1966 Error(ERR_WARN, "missing token/value separator(s):");
1969 token_value_separator_warning = TRUE;
1972 if (filename != NULL)
1973 Error(ERR_INFO, "- line %d: '%s'", line_nr, line_raw);
1975 Error(ERR_INFO, "- line: '%s'", line_raw);
1981 /* cut trailing whitespaces from token */
1982 for (line_ptr = &token[strlen(token)]; line_ptr >= token; line_ptr--)
1983 if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
1986 /* cut leading whitespaces from value */
1987 for (; *value; value++)
1988 if (*value != ' ' && *value != '\t')
1997 boolean getTokenValueFromSetupLine(char *line, char **token, char **value)
1999 /* while the internal (old) interface does not require a token/value
2000 separator (for downwards compatibility with existing files which
2001 don't use them), it is mandatory for the external (new) interface */
2003 return getTokenValueFromSetupLineExt(line, token, value, NULL, NULL, 0, TRUE);
2006 static boolean loadSetupFileData(void *setup_file_data, char *filename,
2007 boolean top_recursion_level, boolean is_hash)
2009 static SetupFileHash *include_filename_hash = NULL;
2010 char line[MAX_LINE_LEN], line_raw[MAX_LINE_LEN], previous_line[MAX_LINE_LEN];
2011 char *token, *value, *line_ptr;
2012 void *insert_ptr = NULL;
2013 boolean read_continued_line = FALSE;
2015 int line_nr = 0, token_count = 0, include_count = 0;
2017 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2018 token_value_separator_warning = FALSE;
2021 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2022 token_already_exists_warning = FALSE;
2025 if (!(file = openFile(filename, MODE_READ)))
2027 Error(ERR_DEBUG, "cannot open configuration file '%s'", filename);
2032 /* use "insert pointer" to store list end for constant insertion complexity */
2034 insert_ptr = setup_file_data;
2036 /* on top invocation, create hash to mark included files (to prevent loops) */
2037 if (top_recursion_level)
2038 include_filename_hash = newSetupFileHash();
2040 /* mark this file as already included (to prevent including it again) */
2041 setHashEntry(include_filename_hash, getBaseNamePtr(filename), "true");
2043 while (!checkEndOfFile(file))
2045 /* read next line of input file */
2046 if (!getStringFromFile(file, line, MAX_LINE_LEN))
2049 /* check if line was completely read and is terminated by line break */
2050 if (strlen(line) > 0 && line[strlen(line) - 1] == '\n')
2053 /* cut trailing line break (this can be newline and/or carriage return) */
2054 for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
2055 if ((*line_ptr == '\n' || *line_ptr == '\r') && *(line_ptr + 1) == '\0')
2058 /* copy raw input line for later use (mainly debugging output) */
2059 strcpy(line_raw, line);
2061 if (read_continued_line)
2063 /* append new line to existing line, if there is enough space */
2064 if (strlen(previous_line) + strlen(line_ptr) < MAX_LINE_LEN)
2065 strcat(previous_line, line_ptr);
2067 strcpy(line, previous_line); /* copy storage buffer to line */
2069 read_continued_line = FALSE;
2072 /* if the last character is '\', continue at next line */
2073 if (strlen(line) > 0 && line[strlen(line) - 1] == '\\')
2075 line[strlen(line) - 1] = '\0'; /* cut off trailing backslash */
2076 strcpy(previous_line, line); /* copy line to storage buffer */
2078 read_continued_line = TRUE;
2083 if (!getTokenValueFromSetupLineExt(line, &token, &value, filename,
2084 line_raw, line_nr, FALSE))
2089 if (strEqual(token, "include"))
2091 if (getHashEntry(include_filename_hash, value) == NULL)
2093 char *basepath = getBasePath(filename);
2094 char *basename = getBaseName(value);
2095 char *filename_include = getPath2(basepath, basename);
2097 loadSetupFileData(setup_file_data, filename_include, FALSE, is_hash);
2101 free(filename_include);
2107 Error(ERR_WARN, "ignoring already processed file '%s'", value);
2114 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2116 getHashEntry((SetupFileHash *)setup_file_data, token);
2118 if (old_value != NULL)
2120 if (!token_already_exists_warning)
2122 Error(ERR_INFO_LINE, "-");
2123 Error(ERR_WARN, "duplicate token(s) found in config file:");
2124 Error(ERR_INFO, "- config file: '%s'", filename);
2126 token_already_exists_warning = TRUE;
2129 Error(ERR_INFO, "- token: '%s' (in line %d)", token, line_nr);
2130 Error(ERR_INFO, " old value: '%s'", old_value);
2131 Error(ERR_INFO, " new value: '%s'", value);
2135 setHashEntry((SetupFileHash *)setup_file_data, token, value);
2139 insert_ptr = addListEntry((SetupFileList *)insert_ptr, token, value);
2149 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2150 if (token_value_separator_warning)
2151 Error(ERR_INFO_LINE, "-");
2154 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2155 if (token_already_exists_warning)
2156 Error(ERR_INFO_LINE, "-");
2159 if (token_count == 0 && include_count == 0)
2160 Error(ERR_WARN, "configuration file '%s' is empty", filename);
2162 if (top_recursion_level)
2163 freeSetupFileHash(include_filename_hash);
2168 void saveSetupFileHash(SetupFileHash *hash, char *filename)
2172 if (!(file = fopen(filename, MODE_WRITE)))
2174 Error(ERR_WARN, "cannot write configuration file '%s'", filename);
2179 BEGIN_HASH_ITERATION(hash, itr)
2181 fprintf(file, "%s\n", getFormattedSetupEntry(HASH_ITERATION_TOKEN(itr),
2182 HASH_ITERATION_VALUE(itr)));
2184 END_HASH_ITERATION(hash, itr)
2189 SetupFileList *loadSetupFileList(char *filename)
2191 SetupFileList *setup_file_list = newSetupFileList("", "");
2192 SetupFileList *first_valid_list_entry;
2194 if (!loadSetupFileData(setup_file_list, filename, TRUE, FALSE))
2196 freeSetupFileList(setup_file_list);
2201 first_valid_list_entry = setup_file_list->next;
2203 /* free empty list header */
2204 setup_file_list->next = NULL;
2205 freeSetupFileList(setup_file_list);
2207 return first_valid_list_entry;
2210 SetupFileHash *loadSetupFileHash(char *filename)
2212 SetupFileHash *setup_file_hash = newSetupFileHash();
2214 if (!loadSetupFileData(setup_file_hash, filename, TRUE, TRUE))
2216 freeSetupFileHash(setup_file_hash);
2221 return setup_file_hash;
2225 /* ========================================================================= */
2226 /* setup file stuff */
2227 /* ========================================================================= */
2229 #define TOKEN_STR_LAST_LEVEL_SERIES "last_level_series"
2230 #define TOKEN_STR_LAST_PLAYED_LEVEL "last_played_level"
2231 #define TOKEN_STR_HANDICAP_LEVEL "handicap_level"
2233 /* level directory info */
2234 #define LEVELINFO_TOKEN_IDENTIFIER 0
2235 #define LEVELINFO_TOKEN_NAME 1
2236 #define LEVELINFO_TOKEN_NAME_SORTING 2
2237 #define LEVELINFO_TOKEN_AUTHOR 3
2238 #define LEVELINFO_TOKEN_YEAR 4
2239 #define LEVELINFO_TOKEN_PROGRAM_TITLE 5
2240 #define LEVELINFO_TOKEN_PROGRAM_COPYRIGHT 6
2241 #define LEVELINFO_TOKEN_PROGRAM_COMPANY 7
2242 #define LEVELINFO_TOKEN_IMPORTED_FROM 8
2243 #define LEVELINFO_TOKEN_IMPORTED_BY 9
2244 #define LEVELINFO_TOKEN_TESTED_BY 10
2245 #define LEVELINFO_TOKEN_LEVELS 11
2246 #define LEVELINFO_TOKEN_FIRST_LEVEL 12
2247 #define LEVELINFO_TOKEN_SORT_PRIORITY 13
2248 #define LEVELINFO_TOKEN_LATEST_ENGINE 14
2249 #define LEVELINFO_TOKEN_LEVEL_GROUP 15
2250 #define LEVELINFO_TOKEN_READONLY 16
2251 #define LEVELINFO_TOKEN_GRAPHICS_SET_ECS 17
2252 #define LEVELINFO_TOKEN_GRAPHICS_SET_AGA 18
2253 #define LEVELINFO_TOKEN_GRAPHICS_SET 19
2254 #define LEVELINFO_TOKEN_SOUNDS_SET 20
2255 #define LEVELINFO_TOKEN_MUSIC_SET 21
2256 #define LEVELINFO_TOKEN_FILENAME 22
2257 #define LEVELINFO_TOKEN_FILETYPE 23
2258 #define LEVELINFO_TOKEN_SPECIAL_FLAGS 24
2259 #define LEVELINFO_TOKEN_HANDICAP 25
2260 #define LEVELINFO_TOKEN_SKIP_LEVELS 26
2262 #define NUM_LEVELINFO_TOKENS 27
2264 static LevelDirTree ldi;
2266 static struct TokenInfo levelinfo_tokens[] =
2268 /* level directory info */
2269 { TYPE_STRING, &ldi.identifier, "identifier" },
2270 { TYPE_STRING, &ldi.name, "name" },
2271 { TYPE_STRING, &ldi.name_sorting, "name_sorting" },
2272 { TYPE_STRING, &ldi.author, "author" },
2273 { TYPE_STRING, &ldi.year, "year" },
2274 { TYPE_STRING, &ldi.program_title, "program_title" },
2275 { TYPE_STRING, &ldi.program_copyright, "program_copyright" },
2276 { TYPE_STRING, &ldi.program_company, "program_company" },
2277 { TYPE_STRING, &ldi.imported_from, "imported_from" },
2278 { TYPE_STRING, &ldi.imported_by, "imported_by" },
2279 { TYPE_STRING, &ldi.tested_by, "tested_by" },
2280 { TYPE_INTEGER, &ldi.levels, "levels" },
2281 { TYPE_INTEGER, &ldi.first_level, "first_level" },
2282 { TYPE_INTEGER, &ldi.sort_priority, "sort_priority" },
2283 { TYPE_BOOLEAN, &ldi.latest_engine, "latest_engine" },
2284 { TYPE_BOOLEAN, &ldi.level_group, "level_group" },
2285 { TYPE_BOOLEAN, &ldi.readonly, "readonly" },
2286 { TYPE_STRING, &ldi.graphics_set_ecs, "graphics_set.ecs" },
2287 { TYPE_STRING, &ldi.graphics_set_aga, "graphics_set.aga" },
2288 { TYPE_STRING, &ldi.graphics_set, "graphics_set" },
2289 { TYPE_STRING, &ldi.sounds_set, "sounds_set" },
2290 { TYPE_STRING, &ldi.music_set, "music_set" },
2291 { TYPE_STRING, &ldi.level_filename, "filename" },
2292 { TYPE_STRING, &ldi.level_filetype, "filetype" },
2293 { TYPE_STRING, &ldi.special_flags, "special_flags" },
2294 { TYPE_BOOLEAN, &ldi.handicap, "handicap" },
2295 { TYPE_BOOLEAN, &ldi.skip_levels, "skip_levels" }
2298 static struct TokenInfo artworkinfo_tokens[] =
2300 /* artwork directory info */
2301 { TYPE_STRING, &ldi.identifier, "identifier" },
2302 { TYPE_STRING, &ldi.subdir, "subdir" },
2303 { TYPE_STRING, &ldi.name, "name" },
2304 { TYPE_STRING, &ldi.name_sorting, "name_sorting" },
2305 { TYPE_STRING, &ldi.author, "author" },
2306 { TYPE_STRING, &ldi.program_title, "program_title" },
2307 { TYPE_STRING, &ldi.program_copyright, "program_copyright" },
2308 { TYPE_STRING, &ldi.program_company, "program_company" },
2309 { TYPE_INTEGER, &ldi.sort_priority, "sort_priority" },
2310 { TYPE_STRING, &ldi.basepath, "basepath" },
2311 { TYPE_STRING, &ldi.fullpath, "fullpath" },
2312 { TYPE_BOOLEAN, &ldi.in_user_dir, "in_user_dir" },
2313 { TYPE_INTEGER, &ldi.color, "color" },
2314 { TYPE_STRING, &ldi.class_desc, "class_desc" },
2319 static void setTreeInfoToDefaults(TreeInfo *ti, int type)
2323 ti->node_top = (ti->type == TREE_TYPE_LEVEL_DIR ? &leveldir_first :
2324 ti->type == TREE_TYPE_GRAPHICS_DIR ? &artwork.gfx_first :
2325 ti->type == TREE_TYPE_SOUNDS_DIR ? &artwork.snd_first :
2326 ti->type == TREE_TYPE_MUSIC_DIR ? &artwork.mus_first :
2329 ti->node_parent = NULL;
2330 ti->node_group = NULL;
2337 ti->fullpath = NULL;
2338 ti->basepath = NULL;
2339 ti->identifier = NULL;
2340 ti->name = getStringCopy(ANONYMOUS_NAME);
2341 ti->name_sorting = NULL;
2342 ti->author = getStringCopy(ANONYMOUS_NAME);
2345 ti->program_title = NULL;
2346 ti->program_copyright = NULL;
2347 ti->program_company = NULL;
2349 ti->sort_priority = LEVELCLASS_UNDEFINED; /* default: least priority */
2350 ti->latest_engine = FALSE; /* default: get from level */
2351 ti->parent_link = FALSE;
2352 ti->in_user_dir = FALSE;
2353 ti->user_defined = FALSE;
2355 ti->class_desc = NULL;
2357 ti->infotext = getStringCopy(TREE_INFOTEXT(ti->type));
2359 if (ti->type == TREE_TYPE_LEVEL_DIR)
2361 ti->imported_from = NULL;
2362 ti->imported_by = NULL;
2363 ti->tested_by = NULL;
2365 ti->graphics_set_ecs = NULL;
2366 ti->graphics_set_aga = NULL;
2367 ti->graphics_set = NULL;
2368 ti->sounds_set = NULL;
2369 ti->music_set = NULL;
2370 ti->graphics_path = getStringCopy(UNDEFINED_FILENAME);
2371 ti->sounds_path = getStringCopy(UNDEFINED_FILENAME);
2372 ti->music_path = getStringCopy(UNDEFINED_FILENAME);
2374 ti->level_filename = NULL;
2375 ti->level_filetype = NULL;
2377 ti->special_flags = NULL;
2380 ti->first_level = 0;
2382 ti->level_group = FALSE;
2383 ti->handicap_level = 0;
2384 ti->readonly = TRUE;
2385 ti->handicap = TRUE;
2386 ti->skip_levels = FALSE;
2390 static void setTreeInfoToDefaultsFromParent(TreeInfo *ti, TreeInfo *parent)
2394 Error(ERR_WARN, "setTreeInfoToDefaultsFromParent(): parent == NULL");
2396 setTreeInfoToDefaults(ti, TREE_TYPE_UNDEFINED);
2401 /* copy all values from the parent structure */
2403 ti->type = parent->type;
2405 ti->node_top = parent->node_top;
2406 ti->node_parent = parent;
2407 ti->node_group = NULL;
2414 ti->fullpath = NULL;
2415 ti->basepath = NULL;
2416 ti->identifier = NULL;
2417 ti->name = getStringCopy(ANONYMOUS_NAME);
2418 ti->name_sorting = NULL;
2419 ti->author = getStringCopy(parent->author);
2420 ti->year = getStringCopy(parent->year);
2422 ti->program_title = getStringCopy(parent->program_title);
2423 ti->program_copyright = getStringCopy(parent->program_copyright);
2424 ti->program_company = getStringCopy(parent->program_company);
2426 ti->sort_priority = parent->sort_priority;
2427 ti->latest_engine = parent->latest_engine;
2428 ti->parent_link = FALSE;
2429 ti->in_user_dir = parent->in_user_dir;
2430 ti->user_defined = parent->user_defined;
2431 ti->color = parent->color;
2432 ti->class_desc = getStringCopy(parent->class_desc);
2434 ti->infotext = getStringCopy(parent->infotext);
2436 if (ti->type == TREE_TYPE_LEVEL_DIR)
2438 ti->imported_from = getStringCopy(parent->imported_from);
2439 ti->imported_by = getStringCopy(parent->imported_by);
2440 ti->tested_by = getStringCopy(parent->tested_by);
2442 ti->graphics_set_ecs = getStringCopy(parent->graphics_set_ecs);
2443 ti->graphics_set_aga = getStringCopy(parent->graphics_set_aga);
2444 ti->graphics_set = getStringCopy(parent->graphics_set);
2445 ti->sounds_set = getStringCopy(parent->sounds_set);
2446 ti->music_set = getStringCopy(parent->music_set);
2447 ti->graphics_path = getStringCopy(UNDEFINED_FILENAME);
2448 ti->sounds_path = getStringCopy(UNDEFINED_FILENAME);
2449 ti->music_path = getStringCopy(UNDEFINED_FILENAME);
2451 ti->level_filename = getStringCopy(parent->level_filename);
2452 ti->level_filetype = getStringCopy(parent->level_filetype);
2454 ti->special_flags = getStringCopy(parent->special_flags);
2456 ti->levels = parent->levels;
2457 ti->first_level = parent->first_level;
2458 ti->last_level = parent->last_level;
2459 ti->level_group = FALSE;
2460 ti->handicap_level = parent->handicap_level;
2461 ti->readonly = parent->readonly;
2462 ti->handicap = parent->handicap;
2463 ti->skip_levels = parent->skip_levels;
2467 static TreeInfo *getTreeInfoCopy(TreeInfo *ti)
2469 TreeInfo *ti_copy = newTreeInfo();
2471 /* copy all values from the original structure */
2473 ti_copy->type = ti->type;
2475 ti_copy->node_top = ti->node_top;
2476 ti_copy->node_parent = ti->node_parent;
2477 ti_copy->node_group = ti->node_group;
2478 ti_copy->next = ti->next;
2480 ti_copy->cl_first = ti->cl_first;
2481 ti_copy->cl_cursor = ti->cl_cursor;
2483 ti_copy->subdir = getStringCopy(ti->subdir);
2484 ti_copy->fullpath = getStringCopy(ti->fullpath);
2485 ti_copy->basepath = getStringCopy(ti->basepath);
2486 ti_copy->identifier = getStringCopy(ti->identifier);
2487 ti_copy->name = getStringCopy(ti->name);
2488 ti_copy->name_sorting = getStringCopy(ti->name_sorting);
2489 ti_copy->author = getStringCopy(ti->author);
2490 ti_copy->year = getStringCopy(ti->year);
2492 ti_copy->program_title = getStringCopy(ti->program_title);
2493 ti_copy->program_copyright = getStringCopy(ti->program_copyright);
2494 ti_copy->program_company = getStringCopy(ti->program_company);
2496 ti_copy->imported_from = getStringCopy(ti->imported_from);
2497 ti_copy->imported_by = getStringCopy(ti->imported_by);
2498 ti_copy->tested_by = getStringCopy(ti->tested_by);
2500 ti_copy->graphics_set_ecs = getStringCopy(ti->graphics_set_ecs);
2501 ti_copy->graphics_set_aga = getStringCopy(ti->graphics_set_aga);
2502 ti_copy->graphics_set = getStringCopy(ti->graphics_set);
2503 ti_copy->sounds_set = getStringCopy(ti->sounds_set);
2504 ti_copy->music_set = getStringCopy(ti->music_set);
2505 ti_copy->graphics_path = getStringCopy(ti->graphics_path);
2506 ti_copy->sounds_path = getStringCopy(ti->sounds_path);
2507 ti_copy->music_path = getStringCopy(ti->music_path);
2509 ti_copy->level_filename = getStringCopy(ti->level_filename);
2510 ti_copy->level_filetype = getStringCopy(ti->level_filetype);
2512 ti_copy->special_flags = getStringCopy(ti->special_flags);
2514 ti_copy->levels = ti->levels;
2515 ti_copy->first_level = ti->first_level;
2516 ti_copy->last_level = ti->last_level;
2517 ti_copy->sort_priority = ti->sort_priority;
2519 ti_copy->latest_engine = ti->latest_engine;
2521 ti_copy->level_group = ti->level_group;
2522 ti_copy->parent_link = ti->parent_link;
2523 ti_copy->in_user_dir = ti->in_user_dir;
2524 ti_copy->user_defined = ti->user_defined;
2525 ti_copy->readonly = ti->readonly;
2526 ti_copy->handicap = ti->handicap;
2527 ti_copy->skip_levels = ti->skip_levels;
2529 ti_copy->color = ti->color;
2530 ti_copy->class_desc = getStringCopy(ti->class_desc);
2531 ti_copy->handicap_level = ti->handicap_level;
2533 ti_copy->infotext = getStringCopy(ti->infotext);
2538 void freeTreeInfo(TreeInfo *ti)
2543 checked_free(ti->subdir);
2544 checked_free(ti->fullpath);
2545 checked_free(ti->basepath);
2546 checked_free(ti->identifier);
2548 checked_free(ti->name);
2549 checked_free(ti->name_sorting);
2550 checked_free(ti->author);
2551 checked_free(ti->year);
2553 checked_free(ti->program_title);
2554 checked_free(ti->program_copyright);
2555 checked_free(ti->program_company);
2557 checked_free(ti->class_desc);
2559 checked_free(ti->infotext);
2561 if (ti->type == TREE_TYPE_LEVEL_DIR)
2563 checked_free(ti->imported_from);
2564 checked_free(ti->imported_by);
2565 checked_free(ti->tested_by);
2567 checked_free(ti->graphics_set_ecs);
2568 checked_free(ti->graphics_set_aga);
2569 checked_free(ti->graphics_set);
2570 checked_free(ti->sounds_set);
2571 checked_free(ti->music_set);
2573 checked_free(ti->graphics_path);
2574 checked_free(ti->sounds_path);
2575 checked_free(ti->music_path);
2577 checked_free(ti->level_filename);
2578 checked_free(ti->level_filetype);
2580 checked_free(ti->special_flags);
2583 // recursively free child node
2585 freeTreeInfo(ti->node_group);
2587 // recursively free next node
2589 freeTreeInfo(ti->next);
2594 void setSetupInfo(struct TokenInfo *token_info,
2595 int token_nr, char *token_value)
2597 int token_type = token_info[token_nr].type;
2598 void *setup_value = token_info[token_nr].value;
2600 if (token_value == NULL)
2603 /* set setup field to corresponding token value */
2608 *(boolean *)setup_value = get_boolean_from_string(token_value);
2612 *(int *)setup_value = get_switch3_from_string(token_value);
2616 *(Key *)setup_value = getKeyFromKeyName(token_value);
2620 *(Key *)setup_value = getKeyFromX11KeyName(token_value);
2624 *(int *)setup_value = get_integer_from_string(token_value);
2628 checked_free(*(char **)setup_value);
2629 *(char **)setup_value = getStringCopy(token_value);
2633 *(int *)setup_value = get_player_nr_from_string(token_value);
2641 static int compareTreeInfoEntries(const void *object1, const void *object2)
2643 const TreeInfo *entry1 = *((TreeInfo **)object1);
2644 const TreeInfo *entry2 = *((TreeInfo **)object2);
2645 int class_sorting1 = 0, class_sorting2 = 0;
2648 if (entry1->type == TREE_TYPE_LEVEL_DIR)
2650 class_sorting1 = LEVELSORTING(entry1);
2651 class_sorting2 = LEVELSORTING(entry2);
2653 else if (entry1->type == TREE_TYPE_GRAPHICS_DIR ||
2654 entry1->type == TREE_TYPE_SOUNDS_DIR ||
2655 entry1->type == TREE_TYPE_MUSIC_DIR)
2657 class_sorting1 = ARTWORKSORTING(entry1);
2658 class_sorting2 = ARTWORKSORTING(entry2);
2661 if (entry1->parent_link || entry2->parent_link)
2662 compare_result = (entry1->parent_link ? -1 : +1);
2663 else if (entry1->sort_priority == entry2->sort_priority)
2665 char *name1 = getStringToLower(entry1->name_sorting);
2666 char *name2 = getStringToLower(entry2->name_sorting);
2668 compare_result = strcmp(name1, name2);
2673 else if (class_sorting1 == class_sorting2)
2674 compare_result = entry1->sort_priority - entry2->sort_priority;
2676 compare_result = class_sorting1 - class_sorting2;
2678 return compare_result;
2681 static TreeInfo *createParentTreeInfoNode(TreeInfo *node_parent)
2685 if (node_parent == NULL)
2688 ti_new = newTreeInfo();
2689 setTreeInfoToDefaults(ti_new, node_parent->type);
2691 ti_new->node_parent = node_parent;
2692 ti_new->parent_link = TRUE;
2694 setString(&ti_new->identifier, node_parent->identifier);
2695 setString(&ti_new->name, ".. (parent directory)");
2696 setString(&ti_new->name_sorting, ti_new->name);
2698 setString(&ti_new->subdir, STRING_PARENT_DIRECTORY);
2699 setString(&ti_new->fullpath, node_parent->fullpath);
2701 ti_new->sort_priority = node_parent->sort_priority;
2702 ti_new->latest_engine = node_parent->latest_engine;
2704 setString(&ti_new->class_desc, getLevelClassDescription(ti_new));
2706 pushTreeInfo(&node_parent->node_group, ti_new);
2711 static TreeInfo *createTopTreeInfoNode(TreeInfo *node_first)
2713 TreeInfo *ti_new, *ti_new2;
2715 if (node_first == NULL)
2718 ti_new = newTreeInfo();
2719 setTreeInfoToDefaults(ti_new, TREE_TYPE_LEVEL_DIR);
2721 ti_new->node_parent = NULL;
2722 ti_new->parent_link = FALSE;
2724 setString(&ti_new->identifier, node_first->identifier);
2725 setString(&ti_new->name, "level sets");
2726 setString(&ti_new->name_sorting, ti_new->name);
2728 setString(&ti_new->subdir, STRING_TOP_DIRECTORY);
2729 setString(&ti_new->fullpath, ".");
2731 ti_new->sort_priority = node_first->sort_priority;;
2732 ti_new->latest_engine = node_first->latest_engine;
2734 setString(&ti_new->class_desc, "level sets");
2736 ti_new->node_group = node_first;
2737 ti_new->level_group = TRUE;
2739 ti_new2 = createParentTreeInfoNode(ti_new);
2741 setString(&ti_new2->name, ".. (main menu)");
2742 setString(&ti_new2->name_sorting, ti_new2->name);
2748 /* -------------------------------------------------------------------------- */
2749 /* functions for handling level and custom artwork info cache */
2750 /* -------------------------------------------------------------------------- */
2752 static void LoadArtworkInfoCache()
2754 InitCacheDirectory();
2756 if (artworkinfo_cache_old == NULL)
2758 char *filename = getPath2(getCacheDir(), ARTWORKINFO_CACHE_FILE);
2760 /* try to load artwork info hash from already existing cache file */
2761 artworkinfo_cache_old = loadSetupFileHash(filename);
2763 /* if no artwork info cache file was found, start with empty hash */
2764 if (artworkinfo_cache_old == NULL)
2765 artworkinfo_cache_old = newSetupFileHash();
2770 if (artworkinfo_cache_new == NULL)
2771 artworkinfo_cache_new = newSetupFileHash();
2774 static void SaveArtworkInfoCache()
2776 char *filename = getPath2(getCacheDir(), ARTWORKINFO_CACHE_FILE);
2778 InitCacheDirectory();
2780 saveSetupFileHash(artworkinfo_cache_new, filename);
2785 static char *getCacheTokenPrefix(char *prefix1, char *prefix2)
2787 static char *prefix = NULL;
2789 checked_free(prefix);
2791 prefix = getStringCat2WithSeparator(prefix1, prefix2, ".");
2796 /* (identical to above function, but separate string buffer needed -- nasty) */
2797 static char *getCacheToken(char *prefix, char *suffix)
2799 static char *token = NULL;
2801 checked_free(token);
2803 token = getStringCat2WithSeparator(prefix, suffix, ".");
2808 static char *getFileTimestampString(char *filename)
2810 return getStringCopy(i_to_a(getFileTimestampEpochSeconds(filename)));
2813 static boolean modifiedFileTimestamp(char *filename, char *timestamp_string)
2815 struct stat file_status;
2817 if (timestamp_string == NULL)
2820 if (stat(filename, &file_status) != 0) /* cannot stat file */
2823 return (file_status.st_mtime != atoi(timestamp_string));
2826 static TreeInfo *getArtworkInfoCacheEntry(LevelDirTree *level_node, int type)
2828 char *identifier = level_node->subdir;
2829 char *type_string = ARTWORK_DIRECTORY(type);
2830 char *token_prefix = getCacheTokenPrefix(type_string, identifier);
2831 char *token_main = getCacheToken(token_prefix, "CACHED");
2832 char *cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
2833 boolean cached = (cache_entry != NULL && strEqual(cache_entry, "true"));
2834 TreeInfo *artwork_info = NULL;
2836 if (!use_artworkinfo_cache)
2843 artwork_info = newTreeInfo();
2844 setTreeInfoToDefaults(artwork_info, type);
2846 /* set all structure fields according to the token/value pairs */
2847 ldi = *artwork_info;
2848 for (i = 0; artworkinfo_tokens[i].type != -1; i++)
2850 char *token = getCacheToken(token_prefix, artworkinfo_tokens[i].text);
2851 char *value = getHashEntry(artworkinfo_cache_old, token);
2853 /* if defined, use value from cache, else keep default value */
2855 setSetupInfo(artworkinfo_tokens, i, value);
2858 *artwork_info = ldi;
2860 char *filename_levelinfo = getPath2(getLevelDirFromTreeInfo(level_node),
2861 LEVELINFO_FILENAME);
2862 char *filename_artworkinfo = getPath2(getSetupArtworkDir(artwork_info),
2863 ARTWORKINFO_FILENAME(type));
2865 /* check if corresponding "levelinfo.conf" file has changed */
2866 token_main = getCacheToken(token_prefix, "TIMESTAMP_LEVELINFO");
2867 cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
2869 if (modifiedFileTimestamp(filename_levelinfo, cache_entry))
2872 /* check if corresponding "<artworkinfo>.conf" file has changed */
2873 token_main = getCacheToken(token_prefix, "TIMESTAMP_ARTWORKINFO");
2874 cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
2876 if (modifiedFileTimestamp(filename_artworkinfo, cache_entry))
2879 checked_free(filename_levelinfo);
2880 checked_free(filename_artworkinfo);
2883 if (!cached && artwork_info != NULL)
2885 freeTreeInfo(artwork_info);
2890 return artwork_info;
2893 static void setArtworkInfoCacheEntry(TreeInfo *artwork_info,
2894 LevelDirTree *level_node, int type)
2896 char *identifier = level_node->subdir;
2897 char *type_string = ARTWORK_DIRECTORY(type);
2898 char *token_prefix = getCacheTokenPrefix(type_string, identifier);
2899 char *token_main = getCacheToken(token_prefix, "CACHED");
2900 boolean set_cache_timestamps = TRUE;
2903 setHashEntry(artworkinfo_cache_new, token_main, "true");
2905 if (set_cache_timestamps)
2907 char *filename_levelinfo = getPath2(getLevelDirFromTreeInfo(level_node),
2908 LEVELINFO_FILENAME);
2909 char *filename_artworkinfo = getPath2(getSetupArtworkDir(artwork_info),
2910 ARTWORKINFO_FILENAME(type));
2911 char *timestamp_levelinfo = getFileTimestampString(filename_levelinfo);
2912 char *timestamp_artworkinfo = getFileTimestampString(filename_artworkinfo);
2914 token_main = getCacheToken(token_prefix, "TIMESTAMP_LEVELINFO");
2915 setHashEntry(artworkinfo_cache_new, token_main, timestamp_levelinfo);
2917 token_main = getCacheToken(token_prefix, "TIMESTAMP_ARTWORKINFO");
2918 setHashEntry(artworkinfo_cache_new, token_main, timestamp_artworkinfo);
2920 checked_free(filename_levelinfo);
2921 checked_free(filename_artworkinfo);
2922 checked_free(timestamp_levelinfo);
2923 checked_free(timestamp_artworkinfo);
2926 ldi = *artwork_info;
2927 for (i = 0; artworkinfo_tokens[i].type != -1; i++)
2929 char *token = getCacheToken(token_prefix, artworkinfo_tokens[i].text);
2930 char *value = getSetupValue(artworkinfo_tokens[i].type,
2931 artworkinfo_tokens[i].value);
2933 setHashEntry(artworkinfo_cache_new, token, value);
2938 /* -------------------------------------------------------------------------- */
2939 /* functions for loading level info and custom artwork info */
2940 /* -------------------------------------------------------------------------- */
2942 /* forward declaration for recursive call by "LoadLevelInfoFromLevelDir()" */
2943 static void LoadLevelInfoFromLevelDir(TreeInfo **, TreeInfo *, char *);
2945 static boolean LoadLevelInfoFromLevelConf(TreeInfo **node_first,
2946 TreeInfo *node_parent,
2947 char *level_directory,
2948 char *directory_name)
2950 char *directory_path = getPath2(level_directory, directory_name);
2951 char *filename = getPath2(directory_path, LEVELINFO_FILENAME);
2952 SetupFileHash *setup_file_hash;
2953 LevelDirTree *leveldir_new = NULL;
2956 /* unless debugging, silently ignore directories without "levelinfo.conf" */
2957 if (!options.debug && !fileExists(filename))
2959 free(directory_path);
2965 setup_file_hash = loadSetupFileHash(filename);
2967 if (setup_file_hash == NULL)
2969 Error(ERR_WARN, "ignoring level directory '%s'", directory_path);
2971 free(directory_path);
2977 leveldir_new = newTreeInfo();
2980 setTreeInfoToDefaultsFromParent(leveldir_new, node_parent);
2982 setTreeInfoToDefaults(leveldir_new, TREE_TYPE_LEVEL_DIR);
2984 leveldir_new->subdir = getStringCopy(directory_name);
2986 /* set all structure fields according to the token/value pairs */
2987 ldi = *leveldir_new;
2988 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
2989 setSetupInfo(levelinfo_tokens, i,
2990 getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
2991 *leveldir_new = ldi;
2993 if (strEqual(leveldir_new->name, ANONYMOUS_NAME))
2994 setString(&leveldir_new->name, leveldir_new->subdir);
2996 if (leveldir_new->identifier == NULL)
2997 leveldir_new->identifier = getStringCopy(leveldir_new->subdir);
2999 if (leveldir_new->name_sorting == NULL)
3000 leveldir_new->name_sorting = getStringCopy(leveldir_new->name);
3002 if (node_parent == NULL) /* top level group */
3004 leveldir_new->basepath = getStringCopy(level_directory);
3005 leveldir_new->fullpath = getStringCopy(leveldir_new->subdir);
3007 else /* sub level group */
3009 leveldir_new->basepath = getStringCopy(node_parent->basepath);
3010 leveldir_new->fullpath = getPath2(node_parent->fullpath, directory_name);
3013 leveldir_new->last_level =
3014 leveldir_new->first_level + leveldir_new->levels - 1;
3016 leveldir_new->in_user_dir =
3017 (!strEqual(leveldir_new->basepath, options.level_directory));
3019 /* adjust some settings if user's private level directory was detected */
3020 if (leveldir_new->sort_priority == LEVELCLASS_UNDEFINED &&
3021 leveldir_new->in_user_dir &&
3022 (strEqual(leveldir_new->subdir, getLoginName()) ||
3023 strEqual(leveldir_new->name, getLoginName()) ||
3024 strEqual(leveldir_new->author, getRealName())))
3026 leveldir_new->sort_priority = LEVELCLASS_PRIVATE_START;
3027 leveldir_new->readonly = FALSE;
3030 leveldir_new->user_defined =
3031 (leveldir_new->in_user_dir && IS_LEVELCLASS_PRIVATE(leveldir_new));
3033 leveldir_new->color = LEVELCOLOR(leveldir_new);
3035 setString(&leveldir_new->class_desc, getLevelClassDescription(leveldir_new));
3037 leveldir_new->handicap_level = /* set handicap to default value */
3038 (leveldir_new->user_defined || !leveldir_new->handicap ?
3039 leveldir_new->last_level : leveldir_new->first_level);
3041 DrawInitText(leveldir_new->name, 150, FC_YELLOW);
3043 pushTreeInfo(node_first, leveldir_new);
3045 freeSetupFileHash(setup_file_hash);
3047 if (leveldir_new->level_group)
3049 /* create node to link back to current level directory */
3050 createParentTreeInfoNode(leveldir_new);
3052 /* recursively step into sub-directory and look for more level series */
3053 LoadLevelInfoFromLevelDir(&leveldir_new->node_group,
3054 leveldir_new, directory_path);
3057 free(directory_path);
3063 static void LoadLevelInfoFromLevelDir(TreeInfo **node_first,
3064 TreeInfo *node_parent,
3065 char *level_directory)
3068 DirectoryEntry *dir_entry;
3069 boolean valid_entry_found = FALSE;
3071 if ((dir = openDirectory(level_directory)) == NULL)
3073 Error(ERR_WARN, "cannot read level directory '%s'", level_directory);
3078 while ((dir_entry = readDirectory(dir)) != NULL) /* loop all entries */
3080 char *directory_name = dir_entry->basename;
3081 char *directory_path = getPath2(level_directory, directory_name);
3083 /* skip entries for current and parent directory */
3084 if (strEqual(directory_name, ".") ||
3085 strEqual(directory_name, ".."))
3087 free(directory_path);
3092 /* find out if directory entry is itself a directory */
3093 if (!dir_entry->is_directory) /* not a directory */
3095 free(directory_path);
3100 free(directory_path);
3102 if (strEqual(directory_name, GRAPHICS_DIRECTORY) ||
3103 strEqual(directory_name, SOUNDS_DIRECTORY) ||
3104 strEqual(directory_name, MUSIC_DIRECTORY))
3107 valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
3112 closeDirectory(dir);
3114 /* special case: top level directory may directly contain "levelinfo.conf" */
3115 if (node_parent == NULL && !valid_entry_found)
3117 /* check if this directory directly contains a file "levelinfo.conf" */
3118 valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
3119 level_directory, ".");
3122 if (!valid_entry_found)
3123 Error(ERR_WARN, "cannot find any valid level series in directory '%s'",
3127 boolean AdjustGraphicsForEMC()
3129 boolean settings_changed = FALSE;
3131 settings_changed |= adjustTreeGraphicsForEMC(leveldir_first_all);
3132 settings_changed |= adjustTreeGraphicsForEMC(leveldir_first);
3134 return settings_changed;
3137 void LoadLevelInfo()
3139 InitUserLevelDirectory(getLoginName());
3141 DrawInitText("Loading level series", 120, FC_GREEN);
3143 LoadLevelInfoFromLevelDir(&leveldir_first, NULL, options.level_directory);
3144 LoadLevelInfoFromLevelDir(&leveldir_first, NULL, getUserLevelDir(NULL));
3146 leveldir_first = createTopTreeInfoNode(leveldir_first);
3148 /* after loading all level set information, clone the level directory tree
3149 and remove all level sets without levels (these may still contain artwork
3150 to be offered in the setup menu as "custom artwork", and are therefore
3151 checked for existing artwork in the function "LoadLevelArtworkInfo()") */
3152 leveldir_first_all = leveldir_first;
3153 cloneTree(&leveldir_first, leveldir_first_all, TRUE);
3155 AdjustGraphicsForEMC();
3157 /* before sorting, the first entries will be from the user directory */
3158 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
3160 if (leveldir_first == NULL)
3161 Error(ERR_EXIT, "cannot find any valid level series in any directory");
3163 sortTreeInfo(&leveldir_first);
3165 #if ENABLE_UNUSED_CODE
3166 dumpTreeInfo(leveldir_first, 0);
3170 static boolean LoadArtworkInfoFromArtworkConf(TreeInfo **node_first,
3171 TreeInfo *node_parent,
3172 char *base_directory,
3173 char *directory_name, int type)
3175 char *directory_path = getPath2(base_directory, directory_name);
3176 char *filename = getPath2(directory_path, ARTWORKINFO_FILENAME(type));
3177 SetupFileHash *setup_file_hash = NULL;
3178 TreeInfo *artwork_new = NULL;
3181 if (fileExists(filename))
3182 setup_file_hash = loadSetupFileHash(filename);
3184 if (setup_file_hash == NULL) /* no config file -- look for artwork files */
3187 DirectoryEntry *dir_entry;
3188 boolean valid_file_found = FALSE;
3190 if ((dir = openDirectory(directory_path)) != NULL)
3192 while ((dir_entry = readDirectory(dir)) != NULL)
3194 if (FileIsArtworkType(dir_entry->filename, type))
3196 valid_file_found = TRUE;
3202 closeDirectory(dir);
3205 if (!valid_file_found)
3207 if (!strEqual(directory_name, "."))
3208 Error(ERR_WARN, "ignoring artwork directory '%s'", directory_path);
3210 free(directory_path);
3217 artwork_new = newTreeInfo();
3220 setTreeInfoToDefaultsFromParent(artwork_new, node_parent);
3222 setTreeInfoToDefaults(artwork_new, type);
3224 artwork_new->subdir = getStringCopy(directory_name);
3226 if (setup_file_hash) /* (before defining ".color" and ".class_desc") */
3228 /* set all structure fields according to the token/value pairs */
3230 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
3231 setSetupInfo(levelinfo_tokens, i,
3232 getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
3235 if (strEqual(artwork_new->name, ANONYMOUS_NAME))
3236 setString(&artwork_new->name, artwork_new->subdir);
3238 if (artwork_new->identifier == NULL)
3239 artwork_new->identifier = getStringCopy(artwork_new->subdir);
3241 if (artwork_new->name_sorting == NULL)
3242 artwork_new->name_sorting = getStringCopy(artwork_new->name);
3245 if (node_parent == NULL) /* top level group */
3247 artwork_new->basepath = getStringCopy(base_directory);
3248 artwork_new->fullpath = getStringCopy(artwork_new->subdir);
3250 else /* sub level group */
3252 artwork_new->basepath = getStringCopy(node_parent->basepath);
3253 artwork_new->fullpath = getPath2(node_parent->fullpath, directory_name);
3256 artwork_new->in_user_dir =
3257 (!strEqual(artwork_new->basepath, OPTIONS_ARTWORK_DIRECTORY(type)));
3259 /* (may use ".sort_priority" from "setup_file_hash" above) */
3260 artwork_new->color = ARTWORKCOLOR(artwork_new);
3262 setString(&artwork_new->class_desc, getLevelClassDescription(artwork_new));
3264 if (setup_file_hash == NULL) /* (after determining ".user_defined") */
3266 if (strEqual(artwork_new->subdir, "."))
3268 if (artwork_new->user_defined)
3270 setString(&artwork_new->identifier, "private");
3271 artwork_new->sort_priority = ARTWORKCLASS_PRIVATE;
3275 setString(&artwork_new->identifier, "classic");
3276 artwork_new->sort_priority = ARTWORKCLASS_CLASSICS;
3279 /* set to new values after changing ".sort_priority" */
3280 artwork_new->color = ARTWORKCOLOR(artwork_new);
3282 setString(&artwork_new->class_desc,
3283 getLevelClassDescription(artwork_new));
3287 setString(&artwork_new->identifier, artwork_new->subdir);
3290 setString(&artwork_new->name, artwork_new->identifier);
3291 setString(&artwork_new->name_sorting, artwork_new->name);
3294 pushTreeInfo(node_first, artwork_new);
3296 freeSetupFileHash(setup_file_hash);
3298 free(directory_path);
3304 static void LoadArtworkInfoFromArtworkDir(TreeInfo **node_first,
3305 TreeInfo *node_parent,
3306 char *base_directory, int type)
3309 DirectoryEntry *dir_entry;
3310 boolean valid_entry_found = FALSE;
3312 if ((dir = openDirectory(base_directory)) == NULL)
3314 /* display error if directory is main "options.graphics_directory" etc. */
3315 if (base_directory == OPTIONS_ARTWORK_DIRECTORY(type))
3316 Error(ERR_WARN, "cannot read directory '%s'", base_directory);
3321 while ((dir_entry = readDirectory(dir)) != NULL) /* loop all entries */
3323 char *directory_name = dir_entry->basename;
3324 char *directory_path = getPath2(base_directory, directory_name);
3326 /* skip directory entries for current and parent directory */
3327 if (strEqual(directory_name, ".") ||
3328 strEqual(directory_name, ".."))
3330 free(directory_path);
3335 /* skip directory entries which are not a directory */
3336 if (!dir_entry->is_directory) /* not a directory */
3338 free(directory_path);
3343 free(directory_path);
3345 /* check if this directory contains artwork with or without config file */
3346 valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
3348 directory_name, type);
3351 closeDirectory(dir);
3353 /* check if this directory directly contains artwork itself */
3354 valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
3355 base_directory, ".",
3357 if (!valid_entry_found)
3358 Error(ERR_WARN, "cannot find any valid artwork in directory '%s'",
3362 static TreeInfo *getDummyArtworkInfo(int type)
3364 /* this is only needed when there is completely no artwork available */
3365 TreeInfo *artwork_new = newTreeInfo();
3367 setTreeInfoToDefaults(artwork_new, type);
3369 setString(&artwork_new->subdir, UNDEFINED_FILENAME);
3370 setString(&artwork_new->fullpath, UNDEFINED_FILENAME);
3371 setString(&artwork_new->basepath, UNDEFINED_FILENAME);
3373 setString(&artwork_new->identifier, UNDEFINED_FILENAME);
3374 setString(&artwork_new->name, UNDEFINED_FILENAME);
3375 setString(&artwork_new->name_sorting, UNDEFINED_FILENAME);
3380 void LoadArtworkInfo()
3382 LoadArtworkInfoCache();
3384 DrawInitText("Looking for custom artwork", 120, FC_GREEN);
3386 LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
3387 options.graphics_directory,
3388 TREE_TYPE_GRAPHICS_DIR);
3389 LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
3390 getUserGraphicsDir(),
3391 TREE_TYPE_GRAPHICS_DIR);
3393 LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
3394 options.sounds_directory,
3395 TREE_TYPE_SOUNDS_DIR);
3396 LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
3398 TREE_TYPE_SOUNDS_DIR);
3400 LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
3401 options.music_directory,
3402 TREE_TYPE_MUSIC_DIR);
3403 LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
3405 TREE_TYPE_MUSIC_DIR);
3407 if (artwork.gfx_first == NULL)
3408 artwork.gfx_first = getDummyArtworkInfo(TREE_TYPE_GRAPHICS_DIR);
3409 if (artwork.snd_first == NULL)
3410 artwork.snd_first = getDummyArtworkInfo(TREE_TYPE_SOUNDS_DIR);
3411 if (artwork.mus_first == NULL)
3412 artwork.mus_first = getDummyArtworkInfo(TREE_TYPE_MUSIC_DIR);
3414 /* before sorting, the first entries will be from the user directory */
3415 artwork.gfx_current =
3416 getTreeInfoFromIdentifier(artwork.gfx_first, setup.graphics_set);
3417 if (artwork.gfx_current == NULL)
3418 artwork.gfx_current =
3419 getTreeInfoFromIdentifier(artwork.gfx_first, GFX_DEFAULT_SUBDIR);
3420 if (artwork.gfx_current == NULL)
3421 artwork.gfx_current = getFirstValidTreeInfoEntry(artwork.gfx_first);
3423 artwork.snd_current =
3424 getTreeInfoFromIdentifier(artwork.snd_first, setup.sounds_set);
3425 if (artwork.snd_current == NULL)
3426 artwork.snd_current =
3427 getTreeInfoFromIdentifier(artwork.snd_first, SND_DEFAULT_SUBDIR);
3428 if (artwork.snd_current == NULL)
3429 artwork.snd_current = getFirstValidTreeInfoEntry(artwork.snd_first);
3431 artwork.mus_current =
3432 getTreeInfoFromIdentifier(artwork.mus_first, setup.music_set);
3433 if (artwork.mus_current == NULL)
3434 artwork.mus_current =
3435 getTreeInfoFromIdentifier(artwork.mus_first, MUS_DEFAULT_SUBDIR);
3436 if (artwork.mus_current == NULL)
3437 artwork.mus_current = getFirstValidTreeInfoEntry(artwork.mus_first);
3439 artwork.gfx_current_identifier = artwork.gfx_current->identifier;
3440 artwork.snd_current_identifier = artwork.snd_current->identifier;
3441 artwork.mus_current_identifier = artwork.mus_current->identifier;
3443 #if ENABLE_UNUSED_CODE
3444 printf("graphics set == %s\n\n", artwork.gfx_current_identifier);
3445 printf("sounds set == %s\n\n", artwork.snd_current_identifier);
3446 printf("music set == %s\n\n", artwork.mus_current_identifier);
3449 sortTreeInfo(&artwork.gfx_first);
3450 sortTreeInfo(&artwork.snd_first);
3451 sortTreeInfo(&artwork.mus_first);
3453 #if ENABLE_UNUSED_CODE
3454 dumpTreeInfo(artwork.gfx_first, 0);
3455 dumpTreeInfo(artwork.snd_first, 0);
3456 dumpTreeInfo(artwork.mus_first, 0);
3460 void LoadArtworkInfoFromLevelInfo(ArtworkDirTree **artwork_node,
3461 LevelDirTree *level_node)
3463 int type = (*artwork_node)->type;
3465 /* recursively check all level directories for artwork sub-directories */
3469 /* check all tree entries for artwork, but skip parent link entries */
3470 if (!level_node->parent_link)
3472 TreeInfo *artwork_new = getArtworkInfoCacheEntry(level_node, type);
3473 boolean cached = (artwork_new != NULL);
3477 pushTreeInfo(artwork_node, artwork_new);
3481 TreeInfo *topnode_last = *artwork_node;
3482 char *path = getPath2(getLevelDirFromTreeInfo(level_node),
3483 ARTWORK_DIRECTORY(type));
3485 LoadArtworkInfoFromArtworkDir(artwork_node, NULL, path, type);
3487 if (topnode_last != *artwork_node) /* check for newly added node */
3489 artwork_new = *artwork_node;
3491 setString(&artwork_new->identifier, level_node->subdir);
3492 setString(&artwork_new->name, level_node->name);
3493 setString(&artwork_new->name_sorting, level_node->name_sorting);
3495 artwork_new->sort_priority = level_node->sort_priority;
3496 artwork_new->color = LEVELCOLOR(artwork_new);
3502 /* insert artwork info (from old cache or filesystem) into new cache */
3503 if (artwork_new != NULL)
3504 setArtworkInfoCacheEntry(artwork_new, level_node, type);
3507 DrawInitText(level_node->name, 150, FC_YELLOW);
3509 if (level_node->node_group != NULL)
3510 LoadArtworkInfoFromLevelInfo(artwork_node, level_node->node_group);
3512 level_node = level_node->next;
3516 void LoadLevelArtworkInfo()
3518 print_timestamp_init("LoadLevelArtworkInfo");
3520 DrawInitText("Looking for custom level artwork", 120, FC_GREEN);
3522 print_timestamp_time("DrawTimeText");
3524 LoadArtworkInfoFromLevelInfo(&artwork.gfx_first, leveldir_first_all);
3525 print_timestamp_time("LoadArtworkInfoFromLevelInfo (gfx)");
3526 LoadArtworkInfoFromLevelInfo(&artwork.snd_first, leveldir_first_all);
3527 print_timestamp_time("LoadArtworkInfoFromLevelInfo (snd)");
3528 LoadArtworkInfoFromLevelInfo(&artwork.mus_first, leveldir_first_all);
3529 print_timestamp_time("LoadArtworkInfoFromLevelInfo (mus)");
3531 SaveArtworkInfoCache();
3533 print_timestamp_time("SaveArtworkInfoCache");
3535 /* needed for reloading level artwork not known at ealier stage */
3537 if (!strEqual(artwork.gfx_current_identifier, setup.graphics_set))
3539 artwork.gfx_current =
3540 getTreeInfoFromIdentifier(artwork.gfx_first, setup.graphics_set);
3541 if (artwork.gfx_current == NULL)
3542 artwork.gfx_current =
3543 getTreeInfoFromIdentifier(artwork.gfx_first, GFX_DEFAULT_SUBDIR);
3544 if (artwork.gfx_current == NULL)
3545 artwork.gfx_current = getFirstValidTreeInfoEntry(artwork.gfx_first);
3548 if (!strEqual(artwork.snd_current_identifier, setup.sounds_set))
3550 artwork.snd_current =
3551 getTreeInfoFromIdentifier(artwork.snd_first, setup.sounds_set);
3552 if (artwork.snd_current == NULL)
3553 artwork.snd_current =
3554 getTreeInfoFromIdentifier(artwork.snd_first, SND_DEFAULT_SUBDIR);
3555 if (artwork.snd_current == NULL)
3556 artwork.snd_current = getFirstValidTreeInfoEntry(artwork.snd_first);
3559 if (!strEqual(artwork.mus_current_identifier, setup.music_set))
3561 artwork.mus_current =
3562 getTreeInfoFromIdentifier(artwork.mus_first, setup.music_set);
3563 if (artwork.mus_current == NULL)
3564 artwork.mus_current =
3565 getTreeInfoFromIdentifier(artwork.mus_first, MUS_DEFAULT_SUBDIR);
3566 if (artwork.mus_current == NULL)
3567 artwork.mus_current = getFirstValidTreeInfoEntry(artwork.mus_first);
3570 print_timestamp_time("getTreeInfoFromIdentifier");
3572 sortTreeInfo(&artwork.gfx_first);
3573 sortTreeInfo(&artwork.snd_first);
3574 sortTreeInfo(&artwork.mus_first);
3576 print_timestamp_time("sortTreeInfo");
3578 #if ENABLE_UNUSED_CODE
3579 dumpTreeInfo(artwork.gfx_first, 0);
3580 dumpTreeInfo(artwork.snd_first, 0);
3581 dumpTreeInfo(artwork.mus_first, 0);
3584 print_timestamp_done("LoadLevelArtworkInfo");
3587 static boolean AddUserLevelSetToLevelInfoExt(char *level_subdir_new)
3589 // get level info tree node of first (original) user level set
3590 char *level_subdir_old = getLoginName();
3591 LevelDirTree *leveldir_old = getTreeInfoFromIdentifier(leveldir_first,
3593 if (leveldir_old == NULL) // should not happen
3596 int draw_deactivation_mask = GetDrawDeactivationMask();
3598 // override draw deactivation mask (temporarily disable drawing)
3599 SetDrawDeactivationMask(REDRAW_ALL);
3601 // load new level set config and add it next to first user level set
3602 LoadLevelInfoFromLevelConf(&leveldir_old->next, NULL,
3603 leveldir_old->basepath, level_subdir_new);
3605 // set draw deactivation mask to previous value
3606 SetDrawDeactivationMask(draw_deactivation_mask);
3608 // get level info tree node of newly added user level set
3609 LevelDirTree *leveldir_new = getTreeInfoFromIdentifier(leveldir_first,
3611 if (leveldir_new == NULL) // should not happen
3614 // correct top link and parent node link of newly created tree node
3615 leveldir_new->node_top = leveldir_old->node_top;
3616 leveldir_new->node_parent = leveldir_old->node_parent;
3618 // sort level info tree to adjust position of newly added level set
3619 sortTreeInfo(&leveldir_first);
3624 void AddUserLevelSetToLevelInfo(char *level_subdir_new)
3626 if (!AddUserLevelSetToLevelInfoExt(level_subdir_new))
3627 Error(ERR_EXIT, "internal level set structure corrupted -- aborting");
3630 char *getArtworkIdentifierForUserLevelSet(int type)
3632 char *classic_artwork_set = getClassicArtworkSet(type);
3634 /* check for custom artwork configured in "levelinfo.conf" */
3635 char *leveldir_artwork_set =
3636 *LEVELDIR_ARTWORK_SET_PTR(leveldir_current, type);
3637 boolean has_leveldir_artwork_set =
3638 (leveldir_artwork_set != NULL && !strEqual(leveldir_artwork_set,
3639 classic_artwork_set));
3641 /* check for custom artwork in sub-directory "graphics" etc. */
3642 TreeInfo *artwork_first_node = ARTWORK_FIRST_NODE(artwork, type);
3643 char *leveldir_identifier = leveldir_current->identifier;
3644 boolean has_artwork_subdir =
3645 (getTreeInfoFromIdentifier(artwork_first_node,
3646 leveldir_identifier) != NULL);
3648 return (has_leveldir_artwork_set ? leveldir_artwork_set :
3649 has_artwork_subdir ? leveldir_identifier :
3650 classic_artwork_set);
3653 TreeInfo *getArtworkTreeInfoForUserLevelSet(int type)
3655 char *artwork_set = getArtworkIdentifierForUserLevelSet(type);
3656 TreeInfo *artwork_first_node = ARTWORK_FIRST_NODE(artwork, type);
3658 return getTreeInfoFromIdentifier(artwork_first_node, artwork_set);
3661 boolean checkIfCustomArtworkExistsForCurrentLevelSet()
3663 char *graphics_set =
3664 getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_GRAPHICS);
3666 getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_SOUNDS);
3668 getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_MUSIC);
3670 return (!strEqual(graphics_set, GFX_CLASSIC_SUBDIR) ||
3671 !strEqual(sounds_set, SND_CLASSIC_SUBDIR) ||
3672 !strEqual(music_set, MUS_CLASSIC_SUBDIR));
3675 boolean UpdateUserLevelSet(char *level_subdir, char *level_name,
3676 char *level_author, int num_levels)
3678 char *filename = getPath2(getUserLevelDir(level_subdir), LEVELINFO_FILENAME);
3679 char *filename_tmp = getStringCat2(filename, ".tmp");
3681 FILE *file_tmp = NULL;
3682 char line[MAX_LINE_LEN];
3683 boolean success = FALSE;
3684 LevelDirTree *leveldir = getTreeInfoFromIdentifier(leveldir_first,
3686 // update values in level directory tree
3688 if (level_name != NULL)
3689 setString(&leveldir->name, level_name);
3691 if (level_author != NULL)
3692 setString(&leveldir->author, level_author);
3694 if (num_levels != -1)
3695 leveldir->levels = num_levels;
3697 // update values that depend on other values
3699 setString(&leveldir->name_sorting, leveldir->name);
3701 leveldir->last_level = leveldir->first_level + leveldir->levels - 1;
3703 // sort order of level sets may have changed
3704 sortTreeInfo(&leveldir_first);
3706 if ((file = fopen(filename, MODE_READ)) &&
3707 (file_tmp = fopen(filename_tmp, MODE_WRITE)))
3709 while (fgets(line, MAX_LINE_LEN, file))
3711 if (strPrefix(line, "name:") && level_name != NULL)
3712 fprintf(file_tmp, "%-32s%s\n", "name:", level_name);
3713 else if (strPrefix(line, "author:") && level_author != NULL)
3714 fprintf(file_tmp, "%-32s%s\n", "author:", level_author);
3715 else if (strPrefix(line, "levels:") && num_levels != -1)
3716 fprintf(file_tmp, "%-32s%d\n", "levels:", num_levels);
3718 fputs(line, file_tmp);
3731 success = (rename(filename_tmp, filename) == 0);
3739 boolean CreateUserLevelSet(char *level_subdir, char *level_name,
3740 char *level_author, int num_levels,
3741 boolean use_artwork_set)
3743 LevelDirTree *level_info;
3748 // create user level sub-directory, if needed
3749 createDirectory(getUserLevelDir(level_subdir), "user level", PERMS_PRIVATE);
3751 filename = getPath2(getUserLevelDir(level_subdir), LEVELINFO_FILENAME);
3753 if (!(file = fopen(filename, MODE_WRITE)))
3755 Error(ERR_WARN, "cannot write level info file '%s'", filename);
3761 level_info = newTreeInfo();
3763 /* always start with reliable default values */
3764 setTreeInfoToDefaults(level_info, TREE_TYPE_LEVEL_DIR);
3766 setString(&level_info->name, level_name);
3767 setString(&level_info->author, level_author);
3768 level_info->levels = num_levels;
3769 level_info->first_level = 1;
3770 level_info->sort_priority = LEVELCLASS_PRIVATE_START;
3771 level_info->readonly = FALSE;
3773 if (use_artwork_set)
3775 level_info->graphics_set =
3776 getStringCopy(getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_GRAPHICS));
3777 level_info->sounds_set =
3778 getStringCopy(getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_SOUNDS));
3779 level_info->music_set =
3780 getStringCopy(getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_MUSIC));
3783 token_value_position = TOKEN_VALUE_POSITION_SHORT;
3785 fprintFileHeader(file, LEVELINFO_FILENAME);
3788 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
3790 if (i == LEVELINFO_TOKEN_NAME ||
3791 i == LEVELINFO_TOKEN_AUTHOR ||
3792 i == LEVELINFO_TOKEN_LEVELS ||
3793 i == LEVELINFO_TOKEN_FIRST_LEVEL ||
3794 i == LEVELINFO_TOKEN_SORT_PRIORITY ||
3795 i == LEVELINFO_TOKEN_READONLY ||
3796 (use_artwork_set && (i == LEVELINFO_TOKEN_GRAPHICS_SET ||
3797 i == LEVELINFO_TOKEN_SOUNDS_SET ||
3798 i == LEVELINFO_TOKEN_MUSIC_SET)))
3799 fprintf(file, "%s\n", getSetupLine(levelinfo_tokens, "", i));
3801 /* just to make things nicer :) */
3802 if (i == LEVELINFO_TOKEN_AUTHOR ||
3803 i == LEVELINFO_TOKEN_FIRST_LEVEL ||
3804 (use_artwork_set && i == LEVELINFO_TOKEN_READONLY))
3805 fprintf(file, "\n");
3808 token_value_position = TOKEN_VALUE_POSITION_DEFAULT;
3812 SetFilePermissions(filename, PERMS_PRIVATE);
3814 freeTreeInfo(level_info);
3820 static void SaveUserLevelInfo()
3822 CreateUserLevelSet(getLoginName(), getLoginName(), getRealName(), 100, FALSE);
3825 char *getSetupValue(int type, void *value)
3827 static char value_string[MAX_LINE_LEN];
3835 strcpy(value_string, (*(boolean *)value ? "true" : "false"));
3839 strcpy(value_string, (*(boolean *)value ? "on" : "off"));
3843 strcpy(value_string, (*(int *)value == AUTO ? "auto" :
3844 *(int *)value == FALSE ? "off" : "on"));
3848 strcpy(value_string, (*(boolean *)value ? "yes" : "no"));
3851 case TYPE_YES_NO_AUTO:
3852 strcpy(value_string, (*(int *)value == AUTO ? "auto" :
3853 *(int *)value == FALSE ? "no" : "yes"));
3857 strcpy(value_string, (*(boolean *)value ? "AGA" : "ECS"));
3861 strcpy(value_string, getKeyNameFromKey(*(Key *)value));
3865 strcpy(value_string, getX11KeyNameFromKey(*(Key *)value));
3869 sprintf(value_string, "%d", *(int *)value);
3873 if (*(char **)value == NULL)
3876 strcpy(value_string, *(char **)value);
3880 sprintf(value_string, "player_%d", *(int *)value + 1);
3884 value_string[0] = '\0';
3888 if (type & TYPE_GHOSTED)
3889 strcpy(value_string, "n/a");
3891 return value_string;
3894 char *getSetupLine(struct TokenInfo *token_info, char *prefix, int token_nr)
3898 static char token_string[MAX_LINE_LEN];
3899 int token_type = token_info[token_nr].type;
3900 void *setup_value = token_info[token_nr].value;
3901 char *token_text = token_info[token_nr].text;
3902 char *value_string = getSetupValue(token_type, setup_value);
3904 /* build complete token string */
3905 sprintf(token_string, "%s%s", prefix, token_text);
3907 /* build setup entry line */
3908 line = getFormattedSetupEntry(token_string, value_string);
3910 if (token_type == TYPE_KEY_X11)
3912 Key key = *(Key *)setup_value;
3913 char *keyname = getKeyNameFromKey(key);
3915 /* add comment, if useful */
3916 if (!strEqual(keyname, "(undefined)") &&
3917 !strEqual(keyname, "(unknown)"))
3919 /* add at least one whitespace */
3921 for (i = strlen(line); i < token_comment_position; i++)
3925 strcat(line, keyname);
3932 void LoadLevelSetup_LastSeries()
3934 /* ----------------------------------------------------------------------- */
3935 /* ~/.<program>/levelsetup.conf */
3936 /* ----------------------------------------------------------------------- */
3938 char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
3939 SetupFileHash *level_setup_hash = NULL;
3941 /* always start with reliable default values */
3942 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
3944 if (!strEqual(DEFAULT_LEVELSET, UNDEFINED_LEVELSET))
3946 leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
3948 if (leveldir_current == NULL)
3949 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
3952 if ((level_setup_hash = loadSetupFileHash(filename)))
3954 char *last_level_series =
3955 getHashEntry(level_setup_hash, TOKEN_STR_LAST_LEVEL_SERIES);
3957 leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
3959 if (leveldir_current == NULL)
3960 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
3962 freeSetupFileHash(level_setup_hash);
3966 Error(ERR_DEBUG, "using default setup values");
3972 static void SaveLevelSetup_LastSeries_Ext(boolean deactivate_last_level_series)
3974 /* ----------------------------------------------------------------------- */
3975 /* ~/.<program>/levelsetup.conf */
3976 /* ----------------------------------------------------------------------- */
3978 // check if the current level directory structure is available at this point
3979 if (leveldir_current == NULL)
3982 char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
3983 char *level_subdir = leveldir_current->subdir;
3986 InitUserDataDirectory();
3988 if (!(file = fopen(filename, MODE_WRITE)))
3990 Error(ERR_WARN, "cannot write setup file '%s'", filename);
3997 fprintFileHeader(file, LEVELSETUP_FILENAME);
3999 if (deactivate_last_level_series)
4000 fprintf(file, "# %s\n# ", "the following level set may have caused a problem and was deactivated");
4002 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_LEVEL_SERIES,
4007 SetFilePermissions(filename, PERMS_PRIVATE);
4012 void SaveLevelSetup_LastSeries()
4014 SaveLevelSetup_LastSeries_Ext(FALSE);
4017 void SaveLevelSetup_LastSeries_Deactivate()
4019 SaveLevelSetup_LastSeries_Ext(TRUE);
4022 static void checkSeriesInfo()
4024 static char *level_directory = NULL;
4027 /* check for more levels besides the 'levels' field of 'levelinfo.conf' */
4029 level_directory = getPath2((leveldir_current->in_user_dir ?
4030 getUserLevelDir(NULL) :
4031 options.level_directory),
4032 leveldir_current->fullpath);
4034 if ((dir = openDirectory(level_directory)) == NULL)
4036 Error(ERR_WARN, "cannot read level directory '%s'", level_directory);
4041 closeDirectory(dir);
4044 void LoadLevelSetup_SeriesInfo()
4047 SetupFileHash *level_setup_hash = NULL;
4048 char *level_subdir = leveldir_current->subdir;
4051 /* always start with reliable default values */
4052 level_nr = leveldir_current->first_level;
4054 for (i = 0; i < MAX_LEVELS; i++)
4056 LevelStats_setPlayed(i, 0);
4057 LevelStats_setSolved(i, 0);
4062 /* ----------------------------------------------------------------------- */
4063 /* ~/.<program>/levelsetup/<level series>/levelsetup.conf */
4064 /* ----------------------------------------------------------------------- */
4066 level_subdir = leveldir_current->subdir;
4068 filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
4070 if ((level_setup_hash = loadSetupFileHash(filename)))
4074 /* get last played level in this level set */
4076 token_value = getHashEntry(level_setup_hash, TOKEN_STR_LAST_PLAYED_LEVEL);
4080 level_nr = atoi(token_value);
4082 if (level_nr < leveldir_current->first_level)
4083 level_nr = leveldir_current->first_level;
4084 if (level_nr > leveldir_current->last_level)
4085 level_nr = leveldir_current->last_level;
4088 /* get handicap level in this level set */
4090 token_value = getHashEntry(level_setup_hash, TOKEN_STR_HANDICAP_LEVEL);
4094 int level_nr = atoi(token_value);
4096 if (level_nr < leveldir_current->first_level)
4097 level_nr = leveldir_current->first_level;
4098 if (level_nr > leveldir_current->last_level + 1)
4099 level_nr = leveldir_current->last_level;
4101 if (leveldir_current->user_defined || !leveldir_current->handicap)
4102 level_nr = leveldir_current->last_level;
4104 leveldir_current->handicap_level = level_nr;
4107 /* get number of played and solved levels in this level set */
4109 BEGIN_HASH_ITERATION(level_setup_hash, itr)
4111 char *token = HASH_ITERATION_TOKEN(itr);
4112 char *value = HASH_ITERATION_VALUE(itr);
4114 if (strlen(token) == 3 &&
4115 token[0] >= '0' && token[0] <= '9' &&
4116 token[1] >= '0' && token[1] <= '9' &&
4117 token[2] >= '0' && token[2] <= '9')
4119 int level_nr = atoi(token);
4122 LevelStats_setPlayed(level_nr, atoi(value)); /* read 1st column */
4124 value = strchr(value, ' ');
4127 LevelStats_setSolved(level_nr, atoi(value)); /* read 2nd column */
4130 END_HASH_ITERATION(hash, itr)
4132 freeSetupFileHash(level_setup_hash);
4136 Error(ERR_DEBUG, "using default setup values");
4142 void SaveLevelSetup_SeriesInfo()
4145 char *level_subdir = leveldir_current->subdir;
4146 char *level_nr_str = int2str(level_nr, 0);
4147 char *handicap_level_str = int2str(leveldir_current->handicap_level, 0);
4151 /* ----------------------------------------------------------------------- */
4152 /* ~/.<program>/levelsetup/<level series>/levelsetup.conf */
4153 /* ----------------------------------------------------------------------- */
4155 InitLevelSetupDirectory(level_subdir);
4157 filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
4159 if (!(file = fopen(filename, MODE_WRITE)))
4161 Error(ERR_WARN, "cannot write setup file '%s'", filename);
4166 fprintFileHeader(file, LEVELSETUP_FILENAME);
4168 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_PLAYED_LEVEL,
4170 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_HANDICAP_LEVEL,
4171 handicap_level_str));
4173 for (i = leveldir_current->first_level; i <= leveldir_current->last_level;
4176 if (LevelStats_getPlayed(i) > 0 ||
4177 LevelStats_getSolved(i) > 0)
4182 sprintf(token, "%03d", i);
4183 sprintf(value, "%d %d", LevelStats_getPlayed(i), LevelStats_getSolved(i));
4185 fprintf(file, "%s\n", getFormattedSetupEntry(token, value));
4191 SetFilePermissions(filename, PERMS_PRIVATE);
4196 int LevelStats_getPlayed(int nr)
4198 return (nr >= 0 && nr < MAX_LEVELS ? level_stats[nr].played : 0);
4201 int LevelStats_getSolved(int nr)
4203 return (nr >= 0 && nr < MAX_LEVELS ? level_stats[nr].solved : 0);
4206 void LevelStats_setPlayed(int nr, int value)
4208 if (nr >= 0 && nr < MAX_LEVELS)
4209 level_stats[nr].played = value;
4212 void LevelStats_setSolved(int nr, int value)
4214 if (nr >= 0 && nr < MAX_LEVELS)
4215 level_stats[nr].solved = value;
4218 void LevelStats_incPlayed(int nr)
4220 if (nr >= 0 && nr < MAX_LEVELS)
4221 level_stats[nr].played++;
4224 void LevelStats_incSolved(int nr)
4226 if (nr >= 0 && nr < MAX_LEVELS)
4227 level_stats[nr].solved++;