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_major, program.version_minor);
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_major, version_minor;
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_major = ptr_cookie2[0] - '0';
1620 version_minor = ptr_cookie2[2] - '0';
1622 return VERSION_IDENT(version_major, version_minor, 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_IMPORTED_FROM 5
2240 #define LEVELINFO_TOKEN_IMPORTED_BY 6
2241 #define LEVELINFO_TOKEN_TESTED_BY 7
2242 #define LEVELINFO_TOKEN_LEVELS 8
2243 #define LEVELINFO_TOKEN_FIRST_LEVEL 9
2244 #define LEVELINFO_TOKEN_SORT_PRIORITY 10
2245 #define LEVELINFO_TOKEN_LATEST_ENGINE 11
2246 #define LEVELINFO_TOKEN_LEVEL_GROUP 12
2247 #define LEVELINFO_TOKEN_READONLY 13
2248 #define LEVELINFO_TOKEN_GRAPHICS_SET_ECS 14
2249 #define LEVELINFO_TOKEN_GRAPHICS_SET_AGA 15
2250 #define LEVELINFO_TOKEN_GRAPHICS_SET 16
2251 #define LEVELINFO_TOKEN_SOUNDS_SET 17
2252 #define LEVELINFO_TOKEN_MUSIC_SET 18
2253 #define LEVELINFO_TOKEN_FILENAME 19
2254 #define LEVELINFO_TOKEN_FILETYPE 20
2255 #define LEVELINFO_TOKEN_SPECIAL_FLAGS 21
2256 #define LEVELINFO_TOKEN_HANDICAP 22
2257 #define LEVELINFO_TOKEN_SKIP_LEVELS 23
2259 #define NUM_LEVELINFO_TOKENS 24
2261 static LevelDirTree ldi;
2263 static struct TokenInfo levelinfo_tokens[] =
2265 /* level directory info */
2266 { TYPE_STRING, &ldi.identifier, "identifier" },
2267 { TYPE_STRING, &ldi.name, "name" },
2268 { TYPE_STRING, &ldi.name_sorting, "name_sorting" },
2269 { TYPE_STRING, &ldi.author, "author" },
2270 { TYPE_STRING, &ldi.year, "year" },
2271 { TYPE_STRING, &ldi.imported_from, "imported_from" },
2272 { TYPE_STRING, &ldi.imported_by, "imported_by" },
2273 { TYPE_STRING, &ldi.tested_by, "tested_by" },
2274 { TYPE_INTEGER, &ldi.levels, "levels" },
2275 { TYPE_INTEGER, &ldi.first_level, "first_level" },
2276 { TYPE_INTEGER, &ldi.sort_priority, "sort_priority" },
2277 { TYPE_BOOLEAN, &ldi.latest_engine, "latest_engine" },
2278 { TYPE_BOOLEAN, &ldi.level_group, "level_group" },
2279 { TYPE_BOOLEAN, &ldi.readonly, "readonly" },
2280 { TYPE_STRING, &ldi.graphics_set_ecs, "graphics_set.ecs" },
2281 { TYPE_STRING, &ldi.graphics_set_aga, "graphics_set.aga" },
2282 { TYPE_STRING, &ldi.graphics_set, "graphics_set" },
2283 { TYPE_STRING, &ldi.sounds_set, "sounds_set" },
2284 { TYPE_STRING, &ldi.music_set, "music_set" },
2285 { TYPE_STRING, &ldi.level_filename, "filename" },
2286 { TYPE_STRING, &ldi.level_filetype, "filetype" },
2287 { TYPE_STRING, &ldi.special_flags, "special_flags" },
2288 { TYPE_BOOLEAN, &ldi.handicap, "handicap" },
2289 { TYPE_BOOLEAN, &ldi.skip_levels, "skip_levels" }
2292 static struct TokenInfo artworkinfo_tokens[] =
2294 /* artwork directory info */
2295 { TYPE_STRING, &ldi.identifier, "identifier" },
2296 { TYPE_STRING, &ldi.subdir, "subdir" },
2297 { TYPE_STRING, &ldi.name, "name" },
2298 { TYPE_STRING, &ldi.name_sorting, "name_sorting" },
2299 { TYPE_STRING, &ldi.author, "author" },
2300 { TYPE_INTEGER, &ldi.sort_priority, "sort_priority" },
2301 { TYPE_STRING, &ldi.basepath, "basepath" },
2302 { TYPE_STRING, &ldi.fullpath, "fullpath" },
2303 { TYPE_BOOLEAN, &ldi.in_user_dir, "in_user_dir" },
2304 { TYPE_INTEGER, &ldi.color, "color" },
2305 { TYPE_STRING, &ldi.class_desc, "class_desc" },
2310 static void setTreeInfoToDefaults(TreeInfo *ti, int type)
2314 ti->node_top = (ti->type == TREE_TYPE_LEVEL_DIR ? &leveldir_first :
2315 ti->type == TREE_TYPE_GRAPHICS_DIR ? &artwork.gfx_first :
2316 ti->type == TREE_TYPE_SOUNDS_DIR ? &artwork.snd_first :
2317 ti->type == TREE_TYPE_MUSIC_DIR ? &artwork.mus_first :
2320 ti->node_parent = NULL;
2321 ti->node_group = NULL;
2328 ti->fullpath = NULL;
2329 ti->basepath = NULL;
2330 ti->identifier = NULL;
2331 ti->name = getStringCopy(ANONYMOUS_NAME);
2332 ti->name_sorting = NULL;
2333 ti->author = getStringCopy(ANONYMOUS_NAME);
2336 ti->sort_priority = LEVELCLASS_UNDEFINED; /* default: least priority */
2337 ti->latest_engine = FALSE; /* default: get from level */
2338 ti->parent_link = FALSE;
2339 ti->in_user_dir = FALSE;
2340 ti->user_defined = FALSE;
2342 ti->class_desc = NULL;
2344 ti->infotext = getStringCopy(TREE_INFOTEXT(ti->type));
2346 if (ti->type == TREE_TYPE_LEVEL_DIR)
2348 ti->imported_from = NULL;
2349 ti->imported_by = NULL;
2350 ti->tested_by = NULL;
2352 ti->graphics_set_ecs = NULL;
2353 ti->graphics_set_aga = NULL;
2354 ti->graphics_set = NULL;
2355 ti->sounds_set = NULL;
2356 ti->music_set = NULL;
2357 ti->graphics_path = getStringCopy(UNDEFINED_FILENAME);
2358 ti->sounds_path = getStringCopy(UNDEFINED_FILENAME);
2359 ti->music_path = getStringCopy(UNDEFINED_FILENAME);
2361 ti->level_filename = NULL;
2362 ti->level_filetype = NULL;
2364 ti->special_flags = NULL;
2367 ti->first_level = 0;
2369 ti->level_group = FALSE;
2370 ti->handicap_level = 0;
2371 ti->readonly = TRUE;
2372 ti->handicap = TRUE;
2373 ti->skip_levels = FALSE;
2377 static void setTreeInfoToDefaultsFromParent(TreeInfo *ti, TreeInfo *parent)
2381 Error(ERR_WARN, "setTreeInfoToDefaultsFromParent(): parent == NULL");
2383 setTreeInfoToDefaults(ti, TREE_TYPE_UNDEFINED);
2388 /* copy all values from the parent structure */
2390 ti->type = parent->type;
2392 ti->node_top = parent->node_top;
2393 ti->node_parent = parent;
2394 ti->node_group = NULL;
2401 ti->fullpath = NULL;
2402 ti->basepath = NULL;
2403 ti->identifier = NULL;
2404 ti->name = getStringCopy(ANONYMOUS_NAME);
2405 ti->name_sorting = NULL;
2406 ti->author = getStringCopy(parent->author);
2407 ti->year = getStringCopy(parent->year);
2409 ti->sort_priority = parent->sort_priority;
2410 ti->latest_engine = parent->latest_engine;
2411 ti->parent_link = FALSE;
2412 ti->in_user_dir = parent->in_user_dir;
2413 ti->user_defined = parent->user_defined;
2414 ti->color = parent->color;
2415 ti->class_desc = getStringCopy(parent->class_desc);
2417 ti->infotext = getStringCopy(parent->infotext);
2419 if (ti->type == TREE_TYPE_LEVEL_DIR)
2421 ti->imported_from = getStringCopy(parent->imported_from);
2422 ti->imported_by = getStringCopy(parent->imported_by);
2423 ti->tested_by = getStringCopy(parent->tested_by);
2425 ti->graphics_set_ecs = getStringCopy(parent->graphics_set_ecs);
2426 ti->graphics_set_aga = getStringCopy(parent->graphics_set_aga);
2427 ti->graphics_set = getStringCopy(parent->graphics_set);
2428 ti->sounds_set = getStringCopy(parent->sounds_set);
2429 ti->music_set = getStringCopy(parent->music_set);
2430 ti->graphics_path = getStringCopy(UNDEFINED_FILENAME);
2431 ti->sounds_path = getStringCopy(UNDEFINED_FILENAME);
2432 ti->music_path = getStringCopy(UNDEFINED_FILENAME);
2434 ti->level_filename = getStringCopy(parent->level_filename);
2435 ti->level_filetype = getStringCopy(parent->level_filetype);
2437 ti->special_flags = getStringCopy(parent->special_flags);
2439 ti->levels = parent->levels;
2440 ti->first_level = parent->first_level;
2441 ti->last_level = parent->last_level;
2442 ti->level_group = FALSE;
2443 ti->handicap_level = parent->handicap_level;
2444 ti->readonly = parent->readonly;
2445 ti->handicap = parent->handicap;
2446 ti->skip_levels = parent->skip_levels;
2450 static TreeInfo *getTreeInfoCopy(TreeInfo *ti)
2452 TreeInfo *ti_copy = newTreeInfo();
2454 /* copy all values from the original structure */
2456 ti_copy->type = ti->type;
2458 ti_copy->node_top = ti->node_top;
2459 ti_copy->node_parent = ti->node_parent;
2460 ti_copy->node_group = ti->node_group;
2461 ti_copy->next = ti->next;
2463 ti_copy->cl_first = ti->cl_first;
2464 ti_copy->cl_cursor = ti->cl_cursor;
2466 ti_copy->subdir = getStringCopy(ti->subdir);
2467 ti_copy->fullpath = getStringCopy(ti->fullpath);
2468 ti_copy->basepath = getStringCopy(ti->basepath);
2469 ti_copy->identifier = getStringCopy(ti->identifier);
2470 ti_copy->name = getStringCopy(ti->name);
2471 ti_copy->name_sorting = getStringCopy(ti->name_sorting);
2472 ti_copy->author = getStringCopy(ti->author);
2473 ti_copy->year = getStringCopy(ti->year);
2474 ti_copy->imported_from = getStringCopy(ti->imported_from);
2475 ti_copy->imported_by = getStringCopy(ti->imported_by);
2476 ti_copy->tested_by = getStringCopy(ti->tested_by);
2478 ti_copy->graphics_set_ecs = getStringCopy(ti->graphics_set_ecs);
2479 ti_copy->graphics_set_aga = getStringCopy(ti->graphics_set_aga);
2480 ti_copy->graphics_set = getStringCopy(ti->graphics_set);
2481 ti_copy->sounds_set = getStringCopy(ti->sounds_set);
2482 ti_copy->music_set = getStringCopy(ti->music_set);
2483 ti_copy->graphics_path = getStringCopy(ti->graphics_path);
2484 ti_copy->sounds_path = getStringCopy(ti->sounds_path);
2485 ti_copy->music_path = getStringCopy(ti->music_path);
2487 ti_copy->level_filename = getStringCopy(ti->level_filename);
2488 ti_copy->level_filetype = getStringCopy(ti->level_filetype);
2490 ti_copy->special_flags = getStringCopy(ti->special_flags);
2492 ti_copy->levels = ti->levels;
2493 ti_copy->first_level = ti->first_level;
2494 ti_copy->last_level = ti->last_level;
2495 ti_copy->sort_priority = ti->sort_priority;
2497 ti_copy->latest_engine = ti->latest_engine;
2499 ti_copy->level_group = ti->level_group;
2500 ti_copy->parent_link = ti->parent_link;
2501 ti_copy->in_user_dir = ti->in_user_dir;
2502 ti_copy->user_defined = ti->user_defined;
2503 ti_copy->readonly = ti->readonly;
2504 ti_copy->handicap = ti->handicap;
2505 ti_copy->skip_levels = ti->skip_levels;
2507 ti_copy->color = ti->color;
2508 ti_copy->class_desc = getStringCopy(ti->class_desc);
2509 ti_copy->handicap_level = ti->handicap_level;
2511 ti_copy->infotext = getStringCopy(ti->infotext);
2516 void freeTreeInfo(TreeInfo *ti)
2521 checked_free(ti->subdir);
2522 checked_free(ti->fullpath);
2523 checked_free(ti->basepath);
2524 checked_free(ti->identifier);
2526 checked_free(ti->name);
2527 checked_free(ti->name_sorting);
2528 checked_free(ti->author);
2529 checked_free(ti->year);
2531 checked_free(ti->class_desc);
2533 checked_free(ti->infotext);
2535 if (ti->type == TREE_TYPE_LEVEL_DIR)
2537 checked_free(ti->imported_from);
2538 checked_free(ti->imported_by);
2539 checked_free(ti->tested_by);
2541 checked_free(ti->graphics_set_ecs);
2542 checked_free(ti->graphics_set_aga);
2543 checked_free(ti->graphics_set);
2544 checked_free(ti->sounds_set);
2545 checked_free(ti->music_set);
2547 checked_free(ti->graphics_path);
2548 checked_free(ti->sounds_path);
2549 checked_free(ti->music_path);
2551 checked_free(ti->level_filename);
2552 checked_free(ti->level_filetype);
2554 checked_free(ti->special_flags);
2557 // recursively free child node
2559 freeTreeInfo(ti->node_group);
2561 // recursively free next node
2563 freeTreeInfo(ti->next);
2568 void setSetupInfo(struct TokenInfo *token_info,
2569 int token_nr, char *token_value)
2571 int token_type = token_info[token_nr].type;
2572 void *setup_value = token_info[token_nr].value;
2574 if (token_value == NULL)
2577 /* set setup field to corresponding token value */
2582 *(boolean *)setup_value = get_boolean_from_string(token_value);
2586 *(int *)setup_value = get_switch3_from_string(token_value);
2590 *(Key *)setup_value = getKeyFromKeyName(token_value);
2594 *(Key *)setup_value = getKeyFromX11KeyName(token_value);
2598 *(int *)setup_value = get_integer_from_string(token_value);
2602 checked_free(*(char **)setup_value);
2603 *(char **)setup_value = getStringCopy(token_value);
2611 static int compareTreeInfoEntries(const void *object1, const void *object2)
2613 const TreeInfo *entry1 = *((TreeInfo **)object1);
2614 const TreeInfo *entry2 = *((TreeInfo **)object2);
2615 int class_sorting1 = 0, class_sorting2 = 0;
2618 if (entry1->type == TREE_TYPE_LEVEL_DIR)
2620 class_sorting1 = LEVELSORTING(entry1);
2621 class_sorting2 = LEVELSORTING(entry2);
2623 else if (entry1->type == TREE_TYPE_GRAPHICS_DIR ||
2624 entry1->type == TREE_TYPE_SOUNDS_DIR ||
2625 entry1->type == TREE_TYPE_MUSIC_DIR)
2627 class_sorting1 = ARTWORKSORTING(entry1);
2628 class_sorting2 = ARTWORKSORTING(entry2);
2631 if (entry1->parent_link || entry2->parent_link)
2632 compare_result = (entry1->parent_link ? -1 : +1);
2633 else if (entry1->sort_priority == entry2->sort_priority)
2635 char *name1 = getStringToLower(entry1->name_sorting);
2636 char *name2 = getStringToLower(entry2->name_sorting);
2638 compare_result = strcmp(name1, name2);
2643 else if (class_sorting1 == class_sorting2)
2644 compare_result = entry1->sort_priority - entry2->sort_priority;
2646 compare_result = class_sorting1 - class_sorting2;
2648 return compare_result;
2651 static TreeInfo *createParentTreeInfoNode(TreeInfo *node_parent)
2655 if (node_parent == NULL)
2658 ti_new = newTreeInfo();
2659 setTreeInfoToDefaults(ti_new, node_parent->type);
2661 ti_new->node_parent = node_parent;
2662 ti_new->parent_link = TRUE;
2664 setString(&ti_new->identifier, node_parent->identifier);
2665 setString(&ti_new->name, ".. (parent directory)");
2666 setString(&ti_new->name_sorting, ti_new->name);
2668 setString(&ti_new->subdir, STRING_PARENT_DIRECTORY);
2669 setString(&ti_new->fullpath, node_parent->fullpath);
2671 ti_new->sort_priority = node_parent->sort_priority;
2672 ti_new->latest_engine = node_parent->latest_engine;
2674 setString(&ti_new->class_desc, getLevelClassDescription(ti_new));
2676 pushTreeInfo(&node_parent->node_group, ti_new);
2681 static TreeInfo *createTopTreeInfoNode(TreeInfo *node_first)
2683 TreeInfo *ti_new, *ti_new2;
2685 if (node_first == NULL)
2688 ti_new = newTreeInfo();
2689 setTreeInfoToDefaults(ti_new, TREE_TYPE_LEVEL_DIR);
2691 ti_new->node_parent = NULL;
2692 ti_new->parent_link = FALSE;
2694 setString(&ti_new->identifier, node_first->identifier);
2695 setString(&ti_new->name, "level sets");
2696 setString(&ti_new->name_sorting, ti_new->name);
2698 setString(&ti_new->subdir, STRING_TOP_DIRECTORY);
2699 setString(&ti_new->fullpath, ".");
2701 ti_new->sort_priority = node_first->sort_priority;;
2702 ti_new->latest_engine = node_first->latest_engine;
2704 setString(&ti_new->class_desc, "level sets");
2706 ti_new->node_group = node_first;
2707 ti_new->level_group = TRUE;
2709 ti_new2 = createParentTreeInfoNode(ti_new);
2711 setString(&ti_new2->name, ".. (main menu)");
2712 setString(&ti_new2->name_sorting, ti_new2->name);
2718 /* -------------------------------------------------------------------------- */
2719 /* functions for handling level and custom artwork info cache */
2720 /* -------------------------------------------------------------------------- */
2722 static void LoadArtworkInfoCache()
2724 InitCacheDirectory();
2726 if (artworkinfo_cache_old == NULL)
2728 char *filename = getPath2(getCacheDir(), ARTWORKINFO_CACHE_FILE);
2730 /* try to load artwork info hash from already existing cache file */
2731 artworkinfo_cache_old = loadSetupFileHash(filename);
2733 /* if no artwork info cache file was found, start with empty hash */
2734 if (artworkinfo_cache_old == NULL)
2735 artworkinfo_cache_old = newSetupFileHash();
2740 if (artworkinfo_cache_new == NULL)
2741 artworkinfo_cache_new = newSetupFileHash();
2744 static void SaveArtworkInfoCache()
2746 char *filename = getPath2(getCacheDir(), ARTWORKINFO_CACHE_FILE);
2748 InitCacheDirectory();
2750 saveSetupFileHash(artworkinfo_cache_new, filename);
2755 static char *getCacheTokenPrefix(char *prefix1, char *prefix2)
2757 static char *prefix = NULL;
2759 checked_free(prefix);
2761 prefix = getStringCat2WithSeparator(prefix1, prefix2, ".");
2766 /* (identical to above function, but separate string buffer needed -- nasty) */
2767 static char *getCacheToken(char *prefix, char *suffix)
2769 static char *token = NULL;
2771 checked_free(token);
2773 token = getStringCat2WithSeparator(prefix, suffix, ".");
2778 static char *getFileTimestampString(char *filename)
2780 return getStringCopy(i_to_a(getFileTimestampEpochSeconds(filename)));
2783 static boolean modifiedFileTimestamp(char *filename, char *timestamp_string)
2785 struct stat file_status;
2787 if (timestamp_string == NULL)
2790 if (stat(filename, &file_status) != 0) /* cannot stat file */
2793 return (file_status.st_mtime != atoi(timestamp_string));
2796 static TreeInfo *getArtworkInfoCacheEntry(LevelDirTree *level_node, int type)
2798 char *identifier = level_node->subdir;
2799 char *type_string = ARTWORK_DIRECTORY(type);
2800 char *token_prefix = getCacheTokenPrefix(type_string, identifier);
2801 char *token_main = getCacheToken(token_prefix, "CACHED");
2802 char *cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
2803 boolean cached = (cache_entry != NULL && strEqual(cache_entry, "true"));
2804 TreeInfo *artwork_info = NULL;
2806 if (!use_artworkinfo_cache)
2813 artwork_info = newTreeInfo();
2814 setTreeInfoToDefaults(artwork_info, type);
2816 /* set all structure fields according to the token/value pairs */
2817 ldi = *artwork_info;
2818 for (i = 0; artworkinfo_tokens[i].type != -1; i++)
2820 char *token = getCacheToken(token_prefix, artworkinfo_tokens[i].text);
2821 char *value = getHashEntry(artworkinfo_cache_old, token);
2823 setSetupInfo(artworkinfo_tokens, i, value);
2825 /* check if cache entry for this item is invalid or incomplete */
2828 Error(ERR_WARN, "cache entry '%s' invalid", token);
2834 *artwork_info = ldi;
2839 char *filename_levelinfo = getPath2(getLevelDirFromTreeInfo(level_node),
2840 LEVELINFO_FILENAME);
2841 char *filename_artworkinfo = getPath2(getSetupArtworkDir(artwork_info),
2842 ARTWORKINFO_FILENAME(type));
2844 /* check if corresponding "levelinfo.conf" file has changed */
2845 token_main = getCacheToken(token_prefix, "TIMESTAMP_LEVELINFO");
2846 cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
2848 if (modifiedFileTimestamp(filename_levelinfo, cache_entry))
2851 /* check if corresponding "<artworkinfo>.conf" file has changed */
2852 token_main = getCacheToken(token_prefix, "TIMESTAMP_ARTWORKINFO");
2853 cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
2855 if (modifiedFileTimestamp(filename_artworkinfo, cache_entry))
2858 checked_free(filename_levelinfo);
2859 checked_free(filename_artworkinfo);
2862 if (!cached && artwork_info != NULL)
2864 freeTreeInfo(artwork_info);
2869 return artwork_info;
2872 static void setArtworkInfoCacheEntry(TreeInfo *artwork_info,
2873 LevelDirTree *level_node, int type)
2875 char *identifier = level_node->subdir;
2876 char *type_string = ARTWORK_DIRECTORY(type);
2877 char *token_prefix = getCacheTokenPrefix(type_string, identifier);
2878 char *token_main = getCacheToken(token_prefix, "CACHED");
2879 boolean set_cache_timestamps = TRUE;
2882 setHashEntry(artworkinfo_cache_new, token_main, "true");
2884 if (set_cache_timestamps)
2886 char *filename_levelinfo = getPath2(getLevelDirFromTreeInfo(level_node),
2887 LEVELINFO_FILENAME);
2888 char *filename_artworkinfo = getPath2(getSetupArtworkDir(artwork_info),
2889 ARTWORKINFO_FILENAME(type));
2890 char *timestamp_levelinfo = getFileTimestampString(filename_levelinfo);
2891 char *timestamp_artworkinfo = getFileTimestampString(filename_artworkinfo);
2893 token_main = getCacheToken(token_prefix, "TIMESTAMP_LEVELINFO");
2894 setHashEntry(artworkinfo_cache_new, token_main, timestamp_levelinfo);
2896 token_main = getCacheToken(token_prefix, "TIMESTAMP_ARTWORKINFO");
2897 setHashEntry(artworkinfo_cache_new, token_main, timestamp_artworkinfo);
2899 checked_free(filename_levelinfo);
2900 checked_free(filename_artworkinfo);
2901 checked_free(timestamp_levelinfo);
2902 checked_free(timestamp_artworkinfo);
2905 ldi = *artwork_info;
2906 for (i = 0; artworkinfo_tokens[i].type != -1; i++)
2908 char *token = getCacheToken(token_prefix, artworkinfo_tokens[i].text);
2909 char *value = getSetupValue(artworkinfo_tokens[i].type,
2910 artworkinfo_tokens[i].value);
2912 setHashEntry(artworkinfo_cache_new, token, value);
2917 /* -------------------------------------------------------------------------- */
2918 /* functions for loading level info and custom artwork info */
2919 /* -------------------------------------------------------------------------- */
2921 /* forward declaration for recursive call by "LoadLevelInfoFromLevelDir()" */
2922 static void LoadLevelInfoFromLevelDir(TreeInfo **, TreeInfo *, char *);
2924 static boolean LoadLevelInfoFromLevelConf(TreeInfo **node_first,
2925 TreeInfo *node_parent,
2926 char *level_directory,
2927 char *directory_name)
2929 char *directory_path = getPath2(level_directory, directory_name);
2930 char *filename = getPath2(directory_path, LEVELINFO_FILENAME);
2931 SetupFileHash *setup_file_hash;
2932 LevelDirTree *leveldir_new = NULL;
2935 /* unless debugging, silently ignore directories without "levelinfo.conf" */
2936 if (!options.debug && !fileExists(filename))
2938 free(directory_path);
2944 setup_file_hash = loadSetupFileHash(filename);
2946 if (setup_file_hash == NULL)
2948 Error(ERR_WARN, "ignoring level directory '%s'", directory_path);
2950 free(directory_path);
2956 leveldir_new = newTreeInfo();
2959 setTreeInfoToDefaultsFromParent(leveldir_new, node_parent);
2961 setTreeInfoToDefaults(leveldir_new, TREE_TYPE_LEVEL_DIR);
2963 leveldir_new->subdir = getStringCopy(directory_name);
2965 /* set all structure fields according to the token/value pairs */
2966 ldi = *leveldir_new;
2967 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
2968 setSetupInfo(levelinfo_tokens, i,
2969 getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
2970 *leveldir_new = ldi;
2972 if (strEqual(leveldir_new->name, ANONYMOUS_NAME))
2973 setString(&leveldir_new->name, leveldir_new->subdir);
2975 if (leveldir_new->identifier == NULL)
2976 leveldir_new->identifier = getStringCopy(leveldir_new->subdir);
2978 if (leveldir_new->name_sorting == NULL)
2979 leveldir_new->name_sorting = getStringCopy(leveldir_new->name);
2981 if (node_parent == NULL) /* top level group */
2983 leveldir_new->basepath = getStringCopy(level_directory);
2984 leveldir_new->fullpath = getStringCopy(leveldir_new->subdir);
2986 else /* sub level group */
2988 leveldir_new->basepath = getStringCopy(node_parent->basepath);
2989 leveldir_new->fullpath = getPath2(node_parent->fullpath, directory_name);
2992 leveldir_new->last_level =
2993 leveldir_new->first_level + leveldir_new->levels - 1;
2995 leveldir_new->in_user_dir =
2996 (!strEqual(leveldir_new->basepath, options.level_directory));
2998 /* adjust some settings if user's private level directory was detected */
2999 if (leveldir_new->sort_priority == LEVELCLASS_UNDEFINED &&
3000 leveldir_new->in_user_dir &&
3001 (strEqual(leveldir_new->subdir, getLoginName()) ||
3002 strEqual(leveldir_new->name, getLoginName()) ||
3003 strEqual(leveldir_new->author, getRealName())))
3005 leveldir_new->sort_priority = LEVELCLASS_PRIVATE_START;
3006 leveldir_new->readonly = FALSE;
3009 leveldir_new->user_defined =
3010 (leveldir_new->in_user_dir && IS_LEVELCLASS_PRIVATE(leveldir_new));
3012 leveldir_new->color = LEVELCOLOR(leveldir_new);
3014 setString(&leveldir_new->class_desc, getLevelClassDescription(leveldir_new));
3016 leveldir_new->handicap_level = /* set handicap to default value */
3017 (leveldir_new->user_defined || !leveldir_new->handicap ?
3018 leveldir_new->last_level : leveldir_new->first_level);
3020 DrawInitText(leveldir_new->name, 150, FC_YELLOW);
3022 pushTreeInfo(node_first, leveldir_new);
3024 freeSetupFileHash(setup_file_hash);
3026 if (leveldir_new->level_group)
3028 /* create node to link back to current level directory */
3029 createParentTreeInfoNode(leveldir_new);
3031 /* recursively step into sub-directory and look for more level series */
3032 LoadLevelInfoFromLevelDir(&leveldir_new->node_group,
3033 leveldir_new, directory_path);
3036 free(directory_path);
3042 static void LoadLevelInfoFromLevelDir(TreeInfo **node_first,
3043 TreeInfo *node_parent,
3044 char *level_directory)
3047 DirectoryEntry *dir_entry;
3048 boolean valid_entry_found = FALSE;
3050 if ((dir = openDirectory(level_directory)) == NULL)
3052 Error(ERR_WARN, "cannot read level directory '%s'", level_directory);
3057 while ((dir_entry = readDirectory(dir)) != NULL) /* loop all entries */
3059 char *directory_name = dir_entry->basename;
3060 char *directory_path = getPath2(level_directory, directory_name);
3062 /* skip entries for current and parent directory */
3063 if (strEqual(directory_name, ".") ||
3064 strEqual(directory_name, ".."))
3066 free(directory_path);
3071 /* find out if directory entry is itself a directory */
3072 if (!dir_entry->is_directory) /* not a directory */
3074 free(directory_path);
3079 free(directory_path);
3081 if (strEqual(directory_name, GRAPHICS_DIRECTORY) ||
3082 strEqual(directory_name, SOUNDS_DIRECTORY) ||
3083 strEqual(directory_name, MUSIC_DIRECTORY))
3086 valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
3091 closeDirectory(dir);
3093 /* special case: top level directory may directly contain "levelinfo.conf" */
3094 if (node_parent == NULL && !valid_entry_found)
3096 /* check if this directory directly contains a file "levelinfo.conf" */
3097 valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
3098 level_directory, ".");
3101 if (!valid_entry_found)
3102 Error(ERR_WARN, "cannot find any valid level series in directory '%s'",
3106 boolean AdjustGraphicsForEMC()
3108 boolean settings_changed = FALSE;
3110 settings_changed |= adjustTreeGraphicsForEMC(leveldir_first_all);
3111 settings_changed |= adjustTreeGraphicsForEMC(leveldir_first);
3113 return settings_changed;
3116 void LoadLevelInfo()
3118 InitUserLevelDirectory(getLoginName());
3120 DrawInitText("Loading level series", 120, FC_GREEN);
3122 LoadLevelInfoFromLevelDir(&leveldir_first, NULL, options.level_directory);
3123 LoadLevelInfoFromLevelDir(&leveldir_first, NULL, getUserLevelDir(NULL));
3125 leveldir_first = createTopTreeInfoNode(leveldir_first);
3127 /* after loading all level set information, clone the level directory tree
3128 and remove all level sets without levels (these may still contain artwork
3129 to be offered in the setup menu as "custom artwork", and are therefore
3130 checked for existing artwork in the function "LoadLevelArtworkInfo()") */
3131 leveldir_first_all = leveldir_first;
3132 cloneTree(&leveldir_first, leveldir_first_all, TRUE);
3134 AdjustGraphicsForEMC();
3136 /* before sorting, the first entries will be from the user directory */
3137 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
3139 if (leveldir_first == NULL)
3140 Error(ERR_EXIT, "cannot find any valid level series in any directory");
3142 sortTreeInfo(&leveldir_first);
3144 #if ENABLE_UNUSED_CODE
3145 dumpTreeInfo(leveldir_first, 0);
3149 static boolean LoadArtworkInfoFromArtworkConf(TreeInfo **node_first,
3150 TreeInfo *node_parent,
3151 char *base_directory,
3152 char *directory_name, int type)
3154 char *directory_path = getPath2(base_directory, directory_name);
3155 char *filename = getPath2(directory_path, ARTWORKINFO_FILENAME(type));
3156 SetupFileHash *setup_file_hash = NULL;
3157 TreeInfo *artwork_new = NULL;
3160 if (fileExists(filename))
3161 setup_file_hash = loadSetupFileHash(filename);
3163 if (setup_file_hash == NULL) /* no config file -- look for artwork files */
3166 DirectoryEntry *dir_entry;
3167 boolean valid_file_found = FALSE;
3169 if ((dir = openDirectory(directory_path)) != NULL)
3171 while ((dir_entry = readDirectory(dir)) != NULL)
3173 if (FileIsArtworkType(dir_entry->filename, type))
3175 valid_file_found = TRUE;
3181 closeDirectory(dir);
3184 if (!valid_file_found)
3186 if (!strEqual(directory_name, "."))
3187 Error(ERR_WARN, "ignoring artwork directory '%s'", directory_path);
3189 free(directory_path);
3196 artwork_new = newTreeInfo();
3199 setTreeInfoToDefaultsFromParent(artwork_new, node_parent);
3201 setTreeInfoToDefaults(artwork_new, type);
3203 artwork_new->subdir = getStringCopy(directory_name);
3205 if (setup_file_hash) /* (before defining ".color" and ".class_desc") */
3207 /* set all structure fields according to the token/value pairs */
3209 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
3210 setSetupInfo(levelinfo_tokens, i,
3211 getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
3214 if (strEqual(artwork_new->name, ANONYMOUS_NAME))
3215 setString(&artwork_new->name, artwork_new->subdir);
3217 if (artwork_new->identifier == NULL)
3218 artwork_new->identifier = getStringCopy(artwork_new->subdir);
3220 if (artwork_new->name_sorting == NULL)
3221 artwork_new->name_sorting = getStringCopy(artwork_new->name);
3224 if (node_parent == NULL) /* top level group */
3226 artwork_new->basepath = getStringCopy(base_directory);
3227 artwork_new->fullpath = getStringCopy(artwork_new->subdir);
3229 else /* sub level group */
3231 artwork_new->basepath = getStringCopy(node_parent->basepath);
3232 artwork_new->fullpath = getPath2(node_parent->fullpath, directory_name);
3235 artwork_new->in_user_dir =
3236 (!strEqual(artwork_new->basepath, OPTIONS_ARTWORK_DIRECTORY(type)));
3238 /* (may use ".sort_priority" from "setup_file_hash" above) */
3239 artwork_new->color = ARTWORKCOLOR(artwork_new);
3241 setString(&artwork_new->class_desc, getLevelClassDescription(artwork_new));
3243 if (setup_file_hash == NULL) /* (after determining ".user_defined") */
3245 if (strEqual(artwork_new->subdir, "."))
3247 if (artwork_new->user_defined)
3249 setString(&artwork_new->identifier, "private");
3250 artwork_new->sort_priority = ARTWORKCLASS_PRIVATE;
3254 setString(&artwork_new->identifier, "classic");
3255 artwork_new->sort_priority = ARTWORKCLASS_CLASSICS;
3258 /* set to new values after changing ".sort_priority" */
3259 artwork_new->color = ARTWORKCOLOR(artwork_new);
3261 setString(&artwork_new->class_desc,
3262 getLevelClassDescription(artwork_new));
3266 setString(&artwork_new->identifier, artwork_new->subdir);
3269 setString(&artwork_new->name, artwork_new->identifier);
3270 setString(&artwork_new->name_sorting, artwork_new->name);
3273 pushTreeInfo(node_first, artwork_new);
3275 freeSetupFileHash(setup_file_hash);
3277 free(directory_path);
3283 static void LoadArtworkInfoFromArtworkDir(TreeInfo **node_first,
3284 TreeInfo *node_parent,
3285 char *base_directory, int type)
3288 DirectoryEntry *dir_entry;
3289 boolean valid_entry_found = FALSE;
3291 if ((dir = openDirectory(base_directory)) == NULL)
3293 /* display error if directory is main "options.graphics_directory" etc. */
3294 if (base_directory == OPTIONS_ARTWORK_DIRECTORY(type))
3295 Error(ERR_WARN, "cannot read directory '%s'", base_directory);
3300 while ((dir_entry = readDirectory(dir)) != NULL) /* loop all entries */
3302 char *directory_name = dir_entry->basename;
3303 char *directory_path = getPath2(base_directory, directory_name);
3305 /* skip directory entries for current and parent directory */
3306 if (strEqual(directory_name, ".") ||
3307 strEqual(directory_name, ".."))
3309 free(directory_path);
3314 /* skip directory entries which are not a directory */
3315 if (!dir_entry->is_directory) /* not a directory */
3317 free(directory_path);
3322 free(directory_path);
3324 /* check if this directory contains artwork with or without config file */
3325 valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
3327 directory_name, type);
3330 closeDirectory(dir);
3332 /* check if this directory directly contains artwork itself */
3333 valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
3334 base_directory, ".",
3336 if (!valid_entry_found)
3337 Error(ERR_WARN, "cannot find any valid artwork in directory '%s'",
3341 static TreeInfo *getDummyArtworkInfo(int type)
3343 /* this is only needed when there is completely no artwork available */
3344 TreeInfo *artwork_new = newTreeInfo();
3346 setTreeInfoToDefaults(artwork_new, type);
3348 setString(&artwork_new->subdir, UNDEFINED_FILENAME);
3349 setString(&artwork_new->fullpath, UNDEFINED_FILENAME);
3350 setString(&artwork_new->basepath, UNDEFINED_FILENAME);
3352 setString(&artwork_new->identifier, UNDEFINED_FILENAME);
3353 setString(&artwork_new->name, UNDEFINED_FILENAME);
3354 setString(&artwork_new->name_sorting, UNDEFINED_FILENAME);
3359 void LoadArtworkInfo()
3361 LoadArtworkInfoCache();
3363 DrawInitText("Looking for custom artwork", 120, FC_GREEN);
3365 LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
3366 options.graphics_directory,
3367 TREE_TYPE_GRAPHICS_DIR);
3368 LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
3369 getUserGraphicsDir(),
3370 TREE_TYPE_GRAPHICS_DIR);
3372 LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
3373 options.sounds_directory,
3374 TREE_TYPE_SOUNDS_DIR);
3375 LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
3377 TREE_TYPE_SOUNDS_DIR);
3379 LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
3380 options.music_directory,
3381 TREE_TYPE_MUSIC_DIR);
3382 LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
3384 TREE_TYPE_MUSIC_DIR);
3386 if (artwork.gfx_first == NULL)
3387 artwork.gfx_first = getDummyArtworkInfo(TREE_TYPE_GRAPHICS_DIR);
3388 if (artwork.snd_first == NULL)
3389 artwork.snd_first = getDummyArtworkInfo(TREE_TYPE_SOUNDS_DIR);
3390 if (artwork.mus_first == NULL)
3391 artwork.mus_first = getDummyArtworkInfo(TREE_TYPE_MUSIC_DIR);
3393 /* before sorting, the first entries will be from the user directory */
3394 artwork.gfx_current =
3395 getTreeInfoFromIdentifier(artwork.gfx_first, setup.graphics_set);
3396 if (artwork.gfx_current == NULL)
3397 artwork.gfx_current =
3398 getTreeInfoFromIdentifier(artwork.gfx_first, GFX_DEFAULT_SUBDIR);
3399 if (artwork.gfx_current == NULL)
3400 artwork.gfx_current = getFirstValidTreeInfoEntry(artwork.gfx_first);
3402 artwork.snd_current =
3403 getTreeInfoFromIdentifier(artwork.snd_first, setup.sounds_set);
3404 if (artwork.snd_current == NULL)
3405 artwork.snd_current =
3406 getTreeInfoFromIdentifier(artwork.snd_first, SND_DEFAULT_SUBDIR);
3407 if (artwork.snd_current == NULL)
3408 artwork.snd_current = getFirstValidTreeInfoEntry(artwork.snd_first);
3410 artwork.mus_current =
3411 getTreeInfoFromIdentifier(artwork.mus_first, setup.music_set);
3412 if (artwork.mus_current == NULL)
3413 artwork.mus_current =
3414 getTreeInfoFromIdentifier(artwork.mus_first, MUS_DEFAULT_SUBDIR);
3415 if (artwork.mus_current == NULL)
3416 artwork.mus_current = getFirstValidTreeInfoEntry(artwork.mus_first);
3418 artwork.gfx_current_identifier = artwork.gfx_current->identifier;
3419 artwork.snd_current_identifier = artwork.snd_current->identifier;
3420 artwork.mus_current_identifier = artwork.mus_current->identifier;
3422 #if ENABLE_UNUSED_CODE
3423 printf("graphics set == %s\n\n", artwork.gfx_current_identifier);
3424 printf("sounds set == %s\n\n", artwork.snd_current_identifier);
3425 printf("music set == %s\n\n", artwork.mus_current_identifier);
3428 sortTreeInfo(&artwork.gfx_first);
3429 sortTreeInfo(&artwork.snd_first);
3430 sortTreeInfo(&artwork.mus_first);
3432 #if ENABLE_UNUSED_CODE
3433 dumpTreeInfo(artwork.gfx_first, 0);
3434 dumpTreeInfo(artwork.snd_first, 0);
3435 dumpTreeInfo(artwork.mus_first, 0);
3439 void LoadArtworkInfoFromLevelInfo(ArtworkDirTree **artwork_node,
3440 LevelDirTree *level_node)
3442 int type = (*artwork_node)->type;
3444 /* recursively check all level directories for artwork sub-directories */
3448 /* check all tree entries for artwork, but skip parent link entries */
3449 if (!level_node->parent_link)
3451 TreeInfo *artwork_new = getArtworkInfoCacheEntry(level_node, type);
3452 boolean cached = (artwork_new != NULL);
3456 pushTreeInfo(artwork_node, artwork_new);
3460 TreeInfo *topnode_last = *artwork_node;
3461 char *path = getPath2(getLevelDirFromTreeInfo(level_node),
3462 ARTWORK_DIRECTORY(type));
3464 LoadArtworkInfoFromArtworkDir(artwork_node, NULL, path, type);
3466 if (topnode_last != *artwork_node) /* check for newly added node */
3468 artwork_new = *artwork_node;
3470 setString(&artwork_new->identifier, level_node->subdir);
3471 setString(&artwork_new->name, level_node->name);
3472 setString(&artwork_new->name_sorting, level_node->name_sorting);
3474 artwork_new->sort_priority = level_node->sort_priority;
3475 artwork_new->color = LEVELCOLOR(artwork_new);
3481 /* insert artwork info (from old cache or filesystem) into new cache */
3482 if (artwork_new != NULL)
3483 setArtworkInfoCacheEntry(artwork_new, level_node, type);
3486 DrawInitText(level_node->name, 150, FC_YELLOW);
3488 if (level_node->node_group != NULL)
3489 LoadArtworkInfoFromLevelInfo(artwork_node, level_node->node_group);
3491 level_node = level_node->next;
3495 void LoadLevelArtworkInfo()
3497 print_timestamp_init("LoadLevelArtworkInfo");
3499 DrawInitText("Looking for custom level artwork", 120, FC_GREEN);
3501 print_timestamp_time("DrawTimeText");
3503 LoadArtworkInfoFromLevelInfo(&artwork.gfx_first, leveldir_first_all);
3504 print_timestamp_time("LoadArtworkInfoFromLevelInfo (gfx)");
3505 LoadArtworkInfoFromLevelInfo(&artwork.snd_first, leveldir_first_all);
3506 print_timestamp_time("LoadArtworkInfoFromLevelInfo (snd)");
3507 LoadArtworkInfoFromLevelInfo(&artwork.mus_first, leveldir_first_all);
3508 print_timestamp_time("LoadArtworkInfoFromLevelInfo (mus)");
3510 SaveArtworkInfoCache();
3512 print_timestamp_time("SaveArtworkInfoCache");
3514 /* needed for reloading level artwork not known at ealier stage */
3516 if (!strEqual(artwork.gfx_current_identifier, setup.graphics_set))
3518 artwork.gfx_current =
3519 getTreeInfoFromIdentifier(artwork.gfx_first, setup.graphics_set);
3520 if (artwork.gfx_current == NULL)
3521 artwork.gfx_current =
3522 getTreeInfoFromIdentifier(artwork.gfx_first, GFX_DEFAULT_SUBDIR);
3523 if (artwork.gfx_current == NULL)
3524 artwork.gfx_current = getFirstValidTreeInfoEntry(artwork.gfx_first);
3527 if (!strEqual(artwork.snd_current_identifier, setup.sounds_set))
3529 artwork.snd_current =
3530 getTreeInfoFromIdentifier(artwork.snd_first, setup.sounds_set);
3531 if (artwork.snd_current == NULL)
3532 artwork.snd_current =
3533 getTreeInfoFromIdentifier(artwork.snd_first, SND_DEFAULT_SUBDIR);
3534 if (artwork.snd_current == NULL)
3535 artwork.snd_current = getFirstValidTreeInfoEntry(artwork.snd_first);
3538 if (!strEqual(artwork.mus_current_identifier, setup.music_set))
3540 artwork.mus_current =
3541 getTreeInfoFromIdentifier(artwork.mus_first, setup.music_set);
3542 if (artwork.mus_current == NULL)
3543 artwork.mus_current =
3544 getTreeInfoFromIdentifier(artwork.mus_first, MUS_DEFAULT_SUBDIR);
3545 if (artwork.mus_current == NULL)
3546 artwork.mus_current = getFirstValidTreeInfoEntry(artwork.mus_first);
3549 print_timestamp_time("getTreeInfoFromIdentifier");
3551 sortTreeInfo(&artwork.gfx_first);
3552 sortTreeInfo(&artwork.snd_first);
3553 sortTreeInfo(&artwork.mus_first);
3555 print_timestamp_time("sortTreeInfo");
3557 #if ENABLE_UNUSED_CODE
3558 dumpTreeInfo(artwork.gfx_first, 0);
3559 dumpTreeInfo(artwork.snd_first, 0);
3560 dumpTreeInfo(artwork.mus_first, 0);
3563 print_timestamp_done("LoadLevelArtworkInfo");
3566 static boolean AddUserLevelSetToLevelInfoExt(char *level_subdir_new)
3568 // get level info tree node of first (original) user level set
3569 char *level_subdir_old = getLoginName();
3570 LevelDirTree *leveldir_old = getTreeInfoFromIdentifier(leveldir_first,
3572 if (leveldir_old == NULL) // should not happen
3575 int draw_deactivation_mask = GetDrawDeactivationMask();
3577 // override draw deactivation mask (temporarily disable drawing)
3578 SetDrawDeactivationMask(REDRAW_ALL);
3580 // load new level set config and add it next to first user level set
3581 LoadLevelInfoFromLevelConf(&leveldir_old->next, NULL,
3582 leveldir_old->basepath, level_subdir_new);
3584 // set draw deactivation mask to previous value
3585 SetDrawDeactivationMask(draw_deactivation_mask);
3587 // get level info tree node of newly added user level set
3588 LevelDirTree *leveldir_new = getTreeInfoFromIdentifier(leveldir_first,
3590 if (leveldir_new == NULL) // should not happen
3593 // correct top link and parent node link of newly created tree node
3594 leveldir_new->node_top = leveldir_old->node_top;
3595 leveldir_new->node_parent = leveldir_old->node_parent;
3597 // sort level info tree to adjust position of newly added level set
3598 sortTreeInfo(&leveldir_first);
3603 void AddUserLevelSetToLevelInfo(char *level_subdir_new)
3605 if (!AddUserLevelSetToLevelInfoExt(level_subdir_new))
3606 Error(ERR_EXIT, "internal level set structure corrupted -- aborting");
3609 char *getArtworkIdentifierForUserLevelSet(int type)
3611 char *classic_artwork_set = getClassicArtworkSet(type);
3613 /* check for custom artwork configured in "levelinfo.conf" */
3614 char *leveldir_artwork_set =
3615 *LEVELDIR_ARTWORK_SET_PTR(leveldir_current, type);
3616 boolean has_leveldir_artwork_set =
3617 (leveldir_artwork_set != NULL && !strEqual(leveldir_artwork_set,
3618 classic_artwork_set));
3620 /* check for custom artwork in sub-directory "graphics" etc. */
3621 TreeInfo *artwork_first_node = ARTWORK_FIRST_NODE(artwork, type);
3622 char *leveldir_identifier = leveldir_current->identifier;
3623 boolean has_artwork_subdir =
3624 (getTreeInfoFromIdentifier(artwork_first_node,
3625 leveldir_identifier) != NULL);
3627 return (has_leveldir_artwork_set ? leveldir_artwork_set :
3628 has_artwork_subdir ? leveldir_identifier :
3629 classic_artwork_set);
3632 boolean checkIfCustomArtworkExistsForCurrentLevelSet()
3634 char *graphics_set =
3635 getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_GRAPHICS);
3637 getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_SOUNDS);
3639 getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_MUSIC);
3641 return (!strEqual(graphics_set, GFX_CLASSIC_SUBDIR) ||
3642 !strEqual(sounds_set, SND_CLASSIC_SUBDIR) ||
3643 !strEqual(music_set, MUS_CLASSIC_SUBDIR));
3646 boolean UpdateUserLevelSet(char *level_subdir, char *level_name,
3647 char *level_author, int num_levels)
3649 char *filename = getPath2(getUserLevelDir(level_subdir), LEVELINFO_FILENAME);
3650 char *filename_tmp = getStringCat2(filename, ".tmp");
3652 FILE *file_tmp = NULL;
3653 char line[MAX_LINE_LEN];
3654 boolean success = FALSE;
3655 LevelDirTree *leveldir = getTreeInfoFromIdentifier(leveldir_first,
3657 // update values in level directory tree
3659 if (level_name != NULL)
3660 setString(&leveldir->name, level_name);
3662 if (level_author != NULL)
3663 setString(&leveldir->author, level_author);
3665 if (num_levels != -1)
3666 leveldir->levels = num_levels;
3668 // update values that depend on other values
3670 setString(&leveldir->name_sorting, leveldir->name);
3672 leveldir->last_level = leveldir->first_level + leveldir->levels - 1;
3674 // sort order of level sets may have changed
3675 sortTreeInfo(&leveldir_first);
3677 if ((file = fopen(filename, MODE_READ)) &&
3678 (file_tmp = fopen(filename_tmp, MODE_WRITE)))
3680 while (fgets(line, MAX_LINE_LEN, file))
3682 if (strPrefix(line, "name:") && level_name != NULL)
3683 fprintf(file_tmp, "%-32s%s\n", "name:", level_name);
3684 else if (strPrefix(line, "author:") && level_author != NULL)
3685 fprintf(file_tmp, "%-32s%s\n", "author:", level_author);
3686 else if (strPrefix(line, "levels:") && num_levels != -1)
3687 fprintf(file_tmp, "%-32s%d\n", "levels:", num_levels);
3689 fputs(line, file_tmp);
3702 success = (rename(filename_tmp, filename) == 0);
3710 boolean CreateUserLevelSet(char *level_subdir, char *level_name,
3711 char *level_author, int num_levels,
3712 boolean use_artwork_set)
3714 LevelDirTree *level_info;
3719 // create user level sub-directory, if needed
3720 createDirectory(getUserLevelDir(level_subdir), "user level", PERMS_PRIVATE);
3722 filename = getPath2(getUserLevelDir(level_subdir), LEVELINFO_FILENAME);
3724 if (!(file = fopen(filename, MODE_WRITE)))
3726 Error(ERR_WARN, "cannot write level info file '%s'", filename);
3732 level_info = newTreeInfo();
3734 /* always start with reliable default values */
3735 setTreeInfoToDefaults(level_info, TREE_TYPE_LEVEL_DIR);
3737 setString(&level_info->name, level_name);
3738 setString(&level_info->author, level_author);
3739 level_info->levels = num_levels;
3740 level_info->first_level = 1;
3741 level_info->sort_priority = LEVELCLASS_PRIVATE_START;
3742 level_info->readonly = FALSE;
3744 if (use_artwork_set)
3746 level_info->graphics_set =
3747 getStringCopy(getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_GRAPHICS));
3748 level_info->sounds_set =
3749 getStringCopy(getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_SOUNDS));
3750 level_info->music_set =
3751 getStringCopy(getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_MUSIC));
3754 token_value_position = TOKEN_VALUE_POSITION_SHORT;
3756 fprintFileHeader(file, LEVELINFO_FILENAME);
3759 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
3761 if (i == LEVELINFO_TOKEN_NAME ||
3762 i == LEVELINFO_TOKEN_AUTHOR ||
3763 i == LEVELINFO_TOKEN_LEVELS ||
3764 i == LEVELINFO_TOKEN_FIRST_LEVEL ||
3765 i == LEVELINFO_TOKEN_SORT_PRIORITY ||
3766 i == LEVELINFO_TOKEN_READONLY ||
3767 (use_artwork_set && (i == LEVELINFO_TOKEN_GRAPHICS_SET ||
3768 i == LEVELINFO_TOKEN_SOUNDS_SET ||
3769 i == LEVELINFO_TOKEN_MUSIC_SET)))
3770 fprintf(file, "%s\n", getSetupLine(levelinfo_tokens, "", i));
3772 /* just to make things nicer :) */
3773 if (i == LEVELINFO_TOKEN_AUTHOR ||
3774 i == LEVELINFO_TOKEN_FIRST_LEVEL ||
3775 (use_artwork_set && i == LEVELINFO_TOKEN_READONLY))
3776 fprintf(file, "\n");
3779 token_value_position = TOKEN_VALUE_POSITION_DEFAULT;
3783 SetFilePermissions(filename, PERMS_PRIVATE);
3785 freeTreeInfo(level_info);
3791 static void SaveUserLevelInfo()
3793 CreateUserLevelSet(getLoginName(), getLoginName(), getRealName(), 100, FALSE);
3796 char *getSetupValue(int type, void *value)
3798 static char value_string[MAX_LINE_LEN];
3806 strcpy(value_string, (*(boolean *)value ? "true" : "false"));
3810 strcpy(value_string, (*(boolean *)value ? "on" : "off"));
3814 strcpy(value_string, (*(int *)value == AUTO ? "auto" :
3815 *(int *)value == FALSE ? "off" : "on"));
3819 strcpy(value_string, (*(boolean *)value ? "yes" : "no"));
3822 case TYPE_YES_NO_AUTO:
3823 strcpy(value_string, (*(int *)value == AUTO ? "auto" :
3824 *(int *)value == FALSE ? "no" : "yes"));
3828 strcpy(value_string, (*(boolean *)value ? "AGA" : "ECS"));
3832 strcpy(value_string, getKeyNameFromKey(*(Key *)value));
3836 strcpy(value_string, getX11KeyNameFromKey(*(Key *)value));
3840 sprintf(value_string, "%d", *(int *)value);
3844 if (*(char **)value == NULL)
3847 strcpy(value_string, *(char **)value);
3851 value_string[0] = '\0';
3855 if (type & TYPE_GHOSTED)
3856 strcpy(value_string, "n/a");
3858 return value_string;
3861 char *getSetupLine(struct TokenInfo *token_info, char *prefix, int token_nr)
3865 static char token_string[MAX_LINE_LEN];
3866 int token_type = token_info[token_nr].type;
3867 void *setup_value = token_info[token_nr].value;
3868 char *token_text = token_info[token_nr].text;
3869 char *value_string = getSetupValue(token_type, setup_value);
3871 /* build complete token string */
3872 sprintf(token_string, "%s%s", prefix, token_text);
3874 /* build setup entry line */
3875 line = getFormattedSetupEntry(token_string, value_string);
3877 if (token_type == TYPE_KEY_X11)
3879 Key key = *(Key *)setup_value;
3880 char *keyname = getKeyNameFromKey(key);
3882 /* add comment, if useful */
3883 if (!strEqual(keyname, "(undefined)") &&
3884 !strEqual(keyname, "(unknown)"))
3886 /* add at least one whitespace */
3888 for (i = strlen(line); i < token_comment_position; i++)
3892 strcat(line, keyname);
3899 void LoadLevelSetup_LastSeries()
3901 /* ----------------------------------------------------------------------- */
3902 /* ~/.<program>/levelsetup.conf */
3903 /* ----------------------------------------------------------------------- */
3905 char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
3906 SetupFileHash *level_setup_hash = NULL;
3908 /* always start with reliable default values */
3909 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
3911 if (!strEqual(DEFAULT_LEVELSET, UNDEFINED_LEVELSET))
3913 leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
3915 if (leveldir_current == NULL)
3916 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
3919 if ((level_setup_hash = loadSetupFileHash(filename)))
3921 char *last_level_series =
3922 getHashEntry(level_setup_hash, TOKEN_STR_LAST_LEVEL_SERIES);
3924 leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
3926 if (leveldir_current == NULL)
3927 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
3929 freeSetupFileHash(level_setup_hash);
3933 Error(ERR_DEBUG, "using default setup values");
3939 static void SaveLevelSetup_LastSeries_Ext(boolean deactivate_last_level_series)
3941 /* ----------------------------------------------------------------------- */
3942 /* ~/.<program>/levelsetup.conf */
3943 /* ----------------------------------------------------------------------- */
3945 // check if the current level directory structure is available at this point
3946 if (leveldir_current == NULL)
3949 char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
3950 char *level_subdir = leveldir_current->subdir;
3953 InitUserDataDirectory();
3955 if (!(file = fopen(filename, MODE_WRITE)))
3957 Error(ERR_WARN, "cannot write setup file '%s'", filename);
3964 fprintFileHeader(file, LEVELSETUP_FILENAME);
3966 if (deactivate_last_level_series)
3967 fprintf(file, "# %s\n# ", "the following level set may have caused a problem and was deactivated");
3969 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_LEVEL_SERIES,
3974 SetFilePermissions(filename, PERMS_PRIVATE);
3979 void SaveLevelSetup_LastSeries()
3981 SaveLevelSetup_LastSeries_Ext(FALSE);
3984 void SaveLevelSetup_LastSeries_Deactivate()
3986 SaveLevelSetup_LastSeries_Ext(TRUE);
3989 static void checkSeriesInfo()
3991 static char *level_directory = NULL;
3994 /* check for more levels besides the 'levels' field of 'levelinfo.conf' */
3996 level_directory = getPath2((leveldir_current->in_user_dir ?
3997 getUserLevelDir(NULL) :
3998 options.level_directory),
3999 leveldir_current->fullpath);
4001 if ((dir = openDirectory(level_directory)) == NULL)
4003 Error(ERR_WARN, "cannot read level directory '%s'", level_directory);
4008 closeDirectory(dir);
4011 void LoadLevelSetup_SeriesInfo()
4014 SetupFileHash *level_setup_hash = NULL;
4015 char *level_subdir = leveldir_current->subdir;
4018 /* always start with reliable default values */
4019 level_nr = leveldir_current->first_level;
4021 for (i = 0; i < MAX_LEVELS; i++)
4023 LevelStats_setPlayed(i, 0);
4024 LevelStats_setSolved(i, 0);
4029 /* ----------------------------------------------------------------------- */
4030 /* ~/.<program>/levelsetup/<level series>/levelsetup.conf */
4031 /* ----------------------------------------------------------------------- */
4033 level_subdir = leveldir_current->subdir;
4035 filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
4037 if ((level_setup_hash = loadSetupFileHash(filename)))
4041 /* get last played level in this level set */
4043 token_value = getHashEntry(level_setup_hash, TOKEN_STR_LAST_PLAYED_LEVEL);
4047 level_nr = atoi(token_value);
4049 if (level_nr < leveldir_current->first_level)
4050 level_nr = leveldir_current->first_level;
4051 if (level_nr > leveldir_current->last_level)
4052 level_nr = leveldir_current->last_level;
4055 /* get handicap level in this level set */
4057 token_value = getHashEntry(level_setup_hash, TOKEN_STR_HANDICAP_LEVEL);
4061 int level_nr = atoi(token_value);
4063 if (level_nr < leveldir_current->first_level)
4064 level_nr = leveldir_current->first_level;
4065 if (level_nr > leveldir_current->last_level + 1)
4066 level_nr = leveldir_current->last_level;
4068 if (leveldir_current->user_defined || !leveldir_current->handicap)
4069 level_nr = leveldir_current->last_level;
4071 leveldir_current->handicap_level = level_nr;
4074 /* get number of played and solved levels in this level set */
4076 BEGIN_HASH_ITERATION(level_setup_hash, itr)
4078 char *token = HASH_ITERATION_TOKEN(itr);
4079 char *value = HASH_ITERATION_VALUE(itr);
4081 if (strlen(token) == 3 &&
4082 token[0] >= '0' && token[0] <= '9' &&
4083 token[1] >= '0' && token[1] <= '9' &&
4084 token[2] >= '0' && token[2] <= '9')
4086 int level_nr = atoi(token);
4089 LevelStats_setPlayed(level_nr, atoi(value)); /* read 1st column */
4091 value = strchr(value, ' ');
4094 LevelStats_setSolved(level_nr, atoi(value)); /* read 2nd column */
4097 END_HASH_ITERATION(hash, itr)
4099 freeSetupFileHash(level_setup_hash);
4103 Error(ERR_DEBUG, "using default setup values");
4109 void SaveLevelSetup_SeriesInfo()
4112 char *level_subdir = leveldir_current->subdir;
4113 char *level_nr_str = int2str(level_nr, 0);
4114 char *handicap_level_str = int2str(leveldir_current->handicap_level, 0);
4118 /* ----------------------------------------------------------------------- */
4119 /* ~/.<program>/levelsetup/<level series>/levelsetup.conf */
4120 /* ----------------------------------------------------------------------- */
4122 InitLevelSetupDirectory(level_subdir);
4124 filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
4126 if (!(file = fopen(filename, MODE_WRITE)))
4128 Error(ERR_WARN, "cannot write setup file '%s'", filename);
4133 fprintFileHeader(file, LEVELSETUP_FILENAME);
4135 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_PLAYED_LEVEL,
4137 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_HANDICAP_LEVEL,
4138 handicap_level_str));
4140 for (i = leveldir_current->first_level; i <= leveldir_current->last_level;
4143 if (LevelStats_getPlayed(i) > 0 ||
4144 LevelStats_getSolved(i) > 0)
4149 sprintf(token, "%03d", i);
4150 sprintf(value, "%d %d", LevelStats_getPlayed(i), LevelStats_getSolved(i));
4152 fprintf(file, "%s\n", getFormattedSetupEntry(token, value));
4158 SetFilePermissions(filename, PERMS_PRIVATE);
4163 int LevelStats_getPlayed(int nr)
4165 return (nr >= 0 && nr < MAX_LEVELS ? level_stats[nr].played : 0);
4168 int LevelStats_getSolved(int nr)
4170 return (nr >= 0 && nr < MAX_LEVELS ? level_stats[nr].solved : 0);
4173 void LevelStats_setPlayed(int nr, int value)
4175 if (nr >= 0 && nr < MAX_LEVELS)
4176 level_stats[nr].played = value;
4179 void LevelStats_setSolved(int nr, int value)
4181 if (nr >= 0 && nr < MAX_LEVELS)
4182 level_stats[nr].solved = value;
4185 void LevelStats_incPlayed(int nr)
4187 if (nr >= 0 && nr < MAX_LEVELS)
4188 level_stats[nr].played++;
4191 void LevelStats_incSolved(int nr)
4193 if (nr >= 0 && nr < MAX_LEVELS)
4194 level_stats[nr].solved++;