1 // ============================================================================
2 // Artsoft Retro-Game Library
3 // ----------------------------------------------------------------------------
4 // (c) 1995-2014 by Artsoft Entertainment
7 // http://www.artsoft.org/
8 // ----------------------------------------------------------------------------
10 // ============================================================================
12 #include <sys/types.h>
21 #if !defined(PLATFORM_WIN32)
23 #include <sys/param.h>
33 #define ENABLE_UNUSED_CODE FALSE /* for currently unused functions */
35 #define NUM_LEVELCLASS_DESC 8
37 static char *levelclass_desc[NUM_LEVELCLASS_DESC] =
50 #define LEVELCOLOR(n) (IS_LEVELCLASS_TUTORIAL(n) ? FC_BLUE : \
51 IS_LEVELCLASS_CLASSICS(n) ? FC_RED : \
52 IS_LEVELCLASS_BD(n) ? FC_YELLOW : \
53 IS_LEVELCLASS_EM(n) ? FC_YELLOW : \
54 IS_LEVELCLASS_SP(n) ? FC_YELLOW : \
55 IS_LEVELCLASS_DX(n) ? FC_YELLOW : \
56 IS_LEVELCLASS_SB(n) ? FC_YELLOW : \
57 IS_LEVELCLASS_CONTRIB(n) ? FC_GREEN : \
58 IS_LEVELCLASS_PRIVATE(n) ? FC_RED : \
61 #define LEVELSORTING(n) (IS_LEVELCLASS_TUTORIAL(n) ? 0 : \
62 IS_LEVELCLASS_CLASSICS(n) ? 1 : \
63 IS_LEVELCLASS_BD(n) ? 2 : \
64 IS_LEVELCLASS_EM(n) ? 3 : \
65 IS_LEVELCLASS_SP(n) ? 4 : \
66 IS_LEVELCLASS_DX(n) ? 5 : \
67 IS_LEVELCLASS_SB(n) ? 6 : \
68 IS_LEVELCLASS_CONTRIB(n) ? 7 : \
69 IS_LEVELCLASS_PRIVATE(n) ? 8 : \
72 #define ARTWORKCOLOR(n) (IS_ARTWORKCLASS_CLASSICS(n) ? FC_RED : \
73 IS_ARTWORKCLASS_CONTRIB(n) ? FC_GREEN : \
74 IS_ARTWORKCLASS_PRIVATE(n) ? FC_RED : \
75 IS_ARTWORKCLASS_LEVEL(n) ? FC_YELLOW : \
78 #define ARTWORKSORTING(n) (IS_ARTWORKCLASS_CLASSICS(n) ? 0 : \
79 IS_ARTWORKCLASS_LEVEL(n) ? 1 : \
80 IS_ARTWORKCLASS_CONTRIB(n) ? 2 : \
81 IS_ARTWORKCLASS_PRIVATE(n) ? 3 : \
84 #define TOKEN_VALUE_POSITION_SHORT 32
85 #define TOKEN_VALUE_POSITION_DEFAULT 40
86 #define TOKEN_COMMENT_POSITION_DEFAULT 60
88 #define MAX_COOKIE_LEN 256
91 static void setTreeInfoToDefaults(TreeInfo *, int);
92 static TreeInfo *getTreeInfoCopy(TreeInfo *ti);
93 static int compareTreeInfoEntries(const void *, const void *);
95 static int token_value_position = TOKEN_VALUE_POSITION_DEFAULT;
96 static int token_comment_position = TOKEN_COMMENT_POSITION_DEFAULT;
98 static SetupFileHash *artworkinfo_cache_old = NULL;
99 static SetupFileHash *artworkinfo_cache_new = NULL;
100 static boolean use_artworkinfo_cache = TRUE;
103 /* ------------------------------------------------------------------------- */
105 /* ------------------------------------------------------------------------- */
107 static char *getLevelClassDescription(TreeInfo *ti)
109 int position = ti->sort_priority / 100;
111 if (position >= 0 && position < NUM_LEVELCLASS_DESC)
112 return levelclass_desc[position];
114 return "Unknown Level Class";
117 static char *getScoreDir(char *level_subdir)
119 static char *score_dir = NULL;
120 static char *score_level_dir = NULL;
121 char *score_subdir = SCORES_DIRECTORY;
123 if (score_dir == NULL)
125 if (program.global_scores)
126 score_dir = getPath2(getCommonDataDir(), score_subdir);
128 score_dir = getPath2(getUserGameDataDir(), score_subdir);
131 if (level_subdir != NULL)
133 checked_free(score_level_dir);
135 score_level_dir = getPath2(score_dir, level_subdir);
137 return score_level_dir;
143 static char *getLevelSetupDir(char *level_subdir)
145 static char *levelsetup_dir = NULL;
146 char *data_dir = getUserGameDataDir();
147 char *levelsetup_subdir = LEVELSETUP_DIRECTORY;
149 checked_free(levelsetup_dir);
151 if (level_subdir != NULL)
152 levelsetup_dir = getPath3(data_dir, levelsetup_subdir, level_subdir);
154 levelsetup_dir = getPath2(data_dir, levelsetup_subdir);
156 return levelsetup_dir;
159 static char *getCacheDir()
161 static char *cache_dir = NULL;
163 if (cache_dir == NULL)
164 cache_dir = getPath2(getUserGameDataDir(), CACHE_DIRECTORY);
169 static char *getLevelDirFromTreeInfo(TreeInfo *node)
171 static char *level_dir = NULL;
174 return options.level_directory;
176 checked_free(level_dir);
178 level_dir = getPath2((node->in_user_dir ? getUserLevelDir(NULL) :
179 options.level_directory), node->fullpath);
184 char *getUserLevelDir(char *level_subdir)
186 static char *userlevel_dir = NULL;
187 char *data_dir = getUserGameDataDir();
188 char *userlevel_subdir = LEVELS_DIRECTORY;
190 checked_free(userlevel_dir);
192 if (level_subdir != NULL)
193 userlevel_dir = getPath3(data_dir, userlevel_subdir, level_subdir);
195 userlevel_dir = getPath2(data_dir, userlevel_subdir);
197 return userlevel_dir;
200 char *getCurrentLevelDir()
202 return getLevelDirFromTreeInfo(leveldir_current);
205 char *getNewUserLevelSubdir()
207 static char *new_level_subdir = NULL;
208 char *subdir_prefix = getLoginName();
209 char subdir_suffix[10];
210 int max_suffix_number = 1000;
213 while (++i < max_suffix_number)
215 sprintf(subdir_suffix, "_%d", i);
217 checked_free(new_level_subdir);
218 new_level_subdir = getStringCat2(subdir_prefix, subdir_suffix);
220 if (!directoryExists(getUserLevelDir(new_level_subdir)))
224 return new_level_subdir;
227 static char *getTapeDir(char *level_subdir)
229 static char *tape_dir = NULL;
230 char *data_dir = getUserGameDataDir();
231 char *tape_subdir = TAPES_DIRECTORY;
233 checked_free(tape_dir);
235 if (level_subdir != NULL)
236 tape_dir = getPath3(data_dir, tape_subdir, level_subdir);
238 tape_dir = getPath2(data_dir, tape_subdir);
243 static char *getSolutionTapeDir()
245 static char *tape_dir = NULL;
246 char *data_dir = getCurrentLevelDir();
247 char *tape_subdir = TAPES_DIRECTORY;
249 checked_free(tape_dir);
251 tape_dir = getPath2(data_dir, tape_subdir);
256 static char *getDefaultGraphicsDir(char *graphics_subdir)
258 static char *graphics_dir = NULL;
260 if (graphics_subdir == NULL)
261 return options.graphics_directory;
263 checked_free(graphics_dir);
265 graphics_dir = getPath2(options.graphics_directory, graphics_subdir);
270 static char *getDefaultSoundsDir(char *sounds_subdir)
272 static char *sounds_dir = NULL;
274 if (sounds_subdir == NULL)
275 return options.sounds_directory;
277 checked_free(sounds_dir);
279 sounds_dir = getPath2(options.sounds_directory, sounds_subdir);
284 static char *getDefaultMusicDir(char *music_subdir)
286 static char *music_dir = NULL;
288 if (music_subdir == NULL)
289 return options.music_directory;
291 checked_free(music_dir);
293 music_dir = getPath2(options.music_directory, music_subdir);
298 static char *getClassicArtworkSet(int type)
300 return (type == TREE_TYPE_GRAPHICS_DIR ? GFX_CLASSIC_SUBDIR :
301 type == TREE_TYPE_SOUNDS_DIR ? SND_CLASSIC_SUBDIR :
302 type == TREE_TYPE_MUSIC_DIR ? MUS_CLASSIC_SUBDIR : "");
305 static char *getClassicArtworkDir(int type)
307 return (type == TREE_TYPE_GRAPHICS_DIR ?
308 getDefaultGraphicsDir(GFX_CLASSIC_SUBDIR) :
309 type == TREE_TYPE_SOUNDS_DIR ?
310 getDefaultSoundsDir(SND_CLASSIC_SUBDIR) :
311 type == TREE_TYPE_MUSIC_DIR ?
312 getDefaultMusicDir(MUS_CLASSIC_SUBDIR) : "");
315 static char *getUserGraphicsDir()
317 static char *usergraphics_dir = NULL;
319 if (usergraphics_dir == NULL)
320 usergraphics_dir = getPath2(getUserGameDataDir(), GRAPHICS_DIRECTORY);
322 return usergraphics_dir;
325 static char *getUserSoundsDir()
327 static char *usersounds_dir = NULL;
329 if (usersounds_dir == NULL)
330 usersounds_dir = getPath2(getUserGameDataDir(), SOUNDS_DIRECTORY);
332 return usersounds_dir;
335 static char *getUserMusicDir()
337 static char *usermusic_dir = NULL;
339 if (usermusic_dir == NULL)
340 usermusic_dir = getPath2(getUserGameDataDir(), MUSIC_DIRECTORY);
342 return usermusic_dir;
345 static char *getSetupArtworkDir(TreeInfo *ti)
347 static char *artwork_dir = NULL;
352 checked_free(artwork_dir);
354 artwork_dir = getPath2(ti->basepath, ti->fullpath);
359 char *setLevelArtworkDir(TreeInfo *ti)
361 char **artwork_path_ptr, **artwork_set_ptr;
362 TreeInfo *level_artwork;
364 if (ti == NULL || leveldir_current == NULL)
367 artwork_path_ptr = LEVELDIR_ARTWORK_PATH_PTR(leveldir_current, ti->type);
368 artwork_set_ptr = LEVELDIR_ARTWORK_SET_PTR( leveldir_current, ti->type);
370 checked_free(*artwork_path_ptr);
372 if ((level_artwork = getTreeInfoFromIdentifier(ti, *artwork_set_ptr)))
374 *artwork_path_ptr = getStringCopy(getSetupArtworkDir(level_artwork));
379 No (or non-existing) artwork configured in "levelinfo.conf". This would
380 normally result in using the artwork configured in the setup menu. But
381 if an artwork subdirectory exists (which might contain custom artwork
382 or an artwork configuration file), this level artwork must be treated
383 as relative to the default "classic" artwork, not to the artwork that
384 is currently configured in the setup menu.
386 Update: For "special" versions of R'n'D (like "R'n'D jue"), do not use
387 the "default" artwork (which would be "jue0" for "R'n'D jue"), but use
388 the real "classic" artwork from the original R'n'D (like "gfx_classic").
391 char *dir = getPath2(getCurrentLevelDir(), ARTWORK_DIRECTORY(ti->type));
393 checked_free(*artwork_set_ptr);
395 if (directoryExists(dir))
397 *artwork_path_ptr = getStringCopy(getClassicArtworkDir(ti->type));
398 *artwork_set_ptr = getStringCopy(getClassicArtworkSet(ti->type));
402 *artwork_path_ptr = getStringCopy(UNDEFINED_FILENAME);
403 *artwork_set_ptr = NULL;
409 return *artwork_set_ptr;
412 inline static char *getLevelArtworkSet(int type)
414 if (leveldir_current == NULL)
417 return LEVELDIR_ARTWORK_SET(leveldir_current, type);
420 inline static char *getLevelArtworkDir(int type)
422 if (leveldir_current == NULL)
423 return UNDEFINED_FILENAME;
425 return LEVELDIR_ARTWORK_PATH(leveldir_current, type);
428 char *getProgramMainDataPath(char *command_filename, char *base_path)
430 /* check if the program's main data base directory is configured */
431 if (!strEqual(base_path, "."))
434 /* if the program is configured to start from current directory (default),
435 determine program package directory from program binary (some versions
436 of KDE/Konqueror and Mac OS X (especially "Mavericks") apparently do not
437 set the current working directory to the program package directory) */
438 char *main_data_path = getBasePath(command_filename);
440 #if defined(PLATFORM_MACOSX)
441 if (strSuffix(main_data_path, MAC_APP_BINARY_SUBDIR))
443 char *main_data_path_old = main_data_path;
445 // cut relative path to Mac OS X application binary directory from path
446 main_data_path[strlen(main_data_path) -
447 strlen(MAC_APP_BINARY_SUBDIR)] = '\0';
449 // cut trailing path separator from path (but not if path is root directory)
450 if (strSuffix(main_data_path, "/") && !strEqual(main_data_path, "/"))
451 main_data_path[strlen(main_data_path) - 1] = '\0';
453 // replace empty path with current directory
454 if (strEqual(main_data_path, ""))
455 main_data_path = ".";
457 // add relative path to Mac OS X application resources directory to path
458 main_data_path = getPath2(main_data_path, MAC_APP_FILES_SUBDIR);
460 free(main_data_path_old);
464 return main_data_path;
467 char *getProgramConfigFilename(char *command_filename)
469 char *command_filename_1 = getStringCopy(command_filename);
471 // strip trailing executable suffix from command filename
472 if (strSuffix(command_filename_1, ".exe"))
473 command_filename_1[strlen(command_filename_1) - 4] = '\0';
475 char *ro_base_path = getProgramMainDataPath(command_filename, RO_BASE_PATH);
476 char *conf_directory = getPath2(ro_base_path, CONF_DIRECTORY);
478 char *command_basepath = getBasePath(command_filename);
479 char *command_basename = getBaseNameNoSuffix(command_filename);
480 char *command_filename_2 = getPath2(command_basepath, command_basename);
482 char *config_filename_1 = getStringCat2(command_filename_1, ".conf");
483 char *config_filename_2 = getStringCat2(command_filename_2, ".conf");
484 char *config_filename_3 = getPath2(conf_directory, SETUP_FILENAME);
486 // 1st try: look for config file that exactly matches the binary filename
487 if (fileExists(config_filename_1))
488 return config_filename_1;
490 // 2nd try: look for config file that matches binary filename without suffix
491 if (fileExists(config_filename_2))
492 return config_filename_2;
494 // 3rd try: return setup config filename in global program config directory
495 return config_filename_3;
498 char *getTapeFilename(int nr)
500 static char *filename = NULL;
501 char basename[MAX_FILENAME_LEN];
503 checked_free(filename);
505 sprintf(basename, "%03d.%s", nr, TAPEFILE_EXTENSION);
506 filename = getPath2(getTapeDir(leveldir_current->subdir), basename);
511 char *getSolutionTapeFilename(int nr)
513 static char *filename = NULL;
514 char basename[MAX_FILENAME_LEN];
516 checked_free(filename);
518 sprintf(basename, "%03d.%s", nr, TAPEFILE_EXTENSION);
519 filename = getPath2(getSolutionTapeDir(), basename);
521 if (!fileExists(filename))
523 static char *filename_sln = NULL;
525 checked_free(filename_sln);
527 sprintf(basename, "%03d.sln", nr);
528 filename_sln = getPath2(getSolutionTapeDir(), basename);
530 if (fileExists(filename_sln))
537 char *getScoreFilename(int nr)
539 static char *filename = NULL;
540 char basename[MAX_FILENAME_LEN];
542 checked_free(filename);
544 sprintf(basename, "%03d.%s", nr, SCOREFILE_EXTENSION);
545 filename = getPath2(getScoreDir(leveldir_current->subdir), basename);
550 char *getSetupFilename()
552 static char *filename = NULL;
554 checked_free(filename);
556 filename = getPath2(getSetupDir(), SETUP_FILENAME);
561 char *getDefaultSetupFilename()
563 return program.config_filename;
566 char *getEditorSetupFilename()
568 static char *filename = NULL;
570 checked_free(filename);
571 filename = getPath2(getCurrentLevelDir(), EDITORSETUP_FILENAME);
573 if (fileExists(filename))
576 checked_free(filename);
577 filename = getPath2(getSetupDir(), EDITORSETUP_FILENAME);
582 char *getHelpAnimFilename()
584 static char *filename = NULL;
586 checked_free(filename);
588 filename = getPath2(getCurrentLevelDir(), HELPANIM_FILENAME);
593 char *getHelpTextFilename()
595 static char *filename = NULL;
597 checked_free(filename);
599 filename = getPath2(getCurrentLevelDir(), HELPTEXT_FILENAME);
604 char *getLevelSetInfoFilename()
606 static char *filename = NULL;
621 for (i = 0; basenames[i] != NULL; i++)
623 checked_free(filename);
624 filename = getPath2(getCurrentLevelDir(), basenames[i]);
626 if (fileExists(filename))
633 char *getLevelSetTitleMessageBasename(int nr, boolean initial)
635 static char basename[32];
637 sprintf(basename, "%s_%d.txt",
638 (initial ? "titlemessage_initial" : "titlemessage"), nr + 1);
643 char *getLevelSetTitleMessageFilename(int nr, boolean initial)
645 static char *filename = NULL;
647 boolean skip_setup_artwork = FALSE;
649 checked_free(filename);
651 basename = getLevelSetTitleMessageBasename(nr, initial);
653 if (!gfx.override_level_graphics)
655 /* 1st try: look for special artwork in current level series directory */
656 filename = getPath3(getCurrentLevelDir(), GRAPHICS_DIRECTORY, basename);
657 if (fileExists(filename))
662 /* 2nd try: look for message file in current level set directory */
663 filename = getPath2(getCurrentLevelDir(), basename);
664 if (fileExists(filename))
669 /* check if there is special artwork configured in level series config */
670 if (getLevelArtworkSet(ARTWORK_TYPE_GRAPHICS) != NULL)
672 /* 3rd try: look for special artwork configured in level series config */
673 filename = getPath2(getLevelArtworkDir(ARTWORK_TYPE_GRAPHICS), basename);
674 if (fileExists(filename))
679 /* take missing artwork configured in level set config from default */
680 skip_setup_artwork = TRUE;
684 if (!skip_setup_artwork)
686 /* 4th try: look for special artwork in configured artwork directory */
687 filename = getPath2(getSetupArtworkDir(artwork.gfx_current), basename);
688 if (fileExists(filename))
694 /* 5th try: look for default artwork in new default artwork directory */
695 filename = getPath2(getDefaultGraphicsDir(GFX_DEFAULT_SUBDIR), basename);
696 if (fileExists(filename))
701 /* 6th try: look for default artwork in old default artwork directory */
702 filename = getPath2(options.graphics_directory, basename);
703 if (fileExists(filename))
706 return NULL; /* cannot find specified artwork file anywhere */
709 static char *getCorrectedArtworkBasename(char *basename)
714 char *getCustomImageFilename(char *basename)
716 static char *filename = NULL;
717 boolean skip_setup_artwork = FALSE;
719 checked_free(filename);
721 basename = getCorrectedArtworkBasename(basename);
723 if (!gfx.override_level_graphics)
725 /* 1st try: look for special artwork in current level series directory */
726 filename = getImg3(getCurrentLevelDir(), GRAPHICS_DIRECTORY, basename);
727 if (fileExists(filename))
732 /* check if there is special artwork configured in level series config */
733 if (getLevelArtworkSet(ARTWORK_TYPE_GRAPHICS) != NULL)
735 /* 2nd try: look for special artwork configured in level series config */
736 filename = getImg2(getLevelArtworkDir(ARTWORK_TYPE_GRAPHICS), basename);
737 if (fileExists(filename))
742 /* take missing artwork configured in level set config from default */
743 skip_setup_artwork = TRUE;
747 if (!skip_setup_artwork)
749 /* 3rd try: look for special artwork in configured artwork directory */
750 filename = getImg2(getSetupArtworkDir(artwork.gfx_current), basename);
751 if (fileExists(filename))
757 /* 4th try: look for default artwork in new default artwork directory */
758 filename = getImg2(getDefaultGraphicsDir(GFX_DEFAULT_SUBDIR), basename);
759 if (fileExists(filename))
764 /* 5th try: look for default artwork in old default artwork directory */
765 filename = getImg2(options.graphics_directory, basename);
766 if (fileExists(filename))
769 if (!strEqual(GFX_FALLBACK_FILENAME, UNDEFINED_FILENAME))
774 Error(ERR_WARN, "cannot find artwork file '%s' (using fallback)",
777 /* 6th try: look for fallback artwork in old default artwork directory */
778 /* (needed to prevent errors when trying to access unused artwork files) */
779 filename = getImg2(options.graphics_directory, GFX_FALLBACK_FILENAME);
780 if (fileExists(filename))
784 return NULL; /* cannot find specified artwork file anywhere */
787 char *getCustomSoundFilename(char *basename)
789 static char *filename = NULL;
790 boolean skip_setup_artwork = FALSE;
792 checked_free(filename);
794 basename = getCorrectedArtworkBasename(basename);
796 if (!gfx.override_level_sounds)
798 /* 1st try: look for special artwork in current level series directory */
799 filename = getPath3(getCurrentLevelDir(), SOUNDS_DIRECTORY, basename);
800 if (fileExists(filename))
805 /* check if there is special artwork configured in level series config */
806 if (getLevelArtworkSet(ARTWORK_TYPE_SOUNDS) != NULL)
808 /* 2nd try: look for special artwork configured in level series config */
809 filename = getPath2(getLevelArtworkDir(TREE_TYPE_SOUNDS_DIR), basename);
810 if (fileExists(filename))
815 /* take missing artwork configured in level set config from default */
816 skip_setup_artwork = TRUE;
820 if (!skip_setup_artwork)
822 /* 3rd try: look for special artwork in configured artwork directory */
823 filename = getPath2(getSetupArtworkDir(artwork.snd_current), basename);
824 if (fileExists(filename))
830 /* 4th try: look for default artwork in new default artwork directory */
831 filename = getPath2(getDefaultSoundsDir(SND_DEFAULT_SUBDIR), basename);
832 if (fileExists(filename))
837 /* 5th try: look for default artwork in old default artwork directory */
838 filename = getPath2(options.sounds_directory, basename);
839 if (fileExists(filename))
842 if (!strEqual(SND_FALLBACK_FILENAME, UNDEFINED_FILENAME))
847 Error(ERR_WARN, "cannot find artwork file '%s' (using fallback)",
850 /* 6th try: look for fallback artwork in old default artwork directory */
851 /* (needed to prevent errors when trying to access unused artwork files) */
852 filename = getPath2(options.sounds_directory, SND_FALLBACK_FILENAME);
853 if (fileExists(filename))
857 return NULL; /* cannot find specified artwork file anywhere */
860 char *getCustomMusicFilename(char *basename)
862 static char *filename = NULL;
863 boolean skip_setup_artwork = FALSE;
865 checked_free(filename);
867 basename = getCorrectedArtworkBasename(basename);
869 if (!gfx.override_level_music)
871 /* 1st try: look for special artwork in current level series directory */
872 filename = getPath3(getCurrentLevelDir(), MUSIC_DIRECTORY, basename);
873 if (fileExists(filename))
878 /* check if there is special artwork configured in level series config */
879 if (getLevelArtworkSet(ARTWORK_TYPE_MUSIC) != NULL)
881 /* 2nd try: look for special artwork configured in level series config */
882 filename = getPath2(getLevelArtworkDir(TREE_TYPE_MUSIC_DIR), basename);
883 if (fileExists(filename))
888 /* take missing artwork configured in level set config from default */
889 skip_setup_artwork = TRUE;
893 if (!skip_setup_artwork)
895 /* 3rd try: look for special artwork in configured artwork directory */
896 filename = getPath2(getSetupArtworkDir(artwork.mus_current), basename);
897 if (fileExists(filename))
903 /* 4th try: look for default artwork in new default artwork directory */
904 filename = getPath2(getDefaultMusicDir(MUS_DEFAULT_SUBDIR), basename);
905 if (fileExists(filename))
910 /* 5th try: look for default artwork in old default artwork directory */
911 filename = getPath2(options.music_directory, basename);
912 if (fileExists(filename))
915 if (!strEqual(MUS_FALLBACK_FILENAME, UNDEFINED_FILENAME))
920 Error(ERR_WARN, "cannot find artwork file '%s' (using fallback)",
923 /* 6th try: look for fallback artwork in old default artwork directory */
924 /* (needed to prevent errors when trying to access unused artwork files) */
925 filename = getPath2(options.music_directory, MUS_FALLBACK_FILENAME);
926 if (fileExists(filename))
930 return NULL; /* cannot find specified artwork file anywhere */
933 char *getCustomArtworkFilename(char *basename, int type)
935 if (type == ARTWORK_TYPE_GRAPHICS)
936 return getCustomImageFilename(basename);
937 else if (type == ARTWORK_TYPE_SOUNDS)
938 return getCustomSoundFilename(basename);
939 else if (type == ARTWORK_TYPE_MUSIC)
940 return getCustomMusicFilename(basename);
942 return UNDEFINED_FILENAME;
945 char *getCustomArtworkConfigFilename(int type)
947 return getCustomArtworkFilename(ARTWORKINFO_FILENAME(type), type);
950 char *getCustomArtworkLevelConfigFilename(int type)
952 static char *filename = NULL;
954 checked_free(filename);
956 filename = getPath2(getLevelArtworkDir(type), ARTWORKINFO_FILENAME(type));
961 char *getCustomMusicDirectory(void)
963 static char *directory = NULL;
964 boolean skip_setup_artwork = FALSE;
966 checked_free(directory);
968 if (!gfx.override_level_music)
970 /* 1st try: look for special artwork in current level series directory */
971 directory = getPath2(getCurrentLevelDir(), MUSIC_DIRECTORY);
972 if (directoryExists(directory))
977 /* check if there is special artwork configured in level series config */
978 if (getLevelArtworkSet(ARTWORK_TYPE_MUSIC) != NULL)
980 /* 2nd try: look for special artwork configured in level series config */
981 directory = getStringCopy(getLevelArtworkDir(TREE_TYPE_MUSIC_DIR));
982 if (directoryExists(directory))
987 /* take missing artwork configured in level set config from default */
988 skip_setup_artwork = TRUE;
992 if (!skip_setup_artwork)
994 /* 3rd try: look for special artwork in configured artwork directory */
995 directory = getStringCopy(getSetupArtworkDir(artwork.mus_current));
996 if (directoryExists(directory))
1002 /* 4th try: look for default artwork in new default artwork directory */
1003 directory = getStringCopy(getDefaultMusicDir(MUS_DEFAULT_SUBDIR));
1004 if (directoryExists(directory))
1009 /* 5th try: look for default artwork in old default artwork directory */
1010 directory = getStringCopy(options.music_directory);
1011 if (directoryExists(directory))
1014 return NULL; /* cannot find specified artwork file anywhere */
1017 void InitTapeDirectory(char *level_subdir)
1019 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
1020 createDirectory(getTapeDir(NULL), "main tape", PERMS_PRIVATE);
1021 createDirectory(getTapeDir(level_subdir), "level tape", PERMS_PRIVATE);
1024 void InitScoreDirectory(char *level_subdir)
1026 int permissions = (program.global_scores ? PERMS_PUBLIC : PERMS_PRIVATE);
1028 if (program.global_scores)
1029 createDirectory(getCommonDataDir(), "common data", permissions);
1031 createDirectory(getUserGameDataDir(), "user data", permissions);
1033 createDirectory(getScoreDir(NULL), "main score", permissions);
1034 createDirectory(getScoreDir(level_subdir), "level score", permissions);
1037 static void SaveUserLevelInfo();
1039 void InitUserLevelDirectory(char *level_subdir)
1041 if (!directoryExists(getUserLevelDir(level_subdir)))
1043 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
1044 createDirectory(getUserLevelDir(NULL), "main user level", PERMS_PRIVATE);
1045 createDirectory(getUserLevelDir(level_subdir), "user level", PERMS_PRIVATE);
1047 SaveUserLevelInfo();
1051 void InitLevelSetupDirectory(char *level_subdir)
1053 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
1054 createDirectory(getLevelSetupDir(NULL), "main level setup", PERMS_PRIVATE);
1055 createDirectory(getLevelSetupDir(level_subdir), "level setup", PERMS_PRIVATE);
1058 void InitCacheDirectory()
1060 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
1061 createDirectory(getCacheDir(), "cache data", PERMS_PRIVATE);
1065 /* ------------------------------------------------------------------------- */
1066 /* some functions to handle lists of level and artwork directories */
1067 /* ------------------------------------------------------------------------- */
1069 TreeInfo *newTreeInfo()
1071 return checked_calloc(sizeof(TreeInfo));
1074 TreeInfo *newTreeInfo_setDefaults(int type)
1076 TreeInfo *ti = newTreeInfo();
1078 setTreeInfoToDefaults(ti, type);
1083 void pushTreeInfo(TreeInfo **node_first, TreeInfo *node_new)
1085 node_new->next = *node_first;
1086 *node_first = node_new;
1089 int numTreeInfo(TreeInfo *node)
1102 boolean validLevelSeries(TreeInfo *node)
1104 return (node != NULL && !node->node_group && !node->parent_link);
1107 TreeInfo *getFirstValidTreeInfoEntry(TreeInfo *node)
1112 if (node->node_group) /* enter level group (step down into tree) */
1113 return getFirstValidTreeInfoEntry(node->node_group);
1114 else if (node->parent_link) /* skip start entry of level group */
1116 if (node->next) /* get first real level series entry */
1117 return getFirstValidTreeInfoEntry(node->next);
1118 else /* leave empty level group and go on */
1119 return getFirstValidTreeInfoEntry(node->node_parent->next);
1121 else /* this seems to be a regular level series */
1125 TreeInfo *getTreeInfoFirstGroupEntry(TreeInfo *node)
1130 if (node->node_parent == NULL) /* top level group */
1131 return *node->node_top;
1132 else /* sub level group */
1133 return node->node_parent->node_group;
1136 int numTreeInfoInGroup(TreeInfo *node)
1138 return numTreeInfo(getTreeInfoFirstGroupEntry(node));
1141 int posTreeInfo(TreeInfo *node)
1143 TreeInfo *node_cmp = getTreeInfoFirstGroupEntry(node);
1148 if (node_cmp == node)
1152 node_cmp = node_cmp->next;
1158 TreeInfo *getTreeInfoFromPos(TreeInfo *node, int pos)
1160 TreeInfo *node_default = node;
1172 return node_default;
1175 TreeInfo *getTreeInfoFromIdentifier(TreeInfo *node, char *identifier)
1177 if (identifier == NULL)
1182 if (node->node_group)
1184 TreeInfo *node_group;
1186 node_group = getTreeInfoFromIdentifier(node->node_group, identifier);
1191 else if (!node->parent_link)
1193 if (strEqual(identifier, node->identifier))
1203 TreeInfo *cloneTreeNode(TreeInfo **node_top, TreeInfo *node_parent,
1204 TreeInfo *node, boolean skip_sets_without_levels)
1211 if (!node->parent_link && !node->level_group &&
1212 skip_sets_without_levels && node->levels == 0)
1213 return cloneTreeNode(node_top, node_parent, node->next,
1214 skip_sets_without_levels);
1216 node_new = getTreeInfoCopy(node); /* copy complete node */
1218 node_new->node_top = node_top; /* correct top node link */
1219 node_new->node_parent = node_parent; /* correct parent node link */
1221 if (node->level_group)
1222 node_new->node_group = cloneTreeNode(node_top, node_new, node->node_group,
1223 skip_sets_without_levels);
1225 node_new->next = cloneTreeNode(node_top, node_parent, node->next,
1226 skip_sets_without_levels);
1231 void cloneTree(TreeInfo **ti_new, TreeInfo *ti, boolean skip_empty_sets)
1233 TreeInfo *ti_cloned = cloneTreeNode(ti_new, NULL, ti, skip_empty_sets);
1235 *ti_new = ti_cloned;
1238 static boolean adjustTreeGraphicsForEMC(TreeInfo *node)
1240 boolean settings_changed = FALSE;
1244 if (node->graphics_set_ecs && !setup.prefer_aga_graphics &&
1245 !strEqual(node->graphics_set, node->graphics_set_ecs))
1247 setString(&node->graphics_set, node->graphics_set_ecs);
1248 settings_changed = TRUE;
1250 else if (node->graphics_set_aga && setup.prefer_aga_graphics &&
1251 !strEqual(node->graphics_set, node->graphics_set_aga))
1253 setString(&node->graphics_set, node->graphics_set_aga);
1254 settings_changed = TRUE;
1257 if (node->node_group != NULL)
1258 settings_changed |= adjustTreeGraphicsForEMC(node->node_group);
1263 return settings_changed;
1266 void dumpTreeInfo(TreeInfo *node, int depth)
1270 printf("Dumping TreeInfo:\n");
1274 for (i = 0; i < (depth + 1) * 3; i++)
1277 printf("'%s' / '%s'\n", node->identifier, node->name);
1280 // use for dumping artwork info tree
1281 printf("subdir == '%s' ['%s', '%s'] [%d])\n",
1282 node->subdir, node->fullpath, node->basepath, node->in_user_dir);
1285 if (node->node_group != NULL)
1286 dumpTreeInfo(node->node_group, depth + 1);
1292 void sortTreeInfoBySortFunction(TreeInfo **node_first,
1293 int (*compare_function)(const void *,
1296 int num_nodes = numTreeInfo(*node_first);
1297 TreeInfo **sort_array;
1298 TreeInfo *node = *node_first;
1304 /* allocate array for sorting structure pointers */
1305 sort_array = checked_calloc(num_nodes * sizeof(TreeInfo *));
1307 /* writing structure pointers to sorting array */
1308 while (i < num_nodes && node) /* double boundary check... */
1310 sort_array[i] = node;
1316 /* sorting the structure pointers in the sorting array */
1317 qsort(sort_array, num_nodes, sizeof(TreeInfo *),
1320 /* update the linkage of list elements with the sorted node array */
1321 for (i = 0; i < num_nodes - 1; i++)
1322 sort_array[i]->next = sort_array[i + 1];
1323 sort_array[num_nodes - 1]->next = NULL;
1325 /* update the linkage of the main list anchor pointer */
1326 *node_first = sort_array[0];
1330 /* now recursively sort the level group structures */
1334 if (node->node_group != NULL)
1335 sortTreeInfoBySortFunction(&node->node_group, compare_function);
1341 void sortTreeInfo(TreeInfo **node_first)
1343 sortTreeInfoBySortFunction(node_first, compareTreeInfoEntries);
1347 /* ========================================================================= */
1348 /* some stuff from "files.c" */
1349 /* ========================================================================= */
1351 #if defined(PLATFORM_WIN32)
1353 #define S_IRGRP S_IRUSR
1356 #define S_IROTH S_IRUSR
1359 #define S_IWGRP S_IWUSR
1362 #define S_IWOTH S_IWUSR
1365 #define S_IXGRP S_IXUSR
1368 #define S_IXOTH S_IXUSR
1371 #define S_IRWXG (S_IRGRP | S_IWGRP | S_IXGRP)
1376 #endif /* PLATFORM_WIN32 */
1378 /* file permissions for newly written files */
1379 #define MODE_R_ALL (S_IRUSR | S_IRGRP | S_IROTH)
1380 #define MODE_W_ALL (S_IWUSR | S_IWGRP | S_IWOTH)
1381 #define MODE_X_ALL (S_IXUSR | S_IXGRP | S_IXOTH)
1383 #define MODE_W_PRIVATE (S_IWUSR)
1384 #define MODE_W_PUBLIC_FILE (S_IWUSR | S_IWGRP)
1385 #define MODE_W_PUBLIC_DIR (S_IWUSR | S_IWGRP | S_ISGID)
1387 #define DIR_PERMS_PRIVATE (MODE_R_ALL | MODE_X_ALL | MODE_W_PRIVATE)
1388 #define DIR_PERMS_PUBLIC (MODE_R_ALL | MODE_X_ALL | MODE_W_PUBLIC_DIR)
1389 #define DIR_PERMS_PUBLIC_ALL (MODE_R_ALL | MODE_X_ALL | MODE_W_ALL)
1391 #define FILE_PERMS_PRIVATE (MODE_R_ALL | MODE_W_PRIVATE)
1392 #define FILE_PERMS_PUBLIC (MODE_R_ALL | MODE_W_PUBLIC_FILE)
1393 #define FILE_PERMS_PUBLIC_ALL (MODE_R_ALL | MODE_W_ALL)
1398 static char *dir = NULL;
1400 #if defined(PLATFORM_WIN32)
1403 dir = checked_malloc(MAX_PATH + 1);
1405 if (!SUCCEEDED(SHGetFolderPath(NULL, CSIDL_PERSONAL, NULL, 0, dir)))
1408 #elif defined(PLATFORM_UNIX)
1411 if ((dir = getenv("HOME")) == NULL)
1415 if ((pwd = getpwuid(getuid())) != NULL)
1416 dir = getStringCopy(pwd->pw_dir);
1428 char *getCommonDataDir(void)
1430 static char *common_data_dir = NULL;
1432 #if defined(PLATFORM_WIN32)
1433 if (common_data_dir == NULL)
1435 char *dir = checked_malloc(MAX_PATH + 1);
1437 if (SUCCEEDED(SHGetFolderPath(NULL, CSIDL_COMMON_DOCUMENTS, NULL, 0, dir))
1438 && !strEqual(dir, "")) /* empty for Windows 95/98 */
1439 common_data_dir = getPath2(dir, program.userdata_subdir);
1441 common_data_dir = options.rw_base_directory;
1444 if (common_data_dir == NULL)
1445 common_data_dir = options.rw_base_directory;
1448 return common_data_dir;
1451 char *getPersonalDataDir(void)
1453 static char *personal_data_dir = NULL;
1455 #if defined(PLATFORM_MACOSX)
1456 if (personal_data_dir == NULL)
1457 personal_data_dir = getPath2(getHomeDir(), "Documents");
1459 if (personal_data_dir == NULL)
1460 personal_data_dir = getHomeDir();
1463 return personal_data_dir;
1466 char *getUserGameDataDir(void)
1468 static char *user_game_data_dir = NULL;
1470 #if defined(PLATFORM_ANDROID)
1471 if (user_game_data_dir == NULL)
1472 user_game_data_dir = (char *)(SDL_AndroidGetExternalStorageState() &
1473 SDL_ANDROID_EXTERNAL_STORAGE_WRITE ?
1474 SDL_AndroidGetExternalStoragePath() :
1475 SDL_AndroidGetInternalStoragePath());
1477 if (user_game_data_dir == NULL)
1478 user_game_data_dir = getPath2(getPersonalDataDir(),
1479 program.userdata_subdir);
1482 return user_game_data_dir;
1487 return getUserGameDataDir();
1490 static mode_t posix_umask(mode_t mask)
1492 #if defined(PLATFORM_UNIX)
1499 static int posix_mkdir(const char *pathname, mode_t mode)
1501 #if defined(PLATFORM_WIN32)
1502 return mkdir(pathname);
1504 return mkdir(pathname, mode);
1508 static boolean posix_process_running_setgid()
1510 #if defined(PLATFORM_UNIX)
1511 return (getgid() != getegid());
1517 void createDirectory(char *dir, char *text, int permission_class)
1519 if (directoryExists(dir))
1522 /* leave "other" permissions in umask untouched, but ensure group parts
1523 of USERDATA_DIR_MODE are not masked */
1524 mode_t dir_mode = (permission_class == PERMS_PRIVATE ?
1525 DIR_PERMS_PRIVATE : DIR_PERMS_PUBLIC);
1526 mode_t last_umask = posix_umask(0);
1527 mode_t group_umask = ~(dir_mode & S_IRWXG);
1528 int running_setgid = posix_process_running_setgid();
1530 if (permission_class == PERMS_PUBLIC)
1532 /* if we're setgid, protect files against "other" */
1533 /* else keep umask(0) to make the dir world-writable */
1536 posix_umask(last_umask & group_umask);
1538 dir_mode = DIR_PERMS_PUBLIC_ALL;
1541 if (posix_mkdir(dir, dir_mode) != 0)
1542 Error(ERR_WARN, "cannot create %s directory '%s': %s",
1543 text, dir, strerror(errno));
1545 if (permission_class == PERMS_PUBLIC && !running_setgid)
1546 chmod(dir, dir_mode);
1548 posix_umask(last_umask); /* restore previous umask */
1551 void InitUserDataDirectory()
1553 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
1556 void SetFilePermissions(char *filename, int permission_class)
1558 int running_setgid = posix_process_running_setgid();
1559 int perms = (permission_class == PERMS_PRIVATE ?
1560 FILE_PERMS_PRIVATE : FILE_PERMS_PUBLIC);
1562 if (permission_class == PERMS_PUBLIC && !running_setgid)
1563 perms = FILE_PERMS_PUBLIC_ALL;
1565 chmod(filename, perms);
1568 char *getCookie(char *file_type)
1570 static char cookie[MAX_COOKIE_LEN + 1];
1572 if (strlen(program.cookie_prefix) + 1 +
1573 strlen(file_type) + strlen("_FILE_VERSION_x.x") > MAX_COOKIE_LEN)
1574 return "[COOKIE ERROR]"; /* should never happen */
1576 sprintf(cookie, "%s_%s_FILE_VERSION_%d.%d",
1577 program.cookie_prefix, file_type,
1578 program.version_super, program.version_major);
1583 void fprintFileHeader(FILE *file, char *basename)
1585 char *prefix = "# ";
1588 fprintf_line_with_prefix(file, prefix, sep1, 77);
1589 fprintf(file, "%s%s\n", prefix, basename);
1590 fprintf_line_with_prefix(file, prefix, sep1, 77);
1591 fprintf(file, "\n");
1594 int getFileVersionFromCookieString(const char *cookie)
1596 const char *ptr_cookie1, *ptr_cookie2;
1597 const char *pattern1 = "_FILE_VERSION_";
1598 const char *pattern2 = "?.?";
1599 const int len_cookie = strlen(cookie);
1600 const int len_pattern1 = strlen(pattern1);
1601 const int len_pattern2 = strlen(pattern2);
1602 const int len_pattern = len_pattern1 + len_pattern2;
1603 int version_super, version_major;
1605 if (len_cookie <= len_pattern)
1608 ptr_cookie1 = &cookie[len_cookie - len_pattern];
1609 ptr_cookie2 = &cookie[len_cookie - len_pattern2];
1611 if (strncmp(ptr_cookie1, pattern1, len_pattern1) != 0)
1614 if (ptr_cookie2[0] < '0' || ptr_cookie2[0] > '9' ||
1615 ptr_cookie2[1] != '.' ||
1616 ptr_cookie2[2] < '0' || ptr_cookie2[2] > '9')
1619 version_super = ptr_cookie2[0] - '0';
1620 version_major = ptr_cookie2[2] - '0';
1622 return VERSION_IDENT(version_super, version_major, 0, 0);
1625 boolean checkCookieString(const char *cookie, const char *template)
1627 const char *pattern = "_FILE_VERSION_?.?";
1628 const int len_cookie = strlen(cookie);
1629 const int len_template = strlen(template);
1630 const int len_pattern = strlen(pattern);
1632 if (len_cookie != len_template)
1635 if (strncmp(cookie, template, len_cookie - len_pattern) != 0)
1642 /* ------------------------------------------------------------------------- */
1643 /* setup file list and hash handling functions */
1644 /* ------------------------------------------------------------------------- */
1646 char *getFormattedSetupEntry(char *token, char *value)
1649 static char entry[MAX_LINE_LEN];
1651 /* if value is an empty string, just return token without value */
1655 /* start with the token and some spaces to format output line */
1656 sprintf(entry, "%s:", token);
1657 for (i = strlen(entry); i < token_value_position; i++)
1660 /* continue with the token's value */
1661 strcat(entry, value);
1666 SetupFileList *newSetupFileList(char *token, char *value)
1668 SetupFileList *new = checked_malloc(sizeof(SetupFileList));
1670 new->token = getStringCopy(token);
1671 new->value = getStringCopy(value);
1678 void freeSetupFileList(SetupFileList *list)
1683 checked_free(list->token);
1684 checked_free(list->value);
1687 freeSetupFileList(list->next);
1692 char *getListEntry(SetupFileList *list, char *token)
1697 if (strEqual(list->token, token))
1700 return getListEntry(list->next, token);
1703 SetupFileList *setListEntry(SetupFileList *list, char *token, char *value)
1708 if (strEqual(list->token, token))
1710 checked_free(list->value);
1712 list->value = getStringCopy(value);
1716 else if (list->next == NULL)
1717 return (list->next = newSetupFileList(token, value));
1719 return setListEntry(list->next, token, value);
1722 SetupFileList *addListEntry(SetupFileList *list, char *token, char *value)
1727 if (list->next == NULL)
1728 return (list->next = newSetupFileList(token, value));
1730 return addListEntry(list->next, token, value);
1733 #if ENABLE_UNUSED_CODE
1735 static void printSetupFileList(SetupFileList *list)
1740 printf("token: '%s'\n", list->token);
1741 printf("value: '%s'\n", list->value);
1743 printSetupFileList(list->next);
1749 DEFINE_HASHTABLE_INSERT(insert_hash_entry, char, char);
1750 DEFINE_HASHTABLE_SEARCH(search_hash_entry, char, char);
1751 DEFINE_HASHTABLE_CHANGE(change_hash_entry, char, char);
1752 DEFINE_HASHTABLE_REMOVE(remove_hash_entry, char, char);
1754 #define insert_hash_entry hashtable_insert
1755 #define search_hash_entry hashtable_search
1756 #define change_hash_entry hashtable_change
1757 #define remove_hash_entry hashtable_remove
1760 unsigned int get_hash_from_key(void *key)
1765 This algorithm (k=33) was first reported by Dan Bernstein many years ago in
1766 'comp.lang.c'. Another version of this algorithm (now favored by Bernstein)
1767 uses XOR: hash(i) = hash(i - 1) * 33 ^ str[i]; the magic of number 33 (why
1768 it works better than many other constants, prime or not) has never been
1769 adequately explained.
1771 If you just want to have a good hash function, and cannot wait, djb2
1772 is one of the best string hash functions i know. It has excellent
1773 distribution and speed on many different sets of keys and table sizes.
1774 You are not likely to do better with one of the "well known" functions
1775 such as PJW, K&R, etc.
1777 Ozan (oz) Yigit [http://www.cs.yorku.ca/~oz/hash.html]
1780 char *str = (char *)key;
1781 unsigned int hash = 5381;
1784 while ((c = *str++))
1785 hash = ((hash << 5) + hash) + c; /* hash * 33 + c */
1790 static int keys_are_equal(void *key1, void *key2)
1792 return (strEqual((char *)key1, (char *)key2));
1795 SetupFileHash *newSetupFileHash()
1797 SetupFileHash *new_hash =
1798 create_hashtable(16, 0.75, get_hash_from_key, keys_are_equal);
1800 if (new_hash == NULL)
1801 Error(ERR_EXIT, "create_hashtable() failed -- out of memory");
1806 void freeSetupFileHash(SetupFileHash *hash)
1811 hashtable_destroy(hash, 1); /* 1 == also free values stored in hash */
1814 char *getHashEntry(SetupFileHash *hash, char *token)
1819 return search_hash_entry(hash, token);
1822 void setHashEntry(SetupFileHash *hash, char *token, char *value)
1829 value_copy = getStringCopy(value);
1831 /* change value; if it does not exist, insert it as new */
1832 if (!change_hash_entry(hash, token, value_copy))
1833 if (!insert_hash_entry(hash, getStringCopy(token), value_copy))
1834 Error(ERR_EXIT, "cannot insert into hash -- aborting");
1837 char *removeHashEntry(SetupFileHash *hash, char *token)
1842 return remove_hash_entry(hash, token);
1845 #if ENABLE_UNUSED_CODE
1847 static void printSetupFileHash(SetupFileHash *hash)
1849 BEGIN_HASH_ITERATION(hash, itr)
1851 printf("token: '%s'\n", HASH_ITERATION_TOKEN(itr));
1852 printf("value: '%s'\n", HASH_ITERATION_VALUE(itr));
1854 END_HASH_ITERATION(hash, itr)
1859 #define ALLOW_TOKEN_VALUE_SEPARATOR_BEING_WHITESPACE 1
1860 #define CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING 0
1861 #define CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH 0
1863 static boolean token_value_separator_found = FALSE;
1864 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
1865 static boolean token_value_separator_warning = FALSE;
1867 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
1868 static boolean token_already_exists_warning = FALSE;
1871 static boolean getTokenValueFromSetupLineExt(char *line,
1872 char **token_ptr, char **value_ptr,
1873 char *filename, char *line_raw,
1875 boolean separator_required)
1877 static char line_copy[MAX_LINE_LEN + 1], line_raw_copy[MAX_LINE_LEN + 1];
1878 char *token, *value, *line_ptr;
1880 /* when externally invoked via ReadTokenValueFromLine(), copy line buffers */
1881 if (line_raw == NULL)
1883 strncpy(line_copy, line, MAX_LINE_LEN);
1884 line_copy[MAX_LINE_LEN] = '\0';
1887 strcpy(line_raw_copy, line_copy);
1888 line_raw = line_raw_copy;
1891 /* cut trailing comment from input line */
1892 for (line_ptr = line; *line_ptr; line_ptr++)
1894 if (*line_ptr == '#')
1901 /* cut trailing whitespaces from input line */
1902 for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
1903 if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
1906 /* ignore empty lines */
1910 /* cut leading whitespaces from token */
1911 for (token = line; *token; token++)
1912 if (*token != ' ' && *token != '\t')
1915 /* start with empty value as reliable default */
1918 token_value_separator_found = FALSE;
1920 /* find end of token to determine start of value */
1921 for (line_ptr = token; *line_ptr; line_ptr++)
1923 /* first look for an explicit token/value separator, like ':' or '=' */
1924 if (*line_ptr == ':' || *line_ptr == '=')
1926 *line_ptr = '\0'; /* terminate token string */
1927 value = line_ptr + 1; /* set beginning of value */
1929 token_value_separator_found = TRUE;
1935 #if ALLOW_TOKEN_VALUE_SEPARATOR_BEING_WHITESPACE
1936 /* fallback: if no token/value separator found, also allow whitespaces */
1937 if (!token_value_separator_found && !separator_required)
1939 for (line_ptr = token; *line_ptr; line_ptr++)
1941 if (*line_ptr == ' ' || *line_ptr == '\t')
1943 *line_ptr = '\0'; /* terminate token string */
1944 value = line_ptr + 1; /* set beginning of value */
1946 token_value_separator_found = TRUE;
1952 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
1953 if (token_value_separator_found)
1955 if (!token_value_separator_warning)
1957 Error(ERR_INFO_LINE, "-");
1959 if (filename != NULL)
1961 Error(ERR_WARN, "missing token/value separator(s) in config file:");
1962 Error(ERR_INFO, "- config file: '%s'", filename);
1966 Error(ERR_WARN, "missing token/value separator(s):");
1969 token_value_separator_warning = TRUE;
1972 if (filename != NULL)
1973 Error(ERR_INFO, "- line %d: '%s'", line_nr, line_raw);
1975 Error(ERR_INFO, "- line: '%s'", line_raw);
1981 /* cut trailing whitespaces from token */
1982 for (line_ptr = &token[strlen(token)]; line_ptr >= token; line_ptr--)
1983 if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
1986 /* cut leading whitespaces from value */
1987 for (; *value; value++)
1988 if (*value != ' ' && *value != '\t')
1997 boolean getTokenValueFromSetupLine(char *line, char **token, char **value)
1999 /* while the internal (old) interface does not require a token/value
2000 separator (for downwards compatibility with existing files which
2001 don't use them), it is mandatory for the external (new) interface */
2003 return getTokenValueFromSetupLineExt(line, token, value, NULL, NULL, 0, TRUE);
2006 static boolean loadSetupFileData(void *setup_file_data, char *filename,
2007 boolean top_recursion_level, boolean is_hash)
2009 static SetupFileHash *include_filename_hash = NULL;
2010 char line[MAX_LINE_LEN], line_raw[MAX_LINE_LEN], previous_line[MAX_LINE_LEN];
2011 char *token, *value, *line_ptr;
2012 void *insert_ptr = NULL;
2013 boolean read_continued_line = FALSE;
2015 int line_nr = 0, token_count = 0, include_count = 0;
2017 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2018 token_value_separator_warning = FALSE;
2021 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2022 token_already_exists_warning = FALSE;
2025 if (!(file = openFile(filename, MODE_READ)))
2027 Error(ERR_DEBUG, "cannot open configuration file '%s'", filename);
2032 /* use "insert pointer" to store list end for constant insertion complexity */
2034 insert_ptr = setup_file_data;
2036 /* on top invocation, create hash to mark included files (to prevent loops) */
2037 if (top_recursion_level)
2038 include_filename_hash = newSetupFileHash();
2040 /* mark this file as already included (to prevent including it again) */
2041 setHashEntry(include_filename_hash, getBaseNamePtr(filename), "true");
2043 while (!checkEndOfFile(file))
2045 /* read next line of input file */
2046 if (!getStringFromFile(file, line, MAX_LINE_LEN))
2049 /* check if line was completely read and is terminated by line break */
2050 if (strlen(line) > 0 && line[strlen(line) - 1] == '\n')
2053 /* cut trailing line break (this can be newline and/or carriage return) */
2054 for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
2055 if ((*line_ptr == '\n' || *line_ptr == '\r') && *(line_ptr + 1) == '\0')
2058 /* copy raw input line for later use (mainly debugging output) */
2059 strcpy(line_raw, line);
2061 if (read_continued_line)
2063 /* append new line to existing line, if there is enough space */
2064 if (strlen(previous_line) + strlen(line_ptr) < MAX_LINE_LEN)
2065 strcat(previous_line, line_ptr);
2067 strcpy(line, previous_line); /* copy storage buffer to line */
2069 read_continued_line = FALSE;
2072 /* if the last character is '\', continue at next line */
2073 if (strlen(line) > 0 && line[strlen(line) - 1] == '\\')
2075 line[strlen(line) - 1] = '\0'; /* cut off trailing backslash */
2076 strcpy(previous_line, line); /* copy line to storage buffer */
2078 read_continued_line = TRUE;
2083 if (!getTokenValueFromSetupLineExt(line, &token, &value, filename,
2084 line_raw, line_nr, FALSE))
2089 if (strEqual(token, "include"))
2091 if (getHashEntry(include_filename_hash, value) == NULL)
2093 char *basepath = getBasePath(filename);
2094 char *basename = getBaseName(value);
2095 char *filename_include = getPath2(basepath, basename);
2097 loadSetupFileData(setup_file_data, filename_include, FALSE, is_hash);
2101 free(filename_include);
2107 Error(ERR_WARN, "ignoring already processed file '%s'", value);
2114 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2116 getHashEntry((SetupFileHash *)setup_file_data, token);
2118 if (old_value != NULL)
2120 if (!token_already_exists_warning)
2122 Error(ERR_INFO_LINE, "-");
2123 Error(ERR_WARN, "duplicate token(s) found in config file:");
2124 Error(ERR_INFO, "- config file: '%s'", filename);
2126 token_already_exists_warning = TRUE;
2129 Error(ERR_INFO, "- token: '%s' (in line %d)", token, line_nr);
2130 Error(ERR_INFO, " old value: '%s'", old_value);
2131 Error(ERR_INFO, " new value: '%s'", value);
2135 setHashEntry((SetupFileHash *)setup_file_data, token, value);
2139 insert_ptr = addListEntry((SetupFileList *)insert_ptr, token, value);
2149 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2150 if (token_value_separator_warning)
2151 Error(ERR_INFO_LINE, "-");
2154 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2155 if (token_already_exists_warning)
2156 Error(ERR_INFO_LINE, "-");
2159 if (token_count == 0 && include_count == 0)
2160 Error(ERR_WARN, "configuration file '%s' is empty", filename);
2162 if (top_recursion_level)
2163 freeSetupFileHash(include_filename_hash);
2168 void saveSetupFileHash(SetupFileHash *hash, char *filename)
2172 if (!(file = fopen(filename, MODE_WRITE)))
2174 Error(ERR_WARN, "cannot write configuration file '%s'", filename);
2179 BEGIN_HASH_ITERATION(hash, itr)
2181 fprintf(file, "%s\n", getFormattedSetupEntry(HASH_ITERATION_TOKEN(itr),
2182 HASH_ITERATION_VALUE(itr)));
2184 END_HASH_ITERATION(hash, itr)
2189 SetupFileList *loadSetupFileList(char *filename)
2191 SetupFileList *setup_file_list = newSetupFileList("", "");
2192 SetupFileList *first_valid_list_entry;
2194 if (!loadSetupFileData(setup_file_list, filename, TRUE, FALSE))
2196 freeSetupFileList(setup_file_list);
2201 first_valid_list_entry = setup_file_list->next;
2203 /* free empty list header */
2204 setup_file_list->next = NULL;
2205 freeSetupFileList(setup_file_list);
2207 return first_valid_list_entry;
2210 SetupFileHash *loadSetupFileHash(char *filename)
2212 SetupFileHash *setup_file_hash = newSetupFileHash();
2214 if (!loadSetupFileData(setup_file_hash, filename, TRUE, TRUE))
2216 freeSetupFileHash(setup_file_hash);
2221 return setup_file_hash;
2225 /* ========================================================================= */
2226 /* setup file stuff */
2227 /* ========================================================================= */
2229 #define TOKEN_STR_LAST_LEVEL_SERIES "last_level_series"
2230 #define TOKEN_STR_LAST_PLAYED_LEVEL "last_played_level"
2231 #define TOKEN_STR_HANDICAP_LEVEL "handicap_level"
2233 /* level directory info */
2234 #define LEVELINFO_TOKEN_IDENTIFIER 0
2235 #define LEVELINFO_TOKEN_NAME 1
2236 #define LEVELINFO_TOKEN_NAME_SORTING 2
2237 #define LEVELINFO_TOKEN_AUTHOR 3
2238 #define LEVELINFO_TOKEN_YEAR 4
2239 #define LEVELINFO_TOKEN_PROGRAM_TITLE 5
2240 #define LEVELINFO_TOKEN_PROGRAM_COPYRIGHT 6
2241 #define LEVELINFO_TOKEN_PROGRAM_COMPANY 7
2242 #define LEVELINFO_TOKEN_IMPORTED_FROM 8
2243 #define LEVELINFO_TOKEN_IMPORTED_BY 9
2244 #define LEVELINFO_TOKEN_TESTED_BY 10
2245 #define LEVELINFO_TOKEN_LEVELS 11
2246 #define LEVELINFO_TOKEN_FIRST_LEVEL 12
2247 #define LEVELINFO_TOKEN_SORT_PRIORITY 13
2248 #define LEVELINFO_TOKEN_LATEST_ENGINE 14
2249 #define LEVELINFO_TOKEN_LEVEL_GROUP 15
2250 #define LEVELINFO_TOKEN_READONLY 16
2251 #define LEVELINFO_TOKEN_GRAPHICS_SET_ECS 17
2252 #define LEVELINFO_TOKEN_GRAPHICS_SET_AGA 18
2253 #define LEVELINFO_TOKEN_GRAPHICS_SET 19
2254 #define LEVELINFO_TOKEN_SOUNDS_SET 20
2255 #define LEVELINFO_TOKEN_MUSIC_SET 21
2256 #define LEVELINFO_TOKEN_FILENAME 22
2257 #define LEVELINFO_TOKEN_FILETYPE 23
2258 #define LEVELINFO_TOKEN_SPECIAL_FLAGS 24
2259 #define LEVELINFO_TOKEN_HANDICAP 25
2260 #define LEVELINFO_TOKEN_SKIP_LEVELS 26
2262 #define NUM_LEVELINFO_TOKENS 27
2264 static LevelDirTree ldi;
2266 static struct TokenInfo levelinfo_tokens[] =
2268 /* level directory info */
2269 { TYPE_STRING, &ldi.identifier, "identifier" },
2270 { TYPE_STRING, &ldi.name, "name" },
2271 { TYPE_STRING, &ldi.name_sorting, "name_sorting" },
2272 { TYPE_STRING, &ldi.author, "author" },
2273 { TYPE_STRING, &ldi.year, "year" },
2274 { TYPE_STRING, &ldi.program_title, "program_title" },
2275 { TYPE_STRING, &ldi.program_copyright, "program_copyright" },
2276 { TYPE_STRING, &ldi.program_company, "program_company" },
2277 { TYPE_STRING, &ldi.imported_from, "imported_from" },
2278 { TYPE_STRING, &ldi.imported_by, "imported_by" },
2279 { TYPE_STRING, &ldi.tested_by, "tested_by" },
2280 { TYPE_INTEGER, &ldi.levels, "levels" },
2281 { TYPE_INTEGER, &ldi.first_level, "first_level" },
2282 { TYPE_INTEGER, &ldi.sort_priority, "sort_priority" },
2283 { TYPE_BOOLEAN, &ldi.latest_engine, "latest_engine" },
2284 { TYPE_BOOLEAN, &ldi.level_group, "level_group" },
2285 { TYPE_BOOLEAN, &ldi.readonly, "readonly" },
2286 { TYPE_STRING, &ldi.graphics_set_ecs, "graphics_set.ecs" },
2287 { TYPE_STRING, &ldi.graphics_set_aga, "graphics_set.aga" },
2288 { TYPE_STRING, &ldi.graphics_set, "graphics_set" },
2289 { TYPE_STRING, &ldi.sounds_set, "sounds_set" },
2290 { TYPE_STRING, &ldi.music_set, "music_set" },
2291 { TYPE_STRING, &ldi.level_filename, "filename" },
2292 { TYPE_STRING, &ldi.level_filetype, "filetype" },
2293 { TYPE_STRING, &ldi.special_flags, "special_flags" },
2294 { TYPE_BOOLEAN, &ldi.handicap, "handicap" },
2295 { TYPE_BOOLEAN, &ldi.skip_levels, "skip_levels" }
2298 static struct TokenInfo artworkinfo_tokens[] =
2300 /* artwork directory info */
2301 { TYPE_STRING, &ldi.identifier, "identifier" },
2302 { TYPE_STRING, &ldi.subdir, "subdir" },
2303 { TYPE_STRING, &ldi.name, "name" },
2304 { TYPE_STRING, &ldi.name_sorting, "name_sorting" },
2305 { TYPE_STRING, &ldi.author, "author" },
2306 { TYPE_STRING, &ldi.program_title, "program_title" },
2307 { TYPE_STRING, &ldi.program_copyright, "program_copyright" },
2308 { TYPE_STRING, &ldi.program_company, "program_company" },
2309 { TYPE_INTEGER, &ldi.sort_priority, "sort_priority" },
2310 { TYPE_STRING, &ldi.basepath, "basepath" },
2311 { TYPE_STRING, &ldi.fullpath, "fullpath" },
2312 { TYPE_BOOLEAN, &ldi.in_user_dir, "in_user_dir" },
2313 { TYPE_INTEGER, &ldi.color, "color" },
2314 { TYPE_STRING, &ldi.class_desc, "class_desc" },
2319 static void setTreeInfoToDefaults(TreeInfo *ti, int type)
2323 ti->node_top = (ti->type == TREE_TYPE_LEVEL_DIR ? &leveldir_first :
2324 ti->type == TREE_TYPE_GRAPHICS_DIR ? &artwork.gfx_first :
2325 ti->type == TREE_TYPE_SOUNDS_DIR ? &artwork.snd_first :
2326 ti->type == TREE_TYPE_MUSIC_DIR ? &artwork.mus_first :
2329 ti->node_parent = NULL;
2330 ti->node_group = NULL;
2337 ti->fullpath = NULL;
2338 ti->basepath = NULL;
2339 ti->identifier = NULL;
2340 ti->name = getStringCopy(ANONYMOUS_NAME);
2341 ti->name_sorting = NULL;
2342 ti->author = getStringCopy(ANONYMOUS_NAME);
2345 ti->program_title = NULL;
2346 ti->program_copyright = NULL;
2347 ti->program_company = NULL;
2349 ti->sort_priority = LEVELCLASS_UNDEFINED; /* default: least priority */
2350 ti->latest_engine = FALSE; /* default: get from level */
2351 ti->parent_link = FALSE;
2352 ti->in_user_dir = FALSE;
2353 ti->user_defined = FALSE;
2355 ti->class_desc = NULL;
2357 ti->infotext = getStringCopy(TREE_INFOTEXT(ti->type));
2359 if (ti->type == TREE_TYPE_LEVEL_DIR)
2361 ti->imported_from = NULL;
2362 ti->imported_by = NULL;
2363 ti->tested_by = NULL;
2365 ti->graphics_set_ecs = NULL;
2366 ti->graphics_set_aga = NULL;
2367 ti->graphics_set = NULL;
2368 ti->sounds_set = NULL;
2369 ti->music_set = NULL;
2370 ti->graphics_path = getStringCopy(UNDEFINED_FILENAME);
2371 ti->sounds_path = getStringCopy(UNDEFINED_FILENAME);
2372 ti->music_path = getStringCopy(UNDEFINED_FILENAME);
2374 ti->level_filename = NULL;
2375 ti->level_filetype = NULL;
2377 ti->special_flags = NULL;
2380 ti->first_level = 0;
2382 ti->level_group = FALSE;
2383 ti->handicap_level = 0;
2384 ti->readonly = TRUE;
2385 ti->handicap = TRUE;
2386 ti->skip_levels = FALSE;
2390 static void setTreeInfoToDefaultsFromParent(TreeInfo *ti, TreeInfo *parent)
2394 Error(ERR_WARN, "setTreeInfoToDefaultsFromParent(): parent == NULL");
2396 setTreeInfoToDefaults(ti, TREE_TYPE_UNDEFINED);
2401 /* copy all values from the parent structure */
2403 ti->type = parent->type;
2405 ti->node_top = parent->node_top;
2406 ti->node_parent = parent;
2407 ti->node_group = NULL;
2414 ti->fullpath = NULL;
2415 ti->basepath = NULL;
2416 ti->identifier = NULL;
2417 ti->name = getStringCopy(ANONYMOUS_NAME);
2418 ti->name_sorting = NULL;
2419 ti->author = getStringCopy(parent->author);
2420 ti->year = getStringCopy(parent->year);
2422 ti->program_title = getStringCopy(parent->program_title);
2423 ti->program_copyright = getStringCopy(parent->program_copyright);
2424 ti->program_company = getStringCopy(parent->program_company);
2426 ti->sort_priority = parent->sort_priority;
2427 ti->latest_engine = parent->latest_engine;
2428 ti->parent_link = FALSE;
2429 ti->in_user_dir = parent->in_user_dir;
2430 ti->user_defined = parent->user_defined;
2431 ti->color = parent->color;
2432 ti->class_desc = getStringCopy(parent->class_desc);
2434 ti->infotext = getStringCopy(parent->infotext);
2436 if (ti->type == TREE_TYPE_LEVEL_DIR)
2438 ti->imported_from = getStringCopy(parent->imported_from);
2439 ti->imported_by = getStringCopy(parent->imported_by);
2440 ti->tested_by = getStringCopy(parent->tested_by);
2442 ti->graphics_set_ecs = getStringCopy(parent->graphics_set_ecs);
2443 ti->graphics_set_aga = getStringCopy(parent->graphics_set_aga);
2444 ti->graphics_set = getStringCopy(parent->graphics_set);
2445 ti->sounds_set = getStringCopy(parent->sounds_set);
2446 ti->music_set = getStringCopy(parent->music_set);
2447 ti->graphics_path = getStringCopy(UNDEFINED_FILENAME);
2448 ti->sounds_path = getStringCopy(UNDEFINED_FILENAME);
2449 ti->music_path = getStringCopy(UNDEFINED_FILENAME);
2451 ti->level_filename = getStringCopy(parent->level_filename);
2452 ti->level_filetype = getStringCopy(parent->level_filetype);
2454 ti->special_flags = getStringCopy(parent->special_flags);
2456 ti->levels = parent->levels;
2457 ti->first_level = parent->first_level;
2458 ti->last_level = parent->last_level;
2459 ti->level_group = FALSE;
2460 ti->handicap_level = parent->handicap_level;
2461 ti->readonly = parent->readonly;
2462 ti->handicap = parent->handicap;
2463 ti->skip_levels = parent->skip_levels;
2467 static TreeInfo *getTreeInfoCopy(TreeInfo *ti)
2469 TreeInfo *ti_copy = newTreeInfo();
2471 /* copy all values from the original structure */
2473 ti_copy->type = ti->type;
2475 ti_copy->node_top = ti->node_top;
2476 ti_copy->node_parent = ti->node_parent;
2477 ti_copy->node_group = ti->node_group;
2478 ti_copy->next = ti->next;
2480 ti_copy->cl_first = ti->cl_first;
2481 ti_copy->cl_cursor = ti->cl_cursor;
2483 ti_copy->subdir = getStringCopy(ti->subdir);
2484 ti_copy->fullpath = getStringCopy(ti->fullpath);
2485 ti_copy->basepath = getStringCopy(ti->basepath);
2486 ti_copy->identifier = getStringCopy(ti->identifier);
2487 ti_copy->name = getStringCopy(ti->name);
2488 ti_copy->name_sorting = getStringCopy(ti->name_sorting);
2489 ti_copy->author = getStringCopy(ti->author);
2490 ti_copy->year = getStringCopy(ti->year);
2492 ti_copy->program_title = getStringCopy(ti->program_title);
2493 ti_copy->program_copyright = getStringCopy(ti->program_copyright);
2494 ti_copy->program_company = getStringCopy(ti->program_company);
2496 ti_copy->imported_from = getStringCopy(ti->imported_from);
2497 ti_copy->imported_by = getStringCopy(ti->imported_by);
2498 ti_copy->tested_by = getStringCopy(ti->tested_by);
2500 ti_copy->graphics_set_ecs = getStringCopy(ti->graphics_set_ecs);
2501 ti_copy->graphics_set_aga = getStringCopy(ti->graphics_set_aga);
2502 ti_copy->graphics_set = getStringCopy(ti->graphics_set);
2503 ti_copy->sounds_set = getStringCopy(ti->sounds_set);
2504 ti_copy->music_set = getStringCopy(ti->music_set);
2505 ti_copy->graphics_path = getStringCopy(ti->graphics_path);
2506 ti_copy->sounds_path = getStringCopy(ti->sounds_path);
2507 ti_copy->music_path = getStringCopy(ti->music_path);
2509 ti_copy->level_filename = getStringCopy(ti->level_filename);
2510 ti_copy->level_filetype = getStringCopy(ti->level_filetype);
2512 ti_copy->special_flags = getStringCopy(ti->special_flags);
2514 ti_copy->levels = ti->levels;
2515 ti_copy->first_level = ti->first_level;
2516 ti_copy->last_level = ti->last_level;
2517 ti_copy->sort_priority = ti->sort_priority;
2519 ti_copy->latest_engine = ti->latest_engine;
2521 ti_copy->level_group = ti->level_group;
2522 ti_copy->parent_link = ti->parent_link;
2523 ti_copy->in_user_dir = ti->in_user_dir;
2524 ti_copy->user_defined = ti->user_defined;
2525 ti_copy->readonly = ti->readonly;
2526 ti_copy->handicap = ti->handicap;
2527 ti_copy->skip_levels = ti->skip_levels;
2529 ti_copy->color = ti->color;
2530 ti_copy->class_desc = getStringCopy(ti->class_desc);
2531 ti_copy->handicap_level = ti->handicap_level;
2533 ti_copy->infotext = getStringCopy(ti->infotext);
2538 void freeTreeInfo(TreeInfo *ti)
2543 checked_free(ti->subdir);
2544 checked_free(ti->fullpath);
2545 checked_free(ti->basepath);
2546 checked_free(ti->identifier);
2548 checked_free(ti->name);
2549 checked_free(ti->name_sorting);
2550 checked_free(ti->author);
2551 checked_free(ti->year);
2553 checked_free(ti->program_title);
2554 checked_free(ti->program_copyright);
2555 checked_free(ti->program_company);
2557 checked_free(ti->class_desc);
2559 checked_free(ti->infotext);
2561 if (ti->type == TREE_TYPE_LEVEL_DIR)
2563 checked_free(ti->imported_from);
2564 checked_free(ti->imported_by);
2565 checked_free(ti->tested_by);
2567 checked_free(ti->graphics_set_ecs);
2568 checked_free(ti->graphics_set_aga);
2569 checked_free(ti->graphics_set);
2570 checked_free(ti->sounds_set);
2571 checked_free(ti->music_set);
2573 checked_free(ti->graphics_path);
2574 checked_free(ti->sounds_path);
2575 checked_free(ti->music_path);
2577 checked_free(ti->level_filename);
2578 checked_free(ti->level_filetype);
2580 checked_free(ti->special_flags);
2583 // recursively free child node
2585 freeTreeInfo(ti->node_group);
2587 // recursively free next node
2589 freeTreeInfo(ti->next);
2594 void setSetupInfo(struct TokenInfo *token_info,
2595 int token_nr, char *token_value)
2597 int token_type = token_info[token_nr].type;
2598 void *setup_value = token_info[token_nr].value;
2600 if (token_value == NULL)
2603 /* set setup field to corresponding token value */
2608 *(boolean *)setup_value = get_boolean_from_string(token_value);
2612 *(int *)setup_value = get_switch3_from_string(token_value);
2616 *(Key *)setup_value = getKeyFromKeyName(token_value);
2620 *(Key *)setup_value = getKeyFromX11KeyName(token_value);
2624 *(int *)setup_value = get_integer_from_string(token_value);
2628 checked_free(*(char **)setup_value);
2629 *(char **)setup_value = getStringCopy(token_value);
2637 static int compareTreeInfoEntries(const void *object1, const void *object2)
2639 const TreeInfo *entry1 = *((TreeInfo **)object1);
2640 const TreeInfo *entry2 = *((TreeInfo **)object2);
2641 int class_sorting1 = 0, class_sorting2 = 0;
2644 if (entry1->type == TREE_TYPE_LEVEL_DIR)
2646 class_sorting1 = LEVELSORTING(entry1);
2647 class_sorting2 = LEVELSORTING(entry2);
2649 else if (entry1->type == TREE_TYPE_GRAPHICS_DIR ||
2650 entry1->type == TREE_TYPE_SOUNDS_DIR ||
2651 entry1->type == TREE_TYPE_MUSIC_DIR)
2653 class_sorting1 = ARTWORKSORTING(entry1);
2654 class_sorting2 = ARTWORKSORTING(entry2);
2657 if (entry1->parent_link || entry2->parent_link)
2658 compare_result = (entry1->parent_link ? -1 : +1);
2659 else if (entry1->sort_priority == entry2->sort_priority)
2661 char *name1 = getStringToLower(entry1->name_sorting);
2662 char *name2 = getStringToLower(entry2->name_sorting);
2664 compare_result = strcmp(name1, name2);
2669 else if (class_sorting1 == class_sorting2)
2670 compare_result = entry1->sort_priority - entry2->sort_priority;
2672 compare_result = class_sorting1 - class_sorting2;
2674 return compare_result;
2677 static TreeInfo *createParentTreeInfoNode(TreeInfo *node_parent)
2681 if (node_parent == NULL)
2684 ti_new = newTreeInfo();
2685 setTreeInfoToDefaults(ti_new, node_parent->type);
2687 ti_new->node_parent = node_parent;
2688 ti_new->parent_link = TRUE;
2690 setString(&ti_new->identifier, node_parent->identifier);
2691 setString(&ti_new->name, ".. (parent directory)");
2692 setString(&ti_new->name_sorting, ti_new->name);
2694 setString(&ti_new->subdir, STRING_PARENT_DIRECTORY);
2695 setString(&ti_new->fullpath, node_parent->fullpath);
2697 ti_new->sort_priority = node_parent->sort_priority;
2698 ti_new->latest_engine = node_parent->latest_engine;
2700 setString(&ti_new->class_desc, getLevelClassDescription(ti_new));
2702 pushTreeInfo(&node_parent->node_group, ti_new);
2707 static TreeInfo *createTopTreeInfoNode(TreeInfo *node_first)
2709 TreeInfo *ti_new, *ti_new2;
2711 if (node_first == NULL)
2714 ti_new = newTreeInfo();
2715 setTreeInfoToDefaults(ti_new, TREE_TYPE_LEVEL_DIR);
2717 ti_new->node_parent = NULL;
2718 ti_new->parent_link = FALSE;
2720 setString(&ti_new->identifier, node_first->identifier);
2721 setString(&ti_new->name, "level sets");
2722 setString(&ti_new->name_sorting, ti_new->name);
2724 setString(&ti_new->subdir, STRING_TOP_DIRECTORY);
2725 setString(&ti_new->fullpath, ".");
2727 ti_new->sort_priority = node_first->sort_priority;;
2728 ti_new->latest_engine = node_first->latest_engine;
2730 setString(&ti_new->class_desc, "level sets");
2732 ti_new->node_group = node_first;
2733 ti_new->level_group = TRUE;
2735 ti_new2 = createParentTreeInfoNode(ti_new);
2737 setString(&ti_new2->name, ".. (main menu)");
2738 setString(&ti_new2->name_sorting, ti_new2->name);
2744 /* -------------------------------------------------------------------------- */
2745 /* functions for handling level and custom artwork info cache */
2746 /* -------------------------------------------------------------------------- */
2748 static void LoadArtworkInfoCache()
2750 InitCacheDirectory();
2752 if (artworkinfo_cache_old == NULL)
2754 char *filename = getPath2(getCacheDir(), ARTWORKINFO_CACHE_FILE);
2756 /* try to load artwork info hash from already existing cache file */
2757 artworkinfo_cache_old = loadSetupFileHash(filename);
2759 /* if no artwork info cache file was found, start with empty hash */
2760 if (artworkinfo_cache_old == NULL)
2761 artworkinfo_cache_old = newSetupFileHash();
2766 if (artworkinfo_cache_new == NULL)
2767 artworkinfo_cache_new = newSetupFileHash();
2770 static void SaveArtworkInfoCache()
2772 char *filename = getPath2(getCacheDir(), ARTWORKINFO_CACHE_FILE);
2774 InitCacheDirectory();
2776 saveSetupFileHash(artworkinfo_cache_new, filename);
2781 static char *getCacheTokenPrefix(char *prefix1, char *prefix2)
2783 static char *prefix = NULL;
2785 checked_free(prefix);
2787 prefix = getStringCat2WithSeparator(prefix1, prefix2, ".");
2792 /* (identical to above function, but separate string buffer needed -- nasty) */
2793 static char *getCacheToken(char *prefix, char *suffix)
2795 static char *token = NULL;
2797 checked_free(token);
2799 token = getStringCat2WithSeparator(prefix, suffix, ".");
2804 static char *getFileTimestampString(char *filename)
2806 return getStringCopy(i_to_a(getFileTimestampEpochSeconds(filename)));
2809 static boolean modifiedFileTimestamp(char *filename, char *timestamp_string)
2811 struct stat file_status;
2813 if (timestamp_string == NULL)
2816 if (stat(filename, &file_status) != 0) /* cannot stat file */
2819 return (file_status.st_mtime != atoi(timestamp_string));
2822 static TreeInfo *getArtworkInfoCacheEntry(LevelDirTree *level_node, int type)
2824 char *identifier = level_node->subdir;
2825 char *type_string = ARTWORK_DIRECTORY(type);
2826 char *token_prefix = getCacheTokenPrefix(type_string, identifier);
2827 char *token_main = getCacheToken(token_prefix, "CACHED");
2828 char *cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
2829 boolean cached = (cache_entry != NULL && strEqual(cache_entry, "true"));
2830 TreeInfo *artwork_info = NULL;
2832 if (!use_artworkinfo_cache)
2839 artwork_info = newTreeInfo();
2840 setTreeInfoToDefaults(artwork_info, type);
2842 /* set all structure fields according to the token/value pairs */
2843 ldi = *artwork_info;
2844 for (i = 0; artworkinfo_tokens[i].type != -1; i++)
2846 char *token = getCacheToken(token_prefix, artworkinfo_tokens[i].text);
2847 char *value = getHashEntry(artworkinfo_cache_old, token);
2849 /* if defined, use value from cache, else keep default value */
2851 setSetupInfo(artworkinfo_tokens, i, value);
2854 *artwork_info = ldi;
2856 char *filename_levelinfo = getPath2(getLevelDirFromTreeInfo(level_node),
2857 LEVELINFO_FILENAME);
2858 char *filename_artworkinfo = getPath2(getSetupArtworkDir(artwork_info),
2859 ARTWORKINFO_FILENAME(type));
2861 /* check if corresponding "levelinfo.conf" file has changed */
2862 token_main = getCacheToken(token_prefix, "TIMESTAMP_LEVELINFO");
2863 cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
2865 if (modifiedFileTimestamp(filename_levelinfo, cache_entry))
2868 /* check if corresponding "<artworkinfo>.conf" file has changed */
2869 token_main = getCacheToken(token_prefix, "TIMESTAMP_ARTWORKINFO");
2870 cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
2872 if (modifiedFileTimestamp(filename_artworkinfo, cache_entry))
2875 checked_free(filename_levelinfo);
2876 checked_free(filename_artworkinfo);
2879 if (!cached && artwork_info != NULL)
2881 freeTreeInfo(artwork_info);
2886 return artwork_info;
2889 static void setArtworkInfoCacheEntry(TreeInfo *artwork_info,
2890 LevelDirTree *level_node, int type)
2892 char *identifier = level_node->subdir;
2893 char *type_string = ARTWORK_DIRECTORY(type);
2894 char *token_prefix = getCacheTokenPrefix(type_string, identifier);
2895 char *token_main = getCacheToken(token_prefix, "CACHED");
2896 boolean set_cache_timestamps = TRUE;
2899 setHashEntry(artworkinfo_cache_new, token_main, "true");
2901 if (set_cache_timestamps)
2903 char *filename_levelinfo = getPath2(getLevelDirFromTreeInfo(level_node),
2904 LEVELINFO_FILENAME);
2905 char *filename_artworkinfo = getPath2(getSetupArtworkDir(artwork_info),
2906 ARTWORKINFO_FILENAME(type));
2907 char *timestamp_levelinfo = getFileTimestampString(filename_levelinfo);
2908 char *timestamp_artworkinfo = getFileTimestampString(filename_artworkinfo);
2910 token_main = getCacheToken(token_prefix, "TIMESTAMP_LEVELINFO");
2911 setHashEntry(artworkinfo_cache_new, token_main, timestamp_levelinfo);
2913 token_main = getCacheToken(token_prefix, "TIMESTAMP_ARTWORKINFO");
2914 setHashEntry(artworkinfo_cache_new, token_main, timestamp_artworkinfo);
2916 checked_free(filename_levelinfo);
2917 checked_free(filename_artworkinfo);
2918 checked_free(timestamp_levelinfo);
2919 checked_free(timestamp_artworkinfo);
2922 ldi = *artwork_info;
2923 for (i = 0; artworkinfo_tokens[i].type != -1; i++)
2925 char *token = getCacheToken(token_prefix, artworkinfo_tokens[i].text);
2926 char *value = getSetupValue(artworkinfo_tokens[i].type,
2927 artworkinfo_tokens[i].value);
2929 setHashEntry(artworkinfo_cache_new, token, value);
2934 /* -------------------------------------------------------------------------- */
2935 /* functions for loading level info and custom artwork info */
2936 /* -------------------------------------------------------------------------- */
2938 /* forward declaration for recursive call by "LoadLevelInfoFromLevelDir()" */
2939 static void LoadLevelInfoFromLevelDir(TreeInfo **, TreeInfo *, char *);
2941 static boolean LoadLevelInfoFromLevelConf(TreeInfo **node_first,
2942 TreeInfo *node_parent,
2943 char *level_directory,
2944 char *directory_name)
2946 char *directory_path = getPath2(level_directory, directory_name);
2947 char *filename = getPath2(directory_path, LEVELINFO_FILENAME);
2948 SetupFileHash *setup_file_hash;
2949 LevelDirTree *leveldir_new = NULL;
2952 /* unless debugging, silently ignore directories without "levelinfo.conf" */
2953 if (!options.debug && !fileExists(filename))
2955 free(directory_path);
2961 setup_file_hash = loadSetupFileHash(filename);
2963 if (setup_file_hash == NULL)
2965 Error(ERR_WARN, "ignoring level directory '%s'", directory_path);
2967 free(directory_path);
2973 leveldir_new = newTreeInfo();
2976 setTreeInfoToDefaultsFromParent(leveldir_new, node_parent);
2978 setTreeInfoToDefaults(leveldir_new, TREE_TYPE_LEVEL_DIR);
2980 leveldir_new->subdir = getStringCopy(directory_name);
2982 /* set all structure fields according to the token/value pairs */
2983 ldi = *leveldir_new;
2984 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
2985 setSetupInfo(levelinfo_tokens, i,
2986 getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
2987 *leveldir_new = ldi;
2989 if (strEqual(leveldir_new->name, ANONYMOUS_NAME))
2990 setString(&leveldir_new->name, leveldir_new->subdir);
2992 if (leveldir_new->identifier == NULL)
2993 leveldir_new->identifier = getStringCopy(leveldir_new->subdir);
2995 if (leveldir_new->name_sorting == NULL)
2996 leveldir_new->name_sorting = getStringCopy(leveldir_new->name);
2998 if (node_parent == NULL) /* top level group */
3000 leveldir_new->basepath = getStringCopy(level_directory);
3001 leveldir_new->fullpath = getStringCopy(leveldir_new->subdir);
3003 else /* sub level group */
3005 leveldir_new->basepath = getStringCopy(node_parent->basepath);
3006 leveldir_new->fullpath = getPath2(node_parent->fullpath, directory_name);
3009 leveldir_new->last_level =
3010 leveldir_new->first_level + leveldir_new->levels - 1;
3012 leveldir_new->in_user_dir =
3013 (!strEqual(leveldir_new->basepath, options.level_directory));
3015 /* adjust some settings if user's private level directory was detected */
3016 if (leveldir_new->sort_priority == LEVELCLASS_UNDEFINED &&
3017 leveldir_new->in_user_dir &&
3018 (strEqual(leveldir_new->subdir, getLoginName()) ||
3019 strEqual(leveldir_new->name, getLoginName()) ||
3020 strEqual(leveldir_new->author, getRealName())))
3022 leveldir_new->sort_priority = LEVELCLASS_PRIVATE_START;
3023 leveldir_new->readonly = FALSE;
3026 leveldir_new->user_defined =
3027 (leveldir_new->in_user_dir && IS_LEVELCLASS_PRIVATE(leveldir_new));
3029 leveldir_new->color = LEVELCOLOR(leveldir_new);
3031 setString(&leveldir_new->class_desc, getLevelClassDescription(leveldir_new));
3033 leveldir_new->handicap_level = /* set handicap to default value */
3034 (leveldir_new->user_defined || !leveldir_new->handicap ?
3035 leveldir_new->last_level : leveldir_new->first_level);
3037 DrawInitText(leveldir_new->name, 150, FC_YELLOW);
3039 pushTreeInfo(node_first, leveldir_new);
3041 freeSetupFileHash(setup_file_hash);
3043 if (leveldir_new->level_group)
3045 /* create node to link back to current level directory */
3046 createParentTreeInfoNode(leveldir_new);
3048 /* recursively step into sub-directory and look for more level series */
3049 LoadLevelInfoFromLevelDir(&leveldir_new->node_group,
3050 leveldir_new, directory_path);
3053 free(directory_path);
3059 static void LoadLevelInfoFromLevelDir(TreeInfo **node_first,
3060 TreeInfo *node_parent,
3061 char *level_directory)
3064 DirectoryEntry *dir_entry;
3065 boolean valid_entry_found = FALSE;
3067 if ((dir = openDirectory(level_directory)) == NULL)
3069 Error(ERR_WARN, "cannot read level directory '%s'", level_directory);
3074 while ((dir_entry = readDirectory(dir)) != NULL) /* loop all entries */
3076 char *directory_name = dir_entry->basename;
3077 char *directory_path = getPath2(level_directory, directory_name);
3079 /* skip entries for current and parent directory */
3080 if (strEqual(directory_name, ".") ||
3081 strEqual(directory_name, ".."))
3083 free(directory_path);
3088 /* find out if directory entry is itself a directory */
3089 if (!dir_entry->is_directory) /* not a directory */
3091 free(directory_path);
3096 free(directory_path);
3098 if (strEqual(directory_name, GRAPHICS_DIRECTORY) ||
3099 strEqual(directory_name, SOUNDS_DIRECTORY) ||
3100 strEqual(directory_name, MUSIC_DIRECTORY))
3103 valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
3108 closeDirectory(dir);
3110 /* special case: top level directory may directly contain "levelinfo.conf" */
3111 if (node_parent == NULL && !valid_entry_found)
3113 /* check if this directory directly contains a file "levelinfo.conf" */
3114 valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
3115 level_directory, ".");
3118 if (!valid_entry_found)
3119 Error(ERR_WARN, "cannot find any valid level series in directory '%s'",
3123 boolean AdjustGraphicsForEMC()
3125 boolean settings_changed = FALSE;
3127 settings_changed |= adjustTreeGraphicsForEMC(leveldir_first_all);
3128 settings_changed |= adjustTreeGraphicsForEMC(leveldir_first);
3130 return settings_changed;
3133 void LoadLevelInfo()
3135 InitUserLevelDirectory(getLoginName());
3137 DrawInitText("Loading level series", 120, FC_GREEN);
3139 LoadLevelInfoFromLevelDir(&leveldir_first, NULL, options.level_directory);
3140 LoadLevelInfoFromLevelDir(&leveldir_first, NULL, getUserLevelDir(NULL));
3142 leveldir_first = createTopTreeInfoNode(leveldir_first);
3144 /* after loading all level set information, clone the level directory tree
3145 and remove all level sets without levels (these may still contain artwork
3146 to be offered in the setup menu as "custom artwork", and are therefore
3147 checked for existing artwork in the function "LoadLevelArtworkInfo()") */
3148 leveldir_first_all = leveldir_first;
3149 cloneTree(&leveldir_first, leveldir_first_all, TRUE);
3151 AdjustGraphicsForEMC();
3153 /* before sorting, the first entries will be from the user directory */
3154 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
3156 if (leveldir_first == NULL)
3157 Error(ERR_EXIT, "cannot find any valid level series in any directory");
3159 sortTreeInfo(&leveldir_first);
3161 #if ENABLE_UNUSED_CODE
3162 dumpTreeInfo(leveldir_first, 0);
3166 static boolean LoadArtworkInfoFromArtworkConf(TreeInfo **node_first,
3167 TreeInfo *node_parent,
3168 char *base_directory,
3169 char *directory_name, int type)
3171 char *directory_path = getPath2(base_directory, directory_name);
3172 char *filename = getPath2(directory_path, ARTWORKINFO_FILENAME(type));
3173 SetupFileHash *setup_file_hash = NULL;
3174 TreeInfo *artwork_new = NULL;
3177 if (fileExists(filename))
3178 setup_file_hash = loadSetupFileHash(filename);
3180 if (setup_file_hash == NULL) /* no config file -- look for artwork files */
3183 DirectoryEntry *dir_entry;
3184 boolean valid_file_found = FALSE;
3186 if ((dir = openDirectory(directory_path)) != NULL)
3188 while ((dir_entry = readDirectory(dir)) != NULL)
3190 if (FileIsArtworkType(dir_entry->filename, type))
3192 valid_file_found = TRUE;
3198 closeDirectory(dir);
3201 if (!valid_file_found)
3203 if (!strEqual(directory_name, "."))
3204 Error(ERR_WARN, "ignoring artwork directory '%s'", directory_path);
3206 free(directory_path);
3213 artwork_new = newTreeInfo();
3216 setTreeInfoToDefaultsFromParent(artwork_new, node_parent);
3218 setTreeInfoToDefaults(artwork_new, type);
3220 artwork_new->subdir = getStringCopy(directory_name);
3222 if (setup_file_hash) /* (before defining ".color" and ".class_desc") */
3224 /* set all structure fields according to the token/value pairs */
3226 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
3227 setSetupInfo(levelinfo_tokens, i,
3228 getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
3231 if (strEqual(artwork_new->name, ANONYMOUS_NAME))
3232 setString(&artwork_new->name, artwork_new->subdir);
3234 if (artwork_new->identifier == NULL)
3235 artwork_new->identifier = getStringCopy(artwork_new->subdir);
3237 if (artwork_new->name_sorting == NULL)
3238 artwork_new->name_sorting = getStringCopy(artwork_new->name);
3241 if (node_parent == NULL) /* top level group */
3243 artwork_new->basepath = getStringCopy(base_directory);
3244 artwork_new->fullpath = getStringCopy(artwork_new->subdir);
3246 else /* sub level group */
3248 artwork_new->basepath = getStringCopy(node_parent->basepath);
3249 artwork_new->fullpath = getPath2(node_parent->fullpath, directory_name);
3252 artwork_new->in_user_dir =
3253 (!strEqual(artwork_new->basepath, OPTIONS_ARTWORK_DIRECTORY(type)));
3255 /* (may use ".sort_priority" from "setup_file_hash" above) */
3256 artwork_new->color = ARTWORKCOLOR(artwork_new);
3258 setString(&artwork_new->class_desc, getLevelClassDescription(artwork_new));
3260 if (setup_file_hash == NULL) /* (after determining ".user_defined") */
3262 if (strEqual(artwork_new->subdir, "."))
3264 if (artwork_new->user_defined)
3266 setString(&artwork_new->identifier, "private");
3267 artwork_new->sort_priority = ARTWORKCLASS_PRIVATE;
3271 setString(&artwork_new->identifier, "classic");
3272 artwork_new->sort_priority = ARTWORKCLASS_CLASSICS;
3275 /* set to new values after changing ".sort_priority" */
3276 artwork_new->color = ARTWORKCOLOR(artwork_new);
3278 setString(&artwork_new->class_desc,
3279 getLevelClassDescription(artwork_new));
3283 setString(&artwork_new->identifier, artwork_new->subdir);
3286 setString(&artwork_new->name, artwork_new->identifier);
3287 setString(&artwork_new->name_sorting, artwork_new->name);
3290 pushTreeInfo(node_first, artwork_new);
3292 freeSetupFileHash(setup_file_hash);
3294 free(directory_path);
3300 static void LoadArtworkInfoFromArtworkDir(TreeInfo **node_first,
3301 TreeInfo *node_parent,
3302 char *base_directory, int type)
3305 DirectoryEntry *dir_entry;
3306 boolean valid_entry_found = FALSE;
3308 if ((dir = openDirectory(base_directory)) == NULL)
3310 /* display error if directory is main "options.graphics_directory" etc. */
3311 if (base_directory == OPTIONS_ARTWORK_DIRECTORY(type))
3312 Error(ERR_WARN, "cannot read directory '%s'", base_directory);
3317 while ((dir_entry = readDirectory(dir)) != NULL) /* loop all entries */
3319 char *directory_name = dir_entry->basename;
3320 char *directory_path = getPath2(base_directory, directory_name);
3322 /* skip directory entries for current and parent directory */
3323 if (strEqual(directory_name, ".") ||
3324 strEqual(directory_name, ".."))
3326 free(directory_path);
3331 /* skip directory entries which are not a directory */
3332 if (!dir_entry->is_directory) /* not a directory */
3334 free(directory_path);
3339 free(directory_path);
3341 /* check if this directory contains artwork with or without config file */
3342 valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
3344 directory_name, type);
3347 closeDirectory(dir);
3349 /* check if this directory directly contains artwork itself */
3350 valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
3351 base_directory, ".",
3353 if (!valid_entry_found)
3354 Error(ERR_WARN, "cannot find any valid artwork in directory '%s'",
3358 static TreeInfo *getDummyArtworkInfo(int type)
3360 /* this is only needed when there is completely no artwork available */
3361 TreeInfo *artwork_new = newTreeInfo();
3363 setTreeInfoToDefaults(artwork_new, type);
3365 setString(&artwork_new->subdir, UNDEFINED_FILENAME);
3366 setString(&artwork_new->fullpath, UNDEFINED_FILENAME);
3367 setString(&artwork_new->basepath, UNDEFINED_FILENAME);
3369 setString(&artwork_new->identifier, UNDEFINED_FILENAME);
3370 setString(&artwork_new->name, UNDEFINED_FILENAME);
3371 setString(&artwork_new->name_sorting, UNDEFINED_FILENAME);
3376 void LoadArtworkInfo()
3378 LoadArtworkInfoCache();
3380 DrawInitText("Looking for custom artwork", 120, FC_GREEN);
3382 LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
3383 options.graphics_directory,
3384 TREE_TYPE_GRAPHICS_DIR);
3385 LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
3386 getUserGraphicsDir(),
3387 TREE_TYPE_GRAPHICS_DIR);
3389 LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
3390 options.sounds_directory,
3391 TREE_TYPE_SOUNDS_DIR);
3392 LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
3394 TREE_TYPE_SOUNDS_DIR);
3396 LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
3397 options.music_directory,
3398 TREE_TYPE_MUSIC_DIR);
3399 LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
3401 TREE_TYPE_MUSIC_DIR);
3403 if (artwork.gfx_first == NULL)
3404 artwork.gfx_first = getDummyArtworkInfo(TREE_TYPE_GRAPHICS_DIR);
3405 if (artwork.snd_first == NULL)
3406 artwork.snd_first = getDummyArtworkInfo(TREE_TYPE_SOUNDS_DIR);
3407 if (artwork.mus_first == NULL)
3408 artwork.mus_first = getDummyArtworkInfo(TREE_TYPE_MUSIC_DIR);
3410 /* before sorting, the first entries will be from the user directory */
3411 artwork.gfx_current =
3412 getTreeInfoFromIdentifier(artwork.gfx_first, setup.graphics_set);
3413 if (artwork.gfx_current == NULL)
3414 artwork.gfx_current =
3415 getTreeInfoFromIdentifier(artwork.gfx_first, GFX_DEFAULT_SUBDIR);
3416 if (artwork.gfx_current == NULL)
3417 artwork.gfx_current = getFirstValidTreeInfoEntry(artwork.gfx_first);
3419 artwork.snd_current =
3420 getTreeInfoFromIdentifier(artwork.snd_first, setup.sounds_set);
3421 if (artwork.snd_current == NULL)
3422 artwork.snd_current =
3423 getTreeInfoFromIdentifier(artwork.snd_first, SND_DEFAULT_SUBDIR);
3424 if (artwork.snd_current == NULL)
3425 artwork.snd_current = getFirstValidTreeInfoEntry(artwork.snd_first);
3427 artwork.mus_current =
3428 getTreeInfoFromIdentifier(artwork.mus_first, setup.music_set);
3429 if (artwork.mus_current == NULL)
3430 artwork.mus_current =
3431 getTreeInfoFromIdentifier(artwork.mus_first, MUS_DEFAULT_SUBDIR);
3432 if (artwork.mus_current == NULL)
3433 artwork.mus_current = getFirstValidTreeInfoEntry(artwork.mus_first);
3435 artwork.gfx_current_identifier = artwork.gfx_current->identifier;
3436 artwork.snd_current_identifier = artwork.snd_current->identifier;
3437 artwork.mus_current_identifier = artwork.mus_current->identifier;
3439 #if ENABLE_UNUSED_CODE
3440 printf("graphics set == %s\n\n", artwork.gfx_current_identifier);
3441 printf("sounds set == %s\n\n", artwork.snd_current_identifier);
3442 printf("music set == %s\n\n", artwork.mus_current_identifier);
3445 sortTreeInfo(&artwork.gfx_first);
3446 sortTreeInfo(&artwork.snd_first);
3447 sortTreeInfo(&artwork.mus_first);
3449 #if ENABLE_UNUSED_CODE
3450 dumpTreeInfo(artwork.gfx_first, 0);
3451 dumpTreeInfo(artwork.snd_first, 0);
3452 dumpTreeInfo(artwork.mus_first, 0);
3456 void LoadArtworkInfoFromLevelInfo(ArtworkDirTree **artwork_node,
3457 LevelDirTree *level_node)
3459 int type = (*artwork_node)->type;
3461 /* recursively check all level directories for artwork sub-directories */
3465 /* check all tree entries for artwork, but skip parent link entries */
3466 if (!level_node->parent_link)
3468 TreeInfo *artwork_new = getArtworkInfoCacheEntry(level_node, type);
3469 boolean cached = (artwork_new != NULL);
3473 pushTreeInfo(artwork_node, artwork_new);
3477 TreeInfo *topnode_last = *artwork_node;
3478 char *path = getPath2(getLevelDirFromTreeInfo(level_node),
3479 ARTWORK_DIRECTORY(type));
3481 LoadArtworkInfoFromArtworkDir(artwork_node, NULL, path, type);
3483 if (topnode_last != *artwork_node) /* check for newly added node */
3485 artwork_new = *artwork_node;
3487 setString(&artwork_new->identifier, level_node->subdir);
3488 setString(&artwork_new->name, level_node->name);
3489 setString(&artwork_new->name_sorting, level_node->name_sorting);
3491 artwork_new->sort_priority = level_node->sort_priority;
3492 artwork_new->color = LEVELCOLOR(artwork_new);
3498 /* insert artwork info (from old cache or filesystem) into new cache */
3499 if (artwork_new != NULL)
3500 setArtworkInfoCacheEntry(artwork_new, level_node, type);
3503 DrawInitText(level_node->name, 150, FC_YELLOW);
3505 if (level_node->node_group != NULL)
3506 LoadArtworkInfoFromLevelInfo(artwork_node, level_node->node_group);
3508 level_node = level_node->next;
3512 void LoadLevelArtworkInfo()
3514 print_timestamp_init("LoadLevelArtworkInfo");
3516 DrawInitText("Looking for custom level artwork", 120, FC_GREEN);
3518 print_timestamp_time("DrawTimeText");
3520 LoadArtworkInfoFromLevelInfo(&artwork.gfx_first, leveldir_first_all);
3521 print_timestamp_time("LoadArtworkInfoFromLevelInfo (gfx)");
3522 LoadArtworkInfoFromLevelInfo(&artwork.snd_first, leveldir_first_all);
3523 print_timestamp_time("LoadArtworkInfoFromLevelInfo (snd)");
3524 LoadArtworkInfoFromLevelInfo(&artwork.mus_first, leveldir_first_all);
3525 print_timestamp_time("LoadArtworkInfoFromLevelInfo (mus)");
3527 SaveArtworkInfoCache();
3529 print_timestamp_time("SaveArtworkInfoCache");
3531 /* needed for reloading level artwork not known at ealier stage */
3533 if (!strEqual(artwork.gfx_current_identifier, setup.graphics_set))
3535 artwork.gfx_current =
3536 getTreeInfoFromIdentifier(artwork.gfx_first, setup.graphics_set);
3537 if (artwork.gfx_current == NULL)
3538 artwork.gfx_current =
3539 getTreeInfoFromIdentifier(artwork.gfx_first, GFX_DEFAULT_SUBDIR);
3540 if (artwork.gfx_current == NULL)
3541 artwork.gfx_current = getFirstValidTreeInfoEntry(artwork.gfx_first);
3544 if (!strEqual(artwork.snd_current_identifier, setup.sounds_set))
3546 artwork.snd_current =
3547 getTreeInfoFromIdentifier(artwork.snd_first, setup.sounds_set);
3548 if (artwork.snd_current == NULL)
3549 artwork.snd_current =
3550 getTreeInfoFromIdentifier(artwork.snd_first, SND_DEFAULT_SUBDIR);
3551 if (artwork.snd_current == NULL)
3552 artwork.snd_current = getFirstValidTreeInfoEntry(artwork.snd_first);
3555 if (!strEqual(artwork.mus_current_identifier, setup.music_set))
3557 artwork.mus_current =
3558 getTreeInfoFromIdentifier(artwork.mus_first, setup.music_set);
3559 if (artwork.mus_current == NULL)
3560 artwork.mus_current =
3561 getTreeInfoFromIdentifier(artwork.mus_first, MUS_DEFAULT_SUBDIR);
3562 if (artwork.mus_current == NULL)
3563 artwork.mus_current = getFirstValidTreeInfoEntry(artwork.mus_first);
3566 print_timestamp_time("getTreeInfoFromIdentifier");
3568 sortTreeInfo(&artwork.gfx_first);
3569 sortTreeInfo(&artwork.snd_first);
3570 sortTreeInfo(&artwork.mus_first);
3572 print_timestamp_time("sortTreeInfo");
3574 #if ENABLE_UNUSED_CODE
3575 dumpTreeInfo(artwork.gfx_first, 0);
3576 dumpTreeInfo(artwork.snd_first, 0);
3577 dumpTreeInfo(artwork.mus_first, 0);
3580 print_timestamp_done("LoadLevelArtworkInfo");
3583 static boolean AddUserLevelSetToLevelInfoExt(char *level_subdir_new)
3585 // get level info tree node of first (original) user level set
3586 char *level_subdir_old = getLoginName();
3587 LevelDirTree *leveldir_old = getTreeInfoFromIdentifier(leveldir_first,
3589 if (leveldir_old == NULL) // should not happen
3592 int draw_deactivation_mask = GetDrawDeactivationMask();
3594 // override draw deactivation mask (temporarily disable drawing)
3595 SetDrawDeactivationMask(REDRAW_ALL);
3597 // load new level set config and add it next to first user level set
3598 LoadLevelInfoFromLevelConf(&leveldir_old->next, NULL,
3599 leveldir_old->basepath, level_subdir_new);
3601 // set draw deactivation mask to previous value
3602 SetDrawDeactivationMask(draw_deactivation_mask);
3604 // get level info tree node of newly added user level set
3605 LevelDirTree *leveldir_new = getTreeInfoFromIdentifier(leveldir_first,
3607 if (leveldir_new == NULL) // should not happen
3610 // correct top link and parent node link of newly created tree node
3611 leveldir_new->node_top = leveldir_old->node_top;
3612 leveldir_new->node_parent = leveldir_old->node_parent;
3614 // sort level info tree to adjust position of newly added level set
3615 sortTreeInfo(&leveldir_first);
3620 void AddUserLevelSetToLevelInfo(char *level_subdir_new)
3622 if (!AddUserLevelSetToLevelInfoExt(level_subdir_new))
3623 Error(ERR_EXIT, "internal level set structure corrupted -- aborting");
3626 char *getArtworkIdentifierForUserLevelSet(int type)
3628 char *classic_artwork_set = getClassicArtworkSet(type);
3630 /* check for custom artwork configured in "levelinfo.conf" */
3631 char *leveldir_artwork_set =
3632 *LEVELDIR_ARTWORK_SET_PTR(leveldir_current, type);
3633 boolean has_leveldir_artwork_set =
3634 (leveldir_artwork_set != NULL && !strEqual(leveldir_artwork_set,
3635 classic_artwork_set));
3637 /* check for custom artwork in sub-directory "graphics" etc. */
3638 TreeInfo *artwork_first_node = ARTWORK_FIRST_NODE(artwork, type);
3639 char *leveldir_identifier = leveldir_current->identifier;
3640 boolean has_artwork_subdir =
3641 (getTreeInfoFromIdentifier(artwork_first_node,
3642 leveldir_identifier) != NULL);
3644 return (has_leveldir_artwork_set ? leveldir_artwork_set :
3645 has_artwork_subdir ? leveldir_identifier :
3646 classic_artwork_set);
3649 TreeInfo *getArtworkTreeInfoForUserLevelSet(int type)
3651 char *artwork_set = getArtworkIdentifierForUserLevelSet(type);
3652 TreeInfo *artwork_first_node = ARTWORK_FIRST_NODE(artwork, type);
3654 return getTreeInfoFromIdentifier(artwork_first_node, artwork_set);
3657 boolean checkIfCustomArtworkExistsForCurrentLevelSet()
3659 char *graphics_set =
3660 getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_GRAPHICS);
3662 getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_SOUNDS);
3664 getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_MUSIC);
3666 return (!strEqual(graphics_set, GFX_CLASSIC_SUBDIR) ||
3667 !strEqual(sounds_set, SND_CLASSIC_SUBDIR) ||
3668 !strEqual(music_set, MUS_CLASSIC_SUBDIR));
3671 boolean UpdateUserLevelSet(char *level_subdir, char *level_name,
3672 char *level_author, int num_levels)
3674 char *filename = getPath2(getUserLevelDir(level_subdir), LEVELINFO_FILENAME);
3675 char *filename_tmp = getStringCat2(filename, ".tmp");
3677 FILE *file_tmp = NULL;
3678 char line[MAX_LINE_LEN];
3679 boolean success = FALSE;
3680 LevelDirTree *leveldir = getTreeInfoFromIdentifier(leveldir_first,
3682 // update values in level directory tree
3684 if (level_name != NULL)
3685 setString(&leveldir->name, level_name);
3687 if (level_author != NULL)
3688 setString(&leveldir->author, level_author);
3690 if (num_levels != -1)
3691 leveldir->levels = num_levels;
3693 // update values that depend on other values
3695 setString(&leveldir->name_sorting, leveldir->name);
3697 leveldir->last_level = leveldir->first_level + leveldir->levels - 1;
3699 // sort order of level sets may have changed
3700 sortTreeInfo(&leveldir_first);
3702 if ((file = fopen(filename, MODE_READ)) &&
3703 (file_tmp = fopen(filename_tmp, MODE_WRITE)))
3705 while (fgets(line, MAX_LINE_LEN, file))
3707 if (strPrefix(line, "name:") && level_name != NULL)
3708 fprintf(file_tmp, "%-32s%s\n", "name:", level_name);
3709 else if (strPrefix(line, "author:") && level_author != NULL)
3710 fprintf(file_tmp, "%-32s%s\n", "author:", level_author);
3711 else if (strPrefix(line, "levels:") && num_levels != -1)
3712 fprintf(file_tmp, "%-32s%d\n", "levels:", num_levels);
3714 fputs(line, file_tmp);
3727 success = (rename(filename_tmp, filename) == 0);
3735 boolean CreateUserLevelSet(char *level_subdir, char *level_name,
3736 char *level_author, int num_levels,
3737 boolean use_artwork_set)
3739 LevelDirTree *level_info;
3744 // create user level sub-directory, if needed
3745 createDirectory(getUserLevelDir(level_subdir), "user level", PERMS_PRIVATE);
3747 filename = getPath2(getUserLevelDir(level_subdir), LEVELINFO_FILENAME);
3749 if (!(file = fopen(filename, MODE_WRITE)))
3751 Error(ERR_WARN, "cannot write level info file '%s'", filename);
3757 level_info = newTreeInfo();
3759 /* always start with reliable default values */
3760 setTreeInfoToDefaults(level_info, TREE_TYPE_LEVEL_DIR);
3762 setString(&level_info->name, level_name);
3763 setString(&level_info->author, level_author);
3764 level_info->levels = num_levels;
3765 level_info->first_level = 1;
3766 level_info->sort_priority = LEVELCLASS_PRIVATE_START;
3767 level_info->readonly = FALSE;
3769 if (use_artwork_set)
3771 level_info->graphics_set =
3772 getStringCopy(getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_GRAPHICS));
3773 level_info->sounds_set =
3774 getStringCopy(getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_SOUNDS));
3775 level_info->music_set =
3776 getStringCopy(getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_MUSIC));
3779 token_value_position = TOKEN_VALUE_POSITION_SHORT;
3781 fprintFileHeader(file, LEVELINFO_FILENAME);
3784 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
3786 if (i == LEVELINFO_TOKEN_NAME ||
3787 i == LEVELINFO_TOKEN_AUTHOR ||
3788 i == LEVELINFO_TOKEN_LEVELS ||
3789 i == LEVELINFO_TOKEN_FIRST_LEVEL ||
3790 i == LEVELINFO_TOKEN_SORT_PRIORITY ||
3791 i == LEVELINFO_TOKEN_READONLY ||
3792 (use_artwork_set && (i == LEVELINFO_TOKEN_GRAPHICS_SET ||
3793 i == LEVELINFO_TOKEN_SOUNDS_SET ||
3794 i == LEVELINFO_TOKEN_MUSIC_SET)))
3795 fprintf(file, "%s\n", getSetupLine(levelinfo_tokens, "", i));
3797 /* just to make things nicer :) */
3798 if (i == LEVELINFO_TOKEN_AUTHOR ||
3799 i == LEVELINFO_TOKEN_FIRST_LEVEL ||
3800 (use_artwork_set && i == LEVELINFO_TOKEN_READONLY))
3801 fprintf(file, "\n");
3804 token_value_position = TOKEN_VALUE_POSITION_DEFAULT;
3808 SetFilePermissions(filename, PERMS_PRIVATE);
3810 freeTreeInfo(level_info);
3816 static void SaveUserLevelInfo()
3818 CreateUserLevelSet(getLoginName(), getLoginName(), getRealName(), 100, FALSE);
3821 char *getSetupValue(int type, void *value)
3823 static char value_string[MAX_LINE_LEN];
3831 strcpy(value_string, (*(boolean *)value ? "true" : "false"));
3835 strcpy(value_string, (*(boolean *)value ? "on" : "off"));
3839 strcpy(value_string, (*(int *)value == AUTO ? "auto" :
3840 *(int *)value == FALSE ? "off" : "on"));
3844 strcpy(value_string, (*(boolean *)value ? "yes" : "no"));
3847 case TYPE_YES_NO_AUTO:
3848 strcpy(value_string, (*(int *)value == AUTO ? "auto" :
3849 *(int *)value == FALSE ? "no" : "yes"));
3853 strcpy(value_string, (*(boolean *)value ? "AGA" : "ECS"));
3857 strcpy(value_string, getKeyNameFromKey(*(Key *)value));
3861 strcpy(value_string, getX11KeyNameFromKey(*(Key *)value));
3865 sprintf(value_string, "%d", *(int *)value);
3869 if (*(char **)value == NULL)
3872 strcpy(value_string, *(char **)value);
3876 value_string[0] = '\0';
3880 if (type & TYPE_GHOSTED)
3881 strcpy(value_string, "n/a");
3883 return value_string;
3886 char *getSetupLine(struct TokenInfo *token_info, char *prefix, int token_nr)
3890 static char token_string[MAX_LINE_LEN];
3891 int token_type = token_info[token_nr].type;
3892 void *setup_value = token_info[token_nr].value;
3893 char *token_text = token_info[token_nr].text;
3894 char *value_string = getSetupValue(token_type, setup_value);
3896 /* build complete token string */
3897 sprintf(token_string, "%s%s", prefix, token_text);
3899 /* build setup entry line */
3900 line = getFormattedSetupEntry(token_string, value_string);
3902 if (token_type == TYPE_KEY_X11)
3904 Key key = *(Key *)setup_value;
3905 char *keyname = getKeyNameFromKey(key);
3907 /* add comment, if useful */
3908 if (!strEqual(keyname, "(undefined)") &&
3909 !strEqual(keyname, "(unknown)"))
3911 /* add at least one whitespace */
3913 for (i = strlen(line); i < token_comment_position; i++)
3917 strcat(line, keyname);
3924 void LoadLevelSetup_LastSeries()
3926 /* ----------------------------------------------------------------------- */
3927 /* ~/.<program>/levelsetup.conf */
3928 /* ----------------------------------------------------------------------- */
3930 char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
3931 SetupFileHash *level_setup_hash = NULL;
3933 /* always start with reliable default values */
3934 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
3936 if (!strEqual(DEFAULT_LEVELSET, UNDEFINED_LEVELSET))
3938 leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
3940 if (leveldir_current == NULL)
3941 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
3944 if ((level_setup_hash = loadSetupFileHash(filename)))
3946 char *last_level_series =
3947 getHashEntry(level_setup_hash, TOKEN_STR_LAST_LEVEL_SERIES);
3949 leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
3951 if (leveldir_current == NULL)
3952 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
3954 freeSetupFileHash(level_setup_hash);
3958 Error(ERR_DEBUG, "using default setup values");
3964 static void SaveLevelSetup_LastSeries_Ext(boolean deactivate_last_level_series)
3966 /* ----------------------------------------------------------------------- */
3967 /* ~/.<program>/levelsetup.conf */
3968 /* ----------------------------------------------------------------------- */
3970 // check if the current level directory structure is available at this point
3971 if (leveldir_current == NULL)
3974 char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
3975 char *level_subdir = leveldir_current->subdir;
3978 InitUserDataDirectory();
3980 if (!(file = fopen(filename, MODE_WRITE)))
3982 Error(ERR_WARN, "cannot write setup file '%s'", filename);
3989 fprintFileHeader(file, LEVELSETUP_FILENAME);
3991 if (deactivate_last_level_series)
3992 fprintf(file, "# %s\n# ", "the following level set may have caused a problem and was deactivated");
3994 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_LEVEL_SERIES,
3999 SetFilePermissions(filename, PERMS_PRIVATE);
4004 void SaveLevelSetup_LastSeries()
4006 SaveLevelSetup_LastSeries_Ext(FALSE);
4009 void SaveLevelSetup_LastSeries_Deactivate()
4011 SaveLevelSetup_LastSeries_Ext(TRUE);
4014 static void checkSeriesInfo()
4016 static char *level_directory = NULL;
4019 /* check for more levels besides the 'levels' field of 'levelinfo.conf' */
4021 level_directory = getPath2((leveldir_current->in_user_dir ?
4022 getUserLevelDir(NULL) :
4023 options.level_directory),
4024 leveldir_current->fullpath);
4026 if ((dir = openDirectory(level_directory)) == NULL)
4028 Error(ERR_WARN, "cannot read level directory '%s'", level_directory);
4033 closeDirectory(dir);
4036 void LoadLevelSetup_SeriesInfo()
4039 SetupFileHash *level_setup_hash = NULL;
4040 char *level_subdir = leveldir_current->subdir;
4043 /* always start with reliable default values */
4044 level_nr = leveldir_current->first_level;
4046 for (i = 0; i < MAX_LEVELS; i++)
4048 LevelStats_setPlayed(i, 0);
4049 LevelStats_setSolved(i, 0);
4054 /* ----------------------------------------------------------------------- */
4055 /* ~/.<program>/levelsetup/<level series>/levelsetup.conf */
4056 /* ----------------------------------------------------------------------- */
4058 level_subdir = leveldir_current->subdir;
4060 filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
4062 if ((level_setup_hash = loadSetupFileHash(filename)))
4066 /* get last played level in this level set */
4068 token_value = getHashEntry(level_setup_hash, TOKEN_STR_LAST_PLAYED_LEVEL);
4072 level_nr = atoi(token_value);
4074 if (level_nr < leveldir_current->first_level)
4075 level_nr = leveldir_current->first_level;
4076 if (level_nr > leveldir_current->last_level)
4077 level_nr = leveldir_current->last_level;
4080 /* get handicap level in this level set */
4082 token_value = getHashEntry(level_setup_hash, TOKEN_STR_HANDICAP_LEVEL);
4086 int level_nr = atoi(token_value);
4088 if (level_nr < leveldir_current->first_level)
4089 level_nr = leveldir_current->first_level;
4090 if (level_nr > leveldir_current->last_level + 1)
4091 level_nr = leveldir_current->last_level;
4093 if (leveldir_current->user_defined || !leveldir_current->handicap)
4094 level_nr = leveldir_current->last_level;
4096 leveldir_current->handicap_level = level_nr;
4099 /* get number of played and solved levels in this level set */
4101 BEGIN_HASH_ITERATION(level_setup_hash, itr)
4103 char *token = HASH_ITERATION_TOKEN(itr);
4104 char *value = HASH_ITERATION_VALUE(itr);
4106 if (strlen(token) == 3 &&
4107 token[0] >= '0' && token[0] <= '9' &&
4108 token[1] >= '0' && token[1] <= '9' &&
4109 token[2] >= '0' && token[2] <= '9')
4111 int level_nr = atoi(token);
4114 LevelStats_setPlayed(level_nr, atoi(value)); /* read 1st column */
4116 value = strchr(value, ' ');
4119 LevelStats_setSolved(level_nr, atoi(value)); /* read 2nd column */
4122 END_HASH_ITERATION(hash, itr)
4124 freeSetupFileHash(level_setup_hash);
4128 Error(ERR_DEBUG, "using default setup values");
4134 void SaveLevelSetup_SeriesInfo()
4137 char *level_subdir = leveldir_current->subdir;
4138 char *level_nr_str = int2str(level_nr, 0);
4139 char *handicap_level_str = int2str(leveldir_current->handicap_level, 0);
4143 /* ----------------------------------------------------------------------- */
4144 /* ~/.<program>/levelsetup/<level series>/levelsetup.conf */
4145 /* ----------------------------------------------------------------------- */
4147 InitLevelSetupDirectory(level_subdir);
4149 filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
4151 if (!(file = fopen(filename, MODE_WRITE)))
4153 Error(ERR_WARN, "cannot write setup file '%s'", filename);
4158 fprintFileHeader(file, LEVELSETUP_FILENAME);
4160 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_PLAYED_LEVEL,
4162 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_HANDICAP_LEVEL,
4163 handicap_level_str));
4165 for (i = leveldir_current->first_level; i <= leveldir_current->last_level;
4168 if (LevelStats_getPlayed(i) > 0 ||
4169 LevelStats_getSolved(i) > 0)
4174 sprintf(token, "%03d", i);
4175 sprintf(value, "%d %d", LevelStats_getPlayed(i), LevelStats_getSolved(i));
4177 fprintf(file, "%s\n", getFormattedSetupEntry(token, value));
4183 SetFilePermissions(filename, PERMS_PRIVATE);
4188 int LevelStats_getPlayed(int nr)
4190 return (nr >= 0 && nr < MAX_LEVELS ? level_stats[nr].played : 0);
4193 int LevelStats_getSolved(int nr)
4195 return (nr >= 0 && nr < MAX_LEVELS ? level_stats[nr].solved : 0);
4198 void LevelStats_setPlayed(int nr, int value)
4200 if (nr >= 0 && nr < MAX_LEVELS)
4201 level_stats[nr].played = value;
4204 void LevelStats_setSolved(int nr, int value)
4206 if (nr >= 0 && nr < MAX_LEVELS)
4207 level_stats[nr].solved = value;
4210 void LevelStats_incPlayed(int nr)
4212 if (nr >= 0 && nr < MAX_LEVELS)
4213 level_stats[nr].played++;
4216 void LevelStats_incSolved(int nr)
4218 if (nr >= 0 && nr < MAX_LEVELS)
4219 level_stats[nr].solved++;