1 // ============================================================================
2 // Artsoft Retro-Game Library
3 // ----------------------------------------------------------------------------
4 // (c) 1995-2014 by Artsoft Entertainment
7 // http://www.artsoft.org/
8 // ----------------------------------------------------------------------------
10 // ============================================================================
12 #include <sys/types.h>
21 #if !defined(PLATFORM_WIN32)
23 #include <sys/param.h>
33 #define ENABLE_UNUSED_CODE FALSE /* for currently unused functions */
34 #define DEBUG_NO_CONFIG_FILE FALSE /* for extra-verbose debug output */
36 #define NUM_LEVELCLASS_DESC 8
38 static char *levelclass_desc[NUM_LEVELCLASS_DESC] =
51 #define LEVELCOLOR(n) (IS_LEVELCLASS_TUTORIAL(n) ? FC_BLUE : \
52 IS_LEVELCLASS_CLASSICS(n) ? FC_RED : \
53 IS_LEVELCLASS_BD(n) ? FC_YELLOW : \
54 IS_LEVELCLASS_EM(n) ? FC_YELLOW : \
55 IS_LEVELCLASS_SP(n) ? FC_YELLOW : \
56 IS_LEVELCLASS_DX(n) ? FC_YELLOW : \
57 IS_LEVELCLASS_SB(n) ? FC_YELLOW : \
58 IS_LEVELCLASS_CONTRIB(n) ? FC_GREEN : \
59 IS_LEVELCLASS_PRIVATE(n) ? FC_RED : \
62 #define LEVELSORTING(n) (IS_LEVELCLASS_TUTORIAL(n) ? 0 : \
63 IS_LEVELCLASS_CLASSICS(n) ? 1 : \
64 IS_LEVELCLASS_BD(n) ? 2 : \
65 IS_LEVELCLASS_EM(n) ? 3 : \
66 IS_LEVELCLASS_SP(n) ? 4 : \
67 IS_LEVELCLASS_DX(n) ? 5 : \
68 IS_LEVELCLASS_SB(n) ? 6 : \
69 IS_LEVELCLASS_CONTRIB(n) ? 7 : \
70 IS_LEVELCLASS_PRIVATE(n) ? 8 : \
73 #define ARTWORKCOLOR(n) (IS_ARTWORKCLASS_CLASSICS(n) ? FC_RED : \
74 IS_ARTWORKCLASS_CONTRIB(n) ? FC_GREEN : \
75 IS_ARTWORKCLASS_PRIVATE(n) ? FC_RED : \
76 IS_ARTWORKCLASS_LEVEL(n) ? FC_YELLOW : \
79 #define ARTWORKSORTING(n) (IS_ARTWORKCLASS_CLASSICS(n) ? 0 : \
80 IS_ARTWORKCLASS_LEVEL(n) ? 1 : \
81 IS_ARTWORKCLASS_CONTRIB(n) ? 2 : \
82 IS_ARTWORKCLASS_PRIVATE(n) ? 3 : \
85 #define TOKEN_VALUE_POSITION_SHORT 32
86 #define TOKEN_VALUE_POSITION_DEFAULT 40
87 #define TOKEN_COMMENT_POSITION_DEFAULT 60
89 #define MAX_COOKIE_LEN 256
92 static void setTreeInfoToDefaults(TreeInfo *, int);
93 static TreeInfo *getTreeInfoCopy(TreeInfo *ti);
94 static int compareTreeInfoEntries(const void *, const void *);
96 static int token_value_position = TOKEN_VALUE_POSITION_DEFAULT;
97 static int token_comment_position = TOKEN_COMMENT_POSITION_DEFAULT;
99 static SetupFileHash *artworkinfo_cache_old = NULL;
100 static SetupFileHash *artworkinfo_cache_new = NULL;
101 static boolean use_artworkinfo_cache = TRUE;
104 /* ------------------------------------------------------------------------- */
106 /* ------------------------------------------------------------------------- */
108 static char *getLevelClassDescription(TreeInfo *ti)
110 int position = ti->sort_priority / 100;
112 if (position >= 0 && position < NUM_LEVELCLASS_DESC)
113 return levelclass_desc[position];
115 return "Unknown Level Class";
118 static char *getScoreDir(char *level_subdir)
120 static char *score_dir = NULL;
121 static char *score_level_dir = NULL;
122 char *score_subdir = SCORES_DIRECTORY;
124 if (score_dir == NULL)
126 if (program.global_scores)
127 score_dir = getPath2(getCommonDataDir(), score_subdir);
129 score_dir = getPath2(getUserGameDataDir(), score_subdir);
132 if (level_subdir != NULL)
134 checked_free(score_level_dir);
136 score_level_dir = getPath2(score_dir, level_subdir);
138 return score_level_dir;
144 static char *getLevelSetupDir(char *level_subdir)
146 static char *levelsetup_dir = NULL;
147 char *data_dir = getUserGameDataDir();
148 char *levelsetup_subdir = LEVELSETUP_DIRECTORY;
150 checked_free(levelsetup_dir);
152 if (level_subdir != NULL)
153 levelsetup_dir = getPath3(data_dir, levelsetup_subdir, level_subdir);
155 levelsetup_dir = getPath2(data_dir, levelsetup_subdir);
157 return levelsetup_dir;
160 static char *getCacheDir()
162 static char *cache_dir = NULL;
164 if (cache_dir == NULL)
165 cache_dir = getPath2(getUserGameDataDir(), CACHE_DIRECTORY);
170 static char *getNetworkDir()
172 static char *network_dir = NULL;
174 if (network_dir == NULL)
175 network_dir = getPath2(getUserGameDataDir(), NETWORK_DIRECTORY);
180 static char *getLevelDirFromTreeInfo(TreeInfo *node)
182 static char *level_dir = NULL;
185 return options.level_directory;
187 checked_free(level_dir);
189 level_dir = getPath2((node->in_user_dir ? getUserLevelDir(NULL) :
190 options.level_directory), node->fullpath);
195 char *getUserLevelDir(char *level_subdir)
197 static char *userlevel_dir = NULL;
198 char *data_dir = getUserGameDataDir();
199 char *userlevel_subdir = LEVELS_DIRECTORY;
201 checked_free(userlevel_dir);
203 if (level_subdir != NULL)
204 userlevel_dir = getPath3(data_dir, userlevel_subdir, level_subdir);
206 userlevel_dir = getPath2(data_dir, userlevel_subdir);
208 return userlevel_dir;
211 char *getNetworkLevelDir(char *level_subdir)
213 static char *network_level_dir = NULL;
214 char *data_dir = getNetworkDir();
215 char *networklevel_subdir = LEVELS_DIRECTORY;
217 checked_free(network_level_dir);
219 if (level_subdir != NULL)
220 network_level_dir = getPath3(data_dir, networklevel_subdir, level_subdir);
222 network_level_dir = getPath2(data_dir, networklevel_subdir);
224 return network_level_dir;
227 char *getCurrentLevelDir()
229 return getLevelDirFromTreeInfo(leveldir_current);
232 char *getNewUserLevelSubdir()
234 static char *new_level_subdir = NULL;
235 char *subdir_prefix = getLoginName();
236 char subdir_suffix[10];
237 int max_suffix_number = 1000;
240 while (++i < max_suffix_number)
242 sprintf(subdir_suffix, "_%d", i);
244 checked_free(new_level_subdir);
245 new_level_subdir = getStringCat2(subdir_prefix, subdir_suffix);
247 if (!directoryExists(getUserLevelDir(new_level_subdir)))
251 return new_level_subdir;
254 static char *getTapeDir(char *level_subdir)
256 static char *tape_dir = NULL;
257 char *data_dir = getUserGameDataDir();
258 char *tape_subdir = TAPES_DIRECTORY;
260 checked_free(tape_dir);
262 if (level_subdir != NULL)
263 tape_dir = getPath3(data_dir, tape_subdir, level_subdir);
265 tape_dir = getPath2(data_dir, tape_subdir);
270 static char *getSolutionTapeDir()
272 static char *tape_dir = NULL;
273 char *data_dir = getCurrentLevelDir();
274 char *tape_subdir = TAPES_DIRECTORY;
276 checked_free(tape_dir);
278 tape_dir = getPath2(data_dir, tape_subdir);
283 static char *getDefaultGraphicsDir(char *graphics_subdir)
285 static char *graphics_dir = NULL;
287 if (graphics_subdir == NULL)
288 return options.graphics_directory;
290 checked_free(graphics_dir);
292 graphics_dir = getPath2(options.graphics_directory, graphics_subdir);
297 static char *getDefaultSoundsDir(char *sounds_subdir)
299 static char *sounds_dir = NULL;
301 if (sounds_subdir == NULL)
302 return options.sounds_directory;
304 checked_free(sounds_dir);
306 sounds_dir = getPath2(options.sounds_directory, sounds_subdir);
311 static char *getDefaultMusicDir(char *music_subdir)
313 static char *music_dir = NULL;
315 if (music_subdir == NULL)
316 return options.music_directory;
318 checked_free(music_dir);
320 music_dir = getPath2(options.music_directory, music_subdir);
325 static char *getClassicArtworkSet(int type)
327 return (type == TREE_TYPE_GRAPHICS_DIR ? GFX_CLASSIC_SUBDIR :
328 type == TREE_TYPE_SOUNDS_DIR ? SND_CLASSIC_SUBDIR :
329 type == TREE_TYPE_MUSIC_DIR ? MUS_CLASSIC_SUBDIR : "");
332 static char *getClassicArtworkDir(int type)
334 return (type == TREE_TYPE_GRAPHICS_DIR ?
335 getDefaultGraphicsDir(GFX_CLASSIC_SUBDIR) :
336 type == TREE_TYPE_SOUNDS_DIR ?
337 getDefaultSoundsDir(SND_CLASSIC_SUBDIR) :
338 type == TREE_TYPE_MUSIC_DIR ?
339 getDefaultMusicDir(MUS_CLASSIC_SUBDIR) : "");
342 static char *getUserGraphicsDir()
344 static char *usergraphics_dir = NULL;
346 if (usergraphics_dir == NULL)
347 usergraphics_dir = getPath2(getUserGameDataDir(), GRAPHICS_DIRECTORY);
349 return usergraphics_dir;
352 static char *getUserSoundsDir()
354 static char *usersounds_dir = NULL;
356 if (usersounds_dir == NULL)
357 usersounds_dir = getPath2(getUserGameDataDir(), SOUNDS_DIRECTORY);
359 return usersounds_dir;
362 static char *getUserMusicDir()
364 static char *usermusic_dir = NULL;
366 if (usermusic_dir == NULL)
367 usermusic_dir = getPath2(getUserGameDataDir(), MUSIC_DIRECTORY);
369 return usermusic_dir;
372 static char *getSetupArtworkDir(TreeInfo *ti)
374 static char *artwork_dir = NULL;
379 checked_free(artwork_dir);
381 artwork_dir = getPath2(ti->basepath, ti->fullpath);
386 char *setLevelArtworkDir(TreeInfo *ti)
388 char **artwork_path_ptr, **artwork_set_ptr;
389 TreeInfo *level_artwork;
391 if (ti == NULL || leveldir_current == NULL)
394 artwork_path_ptr = LEVELDIR_ARTWORK_PATH_PTR(leveldir_current, ti->type);
395 artwork_set_ptr = LEVELDIR_ARTWORK_SET_PTR( leveldir_current, ti->type);
397 checked_free(*artwork_path_ptr);
399 if ((level_artwork = getTreeInfoFromIdentifier(ti, *artwork_set_ptr)))
401 *artwork_path_ptr = getStringCopy(getSetupArtworkDir(level_artwork));
406 No (or non-existing) artwork configured in "levelinfo.conf". This would
407 normally result in using the artwork configured in the setup menu. But
408 if an artwork subdirectory exists (which might contain custom artwork
409 or an artwork configuration file), this level artwork must be treated
410 as relative to the default "classic" artwork, not to the artwork that
411 is currently configured in the setup menu.
413 Update: For "special" versions of R'n'D (like "R'n'D jue"), do not use
414 the "default" artwork (which would be "jue0" for "R'n'D jue"), but use
415 the real "classic" artwork from the original R'n'D (like "gfx_classic").
418 char *dir = getPath2(getCurrentLevelDir(), ARTWORK_DIRECTORY(ti->type));
420 checked_free(*artwork_set_ptr);
422 if (directoryExists(dir))
424 *artwork_path_ptr = getStringCopy(getClassicArtworkDir(ti->type));
425 *artwork_set_ptr = getStringCopy(getClassicArtworkSet(ti->type));
429 *artwork_path_ptr = getStringCopy(UNDEFINED_FILENAME);
430 *artwork_set_ptr = NULL;
436 return *artwork_set_ptr;
439 inline static char *getLevelArtworkSet(int type)
441 if (leveldir_current == NULL)
444 return LEVELDIR_ARTWORK_SET(leveldir_current, type);
447 inline static char *getLevelArtworkDir(int type)
449 if (leveldir_current == NULL)
450 return UNDEFINED_FILENAME;
452 return LEVELDIR_ARTWORK_PATH(leveldir_current, type);
455 char *getProgramMainDataPath(char *command_filename, char *base_path)
457 /* check if the program's main data base directory is configured */
458 if (!strEqual(base_path, "."))
461 /* if the program is configured to start from current directory (default),
462 determine program package directory from program binary (some versions
463 of KDE/Konqueror and Mac OS X (especially "Mavericks") apparently do not
464 set the current working directory to the program package directory) */
465 char *main_data_path = getBasePath(command_filename);
467 #if defined(PLATFORM_MACOSX)
468 if (strSuffix(main_data_path, MAC_APP_BINARY_SUBDIR))
470 char *main_data_path_old = main_data_path;
472 // cut relative path to Mac OS X application binary directory from path
473 main_data_path[strlen(main_data_path) -
474 strlen(MAC_APP_BINARY_SUBDIR)] = '\0';
476 // cut trailing path separator from path (but not if path is root directory)
477 if (strSuffix(main_data_path, "/") && !strEqual(main_data_path, "/"))
478 main_data_path[strlen(main_data_path) - 1] = '\0';
480 // replace empty path with current directory
481 if (strEqual(main_data_path, ""))
482 main_data_path = ".";
484 // add relative path to Mac OS X application resources directory to path
485 main_data_path = getPath2(main_data_path, MAC_APP_FILES_SUBDIR);
487 free(main_data_path_old);
491 return main_data_path;
494 char *getProgramConfigFilename(char *command_filename)
496 char *command_filename_1 = getStringCopy(command_filename);
498 // strip trailing executable suffix from command filename
499 if (strSuffix(command_filename_1, ".exe"))
500 command_filename_1[strlen(command_filename_1) - 4] = '\0';
502 char *ro_base_path = getProgramMainDataPath(command_filename, RO_BASE_PATH);
503 char *conf_directory = getPath2(ro_base_path, CONF_DIRECTORY);
505 char *command_basepath = getBasePath(command_filename);
506 char *command_basename = getBaseNameNoSuffix(command_filename);
507 char *command_filename_2 = getPath2(command_basepath, command_basename);
509 char *config_filename_1 = getStringCat2(command_filename_1, ".conf");
510 char *config_filename_2 = getStringCat2(command_filename_2, ".conf");
511 char *config_filename_3 = getPath2(conf_directory, SETUP_FILENAME);
513 // 1st try: look for config file that exactly matches the binary filename
514 if (fileExists(config_filename_1))
515 return config_filename_1;
517 // 2nd try: look for config file that matches binary filename without suffix
518 if (fileExists(config_filename_2))
519 return config_filename_2;
521 // 3rd try: return setup config filename in global program config directory
522 return config_filename_3;
525 char *getTapeFilename(int nr)
527 static char *filename = NULL;
528 char basename[MAX_FILENAME_LEN];
530 checked_free(filename);
532 sprintf(basename, "%03d.%s", nr, TAPEFILE_EXTENSION);
533 filename = getPath2(getTapeDir(leveldir_current->subdir), basename);
538 char *getSolutionTapeFilename(int nr)
540 static char *filename = NULL;
541 char basename[MAX_FILENAME_LEN];
543 checked_free(filename);
545 sprintf(basename, "%03d.%s", nr, TAPEFILE_EXTENSION);
546 filename = getPath2(getSolutionTapeDir(), basename);
548 if (!fileExists(filename))
550 static char *filename_sln = NULL;
552 checked_free(filename_sln);
554 sprintf(basename, "%03d.sln", nr);
555 filename_sln = getPath2(getSolutionTapeDir(), basename);
557 if (fileExists(filename_sln))
564 char *getScoreFilename(int nr)
566 static char *filename = NULL;
567 char basename[MAX_FILENAME_LEN];
569 checked_free(filename);
571 sprintf(basename, "%03d.%s", nr, SCOREFILE_EXTENSION);
572 filename = getPath2(getScoreDir(leveldir_current->subdir), basename);
577 char *getSetupFilename()
579 static char *filename = NULL;
581 checked_free(filename);
583 filename = getPath2(getSetupDir(), SETUP_FILENAME);
588 char *getDefaultSetupFilename()
590 return program.config_filename;
593 char *getEditorSetupFilename()
595 static char *filename = NULL;
597 checked_free(filename);
598 filename = getPath2(getCurrentLevelDir(), EDITORSETUP_FILENAME);
600 if (fileExists(filename))
603 checked_free(filename);
604 filename = getPath2(getSetupDir(), EDITORSETUP_FILENAME);
609 char *getHelpAnimFilename()
611 static char *filename = NULL;
613 checked_free(filename);
615 filename = getPath2(getCurrentLevelDir(), HELPANIM_FILENAME);
620 char *getHelpTextFilename()
622 static char *filename = NULL;
624 checked_free(filename);
626 filename = getPath2(getCurrentLevelDir(), HELPTEXT_FILENAME);
631 char *getLevelSetInfoFilename()
633 static char *filename = NULL;
648 for (i = 0; basenames[i] != NULL; i++)
650 checked_free(filename);
651 filename = getPath2(getCurrentLevelDir(), basenames[i]);
653 if (fileExists(filename))
660 char *getLevelSetTitleMessageBasename(int nr, boolean initial)
662 static char basename[32];
664 sprintf(basename, "%s_%d.txt",
665 (initial ? "titlemessage_initial" : "titlemessage"), nr + 1);
670 char *getLevelSetTitleMessageFilename(int nr, boolean initial)
672 static char *filename = NULL;
674 boolean skip_setup_artwork = FALSE;
676 checked_free(filename);
678 basename = getLevelSetTitleMessageBasename(nr, initial);
680 if (!gfx.override_level_graphics)
682 /* 1st try: look for special artwork in current level series directory */
683 filename = getPath3(getCurrentLevelDir(), GRAPHICS_DIRECTORY, basename);
684 if (fileExists(filename))
689 /* 2nd try: look for message file in current level set directory */
690 filename = getPath2(getCurrentLevelDir(), basename);
691 if (fileExists(filename))
696 /* check if there is special artwork configured in level series config */
697 if (getLevelArtworkSet(ARTWORK_TYPE_GRAPHICS) != NULL)
699 /* 3rd try: look for special artwork configured in level series config */
700 filename = getPath2(getLevelArtworkDir(ARTWORK_TYPE_GRAPHICS), basename);
701 if (fileExists(filename))
706 /* take missing artwork configured in level set config from default */
707 skip_setup_artwork = TRUE;
711 if (!skip_setup_artwork)
713 /* 4th try: look for special artwork in configured artwork directory */
714 filename = getPath2(getSetupArtworkDir(artwork.gfx_current), basename);
715 if (fileExists(filename))
721 /* 5th try: look for default artwork in new default artwork directory */
722 filename = getPath2(getDefaultGraphicsDir(GFX_DEFAULT_SUBDIR), basename);
723 if (fileExists(filename))
728 /* 6th try: look for default artwork in old default artwork directory */
729 filename = getPath2(options.graphics_directory, basename);
730 if (fileExists(filename))
733 return NULL; /* cannot find specified artwork file anywhere */
736 static char *getCorrectedArtworkBasename(char *basename)
741 char *getCustomImageFilename(char *basename)
743 static char *filename = NULL;
744 boolean skip_setup_artwork = FALSE;
746 checked_free(filename);
748 basename = getCorrectedArtworkBasename(basename);
750 if (!gfx.override_level_graphics)
752 /* 1st try: look for special artwork in current level series directory */
753 filename = getImg3(getCurrentLevelDir(), GRAPHICS_DIRECTORY, basename);
754 if (fileExists(filename))
759 /* check if there is special artwork configured in level series config */
760 if (getLevelArtworkSet(ARTWORK_TYPE_GRAPHICS) != NULL)
762 /* 2nd try: look for special artwork configured in level series config */
763 filename = getImg2(getLevelArtworkDir(ARTWORK_TYPE_GRAPHICS), basename);
764 if (fileExists(filename))
769 /* take missing artwork configured in level set config from default */
770 skip_setup_artwork = TRUE;
774 if (!skip_setup_artwork)
776 /* 3rd try: look for special artwork in configured artwork directory */
777 filename = getImg2(getSetupArtworkDir(artwork.gfx_current), basename);
778 if (fileExists(filename))
784 /* 4th try: look for default artwork in new default artwork directory */
785 filename = getImg2(getDefaultGraphicsDir(GFX_DEFAULT_SUBDIR), basename);
786 if (fileExists(filename))
791 /* 5th try: look for default artwork in old default artwork directory */
792 filename = getImg2(options.graphics_directory, basename);
793 if (fileExists(filename))
796 if (!strEqual(GFX_FALLBACK_FILENAME, UNDEFINED_FILENAME))
801 Error(ERR_WARN, "cannot find artwork file '%s' (using fallback)",
804 /* 6th try: look for fallback artwork in old default artwork directory */
805 /* (needed to prevent errors when trying to access unused artwork files) */
806 filename = getImg2(options.graphics_directory, GFX_FALLBACK_FILENAME);
807 if (fileExists(filename))
811 return NULL; /* cannot find specified artwork file anywhere */
814 char *getCustomSoundFilename(char *basename)
816 static char *filename = NULL;
817 boolean skip_setup_artwork = FALSE;
819 checked_free(filename);
821 basename = getCorrectedArtworkBasename(basename);
823 if (!gfx.override_level_sounds)
825 /* 1st try: look for special artwork in current level series directory */
826 filename = getPath3(getCurrentLevelDir(), SOUNDS_DIRECTORY, basename);
827 if (fileExists(filename))
832 /* check if there is special artwork configured in level series config */
833 if (getLevelArtworkSet(ARTWORK_TYPE_SOUNDS) != NULL)
835 /* 2nd try: look for special artwork configured in level series config */
836 filename = getPath2(getLevelArtworkDir(TREE_TYPE_SOUNDS_DIR), basename);
837 if (fileExists(filename))
842 /* take missing artwork configured in level set config from default */
843 skip_setup_artwork = TRUE;
847 if (!skip_setup_artwork)
849 /* 3rd try: look for special artwork in configured artwork directory */
850 filename = getPath2(getSetupArtworkDir(artwork.snd_current), basename);
851 if (fileExists(filename))
857 /* 4th try: look for default artwork in new default artwork directory */
858 filename = getPath2(getDefaultSoundsDir(SND_DEFAULT_SUBDIR), basename);
859 if (fileExists(filename))
864 /* 5th try: look for default artwork in old default artwork directory */
865 filename = getPath2(options.sounds_directory, basename);
866 if (fileExists(filename))
869 if (!strEqual(SND_FALLBACK_FILENAME, UNDEFINED_FILENAME))
874 Error(ERR_WARN, "cannot find artwork file '%s' (using fallback)",
877 /* 6th try: look for fallback artwork in old default artwork directory */
878 /* (needed to prevent errors when trying to access unused artwork files) */
879 filename = getPath2(options.sounds_directory, SND_FALLBACK_FILENAME);
880 if (fileExists(filename))
884 return NULL; /* cannot find specified artwork file anywhere */
887 char *getCustomMusicFilename(char *basename)
889 static char *filename = NULL;
890 boolean skip_setup_artwork = FALSE;
892 checked_free(filename);
894 basename = getCorrectedArtworkBasename(basename);
896 if (!gfx.override_level_music)
898 /* 1st try: look for special artwork in current level series directory */
899 filename = getPath3(getCurrentLevelDir(), MUSIC_DIRECTORY, basename);
900 if (fileExists(filename))
905 /* check if there is special artwork configured in level series config */
906 if (getLevelArtworkSet(ARTWORK_TYPE_MUSIC) != NULL)
908 /* 2nd try: look for special artwork configured in level series config */
909 filename = getPath2(getLevelArtworkDir(TREE_TYPE_MUSIC_DIR), basename);
910 if (fileExists(filename))
915 /* take missing artwork configured in level set config from default */
916 skip_setup_artwork = TRUE;
920 if (!skip_setup_artwork)
922 /* 3rd try: look for special artwork in configured artwork directory */
923 filename = getPath2(getSetupArtworkDir(artwork.mus_current), basename);
924 if (fileExists(filename))
930 /* 4th try: look for default artwork in new default artwork directory */
931 filename = getPath2(getDefaultMusicDir(MUS_DEFAULT_SUBDIR), basename);
932 if (fileExists(filename))
937 /* 5th try: look for default artwork in old default artwork directory */
938 filename = getPath2(options.music_directory, basename);
939 if (fileExists(filename))
942 if (!strEqual(MUS_FALLBACK_FILENAME, UNDEFINED_FILENAME))
947 Error(ERR_WARN, "cannot find artwork file '%s' (using fallback)",
950 /* 6th try: look for fallback artwork in old default artwork directory */
951 /* (needed to prevent errors when trying to access unused artwork files) */
952 filename = getPath2(options.music_directory, MUS_FALLBACK_FILENAME);
953 if (fileExists(filename))
957 return NULL; /* cannot find specified artwork file anywhere */
960 char *getCustomArtworkFilename(char *basename, int type)
962 if (type == ARTWORK_TYPE_GRAPHICS)
963 return getCustomImageFilename(basename);
964 else if (type == ARTWORK_TYPE_SOUNDS)
965 return getCustomSoundFilename(basename);
966 else if (type == ARTWORK_TYPE_MUSIC)
967 return getCustomMusicFilename(basename);
969 return UNDEFINED_FILENAME;
972 char *getCustomArtworkConfigFilename(int type)
974 return getCustomArtworkFilename(ARTWORKINFO_FILENAME(type), type);
977 char *getCustomArtworkLevelConfigFilename(int type)
979 static char *filename = NULL;
981 checked_free(filename);
983 filename = getPath2(getLevelArtworkDir(type), ARTWORKINFO_FILENAME(type));
988 char *getCustomMusicDirectory(void)
990 static char *directory = NULL;
991 boolean skip_setup_artwork = FALSE;
993 checked_free(directory);
995 if (!gfx.override_level_music)
997 /* 1st try: look for special artwork in current level series directory */
998 directory = getPath2(getCurrentLevelDir(), MUSIC_DIRECTORY);
999 if (directoryExists(directory))
1004 /* check if there is special artwork configured in level series config */
1005 if (getLevelArtworkSet(ARTWORK_TYPE_MUSIC) != NULL)
1007 /* 2nd try: look for special artwork configured in level series config */
1008 directory = getStringCopy(getLevelArtworkDir(TREE_TYPE_MUSIC_DIR));
1009 if (directoryExists(directory))
1014 /* take missing artwork configured in level set config from default */
1015 skip_setup_artwork = TRUE;
1019 if (!skip_setup_artwork)
1021 /* 3rd try: look for special artwork in configured artwork directory */
1022 directory = getStringCopy(getSetupArtworkDir(artwork.mus_current));
1023 if (directoryExists(directory))
1029 /* 4th try: look for default artwork in new default artwork directory */
1030 directory = getStringCopy(getDefaultMusicDir(MUS_DEFAULT_SUBDIR));
1031 if (directoryExists(directory))
1036 /* 5th try: look for default artwork in old default artwork directory */
1037 directory = getStringCopy(options.music_directory);
1038 if (directoryExists(directory))
1041 return NULL; /* cannot find specified artwork file anywhere */
1044 void InitTapeDirectory(char *level_subdir)
1046 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
1047 createDirectory(getTapeDir(NULL), "main tape", PERMS_PRIVATE);
1048 createDirectory(getTapeDir(level_subdir), "level tape", PERMS_PRIVATE);
1051 void InitScoreDirectory(char *level_subdir)
1053 int permissions = (program.global_scores ? PERMS_PUBLIC : PERMS_PRIVATE);
1055 if (program.global_scores)
1056 createDirectory(getCommonDataDir(), "common data", permissions);
1058 createDirectory(getUserGameDataDir(), "user data", permissions);
1060 createDirectory(getScoreDir(NULL), "main score", permissions);
1061 createDirectory(getScoreDir(level_subdir), "level score", permissions);
1064 static void SaveUserLevelInfo();
1066 void InitUserLevelDirectory(char *level_subdir)
1068 if (!directoryExists(getUserLevelDir(level_subdir)))
1070 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
1071 createDirectory(getUserLevelDir(NULL), "main user level", PERMS_PRIVATE);
1072 createDirectory(getUserLevelDir(level_subdir), "user level", PERMS_PRIVATE);
1074 SaveUserLevelInfo();
1078 void InitNetworkLevelDirectory(char *level_subdir)
1080 if (!directoryExists(getNetworkLevelDir(level_subdir)))
1082 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
1083 createDirectory(getNetworkDir(), "network data", PERMS_PRIVATE);
1084 createDirectory(getNetworkLevelDir(NULL), "main network level", PERMS_PRIVATE);
1085 createDirectory(getNetworkLevelDir(level_subdir), "network level", PERMS_PRIVATE);
1089 void InitLevelSetupDirectory(char *level_subdir)
1091 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
1092 createDirectory(getLevelSetupDir(NULL), "main level setup", PERMS_PRIVATE);
1093 createDirectory(getLevelSetupDir(level_subdir), "level setup", PERMS_PRIVATE);
1096 void InitCacheDirectory()
1098 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
1099 createDirectory(getCacheDir(), "cache data", PERMS_PRIVATE);
1103 /* ------------------------------------------------------------------------- */
1104 /* some functions to handle lists of level and artwork directories */
1105 /* ------------------------------------------------------------------------- */
1107 TreeInfo *newTreeInfo()
1109 return checked_calloc(sizeof(TreeInfo));
1112 TreeInfo *newTreeInfo_setDefaults(int type)
1114 TreeInfo *ti = newTreeInfo();
1116 setTreeInfoToDefaults(ti, type);
1121 void pushTreeInfo(TreeInfo **node_first, TreeInfo *node_new)
1123 node_new->next = *node_first;
1124 *node_first = node_new;
1127 int numTreeInfo(TreeInfo *node)
1140 boolean validLevelSeries(TreeInfo *node)
1142 return (node != NULL && !node->node_group && !node->parent_link);
1145 TreeInfo *getFirstValidTreeInfoEntry(TreeInfo *node)
1150 if (node->node_group) /* enter level group (step down into tree) */
1151 return getFirstValidTreeInfoEntry(node->node_group);
1152 else if (node->parent_link) /* skip start entry of level group */
1154 if (node->next) /* get first real level series entry */
1155 return getFirstValidTreeInfoEntry(node->next);
1156 else /* leave empty level group and go on */
1157 return getFirstValidTreeInfoEntry(node->node_parent->next);
1159 else /* this seems to be a regular level series */
1163 TreeInfo *getTreeInfoFirstGroupEntry(TreeInfo *node)
1168 if (node->node_parent == NULL) /* top level group */
1169 return *node->node_top;
1170 else /* sub level group */
1171 return node->node_parent->node_group;
1174 int numTreeInfoInGroup(TreeInfo *node)
1176 return numTreeInfo(getTreeInfoFirstGroupEntry(node));
1179 int posTreeInfo(TreeInfo *node)
1181 TreeInfo *node_cmp = getTreeInfoFirstGroupEntry(node);
1186 if (node_cmp == node)
1190 node_cmp = node_cmp->next;
1196 TreeInfo *getTreeInfoFromPos(TreeInfo *node, int pos)
1198 TreeInfo *node_default = node;
1210 return node_default;
1213 TreeInfo *getTreeInfoFromIdentifier(TreeInfo *node, char *identifier)
1215 if (identifier == NULL)
1220 if (node->node_group)
1222 TreeInfo *node_group;
1224 node_group = getTreeInfoFromIdentifier(node->node_group, identifier);
1229 else if (!node->parent_link)
1231 if (strEqual(identifier, node->identifier))
1241 TreeInfo *cloneTreeNode(TreeInfo **node_top, TreeInfo *node_parent,
1242 TreeInfo *node, boolean skip_sets_without_levels)
1249 if (!node->parent_link && !node->level_group &&
1250 skip_sets_without_levels && node->levels == 0)
1251 return cloneTreeNode(node_top, node_parent, node->next,
1252 skip_sets_without_levels);
1254 node_new = getTreeInfoCopy(node); /* copy complete node */
1256 node_new->node_top = node_top; /* correct top node link */
1257 node_new->node_parent = node_parent; /* correct parent node link */
1259 if (node->level_group)
1260 node_new->node_group = cloneTreeNode(node_top, node_new, node->node_group,
1261 skip_sets_without_levels);
1263 node_new->next = cloneTreeNode(node_top, node_parent, node->next,
1264 skip_sets_without_levels);
1269 void cloneTree(TreeInfo **ti_new, TreeInfo *ti, boolean skip_empty_sets)
1271 TreeInfo *ti_cloned = cloneTreeNode(ti_new, NULL, ti, skip_empty_sets);
1273 *ti_new = ti_cloned;
1276 static boolean adjustTreeGraphicsForEMC(TreeInfo *node)
1278 boolean settings_changed = FALSE;
1282 if (node->graphics_set_ecs && !setup.prefer_aga_graphics &&
1283 !strEqual(node->graphics_set, node->graphics_set_ecs))
1285 setString(&node->graphics_set, node->graphics_set_ecs);
1286 settings_changed = TRUE;
1288 else if (node->graphics_set_aga && setup.prefer_aga_graphics &&
1289 !strEqual(node->graphics_set, node->graphics_set_aga))
1291 setString(&node->graphics_set, node->graphics_set_aga);
1292 settings_changed = TRUE;
1295 if (node->node_group != NULL)
1296 settings_changed |= adjustTreeGraphicsForEMC(node->node_group);
1301 return settings_changed;
1304 void dumpTreeInfo(TreeInfo *node, int depth)
1308 printf("Dumping TreeInfo:\n");
1312 for (i = 0; i < (depth + 1) * 3; i++)
1315 printf("'%s' / '%s'\n", node->identifier, node->name);
1318 // use for dumping artwork info tree
1319 printf("subdir == '%s' ['%s', '%s'] [%d])\n",
1320 node->subdir, node->fullpath, node->basepath, node->in_user_dir);
1323 if (node->node_group != NULL)
1324 dumpTreeInfo(node->node_group, depth + 1);
1330 void sortTreeInfoBySortFunction(TreeInfo **node_first,
1331 int (*compare_function)(const void *,
1334 int num_nodes = numTreeInfo(*node_first);
1335 TreeInfo **sort_array;
1336 TreeInfo *node = *node_first;
1342 /* allocate array for sorting structure pointers */
1343 sort_array = checked_calloc(num_nodes * sizeof(TreeInfo *));
1345 /* writing structure pointers to sorting array */
1346 while (i < num_nodes && node) /* double boundary check... */
1348 sort_array[i] = node;
1354 /* sorting the structure pointers in the sorting array */
1355 qsort(sort_array, num_nodes, sizeof(TreeInfo *),
1358 /* update the linkage of list elements with the sorted node array */
1359 for (i = 0; i < num_nodes - 1; i++)
1360 sort_array[i]->next = sort_array[i + 1];
1361 sort_array[num_nodes - 1]->next = NULL;
1363 /* update the linkage of the main list anchor pointer */
1364 *node_first = sort_array[0];
1368 /* now recursively sort the level group structures */
1372 if (node->node_group != NULL)
1373 sortTreeInfoBySortFunction(&node->node_group, compare_function);
1379 void sortTreeInfo(TreeInfo **node_first)
1381 sortTreeInfoBySortFunction(node_first, compareTreeInfoEntries);
1385 /* ========================================================================= */
1386 /* some stuff from "files.c" */
1387 /* ========================================================================= */
1389 #if defined(PLATFORM_WIN32)
1391 #define S_IRGRP S_IRUSR
1394 #define S_IROTH S_IRUSR
1397 #define S_IWGRP S_IWUSR
1400 #define S_IWOTH S_IWUSR
1403 #define S_IXGRP S_IXUSR
1406 #define S_IXOTH S_IXUSR
1409 #define S_IRWXG (S_IRGRP | S_IWGRP | S_IXGRP)
1414 #endif /* PLATFORM_WIN32 */
1416 /* file permissions for newly written files */
1417 #define MODE_R_ALL (S_IRUSR | S_IRGRP | S_IROTH)
1418 #define MODE_W_ALL (S_IWUSR | S_IWGRP | S_IWOTH)
1419 #define MODE_X_ALL (S_IXUSR | S_IXGRP | S_IXOTH)
1421 #define MODE_W_PRIVATE (S_IWUSR)
1422 #define MODE_W_PUBLIC_FILE (S_IWUSR | S_IWGRP)
1423 #define MODE_W_PUBLIC_DIR (S_IWUSR | S_IWGRP | S_ISGID)
1425 #define DIR_PERMS_PRIVATE (MODE_R_ALL | MODE_X_ALL | MODE_W_PRIVATE)
1426 #define DIR_PERMS_PUBLIC (MODE_R_ALL | MODE_X_ALL | MODE_W_PUBLIC_DIR)
1427 #define DIR_PERMS_PUBLIC_ALL (MODE_R_ALL | MODE_X_ALL | MODE_W_ALL)
1429 #define FILE_PERMS_PRIVATE (MODE_R_ALL | MODE_W_PRIVATE)
1430 #define FILE_PERMS_PUBLIC (MODE_R_ALL | MODE_W_PUBLIC_FILE)
1431 #define FILE_PERMS_PUBLIC_ALL (MODE_R_ALL | MODE_W_ALL)
1436 static char *dir = NULL;
1438 #if defined(PLATFORM_WIN32)
1441 dir = checked_malloc(MAX_PATH + 1);
1443 if (!SUCCEEDED(SHGetFolderPath(NULL, CSIDL_PERSONAL, NULL, 0, dir)))
1446 #elif defined(PLATFORM_UNIX)
1449 if ((dir = getenv("HOME")) == NULL)
1453 if ((pwd = getpwuid(getuid())) != NULL)
1454 dir = getStringCopy(pwd->pw_dir);
1466 char *getCommonDataDir(void)
1468 static char *common_data_dir = NULL;
1470 #if defined(PLATFORM_WIN32)
1471 if (common_data_dir == NULL)
1473 char *dir = checked_malloc(MAX_PATH + 1);
1475 if (SUCCEEDED(SHGetFolderPath(NULL, CSIDL_COMMON_DOCUMENTS, NULL, 0, dir))
1476 && !strEqual(dir, "")) /* empty for Windows 95/98 */
1477 common_data_dir = getPath2(dir, program.userdata_subdir);
1479 common_data_dir = options.rw_base_directory;
1482 if (common_data_dir == NULL)
1483 common_data_dir = options.rw_base_directory;
1486 return common_data_dir;
1489 char *getPersonalDataDir(void)
1491 static char *personal_data_dir = NULL;
1493 #if defined(PLATFORM_MACOSX)
1494 if (personal_data_dir == NULL)
1495 personal_data_dir = getPath2(getHomeDir(), "Documents");
1497 if (personal_data_dir == NULL)
1498 personal_data_dir = getHomeDir();
1501 return personal_data_dir;
1504 char *getUserGameDataDir(void)
1506 static char *user_game_data_dir = NULL;
1508 #if defined(PLATFORM_ANDROID)
1509 if (user_game_data_dir == NULL)
1510 user_game_data_dir = (char *)(SDL_AndroidGetExternalStorageState() &
1511 SDL_ANDROID_EXTERNAL_STORAGE_WRITE ?
1512 SDL_AndroidGetExternalStoragePath() :
1513 SDL_AndroidGetInternalStoragePath());
1515 if (user_game_data_dir == NULL)
1516 user_game_data_dir = getPath2(getPersonalDataDir(),
1517 program.userdata_subdir);
1520 return user_game_data_dir;
1525 return getUserGameDataDir();
1528 static mode_t posix_umask(mode_t mask)
1530 #if defined(PLATFORM_UNIX)
1537 static int posix_mkdir(const char *pathname, mode_t mode)
1539 #if defined(PLATFORM_WIN32)
1540 return mkdir(pathname);
1542 return mkdir(pathname, mode);
1546 static boolean posix_process_running_setgid()
1548 #if defined(PLATFORM_UNIX)
1549 return (getgid() != getegid());
1555 void createDirectory(char *dir, char *text, int permission_class)
1557 if (directoryExists(dir))
1560 /* leave "other" permissions in umask untouched, but ensure group parts
1561 of USERDATA_DIR_MODE are not masked */
1562 mode_t dir_mode = (permission_class == PERMS_PRIVATE ?
1563 DIR_PERMS_PRIVATE : DIR_PERMS_PUBLIC);
1564 mode_t last_umask = posix_umask(0);
1565 mode_t group_umask = ~(dir_mode & S_IRWXG);
1566 int running_setgid = posix_process_running_setgid();
1568 if (permission_class == PERMS_PUBLIC)
1570 /* if we're setgid, protect files against "other" */
1571 /* else keep umask(0) to make the dir world-writable */
1574 posix_umask(last_umask & group_umask);
1576 dir_mode = DIR_PERMS_PUBLIC_ALL;
1579 if (posix_mkdir(dir, dir_mode) != 0)
1580 Error(ERR_WARN, "cannot create %s directory '%s': %s",
1581 text, dir, strerror(errno));
1583 if (permission_class == PERMS_PUBLIC && !running_setgid)
1584 chmod(dir, dir_mode);
1586 posix_umask(last_umask); /* restore previous umask */
1589 void InitUserDataDirectory()
1591 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
1594 void SetFilePermissions(char *filename, int permission_class)
1596 int running_setgid = posix_process_running_setgid();
1597 int perms = (permission_class == PERMS_PRIVATE ?
1598 FILE_PERMS_PRIVATE : FILE_PERMS_PUBLIC);
1600 if (permission_class == PERMS_PUBLIC && !running_setgid)
1601 perms = FILE_PERMS_PUBLIC_ALL;
1603 chmod(filename, perms);
1606 char *getCookie(char *file_type)
1608 static char cookie[MAX_COOKIE_LEN + 1];
1610 if (strlen(program.cookie_prefix) + 1 +
1611 strlen(file_type) + strlen("_FILE_VERSION_x.x") > MAX_COOKIE_LEN)
1612 return "[COOKIE ERROR]"; /* should never happen */
1614 sprintf(cookie, "%s_%s_FILE_VERSION_%d.%d",
1615 program.cookie_prefix, file_type,
1616 program.version_super, program.version_major);
1621 void fprintFileHeader(FILE *file, char *basename)
1623 char *prefix = "# ";
1626 fprintf_line_with_prefix(file, prefix, sep1, 77);
1627 fprintf(file, "%s%s\n", prefix, basename);
1628 fprintf_line_with_prefix(file, prefix, sep1, 77);
1629 fprintf(file, "\n");
1632 int getFileVersionFromCookieString(const char *cookie)
1634 const char *ptr_cookie1, *ptr_cookie2;
1635 const char *pattern1 = "_FILE_VERSION_";
1636 const char *pattern2 = "?.?";
1637 const int len_cookie = strlen(cookie);
1638 const int len_pattern1 = strlen(pattern1);
1639 const int len_pattern2 = strlen(pattern2);
1640 const int len_pattern = len_pattern1 + len_pattern2;
1641 int version_super, version_major;
1643 if (len_cookie <= len_pattern)
1646 ptr_cookie1 = &cookie[len_cookie - len_pattern];
1647 ptr_cookie2 = &cookie[len_cookie - len_pattern2];
1649 if (strncmp(ptr_cookie1, pattern1, len_pattern1) != 0)
1652 if (ptr_cookie2[0] < '0' || ptr_cookie2[0] > '9' ||
1653 ptr_cookie2[1] != '.' ||
1654 ptr_cookie2[2] < '0' || ptr_cookie2[2] > '9')
1657 version_super = ptr_cookie2[0] - '0';
1658 version_major = ptr_cookie2[2] - '0';
1660 return VERSION_IDENT(version_super, version_major, 0, 0);
1663 boolean checkCookieString(const char *cookie, const char *template)
1665 const char *pattern = "_FILE_VERSION_?.?";
1666 const int len_cookie = strlen(cookie);
1667 const int len_template = strlen(template);
1668 const int len_pattern = strlen(pattern);
1670 if (len_cookie != len_template)
1673 if (strncmp(cookie, template, len_cookie - len_pattern) != 0)
1680 /* ------------------------------------------------------------------------- */
1681 /* setup file list and hash handling functions */
1682 /* ------------------------------------------------------------------------- */
1684 char *getFormattedSetupEntry(char *token, char *value)
1687 static char entry[MAX_LINE_LEN];
1689 /* if value is an empty string, just return token without value */
1693 /* start with the token and some spaces to format output line */
1694 sprintf(entry, "%s:", token);
1695 for (i = strlen(entry); i < token_value_position; i++)
1698 /* continue with the token's value */
1699 strcat(entry, value);
1704 SetupFileList *newSetupFileList(char *token, char *value)
1706 SetupFileList *new = checked_malloc(sizeof(SetupFileList));
1708 new->token = getStringCopy(token);
1709 new->value = getStringCopy(value);
1716 void freeSetupFileList(SetupFileList *list)
1721 checked_free(list->token);
1722 checked_free(list->value);
1725 freeSetupFileList(list->next);
1730 char *getListEntry(SetupFileList *list, char *token)
1735 if (strEqual(list->token, token))
1738 return getListEntry(list->next, token);
1741 SetupFileList *setListEntry(SetupFileList *list, char *token, char *value)
1746 if (strEqual(list->token, token))
1748 checked_free(list->value);
1750 list->value = getStringCopy(value);
1754 else if (list->next == NULL)
1755 return (list->next = newSetupFileList(token, value));
1757 return setListEntry(list->next, token, value);
1760 SetupFileList *addListEntry(SetupFileList *list, char *token, char *value)
1765 if (list->next == NULL)
1766 return (list->next = newSetupFileList(token, value));
1768 return addListEntry(list->next, token, value);
1771 #if ENABLE_UNUSED_CODE
1773 static void printSetupFileList(SetupFileList *list)
1778 printf("token: '%s'\n", list->token);
1779 printf("value: '%s'\n", list->value);
1781 printSetupFileList(list->next);
1787 DEFINE_HASHTABLE_INSERT(insert_hash_entry, char, char);
1788 DEFINE_HASHTABLE_SEARCH(search_hash_entry, char, char);
1789 DEFINE_HASHTABLE_CHANGE(change_hash_entry, char, char);
1790 DEFINE_HASHTABLE_REMOVE(remove_hash_entry, char, char);
1792 #define insert_hash_entry hashtable_insert
1793 #define search_hash_entry hashtable_search
1794 #define change_hash_entry hashtable_change
1795 #define remove_hash_entry hashtable_remove
1798 unsigned int get_hash_from_key(void *key)
1803 This algorithm (k=33) was first reported by Dan Bernstein many years ago in
1804 'comp.lang.c'. Another version of this algorithm (now favored by Bernstein)
1805 uses XOR: hash(i) = hash(i - 1) * 33 ^ str[i]; the magic of number 33 (why
1806 it works better than many other constants, prime or not) has never been
1807 adequately explained.
1809 If you just want to have a good hash function, and cannot wait, djb2
1810 is one of the best string hash functions i know. It has excellent
1811 distribution and speed on many different sets of keys and table sizes.
1812 You are not likely to do better with one of the "well known" functions
1813 such as PJW, K&R, etc.
1815 Ozan (oz) Yigit [http://www.cs.yorku.ca/~oz/hash.html]
1818 char *str = (char *)key;
1819 unsigned int hash = 5381;
1822 while ((c = *str++))
1823 hash = ((hash << 5) + hash) + c; /* hash * 33 + c */
1828 static int keys_are_equal(void *key1, void *key2)
1830 return (strEqual((char *)key1, (char *)key2));
1833 SetupFileHash *newSetupFileHash()
1835 SetupFileHash *new_hash =
1836 create_hashtable(16, 0.75, get_hash_from_key, keys_are_equal);
1838 if (new_hash == NULL)
1839 Error(ERR_EXIT, "create_hashtable() failed -- out of memory");
1844 void freeSetupFileHash(SetupFileHash *hash)
1849 hashtable_destroy(hash, 1); /* 1 == also free values stored in hash */
1852 char *getHashEntry(SetupFileHash *hash, char *token)
1857 return search_hash_entry(hash, token);
1860 void setHashEntry(SetupFileHash *hash, char *token, char *value)
1867 value_copy = getStringCopy(value);
1869 /* change value; if it does not exist, insert it as new */
1870 if (!change_hash_entry(hash, token, value_copy))
1871 if (!insert_hash_entry(hash, getStringCopy(token), value_copy))
1872 Error(ERR_EXIT, "cannot insert into hash -- aborting");
1875 char *removeHashEntry(SetupFileHash *hash, char *token)
1880 return remove_hash_entry(hash, token);
1883 #if ENABLE_UNUSED_CODE
1885 static void printSetupFileHash(SetupFileHash *hash)
1887 BEGIN_HASH_ITERATION(hash, itr)
1889 printf("token: '%s'\n", HASH_ITERATION_TOKEN(itr));
1890 printf("value: '%s'\n", HASH_ITERATION_VALUE(itr));
1892 END_HASH_ITERATION(hash, itr)
1897 #define ALLOW_TOKEN_VALUE_SEPARATOR_BEING_WHITESPACE 1
1898 #define CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING 0
1899 #define CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH 0
1901 static boolean token_value_separator_found = FALSE;
1902 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
1903 static boolean token_value_separator_warning = FALSE;
1905 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
1906 static boolean token_already_exists_warning = FALSE;
1909 static boolean getTokenValueFromSetupLineExt(char *line,
1910 char **token_ptr, char **value_ptr,
1911 char *filename, char *line_raw,
1913 boolean separator_required)
1915 static char line_copy[MAX_LINE_LEN + 1], line_raw_copy[MAX_LINE_LEN + 1];
1916 char *token, *value, *line_ptr;
1918 /* when externally invoked via ReadTokenValueFromLine(), copy line buffers */
1919 if (line_raw == NULL)
1921 strncpy(line_copy, line, MAX_LINE_LEN);
1922 line_copy[MAX_LINE_LEN] = '\0';
1925 strcpy(line_raw_copy, line_copy);
1926 line_raw = line_raw_copy;
1929 /* cut trailing comment from input line */
1930 for (line_ptr = line; *line_ptr; line_ptr++)
1932 if (*line_ptr == '#')
1939 /* cut trailing whitespaces from input line */
1940 for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
1941 if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
1944 /* ignore empty lines */
1948 /* cut leading whitespaces from token */
1949 for (token = line; *token; token++)
1950 if (*token != ' ' && *token != '\t')
1953 /* start with empty value as reliable default */
1956 token_value_separator_found = FALSE;
1958 /* find end of token to determine start of value */
1959 for (line_ptr = token; *line_ptr; line_ptr++)
1961 /* first look for an explicit token/value separator, like ':' or '=' */
1962 if (*line_ptr == ':' || *line_ptr == '=')
1964 *line_ptr = '\0'; /* terminate token string */
1965 value = line_ptr + 1; /* set beginning of value */
1967 token_value_separator_found = TRUE;
1973 #if ALLOW_TOKEN_VALUE_SEPARATOR_BEING_WHITESPACE
1974 /* fallback: if no token/value separator found, also allow whitespaces */
1975 if (!token_value_separator_found && !separator_required)
1977 for (line_ptr = token; *line_ptr; line_ptr++)
1979 if (*line_ptr == ' ' || *line_ptr == '\t')
1981 *line_ptr = '\0'; /* terminate token string */
1982 value = line_ptr + 1; /* set beginning of value */
1984 token_value_separator_found = TRUE;
1990 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
1991 if (token_value_separator_found)
1993 if (!token_value_separator_warning)
1995 Error(ERR_INFO_LINE, "-");
1997 if (filename != NULL)
1999 Error(ERR_WARN, "missing token/value separator(s) in config file:");
2000 Error(ERR_INFO, "- config file: '%s'", filename);
2004 Error(ERR_WARN, "missing token/value separator(s):");
2007 token_value_separator_warning = TRUE;
2010 if (filename != NULL)
2011 Error(ERR_INFO, "- line %d: '%s'", line_nr, line_raw);
2013 Error(ERR_INFO, "- line: '%s'", line_raw);
2019 /* cut trailing whitespaces from token */
2020 for (line_ptr = &token[strlen(token)]; line_ptr >= token; line_ptr--)
2021 if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
2024 /* cut leading whitespaces from value */
2025 for (; *value; value++)
2026 if (*value != ' ' && *value != '\t')
2035 boolean getTokenValueFromSetupLine(char *line, char **token, char **value)
2037 /* while the internal (old) interface does not require a token/value
2038 separator (for downwards compatibility with existing files which
2039 don't use them), it is mandatory for the external (new) interface */
2041 return getTokenValueFromSetupLineExt(line, token, value, NULL, NULL, 0, TRUE);
2044 static boolean loadSetupFileData(void *setup_file_data, char *filename,
2045 boolean top_recursion_level, boolean is_hash)
2047 static SetupFileHash *include_filename_hash = NULL;
2048 char line[MAX_LINE_LEN], line_raw[MAX_LINE_LEN], previous_line[MAX_LINE_LEN];
2049 char *token, *value, *line_ptr;
2050 void *insert_ptr = NULL;
2051 boolean read_continued_line = FALSE;
2053 int line_nr = 0, token_count = 0, include_count = 0;
2055 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2056 token_value_separator_warning = FALSE;
2059 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2060 token_already_exists_warning = FALSE;
2063 if (!(file = openFile(filename, MODE_READ)))
2065 #if DEBUG_NO_CONFIG_FILE
2066 Error(ERR_DEBUG, "cannot open configuration file '%s'", filename);
2072 /* use "insert pointer" to store list end for constant insertion complexity */
2074 insert_ptr = setup_file_data;
2076 /* on top invocation, create hash to mark included files (to prevent loops) */
2077 if (top_recursion_level)
2078 include_filename_hash = newSetupFileHash();
2080 /* mark this file as already included (to prevent including it again) */
2081 setHashEntry(include_filename_hash, getBaseNamePtr(filename), "true");
2083 while (!checkEndOfFile(file))
2085 /* read next line of input file */
2086 if (!getStringFromFile(file, line, MAX_LINE_LEN))
2089 /* check if line was completely read and is terminated by line break */
2090 if (strlen(line) > 0 && line[strlen(line) - 1] == '\n')
2093 /* cut trailing line break (this can be newline and/or carriage return) */
2094 for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
2095 if ((*line_ptr == '\n' || *line_ptr == '\r') && *(line_ptr + 1) == '\0')
2098 /* copy raw input line for later use (mainly debugging output) */
2099 strcpy(line_raw, line);
2101 if (read_continued_line)
2103 /* append new line to existing line, if there is enough space */
2104 if (strlen(previous_line) + strlen(line_ptr) < MAX_LINE_LEN)
2105 strcat(previous_line, line_ptr);
2107 strcpy(line, previous_line); /* copy storage buffer to line */
2109 read_continued_line = FALSE;
2112 /* if the last character is '\', continue at next line */
2113 if (strlen(line) > 0 && line[strlen(line) - 1] == '\\')
2115 line[strlen(line) - 1] = '\0'; /* cut off trailing backslash */
2116 strcpy(previous_line, line); /* copy line to storage buffer */
2118 read_continued_line = TRUE;
2123 if (!getTokenValueFromSetupLineExt(line, &token, &value, filename,
2124 line_raw, line_nr, FALSE))
2129 if (strEqual(token, "include"))
2131 if (getHashEntry(include_filename_hash, value) == NULL)
2133 char *basepath = getBasePath(filename);
2134 char *basename = getBaseName(value);
2135 char *filename_include = getPath2(basepath, basename);
2137 loadSetupFileData(setup_file_data, filename_include, FALSE, is_hash);
2141 free(filename_include);
2147 Error(ERR_WARN, "ignoring already processed file '%s'", value);
2154 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2156 getHashEntry((SetupFileHash *)setup_file_data, token);
2158 if (old_value != NULL)
2160 if (!token_already_exists_warning)
2162 Error(ERR_INFO_LINE, "-");
2163 Error(ERR_WARN, "duplicate token(s) found in config file:");
2164 Error(ERR_INFO, "- config file: '%s'", filename);
2166 token_already_exists_warning = TRUE;
2169 Error(ERR_INFO, "- token: '%s' (in line %d)", token, line_nr);
2170 Error(ERR_INFO, " old value: '%s'", old_value);
2171 Error(ERR_INFO, " new value: '%s'", value);
2175 setHashEntry((SetupFileHash *)setup_file_data, token, value);
2179 insert_ptr = addListEntry((SetupFileList *)insert_ptr, token, value);
2189 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2190 if (token_value_separator_warning)
2191 Error(ERR_INFO_LINE, "-");
2194 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2195 if (token_already_exists_warning)
2196 Error(ERR_INFO_LINE, "-");
2199 if (token_count == 0 && include_count == 0)
2200 Error(ERR_WARN, "configuration file '%s' is empty", filename);
2202 if (top_recursion_level)
2203 freeSetupFileHash(include_filename_hash);
2208 void saveSetupFileHash(SetupFileHash *hash, char *filename)
2212 if (!(file = fopen(filename, MODE_WRITE)))
2214 Error(ERR_WARN, "cannot write configuration file '%s'", filename);
2219 BEGIN_HASH_ITERATION(hash, itr)
2221 fprintf(file, "%s\n", getFormattedSetupEntry(HASH_ITERATION_TOKEN(itr),
2222 HASH_ITERATION_VALUE(itr)));
2224 END_HASH_ITERATION(hash, itr)
2229 SetupFileList *loadSetupFileList(char *filename)
2231 SetupFileList *setup_file_list = newSetupFileList("", "");
2232 SetupFileList *first_valid_list_entry;
2234 if (!loadSetupFileData(setup_file_list, filename, TRUE, FALSE))
2236 freeSetupFileList(setup_file_list);
2241 first_valid_list_entry = setup_file_list->next;
2243 /* free empty list header */
2244 setup_file_list->next = NULL;
2245 freeSetupFileList(setup_file_list);
2247 return first_valid_list_entry;
2250 SetupFileHash *loadSetupFileHash(char *filename)
2252 SetupFileHash *setup_file_hash = newSetupFileHash();
2254 if (!loadSetupFileData(setup_file_hash, filename, TRUE, TRUE))
2256 freeSetupFileHash(setup_file_hash);
2261 return setup_file_hash;
2265 /* ========================================================================= */
2266 /* setup file stuff */
2267 /* ========================================================================= */
2269 #define TOKEN_STR_LAST_LEVEL_SERIES "last_level_series"
2270 #define TOKEN_STR_LAST_PLAYED_LEVEL "last_played_level"
2271 #define TOKEN_STR_HANDICAP_LEVEL "handicap_level"
2273 /* level directory info */
2274 #define LEVELINFO_TOKEN_IDENTIFIER 0
2275 #define LEVELINFO_TOKEN_NAME 1
2276 #define LEVELINFO_TOKEN_NAME_SORTING 2
2277 #define LEVELINFO_TOKEN_AUTHOR 3
2278 #define LEVELINFO_TOKEN_YEAR 4
2279 #define LEVELINFO_TOKEN_PROGRAM_TITLE 5
2280 #define LEVELINFO_TOKEN_PROGRAM_COPYRIGHT 6
2281 #define LEVELINFO_TOKEN_PROGRAM_COMPANY 7
2282 #define LEVELINFO_TOKEN_IMPORTED_FROM 8
2283 #define LEVELINFO_TOKEN_IMPORTED_BY 9
2284 #define LEVELINFO_TOKEN_TESTED_BY 10
2285 #define LEVELINFO_TOKEN_LEVELS 11
2286 #define LEVELINFO_TOKEN_FIRST_LEVEL 12
2287 #define LEVELINFO_TOKEN_SORT_PRIORITY 13
2288 #define LEVELINFO_TOKEN_LATEST_ENGINE 14
2289 #define LEVELINFO_TOKEN_LEVEL_GROUP 15
2290 #define LEVELINFO_TOKEN_READONLY 16
2291 #define LEVELINFO_TOKEN_GRAPHICS_SET_ECS 17
2292 #define LEVELINFO_TOKEN_GRAPHICS_SET_AGA 18
2293 #define LEVELINFO_TOKEN_GRAPHICS_SET 19
2294 #define LEVELINFO_TOKEN_SOUNDS_SET 20
2295 #define LEVELINFO_TOKEN_MUSIC_SET 21
2296 #define LEVELINFO_TOKEN_FILENAME 22
2297 #define LEVELINFO_TOKEN_FILETYPE 23
2298 #define LEVELINFO_TOKEN_SPECIAL_FLAGS 24
2299 #define LEVELINFO_TOKEN_HANDICAP 25
2300 #define LEVELINFO_TOKEN_SKIP_LEVELS 26
2302 #define NUM_LEVELINFO_TOKENS 27
2304 static LevelDirTree ldi;
2306 static struct TokenInfo levelinfo_tokens[] =
2308 /* level directory info */
2309 { TYPE_STRING, &ldi.identifier, "identifier" },
2310 { TYPE_STRING, &ldi.name, "name" },
2311 { TYPE_STRING, &ldi.name_sorting, "name_sorting" },
2312 { TYPE_STRING, &ldi.author, "author" },
2313 { TYPE_STRING, &ldi.year, "year" },
2314 { TYPE_STRING, &ldi.program_title, "program_title" },
2315 { TYPE_STRING, &ldi.program_copyright, "program_copyright" },
2316 { TYPE_STRING, &ldi.program_company, "program_company" },
2317 { TYPE_STRING, &ldi.imported_from, "imported_from" },
2318 { TYPE_STRING, &ldi.imported_by, "imported_by" },
2319 { TYPE_STRING, &ldi.tested_by, "tested_by" },
2320 { TYPE_INTEGER, &ldi.levels, "levels" },
2321 { TYPE_INTEGER, &ldi.first_level, "first_level" },
2322 { TYPE_INTEGER, &ldi.sort_priority, "sort_priority" },
2323 { TYPE_BOOLEAN, &ldi.latest_engine, "latest_engine" },
2324 { TYPE_BOOLEAN, &ldi.level_group, "level_group" },
2325 { TYPE_BOOLEAN, &ldi.readonly, "readonly" },
2326 { TYPE_STRING, &ldi.graphics_set_ecs, "graphics_set.ecs" },
2327 { TYPE_STRING, &ldi.graphics_set_aga, "graphics_set.aga" },
2328 { TYPE_STRING, &ldi.graphics_set, "graphics_set" },
2329 { TYPE_STRING, &ldi.sounds_set, "sounds_set" },
2330 { TYPE_STRING, &ldi.music_set, "music_set" },
2331 { TYPE_STRING, &ldi.level_filename, "filename" },
2332 { TYPE_STRING, &ldi.level_filetype, "filetype" },
2333 { TYPE_STRING, &ldi.special_flags, "special_flags" },
2334 { TYPE_BOOLEAN, &ldi.handicap, "handicap" },
2335 { TYPE_BOOLEAN, &ldi.skip_levels, "skip_levels" }
2338 static struct TokenInfo artworkinfo_tokens[] =
2340 /* artwork directory info */
2341 { TYPE_STRING, &ldi.identifier, "identifier" },
2342 { TYPE_STRING, &ldi.subdir, "subdir" },
2343 { TYPE_STRING, &ldi.name, "name" },
2344 { TYPE_STRING, &ldi.name_sorting, "name_sorting" },
2345 { TYPE_STRING, &ldi.author, "author" },
2346 { TYPE_STRING, &ldi.program_title, "program_title" },
2347 { TYPE_STRING, &ldi.program_copyright, "program_copyright" },
2348 { TYPE_STRING, &ldi.program_company, "program_company" },
2349 { TYPE_INTEGER, &ldi.sort_priority, "sort_priority" },
2350 { TYPE_STRING, &ldi.basepath, "basepath" },
2351 { TYPE_STRING, &ldi.fullpath, "fullpath" },
2352 { TYPE_BOOLEAN, &ldi.in_user_dir, "in_user_dir" },
2353 { TYPE_INTEGER, &ldi.color, "color" },
2354 { TYPE_STRING, &ldi.class_desc, "class_desc" },
2359 static void setTreeInfoToDefaults(TreeInfo *ti, int type)
2363 ti->node_top = (ti->type == TREE_TYPE_LEVEL_DIR ? &leveldir_first :
2364 ti->type == TREE_TYPE_GRAPHICS_DIR ? &artwork.gfx_first :
2365 ti->type == TREE_TYPE_SOUNDS_DIR ? &artwork.snd_first :
2366 ti->type == TREE_TYPE_MUSIC_DIR ? &artwork.mus_first :
2369 ti->node_parent = NULL;
2370 ti->node_group = NULL;
2377 ti->fullpath = NULL;
2378 ti->basepath = NULL;
2379 ti->identifier = NULL;
2380 ti->name = getStringCopy(ANONYMOUS_NAME);
2381 ti->name_sorting = NULL;
2382 ti->author = getStringCopy(ANONYMOUS_NAME);
2385 ti->program_title = NULL;
2386 ti->program_copyright = NULL;
2387 ti->program_company = NULL;
2389 ti->sort_priority = LEVELCLASS_UNDEFINED; /* default: least priority */
2390 ti->latest_engine = FALSE; /* default: get from level */
2391 ti->parent_link = FALSE;
2392 ti->in_user_dir = FALSE;
2393 ti->user_defined = FALSE;
2395 ti->class_desc = NULL;
2397 ti->infotext = getStringCopy(TREE_INFOTEXT(ti->type));
2399 if (ti->type == TREE_TYPE_LEVEL_DIR)
2401 ti->imported_from = NULL;
2402 ti->imported_by = NULL;
2403 ti->tested_by = NULL;
2405 ti->graphics_set_ecs = NULL;
2406 ti->graphics_set_aga = NULL;
2407 ti->graphics_set = NULL;
2408 ti->sounds_set = NULL;
2409 ti->music_set = NULL;
2410 ti->graphics_path = getStringCopy(UNDEFINED_FILENAME);
2411 ti->sounds_path = getStringCopy(UNDEFINED_FILENAME);
2412 ti->music_path = getStringCopy(UNDEFINED_FILENAME);
2414 ti->level_filename = NULL;
2415 ti->level_filetype = NULL;
2417 ti->special_flags = NULL;
2420 ti->first_level = 0;
2422 ti->level_group = FALSE;
2423 ti->handicap_level = 0;
2424 ti->readonly = TRUE;
2425 ti->handicap = TRUE;
2426 ti->skip_levels = FALSE;
2430 static void setTreeInfoToDefaultsFromParent(TreeInfo *ti, TreeInfo *parent)
2434 Error(ERR_WARN, "setTreeInfoToDefaultsFromParent(): parent == NULL");
2436 setTreeInfoToDefaults(ti, TREE_TYPE_UNDEFINED);
2441 /* copy all values from the parent structure */
2443 ti->type = parent->type;
2445 ti->node_top = parent->node_top;
2446 ti->node_parent = parent;
2447 ti->node_group = NULL;
2454 ti->fullpath = NULL;
2455 ti->basepath = NULL;
2456 ti->identifier = NULL;
2457 ti->name = getStringCopy(ANONYMOUS_NAME);
2458 ti->name_sorting = NULL;
2459 ti->author = getStringCopy(parent->author);
2460 ti->year = getStringCopy(parent->year);
2462 ti->program_title = getStringCopy(parent->program_title);
2463 ti->program_copyright = getStringCopy(parent->program_copyright);
2464 ti->program_company = getStringCopy(parent->program_company);
2466 ti->sort_priority = parent->sort_priority;
2467 ti->latest_engine = parent->latest_engine;
2468 ti->parent_link = FALSE;
2469 ti->in_user_dir = parent->in_user_dir;
2470 ti->user_defined = parent->user_defined;
2471 ti->color = parent->color;
2472 ti->class_desc = getStringCopy(parent->class_desc);
2474 ti->infotext = getStringCopy(parent->infotext);
2476 if (ti->type == TREE_TYPE_LEVEL_DIR)
2478 ti->imported_from = getStringCopy(parent->imported_from);
2479 ti->imported_by = getStringCopy(parent->imported_by);
2480 ti->tested_by = getStringCopy(parent->tested_by);
2482 ti->graphics_set_ecs = getStringCopy(parent->graphics_set_ecs);
2483 ti->graphics_set_aga = getStringCopy(parent->graphics_set_aga);
2484 ti->graphics_set = getStringCopy(parent->graphics_set);
2485 ti->sounds_set = getStringCopy(parent->sounds_set);
2486 ti->music_set = getStringCopy(parent->music_set);
2487 ti->graphics_path = getStringCopy(UNDEFINED_FILENAME);
2488 ti->sounds_path = getStringCopy(UNDEFINED_FILENAME);
2489 ti->music_path = getStringCopy(UNDEFINED_FILENAME);
2491 ti->level_filename = getStringCopy(parent->level_filename);
2492 ti->level_filetype = getStringCopy(parent->level_filetype);
2494 ti->special_flags = getStringCopy(parent->special_flags);
2496 ti->levels = parent->levels;
2497 ti->first_level = parent->first_level;
2498 ti->last_level = parent->last_level;
2499 ti->level_group = FALSE;
2500 ti->handicap_level = parent->handicap_level;
2501 ti->readonly = parent->readonly;
2502 ti->handicap = parent->handicap;
2503 ti->skip_levels = parent->skip_levels;
2507 static TreeInfo *getTreeInfoCopy(TreeInfo *ti)
2509 TreeInfo *ti_copy = newTreeInfo();
2511 /* copy all values from the original structure */
2513 ti_copy->type = ti->type;
2515 ti_copy->node_top = ti->node_top;
2516 ti_copy->node_parent = ti->node_parent;
2517 ti_copy->node_group = ti->node_group;
2518 ti_copy->next = ti->next;
2520 ti_copy->cl_first = ti->cl_first;
2521 ti_copy->cl_cursor = ti->cl_cursor;
2523 ti_copy->subdir = getStringCopy(ti->subdir);
2524 ti_copy->fullpath = getStringCopy(ti->fullpath);
2525 ti_copy->basepath = getStringCopy(ti->basepath);
2526 ti_copy->identifier = getStringCopy(ti->identifier);
2527 ti_copy->name = getStringCopy(ti->name);
2528 ti_copy->name_sorting = getStringCopy(ti->name_sorting);
2529 ti_copy->author = getStringCopy(ti->author);
2530 ti_copy->year = getStringCopy(ti->year);
2532 ti_copy->program_title = getStringCopy(ti->program_title);
2533 ti_copy->program_copyright = getStringCopy(ti->program_copyright);
2534 ti_copy->program_company = getStringCopy(ti->program_company);
2536 ti_copy->imported_from = getStringCopy(ti->imported_from);
2537 ti_copy->imported_by = getStringCopy(ti->imported_by);
2538 ti_copy->tested_by = getStringCopy(ti->tested_by);
2540 ti_copy->graphics_set_ecs = getStringCopy(ti->graphics_set_ecs);
2541 ti_copy->graphics_set_aga = getStringCopy(ti->graphics_set_aga);
2542 ti_copy->graphics_set = getStringCopy(ti->graphics_set);
2543 ti_copy->sounds_set = getStringCopy(ti->sounds_set);
2544 ti_copy->music_set = getStringCopy(ti->music_set);
2545 ti_copy->graphics_path = getStringCopy(ti->graphics_path);
2546 ti_copy->sounds_path = getStringCopy(ti->sounds_path);
2547 ti_copy->music_path = getStringCopy(ti->music_path);
2549 ti_copy->level_filename = getStringCopy(ti->level_filename);
2550 ti_copy->level_filetype = getStringCopy(ti->level_filetype);
2552 ti_copy->special_flags = getStringCopy(ti->special_flags);
2554 ti_copy->levels = ti->levels;
2555 ti_copy->first_level = ti->first_level;
2556 ti_copy->last_level = ti->last_level;
2557 ti_copy->sort_priority = ti->sort_priority;
2559 ti_copy->latest_engine = ti->latest_engine;
2561 ti_copy->level_group = ti->level_group;
2562 ti_copy->parent_link = ti->parent_link;
2563 ti_copy->in_user_dir = ti->in_user_dir;
2564 ti_copy->user_defined = ti->user_defined;
2565 ti_copy->readonly = ti->readonly;
2566 ti_copy->handicap = ti->handicap;
2567 ti_copy->skip_levels = ti->skip_levels;
2569 ti_copy->color = ti->color;
2570 ti_copy->class_desc = getStringCopy(ti->class_desc);
2571 ti_copy->handicap_level = ti->handicap_level;
2573 ti_copy->infotext = getStringCopy(ti->infotext);
2578 void freeTreeInfo(TreeInfo *ti)
2583 checked_free(ti->subdir);
2584 checked_free(ti->fullpath);
2585 checked_free(ti->basepath);
2586 checked_free(ti->identifier);
2588 checked_free(ti->name);
2589 checked_free(ti->name_sorting);
2590 checked_free(ti->author);
2591 checked_free(ti->year);
2593 checked_free(ti->program_title);
2594 checked_free(ti->program_copyright);
2595 checked_free(ti->program_company);
2597 checked_free(ti->class_desc);
2599 checked_free(ti->infotext);
2601 if (ti->type == TREE_TYPE_LEVEL_DIR)
2603 checked_free(ti->imported_from);
2604 checked_free(ti->imported_by);
2605 checked_free(ti->tested_by);
2607 checked_free(ti->graphics_set_ecs);
2608 checked_free(ti->graphics_set_aga);
2609 checked_free(ti->graphics_set);
2610 checked_free(ti->sounds_set);
2611 checked_free(ti->music_set);
2613 checked_free(ti->graphics_path);
2614 checked_free(ti->sounds_path);
2615 checked_free(ti->music_path);
2617 checked_free(ti->level_filename);
2618 checked_free(ti->level_filetype);
2620 checked_free(ti->special_flags);
2623 // recursively free child node
2625 freeTreeInfo(ti->node_group);
2627 // recursively free next node
2629 freeTreeInfo(ti->next);
2634 void setSetupInfo(struct TokenInfo *token_info,
2635 int token_nr, char *token_value)
2637 int token_type = token_info[token_nr].type;
2638 void *setup_value = token_info[token_nr].value;
2640 if (token_value == NULL)
2643 /* set setup field to corresponding token value */
2648 *(boolean *)setup_value = get_boolean_from_string(token_value);
2652 *(int *)setup_value = get_switch3_from_string(token_value);
2656 *(Key *)setup_value = getKeyFromKeyName(token_value);
2660 *(Key *)setup_value = getKeyFromX11KeyName(token_value);
2664 *(int *)setup_value = get_integer_from_string(token_value);
2668 checked_free(*(char **)setup_value);
2669 *(char **)setup_value = getStringCopy(token_value);
2673 *(int *)setup_value = get_player_nr_from_string(token_value);
2681 static int compareTreeInfoEntries(const void *object1, const void *object2)
2683 const TreeInfo *entry1 = *((TreeInfo **)object1);
2684 const TreeInfo *entry2 = *((TreeInfo **)object2);
2685 int class_sorting1 = 0, class_sorting2 = 0;
2688 if (entry1->type == TREE_TYPE_LEVEL_DIR)
2690 class_sorting1 = LEVELSORTING(entry1);
2691 class_sorting2 = LEVELSORTING(entry2);
2693 else if (entry1->type == TREE_TYPE_GRAPHICS_DIR ||
2694 entry1->type == TREE_TYPE_SOUNDS_DIR ||
2695 entry1->type == TREE_TYPE_MUSIC_DIR)
2697 class_sorting1 = ARTWORKSORTING(entry1);
2698 class_sorting2 = ARTWORKSORTING(entry2);
2701 if (entry1->parent_link || entry2->parent_link)
2702 compare_result = (entry1->parent_link ? -1 : +1);
2703 else if (entry1->sort_priority == entry2->sort_priority)
2705 char *name1 = getStringToLower(entry1->name_sorting);
2706 char *name2 = getStringToLower(entry2->name_sorting);
2708 compare_result = strcmp(name1, name2);
2713 else if (class_sorting1 == class_sorting2)
2714 compare_result = entry1->sort_priority - entry2->sort_priority;
2716 compare_result = class_sorting1 - class_sorting2;
2718 return compare_result;
2721 static TreeInfo *createParentTreeInfoNode(TreeInfo *node_parent)
2725 if (node_parent == NULL)
2728 ti_new = newTreeInfo();
2729 setTreeInfoToDefaults(ti_new, node_parent->type);
2731 ti_new->node_parent = node_parent;
2732 ti_new->parent_link = TRUE;
2734 setString(&ti_new->identifier, node_parent->identifier);
2735 setString(&ti_new->name, ".. (parent directory)");
2736 setString(&ti_new->name_sorting, ti_new->name);
2738 setString(&ti_new->subdir, STRING_PARENT_DIRECTORY);
2739 setString(&ti_new->fullpath, node_parent->fullpath);
2741 ti_new->sort_priority = node_parent->sort_priority;
2742 ti_new->latest_engine = node_parent->latest_engine;
2744 setString(&ti_new->class_desc, getLevelClassDescription(ti_new));
2746 pushTreeInfo(&node_parent->node_group, ti_new);
2751 static TreeInfo *createTopTreeInfoNode(TreeInfo *node_first)
2753 TreeInfo *ti_new, *ti_new2;
2755 if (node_first == NULL)
2758 ti_new = newTreeInfo();
2759 setTreeInfoToDefaults(ti_new, TREE_TYPE_LEVEL_DIR);
2761 ti_new->node_parent = NULL;
2762 ti_new->parent_link = FALSE;
2764 setString(&ti_new->identifier, node_first->identifier);
2765 setString(&ti_new->name, "level sets");
2766 setString(&ti_new->name_sorting, ti_new->name);
2768 setString(&ti_new->subdir, STRING_TOP_DIRECTORY);
2769 setString(&ti_new->fullpath, ".");
2771 ti_new->sort_priority = node_first->sort_priority;;
2772 ti_new->latest_engine = node_first->latest_engine;
2774 setString(&ti_new->class_desc, "level sets");
2776 ti_new->node_group = node_first;
2777 ti_new->level_group = TRUE;
2779 ti_new2 = createParentTreeInfoNode(ti_new);
2781 setString(&ti_new2->name, ".. (main menu)");
2782 setString(&ti_new2->name_sorting, ti_new2->name);
2788 /* -------------------------------------------------------------------------- */
2789 /* functions for handling level and custom artwork info cache */
2790 /* -------------------------------------------------------------------------- */
2792 static void LoadArtworkInfoCache()
2794 InitCacheDirectory();
2796 if (artworkinfo_cache_old == NULL)
2798 char *filename = getPath2(getCacheDir(), ARTWORKINFO_CACHE_FILE);
2800 /* try to load artwork info hash from already existing cache file */
2801 artworkinfo_cache_old = loadSetupFileHash(filename);
2803 /* if no artwork info cache file was found, start with empty hash */
2804 if (artworkinfo_cache_old == NULL)
2805 artworkinfo_cache_old = newSetupFileHash();
2810 if (artworkinfo_cache_new == NULL)
2811 artworkinfo_cache_new = newSetupFileHash();
2814 static void SaveArtworkInfoCache()
2816 char *filename = getPath2(getCacheDir(), ARTWORKINFO_CACHE_FILE);
2818 InitCacheDirectory();
2820 saveSetupFileHash(artworkinfo_cache_new, filename);
2825 static char *getCacheTokenPrefix(char *prefix1, char *prefix2)
2827 static char *prefix = NULL;
2829 checked_free(prefix);
2831 prefix = getStringCat2WithSeparator(prefix1, prefix2, ".");
2836 /* (identical to above function, but separate string buffer needed -- nasty) */
2837 static char *getCacheToken(char *prefix, char *suffix)
2839 static char *token = NULL;
2841 checked_free(token);
2843 token = getStringCat2WithSeparator(prefix, suffix, ".");
2848 static char *getFileTimestampString(char *filename)
2850 return getStringCopy(i_to_a(getFileTimestampEpochSeconds(filename)));
2853 static boolean modifiedFileTimestamp(char *filename, char *timestamp_string)
2855 struct stat file_status;
2857 if (timestamp_string == NULL)
2860 if (stat(filename, &file_status) != 0) /* cannot stat file */
2863 return (file_status.st_mtime != atoi(timestamp_string));
2866 static TreeInfo *getArtworkInfoCacheEntry(LevelDirTree *level_node, int type)
2868 char *identifier = level_node->subdir;
2869 char *type_string = ARTWORK_DIRECTORY(type);
2870 char *token_prefix = getCacheTokenPrefix(type_string, identifier);
2871 char *token_main = getCacheToken(token_prefix, "CACHED");
2872 char *cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
2873 boolean cached = (cache_entry != NULL && strEqual(cache_entry, "true"));
2874 TreeInfo *artwork_info = NULL;
2876 if (!use_artworkinfo_cache)
2883 artwork_info = newTreeInfo();
2884 setTreeInfoToDefaults(artwork_info, type);
2886 /* set all structure fields according to the token/value pairs */
2887 ldi = *artwork_info;
2888 for (i = 0; artworkinfo_tokens[i].type != -1; i++)
2890 char *token = getCacheToken(token_prefix, artworkinfo_tokens[i].text);
2891 char *value = getHashEntry(artworkinfo_cache_old, token);
2893 /* if defined, use value from cache, else keep default value */
2895 setSetupInfo(artworkinfo_tokens, i, value);
2898 *artwork_info = ldi;
2900 char *filename_levelinfo = getPath2(getLevelDirFromTreeInfo(level_node),
2901 LEVELINFO_FILENAME);
2902 char *filename_artworkinfo = getPath2(getSetupArtworkDir(artwork_info),
2903 ARTWORKINFO_FILENAME(type));
2905 /* check if corresponding "levelinfo.conf" file has changed */
2906 token_main = getCacheToken(token_prefix, "TIMESTAMP_LEVELINFO");
2907 cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
2909 if (modifiedFileTimestamp(filename_levelinfo, cache_entry))
2912 /* check if corresponding "<artworkinfo>.conf" file has changed */
2913 token_main = getCacheToken(token_prefix, "TIMESTAMP_ARTWORKINFO");
2914 cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
2916 if (modifiedFileTimestamp(filename_artworkinfo, cache_entry))
2919 checked_free(filename_levelinfo);
2920 checked_free(filename_artworkinfo);
2923 if (!cached && artwork_info != NULL)
2925 freeTreeInfo(artwork_info);
2930 return artwork_info;
2933 static void setArtworkInfoCacheEntry(TreeInfo *artwork_info,
2934 LevelDirTree *level_node, int type)
2936 char *identifier = level_node->subdir;
2937 char *type_string = ARTWORK_DIRECTORY(type);
2938 char *token_prefix = getCacheTokenPrefix(type_string, identifier);
2939 char *token_main = getCacheToken(token_prefix, "CACHED");
2940 boolean set_cache_timestamps = TRUE;
2943 setHashEntry(artworkinfo_cache_new, token_main, "true");
2945 if (set_cache_timestamps)
2947 char *filename_levelinfo = getPath2(getLevelDirFromTreeInfo(level_node),
2948 LEVELINFO_FILENAME);
2949 char *filename_artworkinfo = getPath2(getSetupArtworkDir(artwork_info),
2950 ARTWORKINFO_FILENAME(type));
2951 char *timestamp_levelinfo = getFileTimestampString(filename_levelinfo);
2952 char *timestamp_artworkinfo = getFileTimestampString(filename_artworkinfo);
2954 token_main = getCacheToken(token_prefix, "TIMESTAMP_LEVELINFO");
2955 setHashEntry(artworkinfo_cache_new, token_main, timestamp_levelinfo);
2957 token_main = getCacheToken(token_prefix, "TIMESTAMP_ARTWORKINFO");
2958 setHashEntry(artworkinfo_cache_new, token_main, timestamp_artworkinfo);
2960 checked_free(filename_levelinfo);
2961 checked_free(filename_artworkinfo);
2962 checked_free(timestamp_levelinfo);
2963 checked_free(timestamp_artworkinfo);
2966 ldi = *artwork_info;
2967 for (i = 0; artworkinfo_tokens[i].type != -1; i++)
2969 char *token = getCacheToken(token_prefix, artworkinfo_tokens[i].text);
2970 char *value = getSetupValue(artworkinfo_tokens[i].type,
2971 artworkinfo_tokens[i].value);
2973 setHashEntry(artworkinfo_cache_new, token, value);
2978 /* -------------------------------------------------------------------------- */
2979 /* functions for loading level info and custom artwork info */
2980 /* -------------------------------------------------------------------------- */
2982 /* forward declaration for recursive call by "LoadLevelInfoFromLevelDir()" */
2983 static void LoadLevelInfoFromLevelDir(TreeInfo **, TreeInfo *, char *);
2985 static boolean LoadLevelInfoFromLevelConf(TreeInfo **node_first,
2986 TreeInfo *node_parent,
2987 char *level_directory,
2988 char *directory_name)
2990 char *directory_path = getPath2(level_directory, directory_name);
2991 char *filename = getPath2(directory_path, LEVELINFO_FILENAME);
2992 SetupFileHash *setup_file_hash;
2993 LevelDirTree *leveldir_new = NULL;
2996 /* unless debugging, silently ignore directories without "levelinfo.conf" */
2997 if (!options.debug && !fileExists(filename))
2999 free(directory_path);
3005 setup_file_hash = loadSetupFileHash(filename);
3007 if (setup_file_hash == NULL)
3009 #if DEBUG_NO_CONFIG_FILE
3010 Error(ERR_WARN, "ignoring level directory '%s'", directory_path);
3013 free(directory_path);
3019 leveldir_new = newTreeInfo();
3022 setTreeInfoToDefaultsFromParent(leveldir_new, node_parent);
3024 setTreeInfoToDefaults(leveldir_new, TREE_TYPE_LEVEL_DIR);
3026 leveldir_new->subdir = getStringCopy(directory_name);
3028 /* set all structure fields according to the token/value pairs */
3029 ldi = *leveldir_new;
3030 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
3031 setSetupInfo(levelinfo_tokens, i,
3032 getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
3033 *leveldir_new = ldi;
3035 if (strEqual(leveldir_new->name, ANONYMOUS_NAME))
3036 setString(&leveldir_new->name, leveldir_new->subdir);
3038 if (leveldir_new->identifier == NULL)
3039 leveldir_new->identifier = getStringCopy(leveldir_new->subdir);
3041 if (leveldir_new->name_sorting == NULL)
3042 leveldir_new->name_sorting = getStringCopy(leveldir_new->name);
3044 if (node_parent == NULL) /* top level group */
3046 leveldir_new->basepath = getStringCopy(level_directory);
3047 leveldir_new->fullpath = getStringCopy(leveldir_new->subdir);
3049 else /* sub level group */
3051 leveldir_new->basepath = getStringCopy(node_parent->basepath);
3052 leveldir_new->fullpath = getPath2(node_parent->fullpath, directory_name);
3055 leveldir_new->last_level =
3056 leveldir_new->first_level + leveldir_new->levels - 1;
3058 leveldir_new->in_user_dir =
3059 (!strEqual(leveldir_new->basepath, options.level_directory));
3061 /* adjust some settings if user's private level directory was detected */
3062 if (leveldir_new->sort_priority == LEVELCLASS_UNDEFINED &&
3063 leveldir_new->in_user_dir &&
3064 (strEqual(leveldir_new->subdir, getLoginName()) ||
3065 strEqual(leveldir_new->name, getLoginName()) ||
3066 strEqual(leveldir_new->author, getRealName())))
3068 leveldir_new->sort_priority = LEVELCLASS_PRIVATE_START;
3069 leveldir_new->readonly = FALSE;
3072 leveldir_new->user_defined =
3073 (leveldir_new->in_user_dir && IS_LEVELCLASS_PRIVATE(leveldir_new));
3075 leveldir_new->color = LEVELCOLOR(leveldir_new);
3077 setString(&leveldir_new->class_desc, getLevelClassDescription(leveldir_new));
3079 leveldir_new->handicap_level = /* set handicap to default value */
3080 (leveldir_new->user_defined || !leveldir_new->handicap ?
3081 leveldir_new->last_level : leveldir_new->first_level);
3083 DrawInitText(leveldir_new->name, 150, FC_YELLOW);
3085 pushTreeInfo(node_first, leveldir_new);
3087 freeSetupFileHash(setup_file_hash);
3089 if (leveldir_new->level_group)
3091 /* create node to link back to current level directory */
3092 createParentTreeInfoNode(leveldir_new);
3094 /* recursively step into sub-directory and look for more level series */
3095 LoadLevelInfoFromLevelDir(&leveldir_new->node_group,
3096 leveldir_new, directory_path);
3099 free(directory_path);
3105 static void LoadLevelInfoFromLevelDir(TreeInfo **node_first,
3106 TreeInfo *node_parent,
3107 char *level_directory)
3110 DirectoryEntry *dir_entry;
3111 boolean valid_entry_found = FALSE;
3113 if ((dir = openDirectory(level_directory)) == NULL)
3115 Error(ERR_WARN, "cannot read level directory '%s'", level_directory);
3120 while ((dir_entry = readDirectory(dir)) != NULL) /* loop all entries */
3122 char *directory_name = dir_entry->basename;
3123 char *directory_path = getPath2(level_directory, directory_name);
3125 /* skip entries for current and parent directory */
3126 if (strEqual(directory_name, ".") ||
3127 strEqual(directory_name, ".."))
3129 free(directory_path);
3134 /* find out if directory entry is itself a directory */
3135 if (!dir_entry->is_directory) /* not a directory */
3137 free(directory_path);
3142 free(directory_path);
3144 if (strEqual(directory_name, GRAPHICS_DIRECTORY) ||
3145 strEqual(directory_name, SOUNDS_DIRECTORY) ||
3146 strEqual(directory_name, MUSIC_DIRECTORY))
3149 valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
3154 closeDirectory(dir);
3156 /* special case: top level directory may directly contain "levelinfo.conf" */
3157 if (node_parent == NULL && !valid_entry_found)
3159 /* check if this directory directly contains a file "levelinfo.conf" */
3160 valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
3161 level_directory, ".");
3164 if (!valid_entry_found)
3165 Error(ERR_WARN, "cannot find any valid level series in directory '%s'",
3169 boolean AdjustGraphicsForEMC()
3171 boolean settings_changed = FALSE;
3173 settings_changed |= adjustTreeGraphicsForEMC(leveldir_first_all);
3174 settings_changed |= adjustTreeGraphicsForEMC(leveldir_first);
3176 return settings_changed;
3179 void LoadLevelInfo()
3181 InitUserLevelDirectory(getLoginName());
3183 DrawInitText("Loading level series", 120, FC_GREEN);
3185 LoadLevelInfoFromLevelDir(&leveldir_first, NULL, options.level_directory);
3186 LoadLevelInfoFromLevelDir(&leveldir_first, NULL, getUserLevelDir(NULL));
3188 leveldir_first = createTopTreeInfoNode(leveldir_first);
3190 /* after loading all level set information, clone the level directory tree
3191 and remove all level sets without levels (these may still contain artwork
3192 to be offered in the setup menu as "custom artwork", and are therefore
3193 checked for existing artwork in the function "LoadLevelArtworkInfo()") */
3194 leveldir_first_all = leveldir_first;
3195 cloneTree(&leveldir_first, leveldir_first_all, TRUE);
3197 AdjustGraphicsForEMC();
3199 /* before sorting, the first entries will be from the user directory */
3200 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
3202 if (leveldir_first == NULL)
3203 Error(ERR_EXIT, "cannot find any valid level series in any directory");
3205 sortTreeInfo(&leveldir_first);
3207 #if ENABLE_UNUSED_CODE
3208 dumpTreeInfo(leveldir_first, 0);
3212 static boolean LoadArtworkInfoFromArtworkConf(TreeInfo **node_first,
3213 TreeInfo *node_parent,
3214 char *base_directory,
3215 char *directory_name, int type)
3217 char *directory_path = getPath2(base_directory, directory_name);
3218 char *filename = getPath2(directory_path, ARTWORKINFO_FILENAME(type));
3219 SetupFileHash *setup_file_hash = NULL;
3220 TreeInfo *artwork_new = NULL;
3223 if (fileExists(filename))
3224 setup_file_hash = loadSetupFileHash(filename);
3226 if (setup_file_hash == NULL) /* no config file -- look for artwork files */
3229 DirectoryEntry *dir_entry;
3230 boolean valid_file_found = FALSE;
3232 if ((dir = openDirectory(directory_path)) != NULL)
3234 while ((dir_entry = readDirectory(dir)) != NULL)
3236 if (FileIsArtworkType(dir_entry->filename, type))
3238 valid_file_found = TRUE;
3244 closeDirectory(dir);
3247 if (!valid_file_found)
3249 #if DEBUG_NO_CONFIG_FILE
3250 if (!strEqual(directory_name, "."))
3251 Error(ERR_WARN, "ignoring artwork directory '%s'", directory_path);
3254 free(directory_path);
3261 artwork_new = newTreeInfo();
3264 setTreeInfoToDefaultsFromParent(artwork_new, node_parent);
3266 setTreeInfoToDefaults(artwork_new, type);
3268 artwork_new->subdir = getStringCopy(directory_name);
3270 if (setup_file_hash) /* (before defining ".color" and ".class_desc") */
3272 /* set all structure fields according to the token/value pairs */
3274 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
3275 setSetupInfo(levelinfo_tokens, i,
3276 getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
3279 if (strEqual(artwork_new->name, ANONYMOUS_NAME))
3280 setString(&artwork_new->name, artwork_new->subdir);
3282 if (artwork_new->identifier == NULL)
3283 artwork_new->identifier = getStringCopy(artwork_new->subdir);
3285 if (artwork_new->name_sorting == NULL)
3286 artwork_new->name_sorting = getStringCopy(artwork_new->name);
3289 if (node_parent == NULL) /* top level group */
3291 artwork_new->basepath = getStringCopy(base_directory);
3292 artwork_new->fullpath = getStringCopy(artwork_new->subdir);
3294 else /* sub level group */
3296 artwork_new->basepath = getStringCopy(node_parent->basepath);
3297 artwork_new->fullpath = getPath2(node_parent->fullpath, directory_name);
3300 artwork_new->in_user_dir =
3301 (!strEqual(artwork_new->basepath, OPTIONS_ARTWORK_DIRECTORY(type)));
3303 /* (may use ".sort_priority" from "setup_file_hash" above) */
3304 artwork_new->color = ARTWORKCOLOR(artwork_new);
3306 setString(&artwork_new->class_desc, getLevelClassDescription(artwork_new));
3308 if (setup_file_hash == NULL) /* (after determining ".user_defined") */
3310 if (strEqual(artwork_new->subdir, "."))
3312 if (artwork_new->user_defined)
3314 setString(&artwork_new->identifier, "private");
3315 artwork_new->sort_priority = ARTWORKCLASS_PRIVATE;
3319 setString(&artwork_new->identifier, "classic");
3320 artwork_new->sort_priority = ARTWORKCLASS_CLASSICS;
3323 /* set to new values after changing ".sort_priority" */
3324 artwork_new->color = ARTWORKCOLOR(artwork_new);
3326 setString(&artwork_new->class_desc,
3327 getLevelClassDescription(artwork_new));
3331 setString(&artwork_new->identifier, artwork_new->subdir);
3334 setString(&artwork_new->name, artwork_new->identifier);
3335 setString(&artwork_new->name_sorting, artwork_new->name);
3338 pushTreeInfo(node_first, artwork_new);
3340 freeSetupFileHash(setup_file_hash);
3342 free(directory_path);
3348 static void LoadArtworkInfoFromArtworkDir(TreeInfo **node_first,
3349 TreeInfo *node_parent,
3350 char *base_directory, int type)
3353 DirectoryEntry *dir_entry;
3354 boolean valid_entry_found = FALSE;
3356 if ((dir = openDirectory(base_directory)) == NULL)
3358 /* display error if directory is main "options.graphics_directory" etc. */
3359 if (base_directory == OPTIONS_ARTWORK_DIRECTORY(type))
3360 Error(ERR_WARN, "cannot read directory '%s'", base_directory);
3365 while ((dir_entry = readDirectory(dir)) != NULL) /* loop all entries */
3367 char *directory_name = dir_entry->basename;
3368 char *directory_path = getPath2(base_directory, directory_name);
3370 /* skip directory entries for current and parent directory */
3371 if (strEqual(directory_name, ".") ||
3372 strEqual(directory_name, ".."))
3374 free(directory_path);
3379 /* skip directory entries which are not a directory */
3380 if (!dir_entry->is_directory) /* not a directory */
3382 free(directory_path);
3387 free(directory_path);
3389 /* check if this directory contains artwork with or without config file */
3390 valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
3392 directory_name, type);
3395 closeDirectory(dir);
3397 /* check if this directory directly contains artwork itself */
3398 valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
3399 base_directory, ".",
3401 if (!valid_entry_found)
3402 Error(ERR_WARN, "cannot find any valid artwork in directory '%s'",
3406 static TreeInfo *getDummyArtworkInfo(int type)
3408 /* this is only needed when there is completely no artwork available */
3409 TreeInfo *artwork_new = newTreeInfo();
3411 setTreeInfoToDefaults(artwork_new, type);
3413 setString(&artwork_new->subdir, UNDEFINED_FILENAME);
3414 setString(&artwork_new->fullpath, UNDEFINED_FILENAME);
3415 setString(&artwork_new->basepath, UNDEFINED_FILENAME);
3417 setString(&artwork_new->identifier, UNDEFINED_FILENAME);
3418 setString(&artwork_new->name, UNDEFINED_FILENAME);
3419 setString(&artwork_new->name_sorting, UNDEFINED_FILENAME);
3424 void LoadArtworkInfo()
3426 LoadArtworkInfoCache();
3428 DrawInitText("Looking for custom artwork", 120, FC_GREEN);
3430 LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
3431 options.graphics_directory,
3432 TREE_TYPE_GRAPHICS_DIR);
3433 LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
3434 getUserGraphicsDir(),
3435 TREE_TYPE_GRAPHICS_DIR);
3437 LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
3438 options.sounds_directory,
3439 TREE_TYPE_SOUNDS_DIR);
3440 LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
3442 TREE_TYPE_SOUNDS_DIR);
3444 LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
3445 options.music_directory,
3446 TREE_TYPE_MUSIC_DIR);
3447 LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
3449 TREE_TYPE_MUSIC_DIR);
3451 if (artwork.gfx_first == NULL)
3452 artwork.gfx_first = getDummyArtworkInfo(TREE_TYPE_GRAPHICS_DIR);
3453 if (artwork.snd_first == NULL)
3454 artwork.snd_first = getDummyArtworkInfo(TREE_TYPE_SOUNDS_DIR);
3455 if (artwork.mus_first == NULL)
3456 artwork.mus_first = getDummyArtworkInfo(TREE_TYPE_MUSIC_DIR);
3458 /* before sorting, the first entries will be from the user directory */
3459 artwork.gfx_current =
3460 getTreeInfoFromIdentifier(artwork.gfx_first, setup.graphics_set);
3461 if (artwork.gfx_current == NULL)
3462 artwork.gfx_current =
3463 getTreeInfoFromIdentifier(artwork.gfx_first, GFX_DEFAULT_SUBDIR);
3464 if (artwork.gfx_current == NULL)
3465 artwork.gfx_current = getFirstValidTreeInfoEntry(artwork.gfx_first);
3467 artwork.snd_current =
3468 getTreeInfoFromIdentifier(artwork.snd_first, setup.sounds_set);
3469 if (artwork.snd_current == NULL)
3470 artwork.snd_current =
3471 getTreeInfoFromIdentifier(artwork.snd_first, SND_DEFAULT_SUBDIR);
3472 if (artwork.snd_current == NULL)
3473 artwork.snd_current = getFirstValidTreeInfoEntry(artwork.snd_first);
3475 artwork.mus_current =
3476 getTreeInfoFromIdentifier(artwork.mus_first, setup.music_set);
3477 if (artwork.mus_current == NULL)
3478 artwork.mus_current =
3479 getTreeInfoFromIdentifier(artwork.mus_first, MUS_DEFAULT_SUBDIR);
3480 if (artwork.mus_current == NULL)
3481 artwork.mus_current = getFirstValidTreeInfoEntry(artwork.mus_first);
3483 artwork.gfx_current_identifier = artwork.gfx_current->identifier;
3484 artwork.snd_current_identifier = artwork.snd_current->identifier;
3485 artwork.mus_current_identifier = artwork.mus_current->identifier;
3487 #if ENABLE_UNUSED_CODE
3488 printf("graphics set == %s\n\n", artwork.gfx_current_identifier);
3489 printf("sounds set == %s\n\n", artwork.snd_current_identifier);
3490 printf("music set == %s\n\n", artwork.mus_current_identifier);
3493 sortTreeInfo(&artwork.gfx_first);
3494 sortTreeInfo(&artwork.snd_first);
3495 sortTreeInfo(&artwork.mus_first);
3497 #if ENABLE_UNUSED_CODE
3498 dumpTreeInfo(artwork.gfx_first, 0);
3499 dumpTreeInfo(artwork.snd_first, 0);
3500 dumpTreeInfo(artwork.mus_first, 0);
3504 void LoadArtworkInfoFromLevelInfo(ArtworkDirTree **artwork_node,
3505 LevelDirTree *level_node)
3507 int type = (*artwork_node)->type;
3509 /* recursively check all level directories for artwork sub-directories */
3513 /* check all tree entries for artwork, but skip parent link entries */
3514 if (!level_node->parent_link)
3516 TreeInfo *artwork_new = getArtworkInfoCacheEntry(level_node, type);
3517 boolean cached = (artwork_new != NULL);
3521 pushTreeInfo(artwork_node, artwork_new);
3525 TreeInfo *topnode_last = *artwork_node;
3526 char *path = getPath2(getLevelDirFromTreeInfo(level_node),
3527 ARTWORK_DIRECTORY(type));
3529 LoadArtworkInfoFromArtworkDir(artwork_node, NULL, path, type);
3531 if (topnode_last != *artwork_node) /* check for newly added node */
3533 artwork_new = *artwork_node;
3535 setString(&artwork_new->identifier, level_node->subdir);
3536 setString(&artwork_new->name, level_node->name);
3537 setString(&artwork_new->name_sorting, level_node->name_sorting);
3539 artwork_new->sort_priority = level_node->sort_priority;
3540 artwork_new->color = LEVELCOLOR(artwork_new);
3546 /* insert artwork info (from old cache or filesystem) into new cache */
3547 if (artwork_new != NULL)
3548 setArtworkInfoCacheEntry(artwork_new, level_node, type);
3551 DrawInitText(level_node->name, 150, FC_YELLOW);
3553 if (level_node->node_group != NULL)
3554 LoadArtworkInfoFromLevelInfo(artwork_node, level_node->node_group);
3556 level_node = level_node->next;
3560 void LoadLevelArtworkInfo()
3562 print_timestamp_init("LoadLevelArtworkInfo");
3564 DrawInitText("Looking for custom level artwork", 120, FC_GREEN);
3566 print_timestamp_time("DrawTimeText");
3568 LoadArtworkInfoFromLevelInfo(&artwork.gfx_first, leveldir_first_all);
3569 print_timestamp_time("LoadArtworkInfoFromLevelInfo (gfx)");
3570 LoadArtworkInfoFromLevelInfo(&artwork.snd_first, leveldir_first_all);
3571 print_timestamp_time("LoadArtworkInfoFromLevelInfo (snd)");
3572 LoadArtworkInfoFromLevelInfo(&artwork.mus_first, leveldir_first_all);
3573 print_timestamp_time("LoadArtworkInfoFromLevelInfo (mus)");
3575 SaveArtworkInfoCache();
3577 print_timestamp_time("SaveArtworkInfoCache");
3579 /* needed for reloading level artwork not known at ealier stage */
3581 if (!strEqual(artwork.gfx_current_identifier, setup.graphics_set))
3583 artwork.gfx_current =
3584 getTreeInfoFromIdentifier(artwork.gfx_first, setup.graphics_set);
3585 if (artwork.gfx_current == NULL)
3586 artwork.gfx_current =
3587 getTreeInfoFromIdentifier(artwork.gfx_first, GFX_DEFAULT_SUBDIR);
3588 if (artwork.gfx_current == NULL)
3589 artwork.gfx_current = getFirstValidTreeInfoEntry(artwork.gfx_first);
3592 if (!strEqual(artwork.snd_current_identifier, setup.sounds_set))
3594 artwork.snd_current =
3595 getTreeInfoFromIdentifier(artwork.snd_first, setup.sounds_set);
3596 if (artwork.snd_current == NULL)
3597 artwork.snd_current =
3598 getTreeInfoFromIdentifier(artwork.snd_first, SND_DEFAULT_SUBDIR);
3599 if (artwork.snd_current == NULL)
3600 artwork.snd_current = getFirstValidTreeInfoEntry(artwork.snd_first);
3603 if (!strEqual(artwork.mus_current_identifier, setup.music_set))
3605 artwork.mus_current =
3606 getTreeInfoFromIdentifier(artwork.mus_first, setup.music_set);
3607 if (artwork.mus_current == NULL)
3608 artwork.mus_current =
3609 getTreeInfoFromIdentifier(artwork.mus_first, MUS_DEFAULT_SUBDIR);
3610 if (artwork.mus_current == NULL)
3611 artwork.mus_current = getFirstValidTreeInfoEntry(artwork.mus_first);
3614 print_timestamp_time("getTreeInfoFromIdentifier");
3616 sortTreeInfo(&artwork.gfx_first);
3617 sortTreeInfo(&artwork.snd_first);
3618 sortTreeInfo(&artwork.mus_first);
3620 print_timestamp_time("sortTreeInfo");
3622 #if ENABLE_UNUSED_CODE
3623 dumpTreeInfo(artwork.gfx_first, 0);
3624 dumpTreeInfo(artwork.snd_first, 0);
3625 dumpTreeInfo(artwork.mus_first, 0);
3628 print_timestamp_done("LoadLevelArtworkInfo");
3631 static boolean AddUserLevelSetToLevelInfoExt(char *level_subdir_new)
3633 // get level info tree node of first (original) user level set
3634 char *level_subdir_old = getLoginName();
3635 LevelDirTree *leveldir_old = getTreeInfoFromIdentifier(leveldir_first,
3637 if (leveldir_old == NULL) // should not happen
3640 int draw_deactivation_mask = GetDrawDeactivationMask();
3642 // override draw deactivation mask (temporarily disable drawing)
3643 SetDrawDeactivationMask(REDRAW_ALL);
3645 // load new level set config and add it next to first user level set
3646 LoadLevelInfoFromLevelConf(&leveldir_old->next, NULL,
3647 leveldir_old->basepath, level_subdir_new);
3649 // set draw deactivation mask to previous value
3650 SetDrawDeactivationMask(draw_deactivation_mask);
3652 // get level info tree node of newly added user level set
3653 LevelDirTree *leveldir_new = getTreeInfoFromIdentifier(leveldir_first,
3655 if (leveldir_new == NULL) // should not happen
3658 // correct top link and parent node link of newly created tree node
3659 leveldir_new->node_top = leveldir_old->node_top;
3660 leveldir_new->node_parent = leveldir_old->node_parent;
3662 // sort level info tree to adjust position of newly added level set
3663 sortTreeInfo(&leveldir_first);
3668 void AddUserLevelSetToLevelInfo(char *level_subdir_new)
3670 if (!AddUserLevelSetToLevelInfoExt(level_subdir_new))
3671 Error(ERR_EXIT, "internal level set structure corrupted -- aborting");
3674 char *getArtworkIdentifierForUserLevelSet(int type)
3676 char *classic_artwork_set = getClassicArtworkSet(type);
3678 /* check for custom artwork configured in "levelinfo.conf" */
3679 char *leveldir_artwork_set =
3680 *LEVELDIR_ARTWORK_SET_PTR(leveldir_current, type);
3681 boolean has_leveldir_artwork_set =
3682 (leveldir_artwork_set != NULL && !strEqual(leveldir_artwork_set,
3683 classic_artwork_set));
3685 /* check for custom artwork in sub-directory "graphics" etc. */
3686 TreeInfo *artwork_first_node = ARTWORK_FIRST_NODE(artwork, type);
3687 char *leveldir_identifier = leveldir_current->identifier;
3688 boolean has_artwork_subdir =
3689 (getTreeInfoFromIdentifier(artwork_first_node,
3690 leveldir_identifier) != NULL);
3692 return (has_leveldir_artwork_set ? leveldir_artwork_set :
3693 has_artwork_subdir ? leveldir_identifier :
3694 classic_artwork_set);
3697 TreeInfo *getArtworkTreeInfoForUserLevelSet(int type)
3699 char *artwork_set = getArtworkIdentifierForUserLevelSet(type);
3700 TreeInfo *artwork_first_node = ARTWORK_FIRST_NODE(artwork, type);
3702 return getTreeInfoFromIdentifier(artwork_first_node, artwork_set);
3705 boolean checkIfCustomArtworkExistsForCurrentLevelSet()
3707 char *graphics_set =
3708 getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_GRAPHICS);
3710 getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_SOUNDS);
3712 getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_MUSIC);
3714 return (!strEqual(graphics_set, GFX_CLASSIC_SUBDIR) ||
3715 !strEqual(sounds_set, SND_CLASSIC_SUBDIR) ||
3716 !strEqual(music_set, MUS_CLASSIC_SUBDIR));
3719 boolean UpdateUserLevelSet(char *level_subdir, char *level_name,
3720 char *level_author, int num_levels)
3722 char *filename = getPath2(getUserLevelDir(level_subdir), LEVELINFO_FILENAME);
3723 char *filename_tmp = getStringCat2(filename, ".tmp");
3725 FILE *file_tmp = NULL;
3726 char line[MAX_LINE_LEN];
3727 boolean success = FALSE;
3728 LevelDirTree *leveldir = getTreeInfoFromIdentifier(leveldir_first,
3730 // update values in level directory tree
3732 if (level_name != NULL)
3733 setString(&leveldir->name, level_name);
3735 if (level_author != NULL)
3736 setString(&leveldir->author, level_author);
3738 if (num_levels != -1)
3739 leveldir->levels = num_levels;
3741 // update values that depend on other values
3743 setString(&leveldir->name_sorting, leveldir->name);
3745 leveldir->last_level = leveldir->first_level + leveldir->levels - 1;
3747 // sort order of level sets may have changed
3748 sortTreeInfo(&leveldir_first);
3750 if ((file = fopen(filename, MODE_READ)) &&
3751 (file_tmp = fopen(filename_tmp, MODE_WRITE)))
3753 while (fgets(line, MAX_LINE_LEN, file))
3755 if (strPrefix(line, "name:") && level_name != NULL)
3756 fprintf(file_tmp, "%-32s%s\n", "name:", level_name);
3757 else if (strPrefix(line, "author:") && level_author != NULL)
3758 fprintf(file_tmp, "%-32s%s\n", "author:", level_author);
3759 else if (strPrefix(line, "levels:") && num_levels != -1)
3760 fprintf(file_tmp, "%-32s%d\n", "levels:", num_levels);
3762 fputs(line, file_tmp);
3775 success = (rename(filename_tmp, filename) == 0);
3783 boolean CreateUserLevelSet(char *level_subdir, char *level_name,
3784 char *level_author, int num_levels,
3785 boolean use_artwork_set)
3787 LevelDirTree *level_info;
3792 // create user level sub-directory, if needed
3793 createDirectory(getUserLevelDir(level_subdir), "user level", PERMS_PRIVATE);
3795 filename = getPath2(getUserLevelDir(level_subdir), LEVELINFO_FILENAME);
3797 if (!(file = fopen(filename, MODE_WRITE)))
3799 Error(ERR_WARN, "cannot write level info file '%s'", filename);
3805 level_info = newTreeInfo();
3807 /* always start with reliable default values */
3808 setTreeInfoToDefaults(level_info, TREE_TYPE_LEVEL_DIR);
3810 setString(&level_info->name, level_name);
3811 setString(&level_info->author, level_author);
3812 level_info->levels = num_levels;
3813 level_info->first_level = 1;
3814 level_info->sort_priority = LEVELCLASS_PRIVATE_START;
3815 level_info->readonly = FALSE;
3817 if (use_artwork_set)
3819 level_info->graphics_set =
3820 getStringCopy(getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_GRAPHICS));
3821 level_info->sounds_set =
3822 getStringCopy(getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_SOUNDS));
3823 level_info->music_set =
3824 getStringCopy(getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_MUSIC));
3827 token_value_position = TOKEN_VALUE_POSITION_SHORT;
3829 fprintFileHeader(file, LEVELINFO_FILENAME);
3832 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
3834 if (i == LEVELINFO_TOKEN_NAME ||
3835 i == LEVELINFO_TOKEN_AUTHOR ||
3836 i == LEVELINFO_TOKEN_LEVELS ||
3837 i == LEVELINFO_TOKEN_FIRST_LEVEL ||
3838 i == LEVELINFO_TOKEN_SORT_PRIORITY ||
3839 i == LEVELINFO_TOKEN_READONLY ||
3840 (use_artwork_set && (i == LEVELINFO_TOKEN_GRAPHICS_SET ||
3841 i == LEVELINFO_TOKEN_SOUNDS_SET ||
3842 i == LEVELINFO_TOKEN_MUSIC_SET)))
3843 fprintf(file, "%s\n", getSetupLine(levelinfo_tokens, "", i));
3845 /* just to make things nicer :) */
3846 if (i == LEVELINFO_TOKEN_AUTHOR ||
3847 i == LEVELINFO_TOKEN_FIRST_LEVEL ||
3848 (use_artwork_set && i == LEVELINFO_TOKEN_READONLY))
3849 fprintf(file, "\n");
3852 token_value_position = TOKEN_VALUE_POSITION_DEFAULT;
3856 SetFilePermissions(filename, PERMS_PRIVATE);
3858 freeTreeInfo(level_info);
3864 static void SaveUserLevelInfo()
3866 CreateUserLevelSet(getLoginName(), getLoginName(), getRealName(), 100, FALSE);
3869 char *getSetupValue(int type, void *value)
3871 static char value_string[MAX_LINE_LEN];
3879 strcpy(value_string, (*(boolean *)value ? "true" : "false"));
3883 strcpy(value_string, (*(boolean *)value ? "on" : "off"));
3887 strcpy(value_string, (*(int *)value == AUTO ? "auto" :
3888 *(int *)value == FALSE ? "off" : "on"));
3892 strcpy(value_string, (*(boolean *)value ? "yes" : "no"));
3895 case TYPE_YES_NO_AUTO:
3896 strcpy(value_string, (*(int *)value == AUTO ? "auto" :
3897 *(int *)value == FALSE ? "no" : "yes"));
3901 strcpy(value_string, (*(boolean *)value ? "AGA" : "ECS"));
3905 strcpy(value_string, getKeyNameFromKey(*(Key *)value));
3909 strcpy(value_string, getX11KeyNameFromKey(*(Key *)value));
3913 sprintf(value_string, "%d", *(int *)value);
3917 if (*(char **)value == NULL)
3920 strcpy(value_string, *(char **)value);
3924 sprintf(value_string, "player_%d", *(int *)value + 1);
3928 value_string[0] = '\0';
3932 if (type & TYPE_GHOSTED)
3933 strcpy(value_string, "n/a");
3935 return value_string;
3938 char *getSetupLine(struct TokenInfo *token_info, char *prefix, int token_nr)
3942 static char token_string[MAX_LINE_LEN];
3943 int token_type = token_info[token_nr].type;
3944 void *setup_value = token_info[token_nr].value;
3945 char *token_text = token_info[token_nr].text;
3946 char *value_string = getSetupValue(token_type, setup_value);
3948 /* build complete token string */
3949 sprintf(token_string, "%s%s", prefix, token_text);
3951 /* build setup entry line */
3952 line = getFormattedSetupEntry(token_string, value_string);
3954 if (token_type == TYPE_KEY_X11)
3956 Key key = *(Key *)setup_value;
3957 char *keyname = getKeyNameFromKey(key);
3959 /* add comment, if useful */
3960 if (!strEqual(keyname, "(undefined)") &&
3961 !strEqual(keyname, "(unknown)"))
3963 /* add at least one whitespace */
3965 for (i = strlen(line); i < token_comment_position; i++)
3969 strcat(line, keyname);
3976 void LoadLevelSetup_LastSeries()
3978 /* ----------------------------------------------------------------------- */
3979 /* ~/.<program>/levelsetup.conf */
3980 /* ----------------------------------------------------------------------- */
3982 char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
3983 SetupFileHash *level_setup_hash = NULL;
3985 /* always start with reliable default values */
3986 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
3988 if (!strEqual(DEFAULT_LEVELSET, UNDEFINED_LEVELSET))
3990 leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
3992 if (leveldir_current == NULL)
3993 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
3996 if ((level_setup_hash = loadSetupFileHash(filename)))
3998 char *last_level_series =
3999 getHashEntry(level_setup_hash, TOKEN_STR_LAST_LEVEL_SERIES);
4001 leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
4003 if (leveldir_current == NULL)
4004 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
4006 freeSetupFileHash(level_setup_hash);
4010 Error(ERR_DEBUG, "using default setup values");
4016 static void SaveLevelSetup_LastSeries_Ext(boolean deactivate_last_level_series)
4018 /* ----------------------------------------------------------------------- */
4019 /* ~/.<program>/levelsetup.conf */
4020 /* ----------------------------------------------------------------------- */
4022 // check if the current level directory structure is available at this point
4023 if (leveldir_current == NULL)
4026 char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
4027 char *level_subdir = leveldir_current->subdir;
4030 InitUserDataDirectory();
4032 if (!(file = fopen(filename, MODE_WRITE)))
4034 Error(ERR_WARN, "cannot write setup file '%s'", filename);
4041 fprintFileHeader(file, LEVELSETUP_FILENAME);
4043 if (deactivate_last_level_series)
4044 fprintf(file, "# %s\n# ", "the following level set may have caused a problem and was deactivated");
4046 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_LEVEL_SERIES,
4051 SetFilePermissions(filename, PERMS_PRIVATE);
4056 void SaveLevelSetup_LastSeries()
4058 SaveLevelSetup_LastSeries_Ext(FALSE);
4061 void SaveLevelSetup_LastSeries_Deactivate()
4063 SaveLevelSetup_LastSeries_Ext(TRUE);
4066 static void checkSeriesInfo()
4068 static char *level_directory = NULL;
4071 /* check for more levels besides the 'levels' field of 'levelinfo.conf' */
4073 level_directory = getPath2((leveldir_current->in_user_dir ?
4074 getUserLevelDir(NULL) :
4075 options.level_directory),
4076 leveldir_current->fullpath);
4078 if ((dir = openDirectory(level_directory)) == NULL)
4080 Error(ERR_WARN, "cannot read level directory '%s'", level_directory);
4085 closeDirectory(dir);
4088 void LoadLevelSetup_SeriesInfo()
4091 SetupFileHash *level_setup_hash = NULL;
4092 char *level_subdir = leveldir_current->subdir;
4095 /* always start with reliable default values */
4096 level_nr = leveldir_current->first_level;
4098 for (i = 0; i < MAX_LEVELS; i++)
4100 LevelStats_setPlayed(i, 0);
4101 LevelStats_setSolved(i, 0);
4106 /* ----------------------------------------------------------------------- */
4107 /* ~/.<program>/levelsetup/<level series>/levelsetup.conf */
4108 /* ----------------------------------------------------------------------- */
4110 level_subdir = leveldir_current->subdir;
4112 filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
4114 if ((level_setup_hash = loadSetupFileHash(filename)))
4118 /* get last played level in this level set */
4120 token_value = getHashEntry(level_setup_hash, TOKEN_STR_LAST_PLAYED_LEVEL);
4124 level_nr = atoi(token_value);
4126 if (level_nr < leveldir_current->first_level)
4127 level_nr = leveldir_current->first_level;
4128 if (level_nr > leveldir_current->last_level)
4129 level_nr = leveldir_current->last_level;
4132 /* get handicap level in this level set */
4134 token_value = getHashEntry(level_setup_hash, TOKEN_STR_HANDICAP_LEVEL);
4138 int level_nr = atoi(token_value);
4140 if (level_nr < leveldir_current->first_level)
4141 level_nr = leveldir_current->first_level;
4142 if (level_nr > leveldir_current->last_level + 1)
4143 level_nr = leveldir_current->last_level;
4145 if (leveldir_current->user_defined || !leveldir_current->handicap)
4146 level_nr = leveldir_current->last_level;
4148 leveldir_current->handicap_level = level_nr;
4151 /* get number of played and solved levels in this level set */
4153 BEGIN_HASH_ITERATION(level_setup_hash, itr)
4155 char *token = HASH_ITERATION_TOKEN(itr);
4156 char *value = HASH_ITERATION_VALUE(itr);
4158 if (strlen(token) == 3 &&
4159 token[0] >= '0' && token[0] <= '9' &&
4160 token[1] >= '0' && token[1] <= '9' &&
4161 token[2] >= '0' && token[2] <= '9')
4163 int level_nr = atoi(token);
4166 LevelStats_setPlayed(level_nr, atoi(value)); /* read 1st column */
4168 value = strchr(value, ' ');
4171 LevelStats_setSolved(level_nr, atoi(value)); /* read 2nd column */
4174 END_HASH_ITERATION(hash, itr)
4176 freeSetupFileHash(level_setup_hash);
4180 Error(ERR_DEBUG, "using default setup values");
4186 void SaveLevelSetup_SeriesInfo()
4189 char *level_subdir = leveldir_current->subdir;
4190 char *level_nr_str = int2str(level_nr, 0);
4191 char *handicap_level_str = int2str(leveldir_current->handicap_level, 0);
4195 /* ----------------------------------------------------------------------- */
4196 /* ~/.<program>/levelsetup/<level series>/levelsetup.conf */
4197 /* ----------------------------------------------------------------------- */
4199 InitLevelSetupDirectory(level_subdir);
4201 filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
4203 if (!(file = fopen(filename, MODE_WRITE)))
4205 Error(ERR_WARN, "cannot write setup file '%s'", filename);
4210 fprintFileHeader(file, LEVELSETUP_FILENAME);
4212 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_PLAYED_LEVEL,
4214 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_HANDICAP_LEVEL,
4215 handicap_level_str));
4217 for (i = leveldir_current->first_level; i <= leveldir_current->last_level;
4220 if (LevelStats_getPlayed(i) > 0 ||
4221 LevelStats_getSolved(i) > 0)
4226 sprintf(token, "%03d", i);
4227 sprintf(value, "%d %d", LevelStats_getPlayed(i), LevelStats_getSolved(i));
4229 fprintf(file, "%s\n", getFormattedSetupEntry(token, value));
4235 SetFilePermissions(filename, PERMS_PRIVATE);
4240 int LevelStats_getPlayed(int nr)
4242 return (nr >= 0 && nr < MAX_LEVELS ? level_stats[nr].played : 0);
4245 int LevelStats_getSolved(int nr)
4247 return (nr >= 0 && nr < MAX_LEVELS ? level_stats[nr].solved : 0);
4250 void LevelStats_setPlayed(int nr, int value)
4252 if (nr >= 0 && nr < MAX_LEVELS)
4253 level_stats[nr].played = value;
4256 void LevelStats_setSolved(int nr, int value)
4258 if (nr >= 0 && nr < MAX_LEVELS)
4259 level_stats[nr].solved = value;
4262 void LevelStats_incPlayed(int nr)
4264 if (nr >= 0 && nr < MAX_LEVELS)
4265 level_stats[nr].played++;
4268 void LevelStats_incSolved(int nr)
4270 if (nr >= 0 && nr < MAX_LEVELS)
4271 level_stats[nr].solved++;