1 // ============================================================================
2 // Artsoft Retro-Game Library
3 // ----------------------------------------------------------------------------
4 // (c) 1995-2014 by Artsoft Entertainment
7 // https://www.artsoft.org/
8 // ----------------------------------------------------------------------------
10 // ============================================================================
12 #include <sys/types.h>
26 #include "zip/miniunz.h"
29 #define ENABLE_UNUSED_CODE FALSE // for currently unused functions
30 #define DEBUG_NO_CONFIG_FILE FALSE // for extra-verbose debug output
32 #define NUM_LEVELCLASS_DESC 8
34 static char *levelclass_desc[NUM_LEVELCLASS_DESC] =
46 #define TOKEN_VALUE_POSITION_SHORT 32
47 #define TOKEN_VALUE_POSITION_DEFAULT 40
48 #define TOKEN_COMMENT_POSITION_DEFAULT 60
50 #define MAX_COOKIE_LEN 256
53 static void setTreeInfoToDefaults(TreeInfo *, int);
54 static TreeInfo *getTreeInfoCopy(TreeInfo *ti);
55 static int compareTreeInfoEntries(const void *, const void *);
57 static int token_value_position = TOKEN_VALUE_POSITION_DEFAULT;
58 static int token_comment_position = TOKEN_COMMENT_POSITION_DEFAULT;
60 static SetupFileHash *artworkinfo_cache_old = NULL;
61 static SetupFileHash *artworkinfo_cache_new = NULL;
62 static SetupFileHash *optional_tokens_hash = NULL;
63 static boolean use_artworkinfo_cache = TRUE;
64 static boolean update_artworkinfo_cache = FALSE;
67 // ----------------------------------------------------------------------------
69 // ----------------------------------------------------------------------------
71 static char *getLevelClassDescription(TreeInfo *ti)
73 int position = ti->sort_priority / 100;
75 if (position >= 0 && position < NUM_LEVELCLASS_DESC)
76 return levelclass_desc[position];
78 return "Unknown Level Class";
81 static char *getScoreDir(char *level_subdir)
83 static char *score_dir = NULL;
84 static char *score_level_dir = NULL;
85 char *score_subdir = SCORES_DIRECTORY;
87 if (score_dir == NULL)
89 if (program.global_scores)
90 score_dir = getPath2(getCommonDataDir(), score_subdir);
92 score_dir = getPath2(getMainUserGameDataDir(), score_subdir);
95 if (level_subdir != NULL)
97 checked_free(score_level_dir);
99 score_level_dir = getPath2(score_dir, level_subdir);
101 return score_level_dir;
107 static char *getUserSubdir(int nr)
109 static char user_subdir[16] = { 0 };
111 sprintf(user_subdir, "%03d", nr);
116 static char *getUserDir(int nr)
118 static char *user_dir = NULL;
119 char *main_data_dir = getMainUserGameDataDir();
120 char *users_subdir = USERS_DIRECTORY;
121 char *user_subdir = getUserSubdir(nr);
123 checked_free(user_dir);
126 user_dir = getPath3(main_data_dir, users_subdir, user_subdir);
128 user_dir = getPath2(main_data_dir, users_subdir);
133 static char *getLevelSetupDir(char *level_subdir)
135 static char *levelsetup_dir = NULL;
136 char *data_dir = getUserGameDataDir();
137 char *levelsetup_subdir = LEVELSETUP_DIRECTORY;
139 checked_free(levelsetup_dir);
141 if (level_subdir != NULL)
142 levelsetup_dir = getPath3(data_dir, levelsetup_subdir, level_subdir);
144 levelsetup_dir = getPath2(data_dir, levelsetup_subdir);
146 return levelsetup_dir;
149 static char *getCacheDir(void)
151 static char *cache_dir = NULL;
153 if (cache_dir == NULL)
154 cache_dir = getPath2(getMainUserGameDataDir(), CACHE_DIRECTORY);
159 static char *getNetworkDir(void)
161 static char *network_dir = NULL;
163 if (network_dir == NULL)
164 network_dir = getPath2(getMainUserGameDataDir(), NETWORK_DIRECTORY);
169 char *getLevelDirFromTreeInfo(TreeInfo *node)
171 static char *level_dir = NULL;
174 return options.level_directory;
176 checked_free(level_dir);
178 level_dir = getPath2((node->in_user_dir ? getUserLevelDir(NULL) :
179 options.level_directory), node->fullpath);
184 char *getUserLevelDir(char *level_subdir)
186 static char *userlevel_dir = NULL;
187 char *data_dir = getMainUserGameDataDir();
188 char *userlevel_subdir = LEVELS_DIRECTORY;
190 checked_free(userlevel_dir);
192 if (level_subdir != NULL)
193 userlevel_dir = getPath3(data_dir, userlevel_subdir, level_subdir);
195 userlevel_dir = getPath2(data_dir, userlevel_subdir);
197 return userlevel_dir;
200 char *getNetworkLevelDir(char *level_subdir)
202 static char *network_level_dir = NULL;
203 char *data_dir = getNetworkDir();
204 char *networklevel_subdir = LEVELS_DIRECTORY;
206 checked_free(network_level_dir);
208 if (level_subdir != NULL)
209 network_level_dir = getPath3(data_dir, networklevel_subdir, level_subdir);
211 network_level_dir = getPath2(data_dir, networklevel_subdir);
213 return network_level_dir;
216 char *getCurrentLevelDir(void)
218 return getLevelDirFromTreeInfo(leveldir_current);
221 char *getNewUserLevelSubdir(void)
223 static char *new_level_subdir = NULL;
224 char *subdir_prefix = getLoginName();
225 char subdir_suffix[10];
226 int max_suffix_number = 1000;
229 while (++i < max_suffix_number)
231 sprintf(subdir_suffix, "_%d", i);
233 checked_free(new_level_subdir);
234 new_level_subdir = getStringCat2(subdir_prefix, subdir_suffix);
236 if (!directoryExists(getUserLevelDir(new_level_subdir)))
240 return new_level_subdir;
243 static char *getTapeDir(char *level_subdir)
245 static char *tape_dir = NULL;
246 char *data_dir = getUserGameDataDir();
247 char *tape_subdir = TAPES_DIRECTORY;
249 checked_free(tape_dir);
251 if (level_subdir != NULL)
252 tape_dir = getPath3(data_dir, tape_subdir, level_subdir);
254 tape_dir = getPath2(data_dir, tape_subdir);
259 static char *getSolutionTapeDir(void)
261 static char *tape_dir = NULL;
262 char *data_dir = getCurrentLevelDir();
263 char *tape_subdir = TAPES_DIRECTORY;
265 checked_free(tape_dir);
267 tape_dir = getPath2(data_dir, tape_subdir);
272 static char *getDefaultGraphicsDir(char *graphics_subdir)
274 static char *graphics_dir = NULL;
276 if (graphics_subdir == NULL)
277 return options.graphics_directory;
279 checked_free(graphics_dir);
281 graphics_dir = getPath2(options.graphics_directory, graphics_subdir);
286 static char *getDefaultSoundsDir(char *sounds_subdir)
288 static char *sounds_dir = NULL;
290 if (sounds_subdir == NULL)
291 return options.sounds_directory;
293 checked_free(sounds_dir);
295 sounds_dir = getPath2(options.sounds_directory, sounds_subdir);
300 static char *getDefaultMusicDir(char *music_subdir)
302 static char *music_dir = NULL;
304 if (music_subdir == NULL)
305 return options.music_directory;
307 checked_free(music_dir);
309 music_dir = getPath2(options.music_directory, music_subdir);
314 static char *getClassicArtworkSet(int type)
316 return (type == TREE_TYPE_GRAPHICS_DIR ? GFX_CLASSIC_SUBDIR :
317 type == TREE_TYPE_SOUNDS_DIR ? SND_CLASSIC_SUBDIR :
318 type == TREE_TYPE_MUSIC_DIR ? MUS_CLASSIC_SUBDIR : "");
321 static char *getClassicArtworkDir(int type)
323 return (type == TREE_TYPE_GRAPHICS_DIR ?
324 getDefaultGraphicsDir(GFX_CLASSIC_SUBDIR) :
325 type == TREE_TYPE_SOUNDS_DIR ?
326 getDefaultSoundsDir(SND_CLASSIC_SUBDIR) :
327 type == TREE_TYPE_MUSIC_DIR ?
328 getDefaultMusicDir(MUS_CLASSIC_SUBDIR) : "");
331 char *getUserGraphicsDir(void)
333 static char *usergraphics_dir = NULL;
335 if (usergraphics_dir == NULL)
336 usergraphics_dir = getPath2(getMainUserGameDataDir(), GRAPHICS_DIRECTORY);
338 return usergraphics_dir;
341 char *getUserSoundsDir(void)
343 static char *usersounds_dir = NULL;
345 if (usersounds_dir == NULL)
346 usersounds_dir = getPath2(getMainUserGameDataDir(), SOUNDS_DIRECTORY);
348 return usersounds_dir;
351 char *getUserMusicDir(void)
353 static char *usermusic_dir = NULL;
355 if (usermusic_dir == NULL)
356 usermusic_dir = getPath2(getMainUserGameDataDir(), MUSIC_DIRECTORY);
358 return usermusic_dir;
361 static char *getSetupArtworkDir(TreeInfo *ti)
363 static char *artwork_dir = NULL;
368 checked_free(artwork_dir);
370 artwork_dir = getPath2(ti->basepath, ti->fullpath);
375 char *setLevelArtworkDir(TreeInfo *ti)
377 char **artwork_path_ptr, **artwork_set_ptr;
378 TreeInfo *level_artwork;
380 if (ti == NULL || leveldir_current == NULL)
383 artwork_path_ptr = LEVELDIR_ARTWORK_PATH_PTR(leveldir_current, ti->type);
384 artwork_set_ptr = LEVELDIR_ARTWORK_SET_PTR( leveldir_current, ti->type);
386 checked_free(*artwork_path_ptr);
388 if ((level_artwork = getTreeInfoFromIdentifier(ti, *artwork_set_ptr)))
390 *artwork_path_ptr = getStringCopy(getSetupArtworkDir(level_artwork));
395 No (or non-existing) artwork configured in "levelinfo.conf". This would
396 normally result in using the artwork configured in the setup menu. But
397 if an artwork subdirectory exists (which might contain custom artwork
398 or an artwork configuration file), this level artwork must be treated
399 as relative to the default "classic" artwork, not to the artwork that
400 is currently configured in the setup menu.
402 Update: For "special" versions of R'n'D (like "R'n'D jue"), do not use
403 the "default" artwork (which would be "jue0" for "R'n'D jue"), but use
404 the real "classic" artwork from the original R'n'D (like "gfx_classic").
407 char *dir = getPath2(getCurrentLevelDir(), ARTWORK_DIRECTORY(ti->type));
409 checked_free(*artwork_set_ptr);
411 if (directoryExists(dir))
413 *artwork_path_ptr = getStringCopy(getClassicArtworkDir(ti->type));
414 *artwork_set_ptr = getStringCopy(getClassicArtworkSet(ti->type));
418 *artwork_path_ptr = getStringCopy(UNDEFINED_FILENAME);
419 *artwork_set_ptr = NULL;
425 return *artwork_set_ptr;
428 static char *getLevelArtworkSet(int type)
430 if (leveldir_current == NULL)
433 return LEVELDIR_ARTWORK_SET(leveldir_current, type);
436 static char *getLevelArtworkDir(int type)
438 if (leveldir_current == NULL)
439 return UNDEFINED_FILENAME;
441 return LEVELDIR_ARTWORK_PATH(leveldir_current, type);
444 char *getProgramMainDataPath(char *command_filename, char *base_path)
446 // check if the program's main data base directory is configured
447 if (!strEqual(base_path, "."))
448 return getStringCopy(base_path);
450 /* if the program is configured to start from current directory (default),
451 determine program package directory from program binary (some versions
452 of KDE/Konqueror and Mac OS X (especially "Mavericks") apparently do not
453 set the current working directory to the program package directory) */
454 char *main_data_path = getBasePath(command_filename);
456 #if defined(PLATFORM_MACOSX)
457 if (strSuffix(main_data_path, MAC_APP_BINARY_SUBDIR))
459 char *main_data_path_old = main_data_path;
461 // cut relative path to Mac OS X application binary directory from path
462 main_data_path[strlen(main_data_path) -
463 strlen(MAC_APP_BINARY_SUBDIR)] = '\0';
465 // cut trailing path separator from path (but not if path is root directory)
466 if (strSuffix(main_data_path, "/") && !strEqual(main_data_path, "/"))
467 main_data_path[strlen(main_data_path) - 1] = '\0';
469 // replace empty path with current directory
470 if (strEqual(main_data_path, ""))
471 main_data_path = ".";
473 // add relative path to Mac OS X application resources directory to path
474 main_data_path = getPath2(main_data_path, MAC_APP_FILES_SUBDIR);
476 free(main_data_path_old);
480 return main_data_path;
483 char *getProgramConfigFilename(char *command_filename)
485 static char *config_filename_1 = NULL;
486 static char *config_filename_2 = NULL;
487 static char *config_filename_3 = NULL;
488 static boolean initialized = FALSE;
492 char *command_filename_1 = getStringCopy(command_filename);
494 // strip trailing executable suffix from command filename
495 if (strSuffix(command_filename_1, ".exe"))
496 command_filename_1[strlen(command_filename_1) - 4] = '\0';
498 char *ro_base_path = getProgramMainDataPath(command_filename, RO_BASE_PATH);
499 char *conf_directory = getPath2(ro_base_path, CONF_DIRECTORY);
501 char *command_basepath = getBasePath(command_filename);
502 char *command_basename = getBaseNameNoSuffix(command_filename);
503 char *command_filename_2 = getPath2(command_basepath, command_basename);
505 config_filename_1 = getStringCat2(command_filename_1, ".conf");
506 config_filename_2 = getStringCat2(command_filename_2, ".conf");
507 config_filename_3 = getPath2(conf_directory, SETUP_FILENAME);
509 checked_free(ro_base_path);
510 checked_free(conf_directory);
512 checked_free(command_basepath);
513 checked_free(command_basename);
515 checked_free(command_filename_1);
516 checked_free(command_filename_2);
521 // 1st try: look for config file that exactly matches the binary filename
522 if (fileExists(config_filename_1))
523 return config_filename_1;
525 // 2nd try: look for config file that matches binary filename without suffix
526 if (fileExists(config_filename_2))
527 return config_filename_2;
529 // 3rd try: return setup config filename in global program config directory
530 return config_filename_3;
533 char *getTapeFilename(int nr)
535 static char *filename = NULL;
536 char basename[MAX_FILENAME_LEN];
538 checked_free(filename);
540 sprintf(basename, "%03d.%s", nr, TAPEFILE_EXTENSION);
541 filename = getPath2(getTapeDir(leveldir_current->subdir), basename);
546 char *getSolutionTapeFilename(int nr)
548 static char *filename = NULL;
549 char basename[MAX_FILENAME_LEN];
551 checked_free(filename);
553 sprintf(basename, "%03d.%s", nr, TAPEFILE_EXTENSION);
554 filename = getPath2(getSolutionTapeDir(), basename);
556 if (!fileExists(filename))
558 static char *filename_sln = NULL;
560 checked_free(filename_sln);
562 sprintf(basename, "%03d.sln", nr);
563 filename_sln = getPath2(getSolutionTapeDir(), basename);
565 if (fileExists(filename_sln))
572 char *getScoreFilename(int nr)
574 static char *filename = NULL;
575 char basename[MAX_FILENAME_LEN];
577 checked_free(filename);
579 sprintf(basename, "%03d.%s", nr, SCOREFILE_EXTENSION);
581 // used instead of "leveldir_current->subdir" (for network games)
582 filename = getPath2(getScoreDir(levelset.identifier), basename);
587 char *getSetupFilename(void)
589 static char *filename = NULL;
591 checked_free(filename);
593 filename = getPath2(getSetupDir(), SETUP_FILENAME);
598 char *getDefaultSetupFilename(void)
600 return program.config_filename;
603 char *getEditorSetupFilename(void)
605 static char *filename = NULL;
607 checked_free(filename);
608 filename = getPath2(getCurrentLevelDir(), EDITORSETUP_FILENAME);
610 if (fileExists(filename))
613 checked_free(filename);
614 filename = getPath2(getSetupDir(), EDITORSETUP_FILENAME);
619 char *getHelpAnimFilename(void)
621 static char *filename = NULL;
623 checked_free(filename);
625 filename = getPath2(getCurrentLevelDir(), HELPANIM_FILENAME);
630 char *getHelpTextFilename(void)
632 static char *filename = NULL;
634 checked_free(filename);
636 filename = getPath2(getCurrentLevelDir(), HELPTEXT_FILENAME);
641 char *getLevelSetInfoFilename(void)
643 static char *filename = NULL;
658 for (i = 0; basenames[i] != NULL; i++)
660 checked_free(filename);
661 filename = getPath2(getCurrentLevelDir(), basenames[i]);
663 if (fileExists(filename))
670 static char *getLevelSetTitleMessageBasename(int nr, boolean initial)
672 static char basename[32];
674 sprintf(basename, "%s_%d.txt",
675 (initial ? "titlemessage_initial" : "titlemessage"), nr + 1);
680 char *getLevelSetTitleMessageFilename(int nr, boolean initial)
682 static char *filename = NULL;
684 boolean skip_setup_artwork = FALSE;
686 checked_free(filename);
688 basename = getLevelSetTitleMessageBasename(nr, initial);
690 if (!gfx.override_level_graphics)
692 // 1st try: look for special artwork in current level series directory
693 filename = getPath3(getCurrentLevelDir(), GRAPHICS_DIRECTORY, basename);
694 if (fileExists(filename))
699 // 2nd try: look for message file in current level set directory
700 filename = getPath2(getCurrentLevelDir(), basename);
701 if (fileExists(filename))
706 // check if there is special artwork configured in level series config
707 if (getLevelArtworkSet(ARTWORK_TYPE_GRAPHICS) != NULL)
709 // 3rd try: look for special artwork configured in level series config
710 filename = getPath2(getLevelArtworkDir(ARTWORK_TYPE_GRAPHICS), basename);
711 if (fileExists(filename))
716 // take missing artwork configured in level set config from default
717 skip_setup_artwork = TRUE;
721 if (!skip_setup_artwork)
723 // 4th try: look for special artwork in configured artwork directory
724 filename = getPath2(getSetupArtworkDir(artwork.gfx_current), basename);
725 if (fileExists(filename))
731 // 5th try: look for default artwork in new default artwork directory
732 filename = getPath2(getDefaultGraphicsDir(GFX_DEFAULT_SUBDIR), basename);
733 if (fileExists(filename))
738 // 6th try: look for default artwork in old default artwork directory
739 filename = getPath2(options.graphics_directory, basename);
740 if (fileExists(filename))
743 return NULL; // cannot find specified artwork file anywhere
746 static char *getCorrectedArtworkBasename(char *basename)
751 char *getCustomImageFilename(char *basename)
753 static char *filename = NULL;
754 boolean skip_setup_artwork = FALSE;
756 checked_free(filename);
758 basename = getCorrectedArtworkBasename(basename);
760 if (!gfx.override_level_graphics)
762 // 1st try: look for special artwork in current level series directory
763 filename = getImg3(getCurrentLevelDir(), GRAPHICS_DIRECTORY, basename);
764 if (fileExists(filename))
769 // check if there is special artwork configured in level series config
770 if (getLevelArtworkSet(ARTWORK_TYPE_GRAPHICS) != NULL)
772 // 2nd try: look for special artwork configured in level series config
773 filename = getImg2(getLevelArtworkDir(ARTWORK_TYPE_GRAPHICS), basename);
774 if (fileExists(filename))
779 // take missing artwork configured in level set config from default
780 skip_setup_artwork = TRUE;
784 if (!skip_setup_artwork)
786 // 3rd try: look for special artwork in configured artwork directory
787 filename = getImg2(getSetupArtworkDir(artwork.gfx_current), basename);
788 if (fileExists(filename))
794 // 4th try: look for default artwork in new default artwork directory
795 filename = getImg2(getDefaultGraphicsDir(GFX_DEFAULT_SUBDIR), basename);
796 if (fileExists(filename))
801 // 5th try: look for default artwork in old default artwork directory
802 filename = getImg2(options.graphics_directory, basename);
803 if (fileExists(filename))
806 if (!strEqual(GFX_FALLBACK_FILENAME, UNDEFINED_FILENAME))
810 Warn("cannot find artwork file '%s' (using fallback)", basename);
812 // 6th try: look for fallback artwork in old default artwork directory
813 // (needed to prevent errors when trying to access unused artwork files)
814 filename = getImg2(options.graphics_directory, GFX_FALLBACK_FILENAME);
815 if (fileExists(filename))
819 return NULL; // cannot find specified artwork file anywhere
822 char *getCustomSoundFilename(char *basename)
824 static char *filename = NULL;
825 boolean skip_setup_artwork = FALSE;
827 checked_free(filename);
829 basename = getCorrectedArtworkBasename(basename);
831 if (!gfx.override_level_sounds)
833 // 1st try: look for special artwork in current level series directory
834 filename = getPath3(getCurrentLevelDir(), SOUNDS_DIRECTORY, basename);
835 if (fileExists(filename))
840 // check if there is special artwork configured in level series config
841 if (getLevelArtworkSet(ARTWORK_TYPE_SOUNDS) != NULL)
843 // 2nd try: look for special artwork configured in level series config
844 filename = getPath2(getLevelArtworkDir(TREE_TYPE_SOUNDS_DIR), basename);
845 if (fileExists(filename))
850 // take missing artwork configured in level set config from default
851 skip_setup_artwork = TRUE;
855 if (!skip_setup_artwork)
857 // 3rd try: look for special artwork in configured artwork directory
858 filename = getPath2(getSetupArtworkDir(artwork.snd_current), basename);
859 if (fileExists(filename))
865 // 4th try: look for default artwork in new default artwork directory
866 filename = getPath2(getDefaultSoundsDir(SND_DEFAULT_SUBDIR), basename);
867 if (fileExists(filename))
872 // 5th try: look for default artwork in old default artwork directory
873 filename = getPath2(options.sounds_directory, basename);
874 if (fileExists(filename))
877 if (!strEqual(SND_FALLBACK_FILENAME, UNDEFINED_FILENAME))
881 Warn("cannot find artwork file '%s' (using fallback)", basename);
883 // 6th try: look for fallback artwork in old default artwork directory
884 // (needed to prevent errors when trying to access unused artwork files)
885 filename = getPath2(options.sounds_directory, SND_FALLBACK_FILENAME);
886 if (fileExists(filename))
890 return NULL; // cannot find specified artwork file anywhere
893 char *getCustomMusicFilename(char *basename)
895 static char *filename = NULL;
896 boolean skip_setup_artwork = FALSE;
898 checked_free(filename);
900 basename = getCorrectedArtworkBasename(basename);
902 if (!gfx.override_level_music)
904 // 1st try: look for special artwork in current level series directory
905 filename = getPath3(getCurrentLevelDir(), MUSIC_DIRECTORY, basename);
906 if (fileExists(filename))
911 // check if there is special artwork configured in level series config
912 if (getLevelArtworkSet(ARTWORK_TYPE_MUSIC) != NULL)
914 // 2nd try: look for special artwork configured in level series config
915 filename = getPath2(getLevelArtworkDir(TREE_TYPE_MUSIC_DIR), basename);
916 if (fileExists(filename))
921 // take missing artwork configured in level set config from default
922 skip_setup_artwork = TRUE;
926 if (!skip_setup_artwork)
928 // 3rd try: look for special artwork in configured artwork directory
929 filename = getPath2(getSetupArtworkDir(artwork.mus_current), basename);
930 if (fileExists(filename))
936 // 4th try: look for default artwork in new default artwork directory
937 filename = getPath2(getDefaultMusicDir(MUS_DEFAULT_SUBDIR), basename);
938 if (fileExists(filename))
943 // 5th try: look for default artwork in old default artwork directory
944 filename = getPath2(options.music_directory, basename);
945 if (fileExists(filename))
948 if (!strEqual(MUS_FALLBACK_FILENAME, UNDEFINED_FILENAME))
952 Warn("cannot find artwork file '%s' (using fallback)", basename);
954 // 6th try: look for fallback artwork in old default artwork directory
955 // (needed to prevent errors when trying to access unused artwork files)
956 filename = getPath2(options.music_directory, MUS_FALLBACK_FILENAME);
957 if (fileExists(filename))
961 return NULL; // cannot find specified artwork file anywhere
964 char *getCustomArtworkFilename(char *basename, int type)
966 if (type == ARTWORK_TYPE_GRAPHICS)
967 return getCustomImageFilename(basename);
968 else if (type == ARTWORK_TYPE_SOUNDS)
969 return getCustomSoundFilename(basename);
970 else if (type == ARTWORK_TYPE_MUSIC)
971 return getCustomMusicFilename(basename);
973 return UNDEFINED_FILENAME;
976 char *getCustomArtworkConfigFilename(int type)
978 return getCustomArtworkFilename(ARTWORKINFO_FILENAME(type), type);
981 char *getCustomArtworkLevelConfigFilename(int type)
983 static char *filename = NULL;
985 checked_free(filename);
987 filename = getPath2(getLevelArtworkDir(type), ARTWORKINFO_FILENAME(type));
992 char *getCustomMusicDirectory(void)
994 static char *directory = NULL;
995 boolean skip_setup_artwork = FALSE;
997 checked_free(directory);
999 if (!gfx.override_level_music)
1001 // 1st try: look for special artwork in current level series directory
1002 directory = getPath2(getCurrentLevelDir(), MUSIC_DIRECTORY);
1003 if (directoryExists(directory))
1008 // check if there is special artwork configured in level series config
1009 if (getLevelArtworkSet(ARTWORK_TYPE_MUSIC) != NULL)
1011 // 2nd try: look for special artwork configured in level series config
1012 directory = getStringCopy(getLevelArtworkDir(TREE_TYPE_MUSIC_DIR));
1013 if (directoryExists(directory))
1018 // take missing artwork configured in level set config from default
1019 skip_setup_artwork = TRUE;
1023 if (!skip_setup_artwork)
1025 // 3rd try: look for special artwork in configured artwork directory
1026 directory = getStringCopy(getSetupArtworkDir(artwork.mus_current));
1027 if (directoryExists(directory))
1033 // 4th try: look for default artwork in new default artwork directory
1034 directory = getStringCopy(getDefaultMusicDir(MUS_DEFAULT_SUBDIR));
1035 if (directoryExists(directory))
1040 // 5th try: look for default artwork in old default artwork directory
1041 directory = getStringCopy(options.music_directory);
1042 if (directoryExists(directory))
1045 return NULL; // cannot find specified artwork file anywhere
1048 void InitTapeDirectory(char *level_subdir)
1050 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
1051 createDirectory(getTapeDir(NULL), "main tape", PERMS_PRIVATE);
1052 createDirectory(getTapeDir(level_subdir), "level tape", PERMS_PRIVATE);
1055 void InitScoreDirectory(char *level_subdir)
1057 int permissions = (program.global_scores ? PERMS_PUBLIC : PERMS_PRIVATE);
1059 if (program.global_scores)
1060 createDirectory(getCommonDataDir(), "common data", permissions);
1062 createDirectory(getMainUserGameDataDir(), "main user data", permissions);
1064 createDirectory(getScoreDir(NULL), "main score", permissions);
1065 createDirectory(getScoreDir(level_subdir), "level score", permissions);
1068 static void SaveUserLevelInfo(void);
1070 void InitUserLevelDirectory(char *level_subdir)
1072 if (!directoryExists(getUserLevelDir(level_subdir)))
1074 createDirectory(getMainUserGameDataDir(), "main user data", PERMS_PRIVATE);
1075 createDirectory(getUserLevelDir(NULL), "main user level", PERMS_PRIVATE);
1076 createDirectory(getUserLevelDir(level_subdir), "user level", PERMS_PRIVATE);
1078 if (setup.internal.create_user_levelset)
1079 SaveUserLevelInfo();
1083 void InitNetworkLevelDirectory(char *level_subdir)
1085 if (!directoryExists(getNetworkLevelDir(level_subdir)))
1087 createDirectory(getMainUserGameDataDir(), "main user data", PERMS_PRIVATE);
1088 createDirectory(getNetworkDir(), "network data", PERMS_PRIVATE);
1089 createDirectory(getNetworkLevelDir(NULL), "main network level", PERMS_PRIVATE);
1090 createDirectory(getNetworkLevelDir(level_subdir), "network level", PERMS_PRIVATE);
1094 void InitLevelSetupDirectory(char *level_subdir)
1096 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
1097 createDirectory(getLevelSetupDir(NULL), "main level setup", PERMS_PRIVATE);
1098 createDirectory(getLevelSetupDir(level_subdir), "level setup", PERMS_PRIVATE);
1101 static void InitCacheDirectory(void)
1103 createDirectory(getMainUserGameDataDir(), "main user data", PERMS_PRIVATE);
1104 createDirectory(getCacheDir(), "cache data", PERMS_PRIVATE);
1108 // ----------------------------------------------------------------------------
1109 // some functions to handle lists of level and artwork directories
1110 // ----------------------------------------------------------------------------
1112 TreeInfo *newTreeInfo(void)
1114 return checked_calloc(sizeof(TreeInfo));
1117 TreeInfo *newTreeInfo_setDefaults(int type)
1119 TreeInfo *ti = newTreeInfo();
1121 setTreeInfoToDefaults(ti, type);
1126 void pushTreeInfo(TreeInfo **node_first, TreeInfo *node_new)
1128 node_new->next = *node_first;
1129 *node_first = node_new;
1132 void removeTreeInfo(TreeInfo **node_first)
1134 TreeInfo *node_old = *node_first;
1136 *node_first = node_old->next;
1137 node_old->next = NULL;
1139 freeTreeInfo(node_old);
1142 int numTreeInfo(TreeInfo *node)
1155 boolean validLevelSeries(TreeInfo *node)
1157 return (node != NULL && !node->node_group && !node->parent_link);
1160 TreeInfo *getFirstValidTreeInfoEntry(TreeInfo *node)
1165 if (node->node_group) // enter level group (step down into tree)
1166 return getFirstValidTreeInfoEntry(node->node_group);
1167 else if (node->parent_link) // skip start entry of level group
1169 if (node->next) // get first real level series entry
1170 return getFirstValidTreeInfoEntry(node->next);
1171 else // leave empty level group and go on
1172 return getFirstValidTreeInfoEntry(node->node_parent->next);
1174 else // this seems to be a regular level series
1178 TreeInfo *getTreeInfoFirstGroupEntry(TreeInfo *node)
1183 if (node->node_parent == NULL) // top level group
1184 return *node->node_top;
1185 else // sub level group
1186 return node->node_parent->node_group;
1189 int numTreeInfoInGroup(TreeInfo *node)
1191 return numTreeInfo(getTreeInfoFirstGroupEntry(node));
1194 int getPosFromTreeInfo(TreeInfo *node)
1196 TreeInfo *node_cmp = getTreeInfoFirstGroupEntry(node);
1201 if (node_cmp == node)
1205 node_cmp = node_cmp->next;
1211 TreeInfo *getTreeInfoFromPos(TreeInfo *node, int pos)
1213 TreeInfo *node_default = node;
1225 return node_default;
1228 static TreeInfo *getTreeInfoFromIdentifierExt(TreeInfo *node, char *identifier,
1229 boolean include_node_groups)
1231 if (identifier == NULL)
1236 if (node->node_group)
1238 if (include_node_groups && strEqual(identifier, node->identifier))
1241 TreeInfo *node_group = getTreeInfoFromIdentifierExt(node->node_group,
1243 include_node_groups);
1247 else if (!node->parent_link)
1249 if (strEqual(identifier, node->identifier))
1259 TreeInfo *getTreeInfoFromIdentifier(TreeInfo *node, char *identifier)
1261 return getTreeInfoFromIdentifierExt(node, identifier, FALSE);
1264 static TreeInfo *cloneTreeNode(TreeInfo **node_top, TreeInfo *node_parent,
1265 TreeInfo *node, boolean skip_sets_without_levels)
1272 if (!node->parent_link && !node->level_group &&
1273 skip_sets_without_levels && node->levels == 0)
1274 return cloneTreeNode(node_top, node_parent, node->next,
1275 skip_sets_without_levels);
1277 node_new = getTreeInfoCopy(node); // copy complete node
1279 node_new->node_top = node_top; // correct top node link
1280 node_new->node_parent = node_parent; // correct parent node link
1282 if (node->level_group)
1283 node_new->node_group = cloneTreeNode(node_top, node_new, node->node_group,
1284 skip_sets_without_levels);
1286 node_new->next = cloneTreeNode(node_top, node_parent, node->next,
1287 skip_sets_without_levels);
1292 static void cloneTree(TreeInfo **ti_new, TreeInfo *ti, boolean skip_empty_sets)
1294 TreeInfo *ti_cloned = cloneTreeNode(ti_new, NULL, ti, skip_empty_sets);
1296 *ti_new = ti_cloned;
1299 static boolean adjustTreeGraphicsForEMC(TreeInfo *node)
1301 boolean settings_changed = FALSE;
1305 boolean want_ecs = (setup.prefer_aga_graphics == FALSE);
1306 boolean want_aga = (setup.prefer_aga_graphics == TRUE);
1307 boolean has_only_ecs = (!node->graphics_set && !node->graphics_set_aga);
1308 boolean has_only_aga = (!node->graphics_set && !node->graphics_set_ecs);
1309 char *graphics_set = NULL;
1311 if (node->graphics_set_ecs && (want_ecs || has_only_ecs))
1312 graphics_set = node->graphics_set_ecs;
1314 if (node->graphics_set_aga && (want_aga || has_only_aga))
1315 graphics_set = node->graphics_set_aga;
1317 if (graphics_set && !strEqual(node->graphics_set, graphics_set))
1319 setString(&node->graphics_set, graphics_set);
1320 settings_changed = TRUE;
1323 if (node->node_group != NULL)
1324 settings_changed |= adjustTreeGraphicsForEMC(node->node_group);
1329 return settings_changed;
1332 static boolean adjustTreeSoundsForEMC(TreeInfo *node)
1334 boolean settings_changed = FALSE;
1338 boolean want_default = (setup.prefer_lowpass_sounds == FALSE);
1339 boolean want_lowpass = (setup.prefer_lowpass_sounds == TRUE);
1340 boolean has_only_default = (!node->sounds_set && !node->sounds_set_lowpass);
1341 boolean has_only_lowpass = (!node->sounds_set && !node->sounds_set_default);
1342 char *sounds_set = NULL;
1344 if (node->sounds_set_default && (want_default || has_only_default))
1345 sounds_set = node->sounds_set_default;
1347 if (node->sounds_set_lowpass && (want_lowpass || has_only_lowpass))
1348 sounds_set = node->sounds_set_lowpass;
1350 if (sounds_set && !strEqual(node->sounds_set, sounds_set))
1352 setString(&node->sounds_set, sounds_set);
1353 settings_changed = TRUE;
1356 if (node->node_group != NULL)
1357 settings_changed |= adjustTreeSoundsForEMC(node->node_group);
1362 return settings_changed;
1365 void dumpTreeInfo(TreeInfo *node, int depth)
1367 char bullet_list[] = { '-', '*', 'o' };
1371 Debug("tree", "Dumping TreeInfo:");
1375 char bullet = bullet_list[depth % ARRAY_SIZE(bullet_list)];
1377 for (i = 0; i < depth * 2; i++)
1378 DebugContinued("", " ");
1380 DebugContinued("tree", "%c '%s' ['%s] [PARENT: '%s'] %s\n",
1381 bullet, node->name, node->identifier,
1382 (node->node_parent ? node->node_parent->identifier : "-"),
1383 (node->node_group ? "[GROUP]" : ""));
1386 // use for dumping artwork info tree
1387 Debug("tree", "subdir == '%s' ['%s', '%s'] [%d])",
1388 node->subdir, node->fullpath, node->basepath, node->in_user_dir);
1391 if (node->node_group != NULL)
1392 dumpTreeInfo(node->node_group, depth + 1);
1398 void sortTreeInfoBySortFunction(TreeInfo **node_first,
1399 int (*compare_function)(const void *,
1402 int num_nodes = numTreeInfo(*node_first);
1403 TreeInfo **sort_array;
1404 TreeInfo *node = *node_first;
1410 // allocate array for sorting structure pointers
1411 sort_array = checked_calloc(num_nodes * sizeof(TreeInfo *));
1413 // writing structure pointers to sorting array
1414 while (i < num_nodes && node) // double boundary check...
1416 sort_array[i] = node;
1422 // sorting the structure pointers in the sorting array
1423 qsort(sort_array, num_nodes, sizeof(TreeInfo *),
1426 // update the linkage of list elements with the sorted node array
1427 for (i = 0; i < num_nodes - 1; i++)
1428 sort_array[i]->next = sort_array[i + 1];
1429 sort_array[num_nodes - 1]->next = NULL;
1431 // update the linkage of the main list anchor pointer
1432 *node_first = sort_array[0];
1436 // now recursively sort the level group structures
1440 if (node->node_group != NULL)
1441 sortTreeInfoBySortFunction(&node->node_group, compare_function);
1447 void sortTreeInfo(TreeInfo **node_first)
1449 sortTreeInfoBySortFunction(node_first, compareTreeInfoEntries);
1453 // ============================================================================
1454 // some stuff from "files.c"
1455 // ============================================================================
1457 #if defined(PLATFORM_WIN32)
1459 #define S_IRGRP S_IRUSR
1462 #define S_IROTH S_IRUSR
1465 #define S_IWGRP S_IWUSR
1468 #define S_IWOTH S_IWUSR
1471 #define S_IXGRP S_IXUSR
1474 #define S_IXOTH S_IXUSR
1477 #define S_IRWXG (S_IRGRP | S_IWGRP | S_IXGRP)
1482 #endif // PLATFORM_WIN32
1484 // file permissions for newly written files
1485 #define MODE_R_ALL (S_IRUSR | S_IRGRP | S_IROTH)
1486 #define MODE_W_ALL (S_IWUSR | S_IWGRP | S_IWOTH)
1487 #define MODE_X_ALL (S_IXUSR | S_IXGRP | S_IXOTH)
1489 #define MODE_W_PRIVATE (S_IWUSR)
1490 #define MODE_W_PUBLIC_FILE (S_IWUSR | S_IWGRP)
1491 #define MODE_W_PUBLIC_DIR (S_IWUSR | S_IWGRP | S_ISGID)
1493 #define DIR_PERMS_PRIVATE (MODE_R_ALL | MODE_X_ALL | MODE_W_PRIVATE)
1494 #define DIR_PERMS_PUBLIC (MODE_R_ALL | MODE_X_ALL | MODE_W_PUBLIC_DIR)
1495 #define DIR_PERMS_PUBLIC_ALL (MODE_R_ALL | MODE_X_ALL | MODE_W_ALL)
1497 #define FILE_PERMS_PRIVATE (MODE_R_ALL | MODE_W_PRIVATE)
1498 #define FILE_PERMS_PUBLIC (MODE_R_ALL | MODE_W_PUBLIC_FILE)
1499 #define FILE_PERMS_PUBLIC_ALL (MODE_R_ALL | MODE_W_ALL)
1502 char *getHomeDir(void)
1504 static char *dir = NULL;
1506 #if defined(PLATFORM_WIN32)
1509 dir = checked_malloc(MAX_PATH + 1);
1511 if (!SUCCEEDED(SHGetFolderPath(NULL, CSIDL_PERSONAL, NULL, 0, dir)))
1514 #elif defined(PLATFORM_EMSCRIPTEN)
1515 dir = "/persistent";
1516 #elif defined(PLATFORM_UNIX)
1519 if ((dir = getenv("HOME")) == NULL)
1521 dir = getUnixHomeDir();
1524 dir = getStringCopy(dir);
1536 char *getCommonDataDir(void)
1538 static char *common_data_dir = NULL;
1540 #if defined(PLATFORM_WIN32)
1541 if (common_data_dir == NULL)
1543 char *dir = checked_malloc(MAX_PATH + 1);
1545 if (SUCCEEDED(SHGetFolderPath(NULL, CSIDL_COMMON_DOCUMENTS, NULL, 0, dir))
1546 && !strEqual(dir, "")) // empty for Windows 95/98
1547 common_data_dir = getPath2(dir, program.userdata_subdir);
1549 common_data_dir = options.rw_base_directory;
1552 if (common_data_dir == NULL)
1553 common_data_dir = options.rw_base_directory;
1556 return common_data_dir;
1559 char *getPersonalDataDir(void)
1561 static char *personal_data_dir = NULL;
1563 #if defined(PLATFORM_MACOSX)
1564 if (personal_data_dir == NULL)
1565 personal_data_dir = getPath2(getHomeDir(), "Documents");
1567 if (personal_data_dir == NULL)
1568 personal_data_dir = getHomeDir();
1571 return personal_data_dir;
1574 char *getMainUserGameDataDir(void)
1576 static char *main_user_data_dir = NULL;
1578 #if defined(PLATFORM_ANDROID)
1579 if (main_user_data_dir == NULL)
1580 main_user_data_dir = (char *)(SDL_AndroidGetExternalStorageState() &
1581 SDL_ANDROID_EXTERNAL_STORAGE_WRITE ?
1582 SDL_AndroidGetExternalStoragePath() :
1583 SDL_AndroidGetInternalStoragePath());
1585 if (main_user_data_dir == NULL)
1586 main_user_data_dir = getPath2(getPersonalDataDir(),
1587 program.userdata_subdir);
1590 return main_user_data_dir;
1593 char *getUserGameDataDir(void)
1596 return getMainUserGameDataDir();
1598 return getUserDir(user.nr);
1601 char *getSetupDir(void)
1603 return getUserGameDataDir();
1606 static mode_t posix_umask(mode_t mask)
1608 #if defined(PLATFORM_UNIX)
1615 static int posix_mkdir(const char *pathname, mode_t mode)
1617 #if defined(PLATFORM_WIN32)
1618 return mkdir(pathname);
1620 return mkdir(pathname, mode);
1624 static boolean posix_process_running_setgid(void)
1626 #if defined(PLATFORM_UNIX)
1627 return (getgid() != getegid());
1633 void createDirectory(char *dir, char *text, int permission_class)
1635 if (directoryExists(dir))
1638 // leave "other" permissions in umask untouched, but ensure group parts
1639 // of USERDATA_DIR_MODE are not masked
1640 mode_t dir_mode = (permission_class == PERMS_PRIVATE ?
1641 DIR_PERMS_PRIVATE : DIR_PERMS_PUBLIC);
1642 mode_t last_umask = posix_umask(0);
1643 mode_t group_umask = ~(dir_mode & S_IRWXG);
1644 int running_setgid = posix_process_running_setgid();
1646 if (permission_class == PERMS_PUBLIC)
1648 // if we're setgid, protect files against "other"
1649 // else keep umask(0) to make the dir world-writable
1652 posix_umask(last_umask & group_umask);
1654 dir_mode = DIR_PERMS_PUBLIC_ALL;
1657 if (posix_mkdir(dir, dir_mode) != 0)
1658 Warn("cannot create %s directory '%s': %s", text, dir, strerror(errno));
1660 if (permission_class == PERMS_PUBLIC && !running_setgid)
1661 chmod(dir, dir_mode);
1663 posix_umask(last_umask); // restore previous umask
1666 void InitMainUserDataDirectory(void)
1668 createDirectory(getMainUserGameDataDir(), "main user data", PERMS_PRIVATE);
1671 void InitUserDataDirectory(void)
1673 createDirectory(getMainUserGameDataDir(), "main user data", PERMS_PRIVATE);
1677 createDirectory(getUserDir(-1), "users", PERMS_PRIVATE);
1678 createDirectory(getUserDir(user.nr), "user data", PERMS_PRIVATE);
1682 void SetFilePermissions(char *filename, int permission_class)
1684 int running_setgid = posix_process_running_setgid();
1685 int perms = (permission_class == PERMS_PRIVATE ?
1686 FILE_PERMS_PRIVATE : FILE_PERMS_PUBLIC);
1688 if (permission_class == PERMS_PUBLIC && !running_setgid)
1689 perms = FILE_PERMS_PUBLIC_ALL;
1691 chmod(filename, perms);
1694 char *getCookie(char *file_type)
1696 static char cookie[MAX_COOKIE_LEN + 1];
1698 if (strlen(program.cookie_prefix) + 1 +
1699 strlen(file_type) + strlen("_FILE_VERSION_x.x") > MAX_COOKIE_LEN)
1700 return "[COOKIE ERROR]"; // should never happen
1702 sprintf(cookie, "%s_%s_FILE_VERSION_%d.%d",
1703 program.cookie_prefix, file_type,
1704 program.version_super, program.version_major);
1709 void fprintFileHeader(FILE *file, char *basename)
1711 char *prefix = "# ";
1714 fprintf_line_with_prefix(file, prefix, sep1, 77);
1715 fprintf(file, "%s%s\n", prefix, basename);
1716 fprintf_line_with_prefix(file, prefix, sep1, 77);
1717 fprintf(file, "\n");
1720 int getFileVersionFromCookieString(const char *cookie)
1722 const char *ptr_cookie1, *ptr_cookie2;
1723 const char *pattern1 = "_FILE_VERSION_";
1724 const char *pattern2 = "?.?";
1725 const int len_cookie = strlen(cookie);
1726 const int len_pattern1 = strlen(pattern1);
1727 const int len_pattern2 = strlen(pattern2);
1728 const int len_pattern = len_pattern1 + len_pattern2;
1729 int version_super, version_major;
1731 if (len_cookie <= len_pattern)
1734 ptr_cookie1 = &cookie[len_cookie - len_pattern];
1735 ptr_cookie2 = &cookie[len_cookie - len_pattern2];
1737 if (strncmp(ptr_cookie1, pattern1, len_pattern1) != 0)
1740 if (ptr_cookie2[0] < '0' || ptr_cookie2[0] > '9' ||
1741 ptr_cookie2[1] != '.' ||
1742 ptr_cookie2[2] < '0' || ptr_cookie2[2] > '9')
1745 version_super = ptr_cookie2[0] - '0';
1746 version_major = ptr_cookie2[2] - '0';
1748 return VERSION_IDENT(version_super, version_major, 0, 0);
1751 boolean checkCookieString(const char *cookie, const char *template)
1753 const char *pattern = "_FILE_VERSION_?.?";
1754 const int len_cookie = strlen(cookie);
1755 const int len_template = strlen(template);
1756 const int len_pattern = strlen(pattern);
1758 if (len_cookie != len_template)
1761 if (strncmp(cookie, template, len_cookie - len_pattern) != 0)
1768 // ----------------------------------------------------------------------------
1769 // setup file list and hash handling functions
1770 // ----------------------------------------------------------------------------
1772 char *getFormattedSetupEntry(char *token, char *value)
1775 static char entry[MAX_LINE_LEN];
1777 // if value is an empty string, just return token without value
1781 // start with the token and some spaces to format output line
1782 sprintf(entry, "%s:", token);
1783 for (i = strlen(entry); i < token_value_position; i++)
1786 // continue with the token's value
1787 strcat(entry, value);
1792 SetupFileList *newSetupFileList(char *token, char *value)
1794 SetupFileList *new = checked_malloc(sizeof(SetupFileList));
1796 new->token = getStringCopy(token);
1797 new->value = getStringCopy(value);
1804 void freeSetupFileList(SetupFileList *list)
1809 checked_free(list->token);
1810 checked_free(list->value);
1813 freeSetupFileList(list->next);
1818 char *getListEntry(SetupFileList *list, char *token)
1823 if (strEqual(list->token, token))
1826 return getListEntry(list->next, token);
1829 SetupFileList *setListEntry(SetupFileList *list, char *token, char *value)
1834 if (strEqual(list->token, token))
1836 checked_free(list->value);
1838 list->value = getStringCopy(value);
1842 else if (list->next == NULL)
1843 return (list->next = newSetupFileList(token, value));
1845 return setListEntry(list->next, token, value);
1848 SetupFileList *addListEntry(SetupFileList *list, char *token, char *value)
1853 if (list->next == NULL)
1854 return (list->next = newSetupFileList(token, value));
1856 return addListEntry(list->next, token, value);
1859 #if ENABLE_UNUSED_CODE
1861 static void printSetupFileList(SetupFileList *list)
1866 Debug("setup:printSetupFileList", "token: '%s'", list->token);
1867 Debug("setup:printSetupFileList", "value: '%s'", list->value);
1869 printSetupFileList(list->next);
1875 DEFINE_HASHTABLE_INSERT(insert_hash_entry, char, char);
1876 DEFINE_HASHTABLE_SEARCH(search_hash_entry, char, char);
1877 DEFINE_HASHTABLE_CHANGE(change_hash_entry, char, char);
1878 DEFINE_HASHTABLE_REMOVE(remove_hash_entry, char, char);
1880 #define insert_hash_entry hashtable_insert
1881 #define search_hash_entry hashtable_search
1882 #define change_hash_entry hashtable_change
1883 #define remove_hash_entry hashtable_remove
1886 unsigned int get_hash_from_key(void *key)
1891 This algorithm (k=33) was first reported by Dan Bernstein many years ago in
1892 'comp.lang.c'. Another version of this algorithm (now favored by Bernstein)
1893 uses XOR: hash(i) = hash(i - 1) * 33 ^ str[i]; the magic of number 33 (why
1894 it works better than many other constants, prime or not) has never been
1895 adequately explained.
1897 If you just want to have a good hash function, and cannot wait, djb2
1898 is one of the best string hash functions i know. It has excellent
1899 distribution and speed on many different sets of keys and table sizes.
1900 You are not likely to do better with one of the "well known" functions
1901 such as PJW, K&R, etc.
1903 Ozan (oz) Yigit [http://www.cs.yorku.ca/~oz/hash.html]
1906 char *str = (char *)key;
1907 unsigned int hash = 5381;
1910 while ((c = *str++))
1911 hash = ((hash << 5) + hash) + c; // hash * 33 + c
1916 static int keys_are_equal(void *key1, void *key2)
1918 return (strEqual((char *)key1, (char *)key2));
1921 SetupFileHash *newSetupFileHash(void)
1923 SetupFileHash *new_hash =
1924 create_hashtable(16, 0.75, get_hash_from_key, keys_are_equal);
1926 if (new_hash == NULL)
1927 Fail("create_hashtable() failed -- out of memory");
1932 void freeSetupFileHash(SetupFileHash *hash)
1937 hashtable_destroy(hash, 1); // 1 == also free values stored in hash
1940 char *getHashEntry(SetupFileHash *hash, char *token)
1945 return search_hash_entry(hash, token);
1948 void setHashEntry(SetupFileHash *hash, char *token, char *value)
1955 value_copy = getStringCopy(value);
1957 // change value; if it does not exist, insert it as new
1958 if (!change_hash_entry(hash, token, value_copy))
1959 if (!insert_hash_entry(hash, getStringCopy(token), value_copy))
1960 Fail("cannot insert into hash -- aborting");
1963 char *removeHashEntry(SetupFileHash *hash, char *token)
1968 return remove_hash_entry(hash, token);
1971 #if ENABLE_UNUSED_CODE
1973 static void printSetupFileHash(SetupFileHash *hash)
1975 BEGIN_HASH_ITERATION(hash, itr)
1977 Debug("setup:printSetupFileHash", "token: '%s'", HASH_ITERATION_TOKEN(itr));
1978 Debug("setup:printSetupFileHash", "value: '%s'", HASH_ITERATION_VALUE(itr));
1980 END_HASH_ITERATION(hash, itr)
1985 #define ALLOW_TOKEN_VALUE_SEPARATOR_BEING_WHITESPACE 1
1986 #define CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING 0
1987 #define CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH 0
1989 static boolean token_value_separator_found = FALSE;
1990 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
1991 static boolean token_value_separator_warning = FALSE;
1993 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
1994 static boolean token_already_exists_warning = FALSE;
1997 static boolean getTokenValueFromSetupLineExt(char *line,
1998 char **token_ptr, char **value_ptr,
1999 char *filename, char *line_raw,
2001 boolean separator_required)
2003 static char line_copy[MAX_LINE_LEN + 1], line_raw_copy[MAX_LINE_LEN + 1];
2004 char *token, *value, *line_ptr;
2006 // when externally invoked via ReadTokenValueFromLine(), copy line buffers
2007 if (line_raw == NULL)
2009 strncpy(line_copy, line, MAX_LINE_LEN);
2010 line_copy[MAX_LINE_LEN] = '\0';
2013 strcpy(line_raw_copy, line_copy);
2014 line_raw = line_raw_copy;
2017 // cut trailing comment from input line
2018 for (line_ptr = line; *line_ptr; line_ptr++)
2020 if (*line_ptr == '#')
2027 // cut trailing whitespaces from input line
2028 for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
2029 if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
2032 // ignore empty lines
2036 // cut leading whitespaces from token
2037 for (token = line; *token; token++)
2038 if (*token != ' ' && *token != '\t')
2041 // start with empty value as reliable default
2044 token_value_separator_found = FALSE;
2046 // find end of token to determine start of value
2047 for (line_ptr = token; *line_ptr; line_ptr++)
2049 // first look for an explicit token/value separator, like ':' or '='
2050 if (*line_ptr == ':' || *line_ptr == '=')
2052 *line_ptr = '\0'; // terminate token string
2053 value = line_ptr + 1; // set beginning of value
2055 token_value_separator_found = TRUE;
2061 #if ALLOW_TOKEN_VALUE_SEPARATOR_BEING_WHITESPACE
2062 // fallback: if no token/value separator found, also allow whitespaces
2063 if (!token_value_separator_found && !separator_required)
2065 for (line_ptr = token; *line_ptr; line_ptr++)
2067 if (*line_ptr == ' ' || *line_ptr == '\t')
2069 *line_ptr = '\0'; // terminate token string
2070 value = line_ptr + 1; // set beginning of value
2072 token_value_separator_found = TRUE;
2078 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2079 if (token_value_separator_found)
2081 if (!token_value_separator_warning)
2083 Debug("setup", "---");
2085 if (filename != NULL)
2087 Debug("setup", "missing token/value separator(s) in config file:");
2088 Debug("setup", "- config file: '%s'", filename);
2092 Debug("setup", "missing token/value separator(s):");
2095 token_value_separator_warning = TRUE;
2098 if (filename != NULL)
2099 Debug("setup", "- line %d: '%s'", line_nr, line_raw);
2101 Debug("setup", "- line: '%s'", line_raw);
2107 // cut trailing whitespaces from token
2108 for (line_ptr = &token[strlen(token)]; line_ptr >= token; line_ptr--)
2109 if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
2112 // cut leading whitespaces from value
2113 for (; *value; value++)
2114 if (*value != ' ' && *value != '\t')
2123 boolean getTokenValueFromSetupLine(char *line, char **token, char **value)
2125 // while the internal (old) interface does not require a token/value
2126 // separator (for downwards compatibility with existing files which
2127 // don't use them), it is mandatory for the external (new) interface
2129 return getTokenValueFromSetupLineExt(line, token, value, NULL, NULL, 0, TRUE);
2132 static boolean loadSetupFileData(void *setup_file_data, char *filename,
2133 boolean top_recursion_level, boolean is_hash)
2135 static SetupFileHash *include_filename_hash = NULL;
2136 char line[MAX_LINE_LEN], line_raw[MAX_LINE_LEN], previous_line[MAX_LINE_LEN];
2137 char *token, *value, *line_ptr;
2138 void *insert_ptr = NULL;
2139 boolean read_continued_line = FALSE;
2141 int line_nr = 0, token_count = 0, include_count = 0;
2143 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2144 token_value_separator_warning = FALSE;
2147 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2148 token_already_exists_warning = FALSE;
2151 if (!(file = openFile(filename, MODE_READ)))
2153 #if DEBUG_NO_CONFIG_FILE
2154 Debug("setup", "cannot open configuration file '%s'", filename);
2160 // use "insert pointer" to store list end for constant insertion complexity
2162 insert_ptr = setup_file_data;
2164 // on top invocation, create hash to mark included files (to prevent loops)
2165 if (top_recursion_level)
2166 include_filename_hash = newSetupFileHash();
2168 // mark this file as already included (to prevent including it again)
2169 setHashEntry(include_filename_hash, getBaseNamePtr(filename), "true");
2171 while (!checkEndOfFile(file))
2173 // read next line of input file
2174 if (!getStringFromFile(file, line, MAX_LINE_LEN))
2177 // check if line was completely read and is terminated by line break
2178 if (strlen(line) > 0 && line[strlen(line) - 1] == '\n')
2181 // cut trailing line break (this can be newline and/or carriage return)
2182 for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
2183 if ((*line_ptr == '\n' || *line_ptr == '\r') && *(line_ptr + 1) == '\0')
2186 // copy raw input line for later use (mainly debugging output)
2187 strcpy(line_raw, line);
2189 if (read_continued_line)
2191 // append new line to existing line, if there is enough space
2192 if (strlen(previous_line) + strlen(line_ptr) < MAX_LINE_LEN)
2193 strcat(previous_line, line_ptr);
2195 strcpy(line, previous_line); // copy storage buffer to line
2197 read_continued_line = FALSE;
2200 // if the last character is '\', continue at next line
2201 if (strlen(line) > 0 && line[strlen(line) - 1] == '\\')
2203 line[strlen(line) - 1] = '\0'; // cut off trailing backslash
2204 strcpy(previous_line, line); // copy line to storage buffer
2206 read_continued_line = TRUE;
2211 if (!getTokenValueFromSetupLineExt(line, &token, &value, filename,
2212 line_raw, line_nr, FALSE))
2217 if (strEqual(token, "include"))
2219 if (getHashEntry(include_filename_hash, value) == NULL)
2221 char *basepath = getBasePath(filename);
2222 char *basename = getBaseName(value);
2223 char *filename_include = getPath2(basepath, basename);
2225 loadSetupFileData(setup_file_data, filename_include, FALSE, is_hash);
2229 free(filename_include);
2235 Warn("ignoring already processed file '%s'", value);
2242 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2244 getHashEntry((SetupFileHash *)setup_file_data, token);
2246 if (old_value != NULL)
2248 if (!token_already_exists_warning)
2250 Debug("setup", "---");
2251 Debug("setup", "duplicate token(s) found in config file:");
2252 Debug("setup", "- config file: '%s'", filename);
2254 token_already_exists_warning = TRUE;
2257 Debug("setup", "- token: '%s' (in line %d)", token, line_nr);
2258 Debug("setup", " old value: '%s'", old_value);
2259 Debug("setup", " new value: '%s'", value);
2263 setHashEntry((SetupFileHash *)setup_file_data, token, value);
2267 insert_ptr = addListEntry((SetupFileList *)insert_ptr, token, value);
2277 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2278 if (token_value_separator_warning)
2279 Debug("setup", "---");
2282 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2283 if (token_already_exists_warning)
2284 Debug("setup", "---");
2287 if (token_count == 0 && include_count == 0)
2288 Warn("configuration file '%s' is empty", filename);
2290 if (top_recursion_level)
2291 freeSetupFileHash(include_filename_hash);
2296 static int compareSetupFileData(const void *object1, const void *object2)
2298 const struct ConfigInfo *entry1 = (struct ConfigInfo *)object1;
2299 const struct ConfigInfo *entry2 = (struct ConfigInfo *)object2;
2301 return strcmp(entry1->token, entry2->token);
2304 static void saveSetupFileHash(SetupFileHash *hash, char *filename)
2306 int item_count = hashtable_count(hash);
2307 int item_size = sizeof(struct ConfigInfo);
2308 struct ConfigInfo *sort_array = checked_malloc(item_count * item_size);
2312 // copy string pointers from hash to array
2313 BEGIN_HASH_ITERATION(hash, itr)
2315 sort_array[i].token = HASH_ITERATION_TOKEN(itr);
2316 sort_array[i].value = HASH_ITERATION_VALUE(itr);
2320 if (i > item_count) // should never happen
2323 END_HASH_ITERATION(hash, itr)
2325 // sort string pointers from hash in array
2326 qsort(sort_array, item_count, item_size, compareSetupFileData);
2328 if (!(file = fopen(filename, MODE_WRITE)))
2330 Warn("cannot write configuration file '%s'", filename);
2335 fprintf(file, "%s\n\n", getFormattedSetupEntry("program.version",
2336 program.version_string));
2337 for (i = 0; i < item_count; i++)
2338 fprintf(file, "%s\n", getFormattedSetupEntry(sort_array[i].token,
2339 sort_array[i].value));
2342 checked_free(sort_array);
2345 SetupFileList *loadSetupFileList(char *filename)
2347 SetupFileList *setup_file_list = newSetupFileList("", "");
2348 SetupFileList *first_valid_list_entry;
2350 if (!loadSetupFileData(setup_file_list, filename, TRUE, FALSE))
2352 freeSetupFileList(setup_file_list);
2357 first_valid_list_entry = setup_file_list->next;
2359 // free empty list header
2360 setup_file_list->next = NULL;
2361 freeSetupFileList(setup_file_list);
2363 return first_valid_list_entry;
2366 SetupFileHash *loadSetupFileHash(char *filename)
2368 SetupFileHash *setup_file_hash = newSetupFileHash();
2370 if (!loadSetupFileData(setup_file_hash, filename, TRUE, TRUE))
2372 freeSetupFileHash(setup_file_hash);
2377 return setup_file_hash;
2381 // ============================================================================
2383 // ============================================================================
2385 #define TOKEN_STR_LAST_LEVEL_SERIES "last_level_series"
2386 #define TOKEN_STR_LAST_PLAYED_LEVEL "last_played_level"
2387 #define TOKEN_STR_HANDICAP_LEVEL "handicap_level"
2388 #define TOKEN_STR_LAST_USER "last_user"
2390 // level directory info
2391 #define LEVELINFO_TOKEN_IDENTIFIER 0
2392 #define LEVELINFO_TOKEN_NAME 1
2393 #define LEVELINFO_TOKEN_NAME_SORTING 2
2394 #define LEVELINFO_TOKEN_AUTHOR 3
2395 #define LEVELINFO_TOKEN_YEAR 4
2396 #define LEVELINFO_TOKEN_PROGRAM_TITLE 5
2397 #define LEVELINFO_TOKEN_PROGRAM_COPYRIGHT 6
2398 #define LEVELINFO_TOKEN_PROGRAM_COMPANY 7
2399 #define LEVELINFO_TOKEN_IMPORTED_FROM 8
2400 #define LEVELINFO_TOKEN_IMPORTED_BY 9
2401 #define LEVELINFO_TOKEN_TESTED_BY 10
2402 #define LEVELINFO_TOKEN_LEVELS 11
2403 #define LEVELINFO_TOKEN_FIRST_LEVEL 12
2404 #define LEVELINFO_TOKEN_SORT_PRIORITY 13
2405 #define LEVELINFO_TOKEN_LATEST_ENGINE 14
2406 #define LEVELINFO_TOKEN_LEVEL_GROUP 15
2407 #define LEVELINFO_TOKEN_READONLY 16
2408 #define LEVELINFO_TOKEN_GRAPHICS_SET_ECS 17
2409 #define LEVELINFO_TOKEN_GRAPHICS_SET_AGA 18
2410 #define LEVELINFO_TOKEN_GRAPHICS_SET 19
2411 #define LEVELINFO_TOKEN_SOUNDS_SET_DEFAULT 20
2412 #define LEVELINFO_TOKEN_SOUNDS_SET_LOWPASS 21
2413 #define LEVELINFO_TOKEN_SOUNDS_SET 22
2414 #define LEVELINFO_TOKEN_MUSIC_SET 23
2415 #define LEVELINFO_TOKEN_FILENAME 24
2416 #define LEVELINFO_TOKEN_FILETYPE 25
2417 #define LEVELINFO_TOKEN_SPECIAL_FLAGS 26
2418 #define LEVELINFO_TOKEN_HANDICAP 27
2419 #define LEVELINFO_TOKEN_SKIP_LEVELS 28
2420 #define LEVELINFO_TOKEN_USE_EMC_TILES 29
2422 #define NUM_LEVELINFO_TOKENS 30
2424 static LevelDirTree ldi;
2426 static struct TokenInfo levelinfo_tokens[] =
2428 // level directory info
2429 { TYPE_STRING, &ldi.identifier, "identifier" },
2430 { TYPE_STRING, &ldi.name, "name" },
2431 { TYPE_STRING, &ldi.name_sorting, "name_sorting" },
2432 { TYPE_STRING, &ldi.author, "author" },
2433 { TYPE_STRING, &ldi.year, "year" },
2434 { TYPE_STRING, &ldi.program_title, "program_title" },
2435 { TYPE_STRING, &ldi.program_copyright, "program_copyright" },
2436 { TYPE_STRING, &ldi.program_company, "program_company" },
2437 { TYPE_STRING, &ldi.imported_from, "imported_from" },
2438 { TYPE_STRING, &ldi.imported_by, "imported_by" },
2439 { TYPE_STRING, &ldi.tested_by, "tested_by" },
2440 { TYPE_INTEGER, &ldi.levels, "levels" },
2441 { TYPE_INTEGER, &ldi.first_level, "first_level" },
2442 { TYPE_INTEGER, &ldi.sort_priority, "sort_priority" },
2443 { TYPE_BOOLEAN, &ldi.latest_engine, "latest_engine" },
2444 { TYPE_BOOLEAN, &ldi.level_group, "level_group" },
2445 { TYPE_BOOLEAN, &ldi.readonly, "readonly" },
2446 { TYPE_STRING, &ldi.graphics_set_ecs, "graphics_set.ecs" },
2447 { TYPE_STRING, &ldi.graphics_set_aga, "graphics_set.aga" },
2448 { TYPE_STRING, &ldi.graphics_set, "graphics_set" },
2449 { TYPE_STRING, &ldi.sounds_set_default,"sounds_set.default" },
2450 { TYPE_STRING, &ldi.sounds_set_lowpass,"sounds_set.lowpass" },
2451 { TYPE_STRING, &ldi.sounds_set, "sounds_set" },
2452 { TYPE_STRING, &ldi.music_set, "music_set" },
2453 { TYPE_STRING, &ldi.level_filename, "filename" },
2454 { TYPE_STRING, &ldi.level_filetype, "filetype" },
2455 { TYPE_STRING, &ldi.special_flags, "special_flags" },
2456 { TYPE_BOOLEAN, &ldi.handicap, "handicap" },
2457 { TYPE_BOOLEAN, &ldi.skip_levels, "skip_levels" },
2458 { TYPE_BOOLEAN, &ldi.use_emc_tiles, "use_emc_tiles" }
2461 static struct TokenInfo artworkinfo_tokens[] =
2463 // artwork directory info
2464 { TYPE_STRING, &ldi.identifier, "identifier" },
2465 { TYPE_STRING, &ldi.subdir, "subdir" },
2466 { TYPE_STRING, &ldi.name, "name" },
2467 { TYPE_STRING, &ldi.name_sorting, "name_sorting" },
2468 { TYPE_STRING, &ldi.author, "author" },
2469 { TYPE_STRING, &ldi.program_title, "program_title" },
2470 { TYPE_STRING, &ldi.program_copyright, "program_copyright" },
2471 { TYPE_STRING, &ldi.program_company, "program_company" },
2472 { TYPE_INTEGER, &ldi.sort_priority, "sort_priority" },
2473 { TYPE_STRING, &ldi.basepath, "basepath" },
2474 { TYPE_STRING, &ldi.fullpath, "fullpath" },
2475 { TYPE_BOOLEAN, &ldi.in_user_dir, "in_user_dir" },
2476 { TYPE_STRING, &ldi.class_desc, "class_desc" },
2481 static char *optional_tokens[] =
2484 "program_copyright",
2490 static void setTreeInfoToDefaults(TreeInfo *ti, int type)
2494 ti->node_top = (ti->type == TREE_TYPE_LEVEL_DIR ? &leveldir_first :
2495 ti->type == TREE_TYPE_GRAPHICS_DIR ? &artwork.gfx_first :
2496 ti->type == TREE_TYPE_SOUNDS_DIR ? &artwork.snd_first :
2497 ti->type == TREE_TYPE_MUSIC_DIR ? &artwork.mus_first :
2500 ti->node_parent = NULL;
2501 ti->node_group = NULL;
2508 ti->fullpath = NULL;
2509 ti->basepath = NULL;
2510 ti->identifier = NULL;
2511 ti->name = getStringCopy(ANONYMOUS_NAME);
2512 ti->name_sorting = NULL;
2513 ti->author = getStringCopy(ANONYMOUS_NAME);
2516 ti->program_title = NULL;
2517 ti->program_copyright = NULL;
2518 ti->program_company = NULL;
2520 ti->sort_priority = LEVELCLASS_UNDEFINED; // default: least priority
2521 ti->latest_engine = FALSE; // default: get from level
2522 ti->parent_link = FALSE;
2523 ti->in_user_dir = FALSE;
2524 ti->user_defined = FALSE;
2526 ti->class_desc = NULL;
2528 ti->infotext = getStringCopy(TREE_INFOTEXT(ti->type));
2530 if (ti->type == TREE_TYPE_LEVEL_DIR)
2532 ti->imported_from = NULL;
2533 ti->imported_by = NULL;
2534 ti->tested_by = NULL;
2536 ti->graphics_set_ecs = NULL;
2537 ti->graphics_set_aga = NULL;
2538 ti->graphics_set = NULL;
2539 ti->sounds_set_default = NULL;
2540 ti->sounds_set_lowpass = NULL;
2541 ti->sounds_set = NULL;
2542 ti->music_set = NULL;
2543 ti->graphics_path = getStringCopy(UNDEFINED_FILENAME);
2544 ti->sounds_path = getStringCopy(UNDEFINED_FILENAME);
2545 ti->music_path = getStringCopy(UNDEFINED_FILENAME);
2547 ti->level_filename = NULL;
2548 ti->level_filetype = NULL;
2550 ti->special_flags = NULL;
2553 ti->first_level = 0;
2555 ti->level_group = FALSE;
2556 ti->handicap_level = 0;
2557 ti->readonly = TRUE;
2558 ti->handicap = TRUE;
2559 ti->skip_levels = FALSE;
2561 ti->use_emc_tiles = FALSE;
2565 static void setTreeInfoToDefaultsFromParent(TreeInfo *ti, TreeInfo *parent)
2569 Warn("setTreeInfoToDefaultsFromParent(): parent == NULL");
2571 setTreeInfoToDefaults(ti, TREE_TYPE_UNDEFINED);
2576 // copy all values from the parent structure
2578 ti->type = parent->type;
2580 ti->node_top = parent->node_top;
2581 ti->node_parent = parent;
2582 ti->node_group = NULL;
2589 ti->fullpath = NULL;
2590 ti->basepath = NULL;
2591 ti->identifier = NULL;
2592 ti->name = getStringCopy(ANONYMOUS_NAME);
2593 ti->name_sorting = NULL;
2594 ti->author = getStringCopy(parent->author);
2595 ti->year = getStringCopy(parent->year);
2597 ti->program_title = getStringCopy(parent->program_title);
2598 ti->program_copyright = getStringCopy(parent->program_copyright);
2599 ti->program_company = getStringCopy(parent->program_company);
2601 ti->sort_priority = parent->sort_priority;
2602 ti->latest_engine = parent->latest_engine;
2603 ti->parent_link = FALSE;
2604 ti->in_user_dir = parent->in_user_dir;
2605 ti->user_defined = parent->user_defined;
2606 ti->color = parent->color;
2607 ti->class_desc = getStringCopy(parent->class_desc);
2609 ti->infotext = getStringCopy(parent->infotext);
2611 if (ti->type == TREE_TYPE_LEVEL_DIR)
2613 ti->imported_from = getStringCopy(parent->imported_from);
2614 ti->imported_by = getStringCopy(parent->imported_by);
2615 ti->tested_by = getStringCopy(parent->tested_by);
2617 ti->graphics_set_ecs = getStringCopy(parent->graphics_set_ecs);
2618 ti->graphics_set_aga = getStringCopy(parent->graphics_set_aga);
2619 ti->graphics_set = getStringCopy(parent->graphics_set);
2620 ti->sounds_set_default = getStringCopy(parent->sounds_set_default);
2621 ti->sounds_set_lowpass = getStringCopy(parent->sounds_set_lowpass);
2622 ti->sounds_set = getStringCopy(parent->sounds_set);
2623 ti->music_set = getStringCopy(parent->music_set);
2624 ti->graphics_path = getStringCopy(UNDEFINED_FILENAME);
2625 ti->sounds_path = getStringCopy(UNDEFINED_FILENAME);
2626 ti->music_path = getStringCopy(UNDEFINED_FILENAME);
2628 ti->level_filename = getStringCopy(parent->level_filename);
2629 ti->level_filetype = getStringCopy(parent->level_filetype);
2631 ti->special_flags = getStringCopy(parent->special_flags);
2633 ti->levels = parent->levels;
2634 ti->first_level = parent->first_level;
2635 ti->last_level = parent->last_level;
2636 ti->level_group = FALSE;
2637 ti->handicap_level = parent->handicap_level;
2638 ti->readonly = parent->readonly;
2639 ti->handicap = parent->handicap;
2640 ti->skip_levels = parent->skip_levels;
2642 ti->use_emc_tiles = parent->use_emc_tiles;
2646 static TreeInfo *getTreeInfoCopy(TreeInfo *ti)
2648 TreeInfo *ti_copy = newTreeInfo();
2650 // copy all values from the original structure
2652 ti_copy->type = ti->type;
2654 ti_copy->node_top = ti->node_top;
2655 ti_copy->node_parent = ti->node_parent;
2656 ti_copy->node_group = ti->node_group;
2657 ti_copy->next = ti->next;
2659 ti_copy->cl_first = ti->cl_first;
2660 ti_copy->cl_cursor = ti->cl_cursor;
2662 ti_copy->subdir = getStringCopy(ti->subdir);
2663 ti_copy->fullpath = getStringCopy(ti->fullpath);
2664 ti_copy->basepath = getStringCopy(ti->basepath);
2665 ti_copy->identifier = getStringCopy(ti->identifier);
2666 ti_copy->name = getStringCopy(ti->name);
2667 ti_copy->name_sorting = getStringCopy(ti->name_sorting);
2668 ti_copy->author = getStringCopy(ti->author);
2669 ti_copy->year = getStringCopy(ti->year);
2671 ti_copy->program_title = getStringCopy(ti->program_title);
2672 ti_copy->program_copyright = getStringCopy(ti->program_copyright);
2673 ti_copy->program_company = getStringCopy(ti->program_company);
2675 ti_copy->imported_from = getStringCopy(ti->imported_from);
2676 ti_copy->imported_by = getStringCopy(ti->imported_by);
2677 ti_copy->tested_by = getStringCopy(ti->tested_by);
2679 ti_copy->graphics_set_ecs = getStringCopy(ti->graphics_set_ecs);
2680 ti_copy->graphics_set_aga = getStringCopy(ti->graphics_set_aga);
2681 ti_copy->graphics_set = getStringCopy(ti->graphics_set);
2682 ti_copy->sounds_set_default = getStringCopy(ti->sounds_set_default);
2683 ti_copy->sounds_set_lowpass = getStringCopy(ti->sounds_set_lowpass);
2684 ti_copy->sounds_set = getStringCopy(ti->sounds_set);
2685 ti_copy->music_set = getStringCopy(ti->music_set);
2686 ti_copy->graphics_path = getStringCopy(ti->graphics_path);
2687 ti_copy->sounds_path = getStringCopy(ti->sounds_path);
2688 ti_copy->music_path = getStringCopy(ti->music_path);
2690 ti_copy->level_filename = getStringCopy(ti->level_filename);
2691 ti_copy->level_filetype = getStringCopy(ti->level_filetype);
2693 ti_copy->special_flags = getStringCopy(ti->special_flags);
2695 ti_copy->levels = ti->levels;
2696 ti_copy->first_level = ti->first_level;
2697 ti_copy->last_level = ti->last_level;
2698 ti_copy->sort_priority = ti->sort_priority;
2700 ti_copy->latest_engine = ti->latest_engine;
2702 ti_copy->level_group = ti->level_group;
2703 ti_copy->parent_link = ti->parent_link;
2704 ti_copy->in_user_dir = ti->in_user_dir;
2705 ti_copy->user_defined = ti->user_defined;
2706 ti_copy->readonly = ti->readonly;
2707 ti_copy->handicap = ti->handicap;
2708 ti_copy->skip_levels = ti->skip_levels;
2710 ti_copy->use_emc_tiles = ti->use_emc_tiles;
2712 ti_copy->color = ti->color;
2713 ti_copy->class_desc = getStringCopy(ti->class_desc);
2714 ti_copy->handicap_level = ti->handicap_level;
2716 ti_copy->infotext = getStringCopy(ti->infotext);
2721 void freeTreeInfo(TreeInfo *ti)
2726 checked_free(ti->subdir);
2727 checked_free(ti->fullpath);
2728 checked_free(ti->basepath);
2729 checked_free(ti->identifier);
2731 checked_free(ti->name);
2732 checked_free(ti->name_sorting);
2733 checked_free(ti->author);
2734 checked_free(ti->year);
2736 checked_free(ti->program_title);
2737 checked_free(ti->program_copyright);
2738 checked_free(ti->program_company);
2740 checked_free(ti->class_desc);
2742 checked_free(ti->infotext);
2744 if (ti->type == TREE_TYPE_LEVEL_DIR)
2746 checked_free(ti->imported_from);
2747 checked_free(ti->imported_by);
2748 checked_free(ti->tested_by);
2750 checked_free(ti->graphics_set_ecs);
2751 checked_free(ti->graphics_set_aga);
2752 checked_free(ti->graphics_set);
2753 checked_free(ti->sounds_set_default);
2754 checked_free(ti->sounds_set_lowpass);
2755 checked_free(ti->sounds_set);
2756 checked_free(ti->music_set);
2758 checked_free(ti->graphics_path);
2759 checked_free(ti->sounds_path);
2760 checked_free(ti->music_path);
2762 checked_free(ti->level_filename);
2763 checked_free(ti->level_filetype);
2765 checked_free(ti->special_flags);
2768 // recursively free child node
2770 freeTreeInfo(ti->node_group);
2772 // recursively free next node
2774 freeTreeInfo(ti->next);
2779 void setSetupInfo(struct TokenInfo *token_info,
2780 int token_nr, char *token_value)
2782 int token_type = token_info[token_nr].type;
2783 void *setup_value = token_info[token_nr].value;
2785 if (token_value == NULL)
2788 // set setup field to corresponding token value
2793 *(boolean *)setup_value = get_boolean_from_string(token_value);
2797 *(int *)setup_value = get_switch3_from_string(token_value);
2801 *(Key *)setup_value = getKeyFromKeyName(token_value);
2805 *(Key *)setup_value = getKeyFromX11KeyName(token_value);
2809 *(int *)setup_value = get_integer_from_string(token_value);
2813 checked_free(*(char **)setup_value);
2814 *(char **)setup_value = getStringCopy(token_value);
2818 *(int *)setup_value = get_player_nr_from_string(token_value);
2826 static int compareTreeInfoEntries(const void *object1, const void *object2)
2828 const TreeInfo *entry1 = *((TreeInfo **)object1);
2829 const TreeInfo *entry2 = *((TreeInfo **)object2);
2830 int tree_sorting1 = TREE_SORTING(entry1);
2831 int tree_sorting2 = TREE_SORTING(entry2);
2833 if (tree_sorting1 != tree_sorting2)
2834 return (tree_sorting1 - tree_sorting2);
2836 return strcasecmp(entry1->name_sorting, entry2->name_sorting);
2839 static TreeInfo *createParentTreeInfoNode(TreeInfo *node_parent)
2843 if (node_parent == NULL)
2846 ti_new = newTreeInfo();
2847 setTreeInfoToDefaults(ti_new, node_parent->type);
2849 ti_new->node_parent = node_parent;
2850 ti_new->parent_link = TRUE;
2852 setString(&ti_new->identifier, node_parent->identifier);
2853 setString(&ti_new->name, BACKLINK_TEXT_PARENT);
2854 setString(&ti_new->name_sorting, ti_new->name);
2856 setString(&ti_new->subdir, STRING_PARENT_DIRECTORY);
2857 setString(&ti_new->fullpath, node_parent->fullpath);
2859 ti_new->sort_priority = LEVELCLASS_PARENT;
2860 ti_new->latest_engine = node_parent->latest_engine;
2862 setString(&ti_new->class_desc, getLevelClassDescription(ti_new));
2864 pushTreeInfo(&node_parent->node_group, ti_new);
2869 static TreeInfo *createTopTreeInfoNode(TreeInfo *node_first)
2871 if (node_first == NULL)
2874 TreeInfo *ti_new = newTreeInfo();
2875 int type = node_first->type;
2877 setTreeInfoToDefaults(ti_new, type);
2879 ti_new->node_parent = NULL;
2880 ti_new->parent_link = FALSE;
2882 setString(&ti_new->identifier, "top_tree_node");
2883 setString(&ti_new->name, TREE_INFOTEXT(type));
2884 setString(&ti_new->name_sorting, ti_new->name);
2886 setString(&ti_new->subdir, STRING_TOP_DIRECTORY);
2887 setString(&ti_new->fullpath, ".");
2889 ti_new->sort_priority = LEVELCLASS_TOP;
2890 ti_new->latest_engine = node_first->latest_engine;
2892 setString(&ti_new->class_desc, TREE_INFOTEXT(type));
2894 ti_new->node_group = node_first;
2895 ti_new->level_group = TRUE;
2897 TreeInfo *ti_new2 = createParentTreeInfoNode(ti_new);
2899 setString(&ti_new2->name, TREE_BACKLINK_TEXT(type));
2900 setString(&ti_new2->name_sorting, ti_new2->name);
2905 static void setTreeInfoParentNodes(TreeInfo *node, TreeInfo *node_parent)
2909 if (node->node_group)
2910 setTreeInfoParentNodes(node->node_group, node);
2912 node->node_parent = node_parent;
2919 // ----------------------------------------------------------------------------
2920 // functions for handling level and custom artwork info cache
2921 // ----------------------------------------------------------------------------
2923 static void LoadArtworkInfoCache(void)
2925 InitCacheDirectory();
2927 if (artworkinfo_cache_old == NULL)
2929 char *filename = getPath2(getCacheDir(), ARTWORKINFO_CACHE_FILE);
2931 // try to load artwork info hash from already existing cache file
2932 artworkinfo_cache_old = loadSetupFileHash(filename);
2934 // try to get program version that artwork info cache was written with
2935 char *version = getHashEntry(artworkinfo_cache_old, "program.version");
2937 // check program version of artwork info cache against current version
2938 if (!strEqual(version, program.version_string))
2940 freeSetupFileHash(artworkinfo_cache_old);
2942 artworkinfo_cache_old = NULL;
2945 // if no artwork info cache file was found, start with empty hash
2946 if (artworkinfo_cache_old == NULL)
2947 artworkinfo_cache_old = newSetupFileHash();
2952 if (artworkinfo_cache_new == NULL)
2953 artworkinfo_cache_new = newSetupFileHash();
2955 update_artworkinfo_cache = FALSE;
2958 static void SaveArtworkInfoCache(void)
2960 if (!update_artworkinfo_cache)
2963 char *filename = getPath2(getCacheDir(), ARTWORKINFO_CACHE_FILE);
2965 InitCacheDirectory();
2967 saveSetupFileHash(artworkinfo_cache_new, filename);
2972 static char *getCacheTokenPrefix(char *prefix1, char *prefix2)
2974 static char *prefix = NULL;
2976 checked_free(prefix);
2978 prefix = getStringCat2WithSeparator(prefix1, prefix2, ".");
2983 // (identical to above function, but separate string buffer needed -- nasty)
2984 static char *getCacheToken(char *prefix, char *suffix)
2986 static char *token = NULL;
2988 checked_free(token);
2990 token = getStringCat2WithSeparator(prefix, suffix, ".");
2995 static char *getFileTimestampString(char *filename)
2997 return getStringCopy(i_to_a(getFileTimestampEpochSeconds(filename)));
3000 static boolean modifiedFileTimestamp(char *filename, char *timestamp_string)
3002 struct stat file_status;
3004 if (timestamp_string == NULL)
3007 if (!fileExists(filename)) // file does not exist
3008 return (atoi(timestamp_string) != 0);
3010 if (stat(filename, &file_status) != 0) // cannot stat file
3013 return (file_status.st_mtime != atoi(timestamp_string));
3016 static TreeInfo *getArtworkInfoCacheEntry(LevelDirTree *level_node, int type)
3018 char *identifier = level_node->subdir;
3019 char *type_string = ARTWORK_DIRECTORY(type);
3020 char *token_prefix = getCacheTokenPrefix(type_string, identifier);
3021 char *token_main = getCacheToken(token_prefix, "CACHED");
3022 char *cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
3023 boolean cached = (cache_entry != NULL && strEqual(cache_entry, "true"));
3024 TreeInfo *artwork_info = NULL;
3026 if (!use_artworkinfo_cache)
3029 if (optional_tokens_hash == NULL)
3033 // create hash from list of optional tokens (for quick access)
3034 optional_tokens_hash = newSetupFileHash();
3035 for (i = 0; optional_tokens[i] != NULL; i++)
3036 setHashEntry(optional_tokens_hash, optional_tokens[i], "");
3043 artwork_info = newTreeInfo();
3044 setTreeInfoToDefaults(artwork_info, type);
3046 // set all structure fields according to the token/value pairs
3047 ldi = *artwork_info;
3048 for (i = 0; artworkinfo_tokens[i].type != -1; i++)
3050 char *token_suffix = artworkinfo_tokens[i].text;
3051 char *token = getCacheToken(token_prefix, token_suffix);
3052 char *value = getHashEntry(artworkinfo_cache_old, token);
3054 (getHashEntry(optional_tokens_hash, token_suffix) != NULL);
3056 setSetupInfo(artworkinfo_tokens, i, value);
3058 // check if cache entry for this item is mandatory, but missing
3059 if (value == NULL && !optional)
3061 Warn("missing cache entry '%s'", token);
3067 *artwork_info = ldi;
3072 char *filename_levelinfo = getPath2(getLevelDirFromTreeInfo(level_node),
3073 LEVELINFO_FILENAME);
3074 char *filename_artworkinfo = getPath2(getSetupArtworkDir(artwork_info),
3075 ARTWORKINFO_FILENAME(type));
3077 // check if corresponding "levelinfo.conf" file has changed
3078 token_main = getCacheToken(token_prefix, "TIMESTAMP_LEVELINFO");
3079 cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
3081 if (modifiedFileTimestamp(filename_levelinfo, cache_entry))
3084 // check if corresponding "<artworkinfo>.conf" file has changed
3085 token_main = getCacheToken(token_prefix, "TIMESTAMP_ARTWORKINFO");
3086 cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
3088 if (modifiedFileTimestamp(filename_artworkinfo, cache_entry))
3091 checked_free(filename_levelinfo);
3092 checked_free(filename_artworkinfo);
3095 if (!cached && artwork_info != NULL)
3097 freeTreeInfo(artwork_info);
3102 return artwork_info;
3105 static void setArtworkInfoCacheEntry(TreeInfo *artwork_info,
3106 LevelDirTree *level_node, int type)
3108 char *identifier = level_node->subdir;
3109 char *type_string = ARTWORK_DIRECTORY(type);
3110 char *token_prefix = getCacheTokenPrefix(type_string, identifier);
3111 char *token_main = getCacheToken(token_prefix, "CACHED");
3112 boolean set_cache_timestamps = TRUE;
3115 setHashEntry(artworkinfo_cache_new, token_main, "true");
3117 if (set_cache_timestamps)
3119 char *filename_levelinfo = getPath2(getLevelDirFromTreeInfo(level_node),
3120 LEVELINFO_FILENAME);
3121 char *filename_artworkinfo = getPath2(getSetupArtworkDir(artwork_info),
3122 ARTWORKINFO_FILENAME(type));
3123 char *timestamp_levelinfo = getFileTimestampString(filename_levelinfo);
3124 char *timestamp_artworkinfo = getFileTimestampString(filename_artworkinfo);
3126 token_main = getCacheToken(token_prefix, "TIMESTAMP_LEVELINFO");
3127 setHashEntry(artworkinfo_cache_new, token_main, timestamp_levelinfo);
3129 token_main = getCacheToken(token_prefix, "TIMESTAMP_ARTWORKINFO");
3130 setHashEntry(artworkinfo_cache_new, token_main, timestamp_artworkinfo);
3132 checked_free(filename_levelinfo);
3133 checked_free(filename_artworkinfo);
3134 checked_free(timestamp_levelinfo);
3135 checked_free(timestamp_artworkinfo);
3138 ldi = *artwork_info;
3139 for (i = 0; artworkinfo_tokens[i].type != -1; i++)
3141 char *token = getCacheToken(token_prefix, artworkinfo_tokens[i].text);
3142 char *value = getSetupValue(artworkinfo_tokens[i].type,
3143 artworkinfo_tokens[i].value);
3145 setHashEntry(artworkinfo_cache_new, token, value);
3150 // ----------------------------------------------------------------------------
3151 // functions for loading level info and custom artwork info
3152 // ----------------------------------------------------------------------------
3154 int GetZipFileTreeType(char *zip_filename)
3156 static char *top_dir_path = NULL;
3157 static char *top_dir_conf_filename[NUM_BASE_TREE_TYPES] = { NULL };
3158 static char *conf_basename[NUM_BASE_TREE_TYPES] =
3160 GRAPHICSINFO_FILENAME,
3161 SOUNDSINFO_FILENAME,
3167 checked_free(top_dir_path);
3168 top_dir_path = NULL;
3170 for (j = 0; j < NUM_BASE_TREE_TYPES; j++)
3172 checked_free(top_dir_conf_filename[j]);
3173 top_dir_conf_filename[j] = NULL;
3176 char **zip_entries = zip_list(zip_filename);
3178 // check if zip file successfully opened
3179 if (zip_entries == NULL || zip_entries[0] == NULL)
3180 return TREE_TYPE_UNDEFINED;
3182 // first zip file entry is expected to be top level directory
3183 char *top_dir = zip_entries[0];
3185 // check if valid top level directory found in zip file
3186 if (!strSuffix(top_dir, "/"))
3187 return TREE_TYPE_UNDEFINED;
3189 // get filenames of valid configuration files in top level directory
3190 for (j = 0; j < NUM_BASE_TREE_TYPES; j++)
3191 top_dir_conf_filename[j] = getStringCat2(top_dir, conf_basename[j]);
3193 int tree_type = TREE_TYPE_UNDEFINED;
3196 while (zip_entries[e] != NULL)
3198 // check if every zip file entry is below top level directory
3199 if (!strPrefix(zip_entries[e], top_dir))
3200 return TREE_TYPE_UNDEFINED;
3202 // check if this zip file entry is a valid configuration filename
3203 for (j = 0; j < NUM_BASE_TREE_TYPES; j++)
3205 if (strEqual(zip_entries[e], top_dir_conf_filename[j]))
3207 // only exactly one valid configuration file allowed
3208 if (tree_type != TREE_TYPE_UNDEFINED)
3209 return TREE_TYPE_UNDEFINED;
3221 static boolean CheckZipFileForDirectory(char *zip_filename, char *directory,
3224 static char *top_dir_path = NULL;
3225 static char *top_dir_conf_filename = NULL;
3227 checked_free(top_dir_path);
3228 checked_free(top_dir_conf_filename);
3230 top_dir_path = NULL;
3231 top_dir_conf_filename = NULL;
3233 char *conf_basename = (tree_type == TREE_TYPE_LEVEL_DIR ? LEVELINFO_FILENAME :
3234 ARTWORKINFO_FILENAME(tree_type));
3236 // check if valid configuration filename determined
3237 if (conf_basename == NULL || strEqual(conf_basename, ""))
3240 char **zip_entries = zip_list(zip_filename);
3242 // check if zip file successfully opened
3243 if (zip_entries == NULL || zip_entries[0] == NULL)
3246 // first zip file entry is expected to be top level directory
3247 char *top_dir = zip_entries[0];
3249 // check if valid top level directory found in zip file
3250 if (!strSuffix(top_dir, "/"))
3253 // get path of extracted top level directory
3254 top_dir_path = getPath2(directory, top_dir);
3256 // remove trailing directory separator from top level directory path
3257 // (required to be able to check for file and directory in next step)
3258 top_dir_path[strlen(top_dir_path) - 1] = '\0';
3260 // check if zip file's top level directory already exists in target directory
3261 if (fileExists(top_dir_path)) // (checks for file and directory)
3264 // get filename of configuration file in top level directory
3265 top_dir_conf_filename = getStringCat2(top_dir, conf_basename);
3267 boolean found_top_dir_conf_filename = FALSE;
3270 while (zip_entries[i] != NULL)
3272 // check if every zip file entry is below top level directory
3273 if (!strPrefix(zip_entries[i], top_dir))
3276 // check if this zip file entry is the configuration filename
3277 if (strEqual(zip_entries[i], top_dir_conf_filename))
3278 found_top_dir_conf_filename = TRUE;
3283 // check if valid configuration filename was found in zip file
3284 if (!found_top_dir_conf_filename)
3290 char *ExtractZipFileIntoDirectory(char *zip_filename, char *directory,
3293 boolean zip_file_valid = CheckZipFileForDirectory(zip_filename, directory,
3296 if (!zip_file_valid)
3298 Warn("zip file '%s' rejected!", zip_filename);
3303 char **zip_entries = zip_extract(zip_filename, directory);
3305 if (zip_entries == NULL)
3307 Warn("zip file '%s' could not be extracted!", zip_filename);
3312 Info("zip file '%s' successfully extracted!", zip_filename);
3314 // first zip file entry contains top level directory
3315 char *top_dir = zip_entries[0];
3317 // remove trailing directory separator from top level directory
3318 top_dir[strlen(top_dir) - 1] = '\0';
3323 static void ProcessZipFilesInDirectory(char *directory, int tree_type)
3326 DirectoryEntry *dir_entry;
3328 if ((dir = openDirectory(directory)) == NULL)
3330 // display error if directory is main "options.graphics_directory" etc.
3331 if (tree_type == TREE_TYPE_LEVEL_DIR ||
3332 directory == OPTIONS_ARTWORK_DIRECTORY(tree_type))
3333 Warn("cannot read directory '%s'", directory);
3338 while ((dir_entry = readDirectory(dir)) != NULL) // loop all entries
3340 // skip non-zip files (and also directories with zip extension)
3341 if (!strSuffixLower(dir_entry->basename, ".zip") || dir_entry->is_directory)
3344 char *zip_filename = getPath2(directory, dir_entry->basename);
3345 char *zip_filename_extracted = getStringCat2(zip_filename, ".extracted");
3346 char *zip_filename_rejected = getStringCat2(zip_filename, ".rejected");
3348 // check if zip file hasn't already been extracted or rejected
3349 if (!fileExists(zip_filename_extracted) &&
3350 !fileExists(zip_filename_rejected))
3352 char *top_dir = ExtractZipFileIntoDirectory(zip_filename, directory,
3354 char *marker_filename = (top_dir != NULL ? zip_filename_extracted :
3355 zip_filename_rejected);
3358 // create empty file to mark zip file as extracted or rejected
3359 if ((marker_file = fopen(marker_filename, MODE_WRITE)))
3360 fclose(marker_file);
3363 free(zip_filename_extracted);
3364 free(zip_filename_rejected);
3368 closeDirectory(dir);
3371 // forward declaration for recursive call by "LoadLevelInfoFromLevelDir()"
3372 static void LoadLevelInfoFromLevelDir(TreeInfo **, TreeInfo *, char *);
3374 static boolean LoadLevelInfoFromLevelConf(TreeInfo **node_first,
3375 TreeInfo *node_parent,
3376 char *level_directory,
3377 char *directory_name)
3379 char *directory_path = getPath2(level_directory, directory_name);
3380 char *filename = getPath2(directory_path, LEVELINFO_FILENAME);
3381 SetupFileHash *setup_file_hash;
3382 LevelDirTree *leveldir_new = NULL;
3385 // unless debugging, silently ignore directories without "levelinfo.conf"
3386 if (!options.debug && !fileExists(filename))
3388 free(directory_path);
3394 setup_file_hash = loadSetupFileHash(filename);
3396 if (setup_file_hash == NULL)
3398 #if DEBUG_NO_CONFIG_FILE
3399 Debug("setup", "ignoring level directory '%s'", directory_path);
3402 free(directory_path);
3408 leveldir_new = newTreeInfo();
3411 setTreeInfoToDefaultsFromParent(leveldir_new, node_parent);
3413 setTreeInfoToDefaults(leveldir_new, TREE_TYPE_LEVEL_DIR);
3415 leveldir_new->subdir = getStringCopy(directory_name);
3417 // set all structure fields according to the token/value pairs
3418 ldi = *leveldir_new;
3419 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
3420 setSetupInfo(levelinfo_tokens, i,
3421 getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
3422 *leveldir_new = ldi;
3424 if (strEqual(leveldir_new->name, ANONYMOUS_NAME))
3425 setString(&leveldir_new->name, leveldir_new->subdir);
3427 if (leveldir_new->identifier == NULL)
3428 leveldir_new->identifier = getStringCopy(leveldir_new->subdir);
3430 if (leveldir_new->name_sorting == NULL)
3431 leveldir_new->name_sorting = getStringCopy(leveldir_new->name);
3433 if (node_parent == NULL) // top level group
3435 leveldir_new->basepath = getStringCopy(level_directory);
3436 leveldir_new->fullpath = getStringCopy(leveldir_new->subdir);
3438 else // sub level group
3440 leveldir_new->basepath = getStringCopy(node_parent->basepath);
3441 leveldir_new->fullpath = getPath2(node_parent->fullpath, directory_name);
3444 leveldir_new->last_level =
3445 leveldir_new->first_level + leveldir_new->levels - 1;
3447 leveldir_new->in_user_dir =
3448 (!strEqual(leveldir_new->basepath, options.level_directory));
3450 // adjust some settings if user's private level directory was detected
3451 if (leveldir_new->sort_priority == LEVELCLASS_UNDEFINED &&
3452 leveldir_new->in_user_dir &&
3453 (strEqual(leveldir_new->subdir, getLoginName()) ||
3454 strEqual(leveldir_new->name, getLoginName()) ||
3455 strEqual(leveldir_new->author, getRealName())))
3457 leveldir_new->sort_priority = LEVELCLASS_PRIVATE_START;
3458 leveldir_new->readonly = FALSE;
3461 leveldir_new->user_defined =
3462 (leveldir_new->in_user_dir && IS_LEVELCLASS_PRIVATE(leveldir_new));
3464 setString(&leveldir_new->class_desc, getLevelClassDescription(leveldir_new));
3466 leveldir_new->handicap_level = // set handicap to default value
3467 (leveldir_new->user_defined || !leveldir_new->handicap ?
3468 leveldir_new->last_level : leveldir_new->first_level);
3470 DrawInitText(leveldir_new->name, 150, FC_YELLOW);
3472 pushTreeInfo(node_first, leveldir_new);
3474 freeSetupFileHash(setup_file_hash);
3476 if (leveldir_new->level_group)
3478 // create node to link back to current level directory
3479 createParentTreeInfoNode(leveldir_new);
3481 // recursively step into sub-directory and look for more level series
3482 LoadLevelInfoFromLevelDir(&leveldir_new->node_group,
3483 leveldir_new, directory_path);
3486 free(directory_path);
3492 static void LoadLevelInfoFromLevelDir(TreeInfo **node_first,
3493 TreeInfo *node_parent,
3494 char *level_directory)
3496 // ---------- 1st stage: process any level set zip files ----------
3498 ProcessZipFilesInDirectory(level_directory, TREE_TYPE_LEVEL_DIR);
3500 // ---------- 2nd stage: check for level set directories ----------
3503 DirectoryEntry *dir_entry;
3504 boolean valid_entry_found = FALSE;
3506 if ((dir = openDirectory(level_directory)) == NULL)
3508 Warn("cannot read level directory '%s'", level_directory);
3513 while ((dir_entry = readDirectory(dir)) != NULL) // loop all entries
3515 char *directory_name = dir_entry->basename;
3516 char *directory_path = getPath2(level_directory, directory_name);
3518 // skip entries for current and parent directory
3519 if (strEqual(directory_name, ".") ||
3520 strEqual(directory_name, ".."))
3522 free(directory_path);
3527 // find out if directory entry is itself a directory
3528 if (!dir_entry->is_directory) // not a directory
3530 free(directory_path);
3535 free(directory_path);
3537 if (strEqual(directory_name, GRAPHICS_DIRECTORY) ||
3538 strEqual(directory_name, SOUNDS_DIRECTORY) ||
3539 strEqual(directory_name, MUSIC_DIRECTORY))
3542 valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
3547 closeDirectory(dir);
3549 // special case: top level directory may directly contain "levelinfo.conf"
3550 if (node_parent == NULL && !valid_entry_found)
3552 // check if this directory directly contains a file "levelinfo.conf"
3553 valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
3554 level_directory, ".");
3557 if (!valid_entry_found)
3558 Warn("cannot find any valid level series in directory '%s'",
3562 boolean AdjustGraphicsForEMC(void)
3564 boolean settings_changed = FALSE;
3566 settings_changed |= adjustTreeGraphicsForEMC(leveldir_first_all);
3567 settings_changed |= adjustTreeGraphicsForEMC(leveldir_first);
3569 return settings_changed;
3572 boolean AdjustSoundsForEMC(void)
3574 boolean settings_changed = FALSE;
3576 settings_changed |= adjustTreeSoundsForEMC(leveldir_first_all);
3577 settings_changed |= adjustTreeSoundsForEMC(leveldir_first);
3579 return settings_changed;
3582 void LoadLevelInfo(void)
3584 InitUserLevelDirectory(getLoginName());
3586 DrawInitText("Loading level series", 120, FC_GREEN);
3588 LoadLevelInfoFromLevelDir(&leveldir_first, NULL, options.level_directory);
3589 LoadLevelInfoFromLevelDir(&leveldir_first, NULL, getUserLevelDir(NULL));
3591 leveldir_first = createTopTreeInfoNode(leveldir_first);
3593 /* after loading all level set information, clone the level directory tree
3594 and remove all level sets without levels (these may still contain artwork
3595 to be offered in the setup menu as "custom artwork", and are therefore
3596 checked for existing artwork in the function "LoadLevelArtworkInfo()") */
3597 leveldir_first_all = leveldir_first;
3598 cloneTree(&leveldir_first, leveldir_first_all, TRUE);
3600 AdjustGraphicsForEMC();
3601 AdjustSoundsForEMC();
3603 // before sorting, the first entries will be from the user directory
3604 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
3606 if (leveldir_first == NULL)
3607 Fail("cannot find any valid level series in any directory");
3609 sortTreeInfo(&leveldir_first);
3611 #if ENABLE_UNUSED_CODE
3612 dumpTreeInfo(leveldir_first, 0);
3616 static boolean LoadArtworkInfoFromArtworkConf(TreeInfo **node_first,
3617 TreeInfo *node_parent,
3618 char *base_directory,
3619 char *directory_name, int type)
3621 char *directory_path = getPath2(base_directory, directory_name);
3622 char *filename = getPath2(directory_path, ARTWORKINFO_FILENAME(type));
3623 SetupFileHash *setup_file_hash = NULL;
3624 TreeInfo *artwork_new = NULL;
3627 if (fileExists(filename))
3628 setup_file_hash = loadSetupFileHash(filename);
3630 if (setup_file_hash == NULL) // no config file -- look for artwork files
3633 DirectoryEntry *dir_entry;
3634 boolean valid_file_found = FALSE;
3636 if ((dir = openDirectory(directory_path)) != NULL)
3638 while ((dir_entry = readDirectory(dir)) != NULL)
3640 if (FileIsArtworkType(dir_entry->filename, type))
3642 valid_file_found = TRUE;
3648 closeDirectory(dir);
3651 if (!valid_file_found)
3653 #if DEBUG_NO_CONFIG_FILE
3654 if (!strEqual(directory_name, "."))
3655 Debug("setup", "ignoring artwork directory '%s'", directory_path);
3658 free(directory_path);
3665 artwork_new = newTreeInfo();
3668 setTreeInfoToDefaultsFromParent(artwork_new, node_parent);
3670 setTreeInfoToDefaults(artwork_new, type);
3672 artwork_new->subdir = getStringCopy(directory_name);
3674 if (setup_file_hash) // (before defining ".color" and ".class_desc")
3676 // set all structure fields according to the token/value pairs
3678 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
3679 setSetupInfo(levelinfo_tokens, i,
3680 getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
3683 if (strEqual(artwork_new->name, ANONYMOUS_NAME))
3684 setString(&artwork_new->name, artwork_new->subdir);
3686 if (artwork_new->identifier == NULL)
3687 artwork_new->identifier = getStringCopy(artwork_new->subdir);
3689 if (artwork_new->name_sorting == NULL)
3690 artwork_new->name_sorting = getStringCopy(artwork_new->name);
3693 if (node_parent == NULL) // top level group
3695 artwork_new->basepath = getStringCopy(base_directory);
3696 artwork_new->fullpath = getStringCopy(artwork_new->subdir);
3698 else // sub level group
3700 artwork_new->basepath = getStringCopy(node_parent->basepath);
3701 artwork_new->fullpath = getPath2(node_parent->fullpath, directory_name);
3704 artwork_new->in_user_dir =
3705 (!strEqual(artwork_new->basepath, OPTIONS_ARTWORK_DIRECTORY(type)));
3707 setString(&artwork_new->class_desc, getLevelClassDescription(artwork_new));
3709 if (setup_file_hash == NULL) // (after determining ".user_defined")
3711 if (strEqual(artwork_new->subdir, "."))
3713 if (artwork_new->user_defined)
3715 setString(&artwork_new->identifier, "private");
3716 artwork_new->sort_priority = ARTWORKCLASS_PRIVATE;
3720 setString(&artwork_new->identifier, "classic");
3721 artwork_new->sort_priority = ARTWORKCLASS_CLASSICS;
3724 setString(&artwork_new->class_desc,
3725 getLevelClassDescription(artwork_new));
3729 setString(&artwork_new->identifier, artwork_new->subdir);
3732 setString(&artwork_new->name, artwork_new->identifier);
3733 setString(&artwork_new->name_sorting, artwork_new->name);
3736 pushTreeInfo(node_first, artwork_new);
3738 freeSetupFileHash(setup_file_hash);
3740 free(directory_path);
3746 static void LoadArtworkInfoFromArtworkDir(TreeInfo **node_first,
3747 TreeInfo *node_parent,
3748 char *base_directory, int type)
3750 // ---------- 1st stage: process any artwork set zip files ----------
3752 ProcessZipFilesInDirectory(base_directory, type);
3754 // ---------- 2nd stage: check for artwork set directories ----------
3757 DirectoryEntry *dir_entry;
3758 boolean valid_entry_found = FALSE;
3760 if ((dir = openDirectory(base_directory)) == NULL)
3762 // display error if directory is main "options.graphics_directory" etc.
3763 if (base_directory == OPTIONS_ARTWORK_DIRECTORY(type))
3764 Warn("cannot read directory '%s'", base_directory);
3769 while ((dir_entry = readDirectory(dir)) != NULL) // loop all entries
3771 char *directory_name = dir_entry->basename;
3772 char *directory_path = getPath2(base_directory, directory_name);
3774 // skip directory entries for current and parent directory
3775 if (strEqual(directory_name, ".") ||
3776 strEqual(directory_name, ".."))
3778 free(directory_path);
3783 // skip directory entries which are not a directory
3784 if (!dir_entry->is_directory) // not a directory
3786 free(directory_path);
3791 free(directory_path);
3793 // check if this directory contains artwork with or without config file
3794 valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
3796 directory_name, type);
3799 closeDirectory(dir);
3801 // check if this directory directly contains artwork itself
3802 valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
3803 base_directory, ".",
3805 if (!valid_entry_found)
3806 Warn("cannot find any valid artwork in directory '%s'", base_directory);
3809 static TreeInfo *getDummyArtworkInfo(int type)
3811 // this is only needed when there is completely no artwork available
3812 TreeInfo *artwork_new = newTreeInfo();
3814 setTreeInfoToDefaults(artwork_new, type);
3816 setString(&artwork_new->subdir, UNDEFINED_FILENAME);
3817 setString(&artwork_new->fullpath, UNDEFINED_FILENAME);
3818 setString(&artwork_new->basepath, UNDEFINED_FILENAME);
3820 setString(&artwork_new->identifier, UNDEFINED_FILENAME);
3821 setString(&artwork_new->name, UNDEFINED_FILENAME);
3822 setString(&artwork_new->name_sorting, UNDEFINED_FILENAME);
3827 void SetCurrentArtwork(int type)
3829 ArtworkDirTree **current_ptr = ARTWORK_CURRENT_PTR(artwork, type);
3830 ArtworkDirTree *first_node = ARTWORK_FIRST_NODE(artwork, type);
3831 char *setup_set = SETUP_ARTWORK_SET(setup, type);
3832 char *default_subdir = ARTWORK_DEFAULT_SUBDIR(type);
3834 // set current artwork to artwork configured in setup menu
3835 *current_ptr = getTreeInfoFromIdentifier(first_node, setup_set);
3837 // if not found, set current artwork to default artwork
3838 if (*current_ptr == NULL)
3839 *current_ptr = getTreeInfoFromIdentifier(first_node, default_subdir);
3841 // if not found, set current artwork to first artwork in tree
3842 if (*current_ptr == NULL)
3843 *current_ptr = getFirstValidTreeInfoEntry(first_node);
3846 void ChangeCurrentArtworkIfNeeded(int type)
3848 char *current_identifier = ARTWORK_CURRENT_IDENTIFIER(artwork, type);
3849 char *setup_set = SETUP_ARTWORK_SET(setup, type);
3851 if (!strEqual(current_identifier, setup_set))
3852 SetCurrentArtwork(type);
3855 void LoadArtworkInfo(void)
3857 LoadArtworkInfoCache();
3859 DrawInitText("Looking for custom artwork", 120, FC_GREEN);
3861 LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
3862 options.graphics_directory,
3863 TREE_TYPE_GRAPHICS_DIR);
3864 LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
3865 getUserGraphicsDir(),
3866 TREE_TYPE_GRAPHICS_DIR);
3868 LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
3869 options.sounds_directory,
3870 TREE_TYPE_SOUNDS_DIR);
3871 LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
3873 TREE_TYPE_SOUNDS_DIR);
3875 LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
3876 options.music_directory,
3877 TREE_TYPE_MUSIC_DIR);
3878 LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
3880 TREE_TYPE_MUSIC_DIR);
3882 if (artwork.gfx_first == NULL)
3883 artwork.gfx_first = getDummyArtworkInfo(TREE_TYPE_GRAPHICS_DIR);
3884 if (artwork.snd_first == NULL)
3885 artwork.snd_first = getDummyArtworkInfo(TREE_TYPE_SOUNDS_DIR);
3886 if (artwork.mus_first == NULL)
3887 artwork.mus_first = getDummyArtworkInfo(TREE_TYPE_MUSIC_DIR);
3889 // before sorting, the first entries will be from the user directory
3890 SetCurrentArtwork(ARTWORK_TYPE_GRAPHICS);
3891 SetCurrentArtwork(ARTWORK_TYPE_SOUNDS);
3892 SetCurrentArtwork(ARTWORK_TYPE_MUSIC);
3894 artwork.gfx_current_identifier = artwork.gfx_current->identifier;
3895 artwork.snd_current_identifier = artwork.snd_current->identifier;
3896 artwork.mus_current_identifier = artwork.mus_current->identifier;
3898 #if ENABLE_UNUSED_CODE
3899 Debug("setup:LoadArtworkInfo", "graphics set == %s",
3900 artwork.gfx_current_identifier);
3901 Debug("setup:LoadArtworkInfo", "sounds set == %s",
3902 artwork.snd_current_identifier);
3903 Debug("setup:LoadArtworkInfo", "music set == %s",
3904 artwork.mus_current_identifier);
3907 sortTreeInfo(&artwork.gfx_first);
3908 sortTreeInfo(&artwork.snd_first);
3909 sortTreeInfo(&artwork.mus_first);
3911 #if ENABLE_UNUSED_CODE
3912 dumpTreeInfo(artwork.gfx_first, 0);
3913 dumpTreeInfo(artwork.snd_first, 0);
3914 dumpTreeInfo(artwork.mus_first, 0);
3918 static void MoveArtworkInfoIntoSubTree(ArtworkDirTree **artwork_node)
3920 ArtworkDirTree *artwork_new = newTreeInfo();
3921 char *top_node_name = "standalone artwork";
3923 setTreeInfoToDefaults(artwork_new, (*artwork_node)->type);
3925 artwork_new->level_group = TRUE;
3927 setString(&artwork_new->identifier, top_node_name);
3928 setString(&artwork_new->name, top_node_name);
3929 setString(&artwork_new->name_sorting, top_node_name);
3931 // create node to link back to current custom artwork directory
3932 createParentTreeInfoNode(artwork_new);
3934 // move existing custom artwork tree into newly created sub-tree
3935 artwork_new->node_group->next = *artwork_node;
3937 // change custom artwork tree to contain only newly created node
3938 *artwork_node = artwork_new;
3941 static void LoadArtworkInfoFromLevelInfoExt(ArtworkDirTree **artwork_node,
3942 ArtworkDirTree *node_parent,
3943 LevelDirTree *level_node,
3944 boolean empty_level_set_mode)
3946 int type = (*artwork_node)->type;
3948 // recursively check all level directories for artwork sub-directories
3952 boolean empty_level_set = (level_node->levels == 0);
3954 // check all tree entries for artwork, but skip parent link entries
3955 if (!level_node->parent_link && empty_level_set == empty_level_set_mode)
3957 TreeInfo *artwork_new = getArtworkInfoCacheEntry(level_node, type);
3958 boolean cached = (artwork_new != NULL);
3962 pushTreeInfo(artwork_node, artwork_new);
3966 TreeInfo *topnode_last = *artwork_node;
3967 char *path = getPath2(getLevelDirFromTreeInfo(level_node),
3968 ARTWORK_DIRECTORY(type));
3970 LoadArtworkInfoFromArtworkDir(artwork_node, NULL, path, type);
3972 if (topnode_last != *artwork_node) // check for newly added node
3974 artwork_new = *artwork_node;
3976 setString(&artwork_new->identifier, level_node->subdir);
3977 setString(&artwork_new->name, level_node->name);
3978 setString(&artwork_new->name_sorting, level_node->name_sorting);
3980 artwork_new->sort_priority = level_node->sort_priority;
3981 artwork_new->in_user_dir = level_node->in_user_dir;
3983 update_artworkinfo_cache = TRUE;
3989 // insert artwork info (from old cache or filesystem) into new cache
3990 if (artwork_new != NULL)
3991 setArtworkInfoCacheEntry(artwork_new, level_node, type);
3994 DrawInitText(level_node->name, 150, FC_YELLOW);
3996 if (level_node->node_group != NULL)
3998 TreeInfo *artwork_new = newTreeInfo();
4001 setTreeInfoToDefaultsFromParent(artwork_new, node_parent);
4003 setTreeInfoToDefaults(artwork_new, type);
4005 artwork_new->level_group = TRUE;
4007 setString(&artwork_new->identifier, level_node->subdir);
4009 if (node_parent == NULL) // check for top tree node
4011 char *top_node_name = (empty_level_set_mode ?
4012 "artwork for certain level sets" :
4013 "artwork included in level sets");
4015 setString(&artwork_new->name, top_node_name);
4016 setString(&artwork_new->name_sorting, top_node_name);
4020 setString(&artwork_new->name, level_node->name);
4021 setString(&artwork_new->name_sorting, level_node->name_sorting);
4024 pushTreeInfo(artwork_node, artwork_new);
4026 // create node to link back to current custom artwork directory
4027 createParentTreeInfoNode(artwork_new);
4029 // recursively step into sub-directory and look for more custom artwork
4030 LoadArtworkInfoFromLevelInfoExt(&artwork_new->node_group, artwork_new,
4031 level_node->node_group,
4032 empty_level_set_mode);
4034 // if sub-tree has no custom artwork at all, remove it
4035 if (artwork_new->node_group->next == NULL)
4036 removeTreeInfo(artwork_node);
4039 level_node = level_node->next;
4043 static void LoadArtworkInfoFromLevelInfo(ArtworkDirTree **artwork_node)
4045 // move peviously loaded artwork tree into separate sub-tree
4046 MoveArtworkInfoIntoSubTree(artwork_node);
4048 // load artwork from level sets into separate sub-trees
4049 LoadArtworkInfoFromLevelInfoExt(artwork_node, NULL, leveldir_first_all, TRUE);
4050 LoadArtworkInfoFromLevelInfoExt(artwork_node, NULL, leveldir_first_all, FALSE);
4052 // add top tree node over all three separate sub-trees
4053 *artwork_node = createTopTreeInfoNode(*artwork_node);
4055 // set all parent links (back links) in complete artwork tree
4056 setTreeInfoParentNodes(*artwork_node, NULL);
4059 void LoadLevelArtworkInfo(void)
4061 print_timestamp_init("LoadLevelArtworkInfo");
4063 DrawInitText("Looking for custom level artwork", 120, FC_GREEN);
4065 print_timestamp_time("DrawTimeText");
4067 LoadArtworkInfoFromLevelInfo(&artwork.gfx_first);
4068 print_timestamp_time("LoadArtworkInfoFromLevelInfo (gfx)");
4069 LoadArtworkInfoFromLevelInfo(&artwork.snd_first);
4070 print_timestamp_time("LoadArtworkInfoFromLevelInfo (snd)");
4071 LoadArtworkInfoFromLevelInfo(&artwork.mus_first);
4072 print_timestamp_time("LoadArtworkInfoFromLevelInfo (mus)");
4074 SaveArtworkInfoCache();
4076 print_timestamp_time("SaveArtworkInfoCache");
4078 // needed for reloading level artwork not known at ealier stage
4079 ChangeCurrentArtworkIfNeeded(ARTWORK_TYPE_GRAPHICS);
4080 ChangeCurrentArtworkIfNeeded(ARTWORK_TYPE_SOUNDS);
4081 ChangeCurrentArtworkIfNeeded(ARTWORK_TYPE_MUSIC);
4083 print_timestamp_time("getTreeInfoFromIdentifier");
4085 sortTreeInfo(&artwork.gfx_first);
4086 sortTreeInfo(&artwork.snd_first);
4087 sortTreeInfo(&artwork.mus_first);
4089 print_timestamp_time("sortTreeInfo");
4091 #if ENABLE_UNUSED_CODE
4092 dumpTreeInfo(artwork.gfx_first, 0);
4093 dumpTreeInfo(artwork.snd_first, 0);
4094 dumpTreeInfo(artwork.mus_first, 0);
4097 print_timestamp_done("LoadLevelArtworkInfo");
4100 static boolean AddTreeSetToTreeInfoExt(TreeInfo *tree_node_old, char *tree_dir,
4101 char *tree_subdir_new, int type)
4103 if (tree_node_old == NULL)
4105 if (type == TREE_TYPE_LEVEL_DIR)
4107 // get level info tree node of personal user level set
4108 tree_node_old = getTreeInfoFromIdentifier(leveldir_first, getLoginName());
4110 // this may happen if "setup.internal.create_user_levelset" is FALSE
4111 // or if file "levelinfo.conf" is missing in personal user level set
4112 if (tree_node_old == NULL)
4113 tree_node_old = leveldir_first->node_group;
4117 // get artwork info tree node of first artwork set
4118 tree_node_old = ARTWORK_FIRST_NODE(artwork, type);
4122 if (tree_dir == NULL)
4123 tree_dir = TREE_USERDIR(type);
4125 if (tree_node_old == NULL ||
4127 tree_subdir_new == NULL) // should not happen
4130 int draw_deactivation_mask = GetDrawDeactivationMask();
4132 // override draw deactivation mask (temporarily disable drawing)
4133 SetDrawDeactivationMask(REDRAW_ALL);
4135 if (type == TREE_TYPE_LEVEL_DIR)
4137 // load new level set config and add it next to first user level set
4138 LoadLevelInfoFromLevelConf(&tree_node_old->next,
4139 tree_node_old->node_parent,
4140 tree_dir, tree_subdir_new);
4144 // load new artwork set config and add it next to first artwork set
4145 LoadArtworkInfoFromArtworkConf(&tree_node_old->next,
4146 tree_node_old->node_parent,
4147 tree_dir, tree_subdir_new, type);
4150 // set draw deactivation mask to previous value
4151 SetDrawDeactivationMask(draw_deactivation_mask);
4153 // get first node of level or artwork info tree
4154 TreeInfo **tree_node_first = TREE_FIRST_NODE_PTR(type);
4156 // get tree info node of newly added level or artwork set
4157 TreeInfo *tree_node_new = getTreeInfoFromIdentifier(*tree_node_first,
4160 if (tree_node_new == NULL) // should not happen
4163 // correct top link and parent node link of newly created tree node
4164 tree_node_new->node_top = tree_node_old->node_top;
4165 tree_node_new->node_parent = tree_node_old->node_parent;
4167 // sort tree info to adjust position of newly added tree set
4168 sortTreeInfo(tree_node_first);
4173 void AddTreeSetToTreeInfo(TreeInfo *tree_node, char *tree_dir,
4174 char *tree_subdir_new, int type)
4176 if (!AddTreeSetToTreeInfoExt(tree_node, tree_dir, tree_subdir_new, type))
4177 Fail("internal tree info structure corrupted -- aborting");
4180 void AddUserLevelSetToLevelInfo(char *level_subdir_new)
4182 AddTreeSetToTreeInfo(NULL, NULL, level_subdir_new, TREE_TYPE_LEVEL_DIR);
4185 char *getArtworkIdentifierForUserLevelSet(int type)
4187 char *classic_artwork_set = getClassicArtworkSet(type);
4189 // check for custom artwork configured in "levelinfo.conf"
4190 char *leveldir_artwork_set =
4191 *LEVELDIR_ARTWORK_SET_PTR(leveldir_current, type);
4192 boolean has_leveldir_artwork_set =
4193 (leveldir_artwork_set != NULL && !strEqual(leveldir_artwork_set,
4194 classic_artwork_set));
4196 // check for custom artwork in sub-directory "graphics" etc.
4197 TreeInfo *artwork_first_node = ARTWORK_FIRST_NODE(artwork, type);
4198 char *leveldir_identifier = leveldir_current->identifier;
4199 boolean has_artwork_subdir =
4200 (getTreeInfoFromIdentifier(artwork_first_node,
4201 leveldir_identifier) != NULL);
4203 return (has_leveldir_artwork_set ? leveldir_artwork_set :
4204 has_artwork_subdir ? leveldir_identifier :
4205 classic_artwork_set);
4208 TreeInfo *getArtworkTreeInfoForUserLevelSet(int type)
4210 char *artwork_set = getArtworkIdentifierForUserLevelSet(type);
4211 TreeInfo *artwork_first_node = ARTWORK_FIRST_NODE(artwork, type);
4212 TreeInfo *ti = getTreeInfoFromIdentifier(artwork_first_node, artwork_set);
4216 ti = getTreeInfoFromIdentifier(artwork_first_node,
4217 ARTWORK_DEFAULT_SUBDIR(type));
4219 Fail("cannot find default graphics -- should not happen");
4225 boolean checkIfCustomArtworkExistsForCurrentLevelSet(void)
4227 char *graphics_set =
4228 getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_GRAPHICS);
4230 getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_SOUNDS);
4232 getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_MUSIC);
4234 return (!strEqual(graphics_set, GFX_CLASSIC_SUBDIR) ||
4235 !strEqual(sounds_set, SND_CLASSIC_SUBDIR) ||
4236 !strEqual(music_set, MUS_CLASSIC_SUBDIR));
4239 boolean UpdateUserLevelSet(char *level_subdir, char *level_name,
4240 char *level_author, int num_levels)
4242 char *filename = getPath2(getUserLevelDir(level_subdir), LEVELINFO_FILENAME);
4243 char *filename_tmp = getStringCat2(filename, ".tmp");
4245 FILE *file_tmp = NULL;
4246 char line[MAX_LINE_LEN];
4247 boolean success = FALSE;
4248 LevelDirTree *leveldir = getTreeInfoFromIdentifier(leveldir_first,
4250 // update values in level directory tree
4252 if (level_name != NULL)
4253 setString(&leveldir->name, level_name);
4255 if (level_author != NULL)
4256 setString(&leveldir->author, level_author);
4258 if (num_levels != -1)
4259 leveldir->levels = num_levels;
4261 // update values that depend on other values
4263 setString(&leveldir->name_sorting, leveldir->name);
4265 leveldir->last_level = leveldir->first_level + leveldir->levels - 1;
4267 // sort order of level sets may have changed
4268 sortTreeInfo(&leveldir_first);
4270 if ((file = fopen(filename, MODE_READ)) &&
4271 (file_tmp = fopen(filename_tmp, MODE_WRITE)))
4273 while (fgets(line, MAX_LINE_LEN, file))
4275 if (strPrefix(line, "name:") && level_name != NULL)
4276 fprintf(file_tmp, "%-32s%s\n", "name:", level_name);
4277 else if (strPrefix(line, "author:") && level_author != NULL)
4278 fprintf(file_tmp, "%-32s%s\n", "author:", level_author);
4279 else if (strPrefix(line, "levels:") && num_levels != -1)
4280 fprintf(file_tmp, "%-32s%d\n", "levels:", num_levels);
4282 fputs(line, file_tmp);
4295 success = (rename(filename_tmp, filename) == 0);
4303 boolean CreateUserLevelSet(char *level_subdir, char *level_name,
4304 char *level_author, int num_levels,
4305 boolean use_artwork_set)
4307 LevelDirTree *level_info;
4312 // create user level sub-directory, if needed
4313 createDirectory(getUserLevelDir(level_subdir), "user level", PERMS_PRIVATE);
4315 filename = getPath2(getUserLevelDir(level_subdir), LEVELINFO_FILENAME);
4317 if (!(file = fopen(filename, MODE_WRITE)))
4319 Warn("cannot write level info file '%s'", filename);
4326 level_info = newTreeInfo();
4328 // always start with reliable default values
4329 setTreeInfoToDefaults(level_info, TREE_TYPE_LEVEL_DIR);
4331 setString(&level_info->name, level_name);
4332 setString(&level_info->author, level_author);
4333 level_info->levels = num_levels;
4334 level_info->first_level = 1;
4335 level_info->sort_priority = LEVELCLASS_PRIVATE_START;
4336 level_info->readonly = FALSE;
4338 if (use_artwork_set)
4340 level_info->graphics_set =
4341 getStringCopy(getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_GRAPHICS));
4342 level_info->sounds_set =
4343 getStringCopy(getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_SOUNDS));
4344 level_info->music_set =
4345 getStringCopy(getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_MUSIC));
4348 token_value_position = TOKEN_VALUE_POSITION_SHORT;
4350 fprintFileHeader(file, LEVELINFO_FILENAME);
4353 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
4355 if (i == LEVELINFO_TOKEN_NAME ||
4356 i == LEVELINFO_TOKEN_AUTHOR ||
4357 i == LEVELINFO_TOKEN_LEVELS ||
4358 i == LEVELINFO_TOKEN_FIRST_LEVEL ||
4359 i == LEVELINFO_TOKEN_SORT_PRIORITY ||
4360 i == LEVELINFO_TOKEN_READONLY ||
4361 (use_artwork_set && (i == LEVELINFO_TOKEN_GRAPHICS_SET ||
4362 i == LEVELINFO_TOKEN_SOUNDS_SET ||
4363 i == LEVELINFO_TOKEN_MUSIC_SET)))
4364 fprintf(file, "%s\n", getSetupLine(levelinfo_tokens, "", i));
4366 // just to make things nicer :)
4367 if (i == LEVELINFO_TOKEN_AUTHOR ||
4368 i == LEVELINFO_TOKEN_FIRST_LEVEL ||
4369 (use_artwork_set && i == LEVELINFO_TOKEN_READONLY))
4370 fprintf(file, "\n");
4373 token_value_position = TOKEN_VALUE_POSITION_DEFAULT;
4377 SetFilePermissions(filename, PERMS_PRIVATE);
4379 freeTreeInfo(level_info);
4385 static void SaveUserLevelInfo(void)
4387 CreateUserLevelSet(getLoginName(), getLoginName(), getRealName(), 100, FALSE);
4390 char *getSetupValue(int type, void *value)
4392 static char value_string[MAX_LINE_LEN];
4400 strcpy(value_string, (*(boolean *)value ? "true" : "false"));
4404 strcpy(value_string, (*(boolean *)value ? "on" : "off"));
4408 strcpy(value_string, (*(int *)value == AUTO ? "auto" :
4409 *(int *)value == FALSE ? "off" : "on"));
4413 strcpy(value_string, (*(boolean *)value ? "yes" : "no"));
4416 case TYPE_YES_NO_AUTO:
4417 strcpy(value_string, (*(int *)value == AUTO ? "auto" :
4418 *(int *)value == FALSE ? "no" : "yes"));
4422 strcpy(value_string, (*(boolean *)value ? "AGA" : "ECS"));
4426 strcpy(value_string, getKeyNameFromKey(*(Key *)value));
4430 strcpy(value_string, getX11KeyNameFromKey(*(Key *)value));
4434 sprintf(value_string, "%d", *(int *)value);
4438 if (*(char **)value == NULL)
4441 strcpy(value_string, *(char **)value);
4445 sprintf(value_string, "player_%d", *(int *)value + 1);
4449 value_string[0] = '\0';
4453 if (type & TYPE_GHOSTED)
4454 strcpy(value_string, "n/a");
4456 return value_string;
4459 char *getSetupLine(struct TokenInfo *token_info, char *prefix, int token_nr)
4463 static char token_string[MAX_LINE_LEN];
4464 int token_type = token_info[token_nr].type;
4465 void *setup_value = token_info[token_nr].value;
4466 char *token_text = token_info[token_nr].text;
4467 char *value_string = getSetupValue(token_type, setup_value);
4469 // build complete token string
4470 sprintf(token_string, "%s%s", prefix, token_text);
4472 // build setup entry line
4473 line = getFormattedSetupEntry(token_string, value_string);
4475 if (token_type == TYPE_KEY_X11)
4477 Key key = *(Key *)setup_value;
4478 char *keyname = getKeyNameFromKey(key);
4480 // add comment, if useful
4481 if (!strEqual(keyname, "(undefined)") &&
4482 !strEqual(keyname, "(unknown)"))
4484 // add at least one whitespace
4486 for (i = strlen(line); i < token_comment_position; i++)
4490 strcat(line, keyname);
4497 static void InitLastPlayedLevels_ParentNode(void)
4499 LevelDirTree **leveldir_top = &leveldir_first->node_group->next;
4500 LevelDirTree *leveldir_new = NULL;
4502 // check if parent node for last played levels already exists
4503 if (strEqual((*leveldir_top)->identifier, TOKEN_STR_LAST_LEVEL_SERIES))
4506 leveldir_new = newTreeInfo();
4508 setTreeInfoToDefaultsFromParent(leveldir_new, leveldir_first);
4510 leveldir_new->level_group = TRUE;
4511 leveldir_new->sort_priority = LEVELCLASS_LAST_PLAYED_LEVEL;
4513 setString(&leveldir_new->identifier, TOKEN_STR_LAST_LEVEL_SERIES);
4514 setString(&leveldir_new->name, "<< (last played level sets)");
4515 setString(&leveldir_new->name_sorting, leveldir_new->name);
4517 pushTreeInfo(leveldir_top, leveldir_new);
4519 // create node to link back to current level directory
4520 createParentTreeInfoNode(leveldir_new);
4523 void UpdateLastPlayedLevels_TreeInfo(void)
4525 char **last_level_series = setup.level_setup.last_level_series;
4526 boolean reset_leveldir_current = FALSE;
4527 LevelDirTree *leveldir_last;
4528 TreeInfo **node_new = NULL;
4531 if (last_level_series[0] == NULL)
4534 InitLastPlayedLevels_ParentNode();
4536 // check if current level set is from "last played" sub-tree to be rebuilt
4537 reset_leveldir_current = strEqual(leveldir_current->node_parent->identifier,
4538 TOKEN_STR_LAST_LEVEL_SERIES);
4540 leveldir_last = getTreeInfoFromIdentifierExt(leveldir_first,
4541 TOKEN_STR_LAST_LEVEL_SERIES,
4543 if (leveldir_last == NULL)
4546 node_new = &leveldir_last->node_group->next;
4548 freeTreeInfo(*node_new);
4550 for (i = 0; last_level_series[i] != NULL; i++)
4552 LevelDirTree *node_last = getTreeInfoFromIdentifier(leveldir_first,
4553 last_level_series[i]);
4555 *node_new = getTreeInfoCopy(node_last); // copy complete node
4557 (*node_new)->node_top = &leveldir_first; // correct top node link
4558 (*node_new)->node_parent = leveldir_last; // correct parent node link
4560 (*node_new)->node_group = NULL;
4561 (*node_new)->next = NULL;
4563 (*node_new)->cl_first = -1; // force setting tree cursor
4565 node_new = &((*node_new)->next);
4568 if (reset_leveldir_current)
4569 leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
4570 last_level_series[0]);
4573 static void UpdateLastPlayedLevels_List(void)
4575 char **last_level_series = setup.level_setup.last_level_series;
4576 int pos = MAX_LEVELDIR_HISTORY - 1;
4579 // search for potentially already existing entry in list of level sets
4580 for (i = 0; last_level_series[i] != NULL; i++)
4581 if (strEqual(last_level_series[i], leveldir_current->identifier))
4584 // move list of level sets one entry down (using potentially free entry)
4585 for (i = pos; i > 0; i--)
4586 setString(&last_level_series[i], last_level_series[i - 1]);
4588 // put last played level set at top position
4589 setString(&last_level_series[0], leveldir_current->identifier);
4592 void LoadLevelSetup_LastSeries(void)
4594 // --------------------------------------------------------------------------
4595 // ~/.<program>/levelsetup.conf
4596 // --------------------------------------------------------------------------
4598 char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
4599 SetupFileHash *level_setup_hash = NULL;
4603 // always start with reliable default values
4604 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
4606 // start with empty history of last played level sets
4607 setString(&setup.level_setup.last_level_series[0], NULL);
4609 if (!strEqual(DEFAULT_LEVELSET, UNDEFINED_LEVELSET))
4611 leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
4613 if (leveldir_current == NULL)
4614 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
4617 if ((level_setup_hash = loadSetupFileHash(filename)))
4619 char *last_level_series =
4620 getHashEntry(level_setup_hash, TOKEN_STR_LAST_LEVEL_SERIES);
4622 leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
4624 if (leveldir_current == NULL)
4625 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
4627 for (i = 0; i < MAX_LEVELDIR_HISTORY; i++)
4629 char token[strlen(TOKEN_STR_LAST_LEVEL_SERIES) + 10];
4630 LevelDirTree *leveldir_last;
4632 sprintf(token, "%s.%03d", TOKEN_STR_LAST_LEVEL_SERIES, i);
4634 last_level_series = getHashEntry(level_setup_hash, token);
4636 leveldir_last = getTreeInfoFromIdentifier(leveldir_first,
4638 if (leveldir_last != NULL)
4639 setString(&setup.level_setup.last_level_series[pos++],
4643 setString(&setup.level_setup.last_level_series[pos], NULL);
4645 freeSetupFileHash(level_setup_hash);
4649 Debug("setup", "using default setup values");
4655 static void SaveLevelSetup_LastSeries_Ext(boolean deactivate_last_level_series)
4657 // --------------------------------------------------------------------------
4658 // ~/.<program>/levelsetup.conf
4659 // --------------------------------------------------------------------------
4661 // check if the current level directory structure is available at this point
4662 if (leveldir_current == NULL)
4665 char **last_level_series = setup.level_setup.last_level_series;
4666 char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
4670 InitUserDataDirectory();
4672 UpdateLastPlayedLevels_List();
4674 if (!(file = fopen(filename, MODE_WRITE)))
4676 Warn("cannot write setup file '%s'", filename);
4683 fprintFileHeader(file, LEVELSETUP_FILENAME);
4685 if (deactivate_last_level_series)
4686 fprintf(file, "# %s\n# ", "the following level set may have caused a problem and was deactivated");
4688 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_LAST_LEVEL_SERIES,
4689 leveldir_current->identifier));
4691 for (i = 0; last_level_series[i] != NULL; i++)
4693 char token[strlen(TOKEN_STR_LAST_LEVEL_SERIES) + 10];
4695 sprintf(token, "%s.%03d", TOKEN_STR_LAST_LEVEL_SERIES, i);
4697 fprintf(file, "%s\n", getFormattedSetupEntry(token, last_level_series[i]));
4702 SetFilePermissions(filename, PERMS_PRIVATE);
4707 void SaveLevelSetup_LastSeries(void)
4709 SaveLevelSetup_LastSeries_Ext(FALSE);
4712 void SaveLevelSetup_LastSeries_Deactivate(void)
4714 SaveLevelSetup_LastSeries_Ext(TRUE);
4717 static void checkSeriesInfo(void)
4719 static char *level_directory = NULL;
4722 DirectoryEntry *dir_entry;
4725 checked_free(level_directory);
4727 // check for more levels besides the 'levels' field of 'levelinfo.conf'
4729 level_directory = getPath2((leveldir_current->in_user_dir ?
4730 getUserLevelDir(NULL) :
4731 options.level_directory),
4732 leveldir_current->fullpath);
4734 if ((dir = openDirectory(level_directory)) == NULL)
4736 Warn("cannot read level directory '%s'", level_directory);
4742 while ((dir_entry = readDirectory(dir)) != NULL) // loop all entries
4744 if (strlen(dir_entry->basename) > 4 &&
4745 dir_entry->basename[3] == '.' &&
4746 strEqual(&dir_entry->basename[4], LEVELFILE_EXTENSION))
4748 char levelnum_str[4];
4751 strncpy(levelnum_str, dir_entry->basename, 3);
4752 levelnum_str[3] = '\0';
4754 levelnum_value = atoi(levelnum_str);
4756 if (levelnum_value < leveldir_current->first_level)
4758 Warn("additional level %d found", levelnum_value);
4760 leveldir_current->first_level = levelnum_value;
4762 else if (levelnum_value > leveldir_current->last_level)
4764 Warn("additional level %d found", levelnum_value);
4766 leveldir_current->last_level = levelnum_value;
4772 closeDirectory(dir);
4775 void LoadLevelSetup_SeriesInfo(void)
4778 SetupFileHash *level_setup_hash = NULL;
4779 char *level_subdir = leveldir_current->subdir;
4782 // always start with reliable default values
4783 level_nr = leveldir_current->first_level;
4785 for (i = 0; i < MAX_LEVELS; i++)
4787 LevelStats_setPlayed(i, 0);
4788 LevelStats_setSolved(i, 0);
4793 // --------------------------------------------------------------------------
4794 // ~/.<program>/levelsetup/<level series>/levelsetup.conf
4795 // --------------------------------------------------------------------------
4797 level_subdir = leveldir_current->subdir;
4799 filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
4801 if ((level_setup_hash = loadSetupFileHash(filename)))
4805 // get last played level in this level set
4807 token_value = getHashEntry(level_setup_hash, TOKEN_STR_LAST_PLAYED_LEVEL);
4811 level_nr = atoi(token_value);
4813 if (level_nr < leveldir_current->first_level)
4814 level_nr = leveldir_current->first_level;
4815 if (level_nr > leveldir_current->last_level)
4816 level_nr = leveldir_current->last_level;
4819 // get handicap level in this level set
4821 token_value = getHashEntry(level_setup_hash, TOKEN_STR_HANDICAP_LEVEL);
4825 int level_nr = atoi(token_value);
4827 if (level_nr < leveldir_current->first_level)
4828 level_nr = leveldir_current->first_level;
4829 if (level_nr > leveldir_current->last_level + 1)
4830 level_nr = leveldir_current->last_level;
4832 if (leveldir_current->user_defined || !leveldir_current->handicap)
4833 level_nr = leveldir_current->last_level;
4835 leveldir_current->handicap_level = level_nr;
4838 // get number of played and solved levels in this level set
4840 BEGIN_HASH_ITERATION(level_setup_hash, itr)
4842 char *token = HASH_ITERATION_TOKEN(itr);
4843 char *value = HASH_ITERATION_VALUE(itr);
4845 if (strlen(token) == 3 &&
4846 token[0] >= '0' && token[0] <= '9' &&
4847 token[1] >= '0' && token[1] <= '9' &&
4848 token[2] >= '0' && token[2] <= '9')
4850 int level_nr = atoi(token);
4853 LevelStats_setPlayed(level_nr, atoi(value)); // read 1st column
4855 value = strchr(value, ' ');
4858 LevelStats_setSolved(level_nr, atoi(value)); // read 2nd column
4861 END_HASH_ITERATION(hash, itr)
4863 freeSetupFileHash(level_setup_hash);
4867 Debug("setup", "using default setup values");
4873 void SaveLevelSetup_SeriesInfo(void)
4876 char *level_subdir = leveldir_current->subdir;
4877 char *level_nr_str = int2str(level_nr, 0);
4878 char *handicap_level_str = int2str(leveldir_current->handicap_level, 0);
4882 // --------------------------------------------------------------------------
4883 // ~/.<program>/levelsetup/<level series>/levelsetup.conf
4884 // --------------------------------------------------------------------------
4886 InitLevelSetupDirectory(level_subdir);
4888 filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
4890 if (!(file = fopen(filename, MODE_WRITE)))
4892 Warn("cannot write setup file '%s'", filename);
4899 fprintFileHeader(file, LEVELSETUP_FILENAME);
4901 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_PLAYED_LEVEL,
4903 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_HANDICAP_LEVEL,
4904 handicap_level_str));
4906 for (i = leveldir_current->first_level; i <= leveldir_current->last_level;
4909 if (LevelStats_getPlayed(i) > 0 ||
4910 LevelStats_getSolved(i) > 0)
4915 sprintf(token, "%03d", i);
4916 sprintf(value, "%d %d", LevelStats_getPlayed(i), LevelStats_getSolved(i));
4918 fprintf(file, "%s\n", getFormattedSetupEntry(token, value));
4924 SetFilePermissions(filename, PERMS_PRIVATE);
4929 int LevelStats_getPlayed(int nr)
4931 return (nr >= 0 && nr < MAX_LEVELS ? level_stats[nr].played : 0);
4934 int LevelStats_getSolved(int nr)
4936 return (nr >= 0 && nr < MAX_LEVELS ? level_stats[nr].solved : 0);
4939 void LevelStats_setPlayed(int nr, int value)
4941 if (nr >= 0 && nr < MAX_LEVELS)
4942 level_stats[nr].played = value;
4945 void LevelStats_setSolved(int nr, int value)
4947 if (nr >= 0 && nr < MAX_LEVELS)
4948 level_stats[nr].solved = value;
4951 void LevelStats_incPlayed(int nr)
4953 if (nr >= 0 && nr < MAX_LEVELS)
4954 level_stats[nr].played++;
4957 void LevelStats_incSolved(int nr)
4959 if (nr >= 0 && nr < MAX_LEVELS)
4960 level_stats[nr].solved++;
4963 void LoadUserSetup(void)
4965 // --------------------------------------------------------------------------
4966 // ~/.<program>/usersetup.conf
4967 // --------------------------------------------------------------------------
4969 char *filename = getPath2(getMainUserGameDataDir(), USERSETUP_FILENAME);
4970 SetupFileHash *user_setup_hash = NULL;
4972 // always start with reliable default values
4975 if ((user_setup_hash = loadSetupFileHash(filename)))
4979 // get last selected user number
4980 token_value = getHashEntry(user_setup_hash, TOKEN_STR_LAST_USER);
4983 user.nr = MIN(MAX(0, atoi(token_value)), MAX_PLAYER_NAMES - 1);
4985 freeSetupFileHash(user_setup_hash);
4989 Debug("setup", "using default setup values");
4995 void SaveUserSetup(void)
4997 // --------------------------------------------------------------------------
4998 // ~/.<program>/usersetup.conf
4999 // --------------------------------------------------------------------------
5001 char *filename = getPath2(getMainUserGameDataDir(), USERSETUP_FILENAME);
5004 InitMainUserDataDirectory();
5006 if (!(file = fopen(filename, MODE_WRITE)))
5008 Warn("cannot write setup file '%s'", filename);
5015 fprintFileHeader(file, USERSETUP_FILENAME);
5017 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_USER,
5021 SetFilePermissions(filename, PERMS_PRIVATE);