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_UNIX)
1517 if ((dir = getenv("HOME")) == NULL)
1519 dir = getUnixHomeDir();
1522 dir = getStringCopy(dir);
1534 char *getCommonDataDir(void)
1536 static char *common_data_dir = NULL;
1538 #if defined(PLATFORM_WIN32)
1539 if (common_data_dir == NULL)
1541 char *dir = checked_malloc(MAX_PATH + 1);
1543 if (SUCCEEDED(SHGetFolderPath(NULL, CSIDL_COMMON_DOCUMENTS, NULL, 0, dir))
1544 && !strEqual(dir, "")) // empty for Windows 95/98
1545 common_data_dir = getPath2(dir, program.userdata_subdir);
1547 common_data_dir = options.rw_base_directory;
1550 if (common_data_dir == NULL)
1551 common_data_dir = options.rw_base_directory;
1554 return common_data_dir;
1557 char *getPersonalDataDir(void)
1559 static char *personal_data_dir = NULL;
1561 #if defined(PLATFORM_MACOSX)
1562 if (personal_data_dir == NULL)
1563 personal_data_dir = getPath2(getHomeDir(), "Documents");
1565 if (personal_data_dir == NULL)
1566 personal_data_dir = getHomeDir();
1569 return personal_data_dir;
1572 char *getMainUserGameDataDir(void)
1574 static char *main_user_data_dir = NULL;
1576 #if defined(PLATFORM_ANDROID)
1577 if (main_user_data_dir == NULL)
1578 main_user_data_dir = (char *)(SDL_AndroidGetExternalStorageState() &
1579 SDL_ANDROID_EXTERNAL_STORAGE_WRITE ?
1580 SDL_AndroidGetExternalStoragePath() :
1581 SDL_AndroidGetInternalStoragePath());
1583 if (main_user_data_dir == NULL)
1584 main_user_data_dir = getPath2(getPersonalDataDir(),
1585 program.userdata_subdir);
1588 return main_user_data_dir;
1591 char *getUserGameDataDir(void)
1594 return getMainUserGameDataDir();
1596 return getUserDir(user.nr);
1599 char *getSetupDir(void)
1601 return getUserGameDataDir();
1604 static mode_t posix_umask(mode_t mask)
1606 #if defined(PLATFORM_UNIX)
1613 static int posix_mkdir(const char *pathname, mode_t mode)
1615 #if defined(PLATFORM_WIN32)
1616 return mkdir(pathname);
1618 return mkdir(pathname, mode);
1622 static boolean posix_process_running_setgid(void)
1624 #if defined(PLATFORM_UNIX)
1625 return (getgid() != getegid());
1631 void createDirectory(char *dir, char *text, int permission_class)
1633 if (directoryExists(dir))
1636 // leave "other" permissions in umask untouched, but ensure group parts
1637 // of USERDATA_DIR_MODE are not masked
1638 mode_t dir_mode = (permission_class == PERMS_PRIVATE ?
1639 DIR_PERMS_PRIVATE : DIR_PERMS_PUBLIC);
1640 mode_t last_umask = posix_umask(0);
1641 mode_t group_umask = ~(dir_mode & S_IRWXG);
1642 int running_setgid = posix_process_running_setgid();
1644 if (permission_class == PERMS_PUBLIC)
1646 // if we're setgid, protect files against "other"
1647 // else keep umask(0) to make the dir world-writable
1650 posix_umask(last_umask & group_umask);
1652 dir_mode = DIR_PERMS_PUBLIC_ALL;
1655 if (posix_mkdir(dir, dir_mode) != 0)
1656 Warn("cannot create %s directory '%s': %s", text, dir, strerror(errno));
1658 if (permission_class == PERMS_PUBLIC && !running_setgid)
1659 chmod(dir, dir_mode);
1661 posix_umask(last_umask); // restore previous umask
1664 void InitMainUserDataDirectory(void)
1666 createDirectory(getMainUserGameDataDir(), "main user data", PERMS_PRIVATE);
1669 void InitUserDataDirectory(void)
1671 createDirectory(getMainUserGameDataDir(), "main user data", PERMS_PRIVATE);
1675 createDirectory(getUserDir(-1), "users", PERMS_PRIVATE);
1676 createDirectory(getUserDir(user.nr), "user data", PERMS_PRIVATE);
1680 void SetFilePermissions(char *filename, int permission_class)
1682 int running_setgid = posix_process_running_setgid();
1683 int perms = (permission_class == PERMS_PRIVATE ?
1684 FILE_PERMS_PRIVATE : FILE_PERMS_PUBLIC);
1686 if (permission_class == PERMS_PUBLIC && !running_setgid)
1687 perms = FILE_PERMS_PUBLIC_ALL;
1689 chmod(filename, perms);
1692 char *getCookie(char *file_type)
1694 static char cookie[MAX_COOKIE_LEN + 1];
1696 if (strlen(program.cookie_prefix) + 1 +
1697 strlen(file_type) + strlen("_FILE_VERSION_x.x") > MAX_COOKIE_LEN)
1698 return "[COOKIE ERROR]"; // should never happen
1700 sprintf(cookie, "%s_%s_FILE_VERSION_%d.%d",
1701 program.cookie_prefix, file_type,
1702 program.version_super, program.version_major);
1707 void fprintFileHeader(FILE *file, char *basename)
1709 char *prefix = "# ";
1712 fprintf_line_with_prefix(file, prefix, sep1, 77);
1713 fprintf(file, "%s%s\n", prefix, basename);
1714 fprintf_line_with_prefix(file, prefix, sep1, 77);
1715 fprintf(file, "\n");
1718 int getFileVersionFromCookieString(const char *cookie)
1720 const char *ptr_cookie1, *ptr_cookie2;
1721 const char *pattern1 = "_FILE_VERSION_";
1722 const char *pattern2 = "?.?";
1723 const int len_cookie = strlen(cookie);
1724 const int len_pattern1 = strlen(pattern1);
1725 const int len_pattern2 = strlen(pattern2);
1726 const int len_pattern = len_pattern1 + len_pattern2;
1727 int version_super, version_major;
1729 if (len_cookie <= len_pattern)
1732 ptr_cookie1 = &cookie[len_cookie - len_pattern];
1733 ptr_cookie2 = &cookie[len_cookie - len_pattern2];
1735 if (strncmp(ptr_cookie1, pattern1, len_pattern1) != 0)
1738 if (ptr_cookie2[0] < '0' || ptr_cookie2[0] > '9' ||
1739 ptr_cookie2[1] != '.' ||
1740 ptr_cookie2[2] < '0' || ptr_cookie2[2] > '9')
1743 version_super = ptr_cookie2[0] - '0';
1744 version_major = ptr_cookie2[2] - '0';
1746 return VERSION_IDENT(version_super, version_major, 0, 0);
1749 boolean checkCookieString(const char *cookie, const char *template)
1751 const char *pattern = "_FILE_VERSION_?.?";
1752 const int len_cookie = strlen(cookie);
1753 const int len_template = strlen(template);
1754 const int len_pattern = strlen(pattern);
1756 if (len_cookie != len_template)
1759 if (strncmp(cookie, template, len_cookie - len_pattern) != 0)
1766 // ----------------------------------------------------------------------------
1767 // setup file list and hash handling functions
1768 // ----------------------------------------------------------------------------
1770 char *getFormattedSetupEntry(char *token, char *value)
1773 static char entry[MAX_LINE_LEN];
1775 // if value is an empty string, just return token without value
1779 // start with the token and some spaces to format output line
1780 sprintf(entry, "%s:", token);
1781 for (i = strlen(entry); i < token_value_position; i++)
1784 // continue with the token's value
1785 strcat(entry, value);
1790 SetupFileList *newSetupFileList(char *token, char *value)
1792 SetupFileList *new = checked_malloc(sizeof(SetupFileList));
1794 new->token = getStringCopy(token);
1795 new->value = getStringCopy(value);
1802 void freeSetupFileList(SetupFileList *list)
1807 checked_free(list->token);
1808 checked_free(list->value);
1811 freeSetupFileList(list->next);
1816 char *getListEntry(SetupFileList *list, char *token)
1821 if (strEqual(list->token, token))
1824 return getListEntry(list->next, token);
1827 SetupFileList *setListEntry(SetupFileList *list, char *token, char *value)
1832 if (strEqual(list->token, token))
1834 checked_free(list->value);
1836 list->value = getStringCopy(value);
1840 else if (list->next == NULL)
1841 return (list->next = newSetupFileList(token, value));
1843 return setListEntry(list->next, token, value);
1846 SetupFileList *addListEntry(SetupFileList *list, char *token, char *value)
1851 if (list->next == NULL)
1852 return (list->next = newSetupFileList(token, value));
1854 return addListEntry(list->next, token, value);
1857 #if ENABLE_UNUSED_CODE
1859 static void printSetupFileList(SetupFileList *list)
1864 Debug("setup:printSetupFileList", "token: '%s'", list->token);
1865 Debug("setup:printSetupFileList", "value: '%s'", list->value);
1867 printSetupFileList(list->next);
1873 DEFINE_HASHTABLE_INSERT(insert_hash_entry, char, char);
1874 DEFINE_HASHTABLE_SEARCH(search_hash_entry, char, char);
1875 DEFINE_HASHTABLE_CHANGE(change_hash_entry, char, char);
1876 DEFINE_HASHTABLE_REMOVE(remove_hash_entry, char, char);
1878 #define insert_hash_entry hashtable_insert
1879 #define search_hash_entry hashtable_search
1880 #define change_hash_entry hashtable_change
1881 #define remove_hash_entry hashtable_remove
1884 unsigned int get_hash_from_key(void *key)
1889 This algorithm (k=33) was first reported by Dan Bernstein many years ago in
1890 'comp.lang.c'. Another version of this algorithm (now favored by Bernstein)
1891 uses XOR: hash(i) = hash(i - 1) * 33 ^ str[i]; the magic of number 33 (why
1892 it works better than many other constants, prime or not) has never been
1893 adequately explained.
1895 If you just want to have a good hash function, and cannot wait, djb2
1896 is one of the best string hash functions i know. It has excellent
1897 distribution and speed on many different sets of keys and table sizes.
1898 You are not likely to do better with one of the "well known" functions
1899 such as PJW, K&R, etc.
1901 Ozan (oz) Yigit [http://www.cs.yorku.ca/~oz/hash.html]
1904 char *str = (char *)key;
1905 unsigned int hash = 5381;
1908 while ((c = *str++))
1909 hash = ((hash << 5) + hash) + c; // hash * 33 + c
1914 static int keys_are_equal(void *key1, void *key2)
1916 return (strEqual((char *)key1, (char *)key2));
1919 SetupFileHash *newSetupFileHash(void)
1921 SetupFileHash *new_hash =
1922 create_hashtable(16, 0.75, get_hash_from_key, keys_are_equal);
1924 if (new_hash == NULL)
1925 Fail("create_hashtable() failed -- out of memory");
1930 void freeSetupFileHash(SetupFileHash *hash)
1935 hashtable_destroy(hash, 1); // 1 == also free values stored in hash
1938 char *getHashEntry(SetupFileHash *hash, char *token)
1943 return search_hash_entry(hash, token);
1946 void setHashEntry(SetupFileHash *hash, char *token, char *value)
1953 value_copy = getStringCopy(value);
1955 // change value; if it does not exist, insert it as new
1956 if (!change_hash_entry(hash, token, value_copy))
1957 if (!insert_hash_entry(hash, getStringCopy(token), value_copy))
1958 Fail("cannot insert into hash -- aborting");
1961 char *removeHashEntry(SetupFileHash *hash, char *token)
1966 return remove_hash_entry(hash, token);
1969 #if ENABLE_UNUSED_CODE
1971 static void printSetupFileHash(SetupFileHash *hash)
1973 BEGIN_HASH_ITERATION(hash, itr)
1975 Debug("setup:printSetupFileHash", "token: '%s'", HASH_ITERATION_TOKEN(itr));
1976 Debug("setup:printSetupFileHash", "value: '%s'", HASH_ITERATION_VALUE(itr));
1978 END_HASH_ITERATION(hash, itr)
1983 #define ALLOW_TOKEN_VALUE_SEPARATOR_BEING_WHITESPACE 1
1984 #define CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING 0
1985 #define CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH 0
1987 static boolean token_value_separator_found = FALSE;
1988 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
1989 static boolean token_value_separator_warning = FALSE;
1991 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
1992 static boolean token_already_exists_warning = FALSE;
1995 static boolean getTokenValueFromSetupLineExt(char *line,
1996 char **token_ptr, char **value_ptr,
1997 char *filename, char *line_raw,
1999 boolean separator_required)
2001 static char line_copy[MAX_LINE_LEN + 1], line_raw_copy[MAX_LINE_LEN + 1];
2002 char *token, *value, *line_ptr;
2004 // when externally invoked via ReadTokenValueFromLine(), copy line buffers
2005 if (line_raw == NULL)
2007 strncpy(line_copy, line, MAX_LINE_LEN);
2008 line_copy[MAX_LINE_LEN] = '\0';
2011 strcpy(line_raw_copy, line_copy);
2012 line_raw = line_raw_copy;
2015 // cut trailing comment from input line
2016 for (line_ptr = line; *line_ptr; line_ptr++)
2018 if (*line_ptr == '#')
2025 // cut trailing whitespaces from input line
2026 for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
2027 if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
2030 // ignore empty lines
2034 // cut leading whitespaces from token
2035 for (token = line; *token; token++)
2036 if (*token != ' ' && *token != '\t')
2039 // start with empty value as reliable default
2042 token_value_separator_found = FALSE;
2044 // find end of token to determine start of value
2045 for (line_ptr = token; *line_ptr; line_ptr++)
2047 // first look for an explicit token/value separator, like ':' or '='
2048 if (*line_ptr == ':' || *line_ptr == '=')
2050 *line_ptr = '\0'; // terminate token string
2051 value = line_ptr + 1; // set beginning of value
2053 token_value_separator_found = TRUE;
2059 #if ALLOW_TOKEN_VALUE_SEPARATOR_BEING_WHITESPACE
2060 // fallback: if no token/value separator found, also allow whitespaces
2061 if (!token_value_separator_found && !separator_required)
2063 for (line_ptr = token; *line_ptr; line_ptr++)
2065 if (*line_ptr == ' ' || *line_ptr == '\t')
2067 *line_ptr = '\0'; // terminate token string
2068 value = line_ptr + 1; // set beginning of value
2070 token_value_separator_found = TRUE;
2076 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2077 if (token_value_separator_found)
2079 if (!token_value_separator_warning)
2081 Debug("setup", "---");
2083 if (filename != NULL)
2085 Debug("setup", "missing token/value separator(s) in config file:");
2086 Debug("setup", "- config file: '%s'", filename);
2090 Debug("setup", "missing token/value separator(s):");
2093 token_value_separator_warning = TRUE;
2096 if (filename != NULL)
2097 Debug("setup", "- line %d: '%s'", line_nr, line_raw);
2099 Debug("setup", "- line: '%s'", line_raw);
2105 // cut trailing whitespaces from token
2106 for (line_ptr = &token[strlen(token)]; line_ptr >= token; line_ptr--)
2107 if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
2110 // cut leading whitespaces from value
2111 for (; *value; value++)
2112 if (*value != ' ' && *value != '\t')
2121 boolean getTokenValueFromSetupLine(char *line, char **token, char **value)
2123 // while the internal (old) interface does not require a token/value
2124 // separator (for downwards compatibility with existing files which
2125 // don't use them), it is mandatory for the external (new) interface
2127 return getTokenValueFromSetupLineExt(line, token, value, NULL, NULL, 0, TRUE);
2130 static boolean loadSetupFileData(void *setup_file_data, char *filename,
2131 boolean top_recursion_level, boolean is_hash)
2133 static SetupFileHash *include_filename_hash = NULL;
2134 char line[MAX_LINE_LEN], line_raw[MAX_LINE_LEN], previous_line[MAX_LINE_LEN];
2135 char *token, *value, *line_ptr;
2136 void *insert_ptr = NULL;
2137 boolean read_continued_line = FALSE;
2139 int line_nr = 0, token_count = 0, include_count = 0;
2141 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2142 token_value_separator_warning = FALSE;
2145 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2146 token_already_exists_warning = FALSE;
2149 if (!(file = openFile(filename, MODE_READ)))
2151 #if DEBUG_NO_CONFIG_FILE
2152 Debug("setup", "cannot open configuration file '%s'", filename);
2158 // use "insert pointer" to store list end for constant insertion complexity
2160 insert_ptr = setup_file_data;
2162 // on top invocation, create hash to mark included files (to prevent loops)
2163 if (top_recursion_level)
2164 include_filename_hash = newSetupFileHash();
2166 // mark this file as already included (to prevent including it again)
2167 setHashEntry(include_filename_hash, getBaseNamePtr(filename), "true");
2169 while (!checkEndOfFile(file))
2171 // read next line of input file
2172 if (!getStringFromFile(file, line, MAX_LINE_LEN))
2175 // check if line was completely read and is terminated by line break
2176 if (strlen(line) > 0 && line[strlen(line) - 1] == '\n')
2179 // cut trailing line break (this can be newline and/or carriage return)
2180 for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
2181 if ((*line_ptr == '\n' || *line_ptr == '\r') && *(line_ptr + 1) == '\0')
2184 // copy raw input line for later use (mainly debugging output)
2185 strcpy(line_raw, line);
2187 if (read_continued_line)
2189 // append new line to existing line, if there is enough space
2190 if (strlen(previous_line) + strlen(line_ptr) < MAX_LINE_LEN)
2191 strcat(previous_line, line_ptr);
2193 strcpy(line, previous_line); // copy storage buffer to line
2195 read_continued_line = FALSE;
2198 // if the last character is '\', continue at next line
2199 if (strlen(line) > 0 && line[strlen(line) - 1] == '\\')
2201 line[strlen(line) - 1] = '\0'; // cut off trailing backslash
2202 strcpy(previous_line, line); // copy line to storage buffer
2204 read_continued_line = TRUE;
2209 if (!getTokenValueFromSetupLineExt(line, &token, &value, filename,
2210 line_raw, line_nr, FALSE))
2215 if (strEqual(token, "include"))
2217 if (getHashEntry(include_filename_hash, value) == NULL)
2219 char *basepath = getBasePath(filename);
2220 char *basename = getBaseName(value);
2221 char *filename_include = getPath2(basepath, basename);
2223 loadSetupFileData(setup_file_data, filename_include, FALSE, is_hash);
2227 free(filename_include);
2233 Warn("ignoring already processed file '%s'", value);
2240 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2242 getHashEntry((SetupFileHash *)setup_file_data, token);
2244 if (old_value != NULL)
2246 if (!token_already_exists_warning)
2248 Debug("setup", "---");
2249 Debug("setup", "duplicate token(s) found in config file:");
2250 Debug("setup", "- config file: '%s'", filename);
2252 token_already_exists_warning = TRUE;
2255 Debug("setup", "- token: '%s' (in line %d)", token, line_nr);
2256 Debug("setup", " old value: '%s'", old_value);
2257 Debug("setup", " new value: '%s'", value);
2261 setHashEntry((SetupFileHash *)setup_file_data, token, value);
2265 insert_ptr = addListEntry((SetupFileList *)insert_ptr, token, value);
2275 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2276 if (token_value_separator_warning)
2277 Debug("setup", "---");
2280 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2281 if (token_already_exists_warning)
2282 Debug("setup", "---");
2285 if (token_count == 0 && include_count == 0)
2286 Warn("configuration file '%s' is empty", filename);
2288 if (top_recursion_level)
2289 freeSetupFileHash(include_filename_hash);
2294 static int compareSetupFileData(const void *object1, const void *object2)
2296 const struct ConfigInfo *entry1 = (struct ConfigInfo *)object1;
2297 const struct ConfigInfo *entry2 = (struct ConfigInfo *)object2;
2299 return strcmp(entry1->token, entry2->token);
2302 static void saveSetupFileHash(SetupFileHash *hash, char *filename)
2304 int item_count = hashtable_count(hash);
2305 int item_size = sizeof(struct ConfigInfo);
2306 struct ConfigInfo *sort_array = checked_malloc(item_count * item_size);
2310 // copy string pointers from hash to array
2311 BEGIN_HASH_ITERATION(hash, itr)
2313 sort_array[i].token = HASH_ITERATION_TOKEN(itr);
2314 sort_array[i].value = HASH_ITERATION_VALUE(itr);
2318 if (i > item_count) // should never happen
2321 END_HASH_ITERATION(hash, itr)
2323 // sort string pointers from hash in array
2324 qsort(sort_array, item_count, item_size, compareSetupFileData);
2326 if (!(file = fopen(filename, MODE_WRITE)))
2328 Warn("cannot write configuration file '%s'", filename);
2333 fprintf(file, "%s\n\n", getFormattedSetupEntry("program.version",
2334 program.version_string));
2335 for (i = 0; i < item_count; i++)
2336 fprintf(file, "%s\n", getFormattedSetupEntry(sort_array[i].token,
2337 sort_array[i].value));
2340 checked_free(sort_array);
2343 SetupFileList *loadSetupFileList(char *filename)
2345 SetupFileList *setup_file_list = newSetupFileList("", "");
2346 SetupFileList *first_valid_list_entry;
2348 if (!loadSetupFileData(setup_file_list, filename, TRUE, FALSE))
2350 freeSetupFileList(setup_file_list);
2355 first_valid_list_entry = setup_file_list->next;
2357 // free empty list header
2358 setup_file_list->next = NULL;
2359 freeSetupFileList(setup_file_list);
2361 return first_valid_list_entry;
2364 SetupFileHash *loadSetupFileHash(char *filename)
2366 SetupFileHash *setup_file_hash = newSetupFileHash();
2368 if (!loadSetupFileData(setup_file_hash, filename, TRUE, TRUE))
2370 freeSetupFileHash(setup_file_hash);
2375 return setup_file_hash;
2379 // ============================================================================
2381 // ============================================================================
2383 #define TOKEN_STR_LAST_LEVEL_SERIES "last_level_series"
2384 #define TOKEN_STR_LAST_PLAYED_LEVEL "last_played_level"
2385 #define TOKEN_STR_HANDICAP_LEVEL "handicap_level"
2386 #define TOKEN_STR_LAST_USER "last_user"
2388 // level directory info
2389 #define LEVELINFO_TOKEN_IDENTIFIER 0
2390 #define LEVELINFO_TOKEN_NAME 1
2391 #define LEVELINFO_TOKEN_NAME_SORTING 2
2392 #define LEVELINFO_TOKEN_AUTHOR 3
2393 #define LEVELINFO_TOKEN_YEAR 4
2394 #define LEVELINFO_TOKEN_PROGRAM_TITLE 5
2395 #define LEVELINFO_TOKEN_PROGRAM_COPYRIGHT 6
2396 #define LEVELINFO_TOKEN_PROGRAM_COMPANY 7
2397 #define LEVELINFO_TOKEN_IMPORTED_FROM 8
2398 #define LEVELINFO_TOKEN_IMPORTED_BY 9
2399 #define LEVELINFO_TOKEN_TESTED_BY 10
2400 #define LEVELINFO_TOKEN_LEVELS 11
2401 #define LEVELINFO_TOKEN_FIRST_LEVEL 12
2402 #define LEVELINFO_TOKEN_SORT_PRIORITY 13
2403 #define LEVELINFO_TOKEN_LATEST_ENGINE 14
2404 #define LEVELINFO_TOKEN_LEVEL_GROUP 15
2405 #define LEVELINFO_TOKEN_READONLY 16
2406 #define LEVELINFO_TOKEN_GRAPHICS_SET_ECS 17
2407 #define LEVELINFO_TOKEN_GRAPHICS_SET_AGA 18
2408 #define LEVELINFO_TOKEN_GRAPHICS_SET 19
2409 #define LEVELINFO_TOKEN_SOUNDS_SET_DEFAULT 20
2410 #define LEVELINFO_TOKEN_SOUNDS_SET_LOWPASS 21
2411 #define LEVELINFO_TOKEN_SOUNDS_SET 22
2412 #define LEVELINFO_TOKEN_MUSIC_SET 23
2413 #define LEVELINFO_TOKEN_FILENAME 24
2414 #define LEVELINFO_TOKEN_FILETYPE 25
2415 #define LEVELINFO_TOKEN_SPECIAL_FLAGS 26
2416 #define LEVELINFO_TOKEN_HANDICAP 27
2417 #define LEVELINFO_TOKEN_SKIP_LEVELS 28
2418 #define LEVELINFO_TOKEN_USE_EMC_TILES 29
2420 #define NUM_LEVELINFO_TOKENS 30
2422 static LevelDirTree ldi;
2424 static struct TokenInfo levelinfo_tokens[] =
2426 // level directory info
2427 { TYPE_STRING, &ldi.identifier, "identifier" },
2428 { TYPE_STRING, &ldi.name, "name" },
2429 { TYPE_STRING, &ldi.name_sorting, "name_sorting" },
2430 { TYPE_STRING, &ldi.author, "author" },
2431 { TYPE_STRING, &ldi.year, "year" },
2432 { TYPE_STRING, &ldi.program_title, "program_title" },
2433 { TYPE_STRING, &ldi.program_copyright, "program_copyright" },
2434 { TYPE_STRING, &ldi.program_company, "program_company" },
2435 { TYPE_STRING, &ldi.imported_from, "imported_from" },
2436 { TYPE_STRING, &ldi.imported_by, "imported_by" },
2437 { TYPE_STRING, &ldi.tested_by, "tested_by" },
2438 { TYPE_INTEGER, &ldi.levels, "levels" },
2439 { TYPE_INTEGER, &ldi.first_level, "first_level" },
2440 { TYPE_INTEGER, &ldi.sort_priority, "sort_priority" },
2441 { TYPE_BOOLEAN, &ldi.latest_engine, "latest_engine" },
2442 { TYPE_BOOLEAN, &ldi.level_group, "level_group" },
2443 { TYPE_BOOLEAN, &ldi.readonly, "readonly" },
2444 { TYPE_STRING, &ldi.graphics_set_ecs, "graphics_set.ecs" },
2445 { TYPE_STRING, &ldi.graphics_set_aga, "graphics_set.aga" },
2446 { TYPE_STRING, &ldi.graphics_set, "graphics_set" },
2447 { TYPE_STRING, &ldi.sounds_set_default,"sounds_set.default" },
2448 { TYPE_STRING, &ldi.sounds_set_lowpass,"sounds_set.lowpass" },
2449 { TYPE_STRING, &ldi.sounds_set, "sounds_set" },
2450 { TYPE_STRING, &ldi.music_set, "music_set" },
2451 { TYPE_STRING, &ldi.level_filename, "filename" },
2452 { TYPE_STRING, &ldi.level_filetype, "filetype" },
2453 { TYPE_STRING, &ldi.special_flags, "special_flags" },
2454 { TYPE_BOOLEAN, &ldi.handicap, "handicap" },
2455 { TYPE_BOOLEAN, &ldi.skip_levels, "skip_levels" },
2456 { TYPE_BOOLEAN, &ldi.use_emc_tiles, "use_emc_tiles" }
2459 static struct TokenInfo artworkinfo_tokens[] =
2461 // artwork directory info
2462 { TYPE_STRING, &ldi.identifier, "identifier" },
2463 { TYPE_STRING, &ldi.subdir, "subdir" },
2464 { TYPE_STRING, &ldi.name, "name" },
2465 { TYPE_STRING, &ldi.name_sorting, "name_sorting" },
2466 { TYPE_STRING, &ldi.author, "author" },
2467 { TYPE_STRING, &ldi.program_title, "program_title" },
2468 { TYPE_STRING, &ldi.program_copyright, "program_copyright" },
2469 { TYPE_STRING, &ldi.program_company, "program_company" },
2470 { TYPE_INTEGER, &ldi.sort_priority, "sort_priority" },
2471 { TYPE_STRING, &ldi.basepath, "basepath" },
2472 { TYPE_STRING, &ldi.fullpath, "fullpath" },
2473 { TYPE_BOOLEAN, &ldi.in_user_dir, "in_user_dir" },
2474 { TYPE_STRING, &ldi.class_desc, "class_desc" },
2479 static char *optional_tokens[] =
2482 "program_copyright",
2488 static void setTreeInfoToDefaults(TreeInfo *ti, int type)
2492 ti->node_top = (ti->type == TREE_TYPE_LEVEL_DIR ? &leveldir_first :
2493 ti->type == TREE_TYPE_GRAPHICS_DIR ? &artwork.gfx_first :
2494 ti->type == TREE_TYPE_SOUNDS_DIR ? &artwork.snd_first :
2495 ti->type == TREE_TYPE_MUSIC_DIR ? &artwork.mus_first :
2498 ti->node_parent = NULL;
2499 ti->node_group = NULL;
2506 ti->fullpath = NULL;
2507 ti->basepath = NULL;
2508 ti->identifier = NULL;
2509 ti->name = getStringCopy(ANONYMOUS_NAME);
2510 ti->name_sorting = NULL;
2511 ti->author = getStringCopy(ANONYMOUS_NAME);
2514 ti->program_title = NULL;
2515 ti->program_copyright = NULL;
2516 ti->program_company = NULL;
2518 ti->sort_priority = LEVELCLASS_UNDEFINED; // default: least priority
2519 ti->latest_engine = FALSE; // default: get from level
2520 ti->parent_link = FALSE;
2521 ti->in_user_dir = FALSE;
2522 ti->user_defined = FALSE;
2524 ti->class_desc = NULL;
2526 ti->infotext = getStringCopy(TREE_INFOTEXT(ti->type));
2528 if (ti->type == TREE_TYPE_LEVEL_DIR)
2530 ti->imported_from = NULL;
2531 ti->imported_by = NULL;
2532 ti->tested_by = NULL;
2534 ti->graphics_set_ecs = NULL;
2535 ti->graphics_set_aga = NULL;
2536 ti->graphics_set = NULL;
2537 ti->sounds_set_default = NULL;
2538 ti->sounds_set_lowpass = NULL;
2539 ti->sounds_set = NULL;
2540 ti->music_set = NULL;
2541 ti->graphics_path = getStringCopy(UNDEFINED_FILENAME);
2542 ti->sounds_path = getStringCopy(UNDEFINED_FILENAME);
2543 ti->music_path = getStringCopy(UNDEFINED_FILENAME);
2545 ti->level_filename = NULL;
2546 ti->level_filetype = NULL;
2548 ti->special_flags = NULL;
2551 ti->first_level = 0;
2553 ti->level_group = FALSE;
2554 ti->handicap_level = 0;
2555 ti->readonly = TRUE;
2556 ti->handicap = TRUE;
2557 ti->skip_levels = FALSE;
2559 ti->use_emc_tiles = FALSE;
2563 static void setTreeInfoToDefaultsFromParent(TreeInfo *ti, TreeInfo *parent)
2567 Warn("setTreeInfoToDefaultsFromParent(): parent == NULL");
2569 setTreeInfoToDefaults(ti, TREE_TYPE_UNDEFINED);
2574 // copy all values from the parent structure
2576 ti->type = parent->type;
2578 ti->node_top = parent->node_top;
2579 ti->node_parent = parent;
2580 ti->node_group = NULL;
2587 ti->fullpath = NULL;
2588 ti->basepath = NULL;
2589 ti->identifier = NULL;
2590 ti->name = getStringCopy(ANONYMOUS_NAME);
2591 ti->name_sorting = NULL;
2592 ti->author = getStringCopy(parent->author);
2593 ti->year = getStringCopy(parent->year);
2595 ti->program_title = getStringCopy(parent->program_title);
2596 ti->program_copyright = getStringCopy(parent->program_copyright);
2597 ti->program_company = getStringCopy(parent->program_company);
2599 ti->sort_priority = parent->sort_priority;
2600 ti->latest_engine = parent->latest_engine;
2601 ti->parent_link = FALSE;
2602 ti->in_user_dir = parent->in_user_dir;
2603 ti->user_defined = parent->user_defined;
2604 ti->color = parent->color;
2605 ti->class_desc = getStringCopy(parent->class_desc);
2607 ti->infotext = getStringCopy(parent->infotext);
2609 if (ti->type == TREE_TYPE_LEVEL_DIR)
2611 ti->imported_from = getStringCopy(parent->imported_from);
2612 ti->imported_by = getStringCopy(parent->imported_by);
2613 ti->tested_by = getStringCopy(parent->tested_by);
2615 ti->graphics_set_ecs = getStringCopy(parent->graphics_set_ecs);
2616 ti->graphics_set_aga = getStringCopy(parent->graphics_set_aga);
2617 ti->graphics_set = getStringCopy(parent->graphics_set);
2618 ti->sounds_set_default = getStringCopy(parent->sounds_set_default);
2619 ti->sounds_set_lowpass = getStringCopy(parent->sounds_set_lowpass);
2620 ti->sounds_set = getStringCopy(parent->sounds_set);
2621 ti->music_set = getStringCopy(parent->music_set);
2622 ti->graphics_path = getStringCopy(UNDEFINED_FILENAME);
2623 ti->sounds_path = getStringCopy(UNDEFINED_FILENAME);
2624 ti->music_path = getStringCopy(UNDEFINED_FILENAME);
2626 ti->level_filename = getStringCopy(parent->level_filename);
2627 ti->level_filetype = getStringCopy(parent->level_filetype);
2629 ti->special_flags = getStringCopy(parent->special_flags);
2631 ti->levels = parent->levels;
2632 ti->first_level = parent->first_level;
2633 ti->last_level = parent->last_level;
2634 ti->level_group = FALSE;
2635 ti->handicap_level = parent->handicap_level;
2636 ti->readonly = parent->readonly;
2637 ti->handicap = parent->handicap;
2638 ti->skip_levels = parent->skip_levels;
2640 ti->use_emc_tiles = parent->use_emc_tiles;
2644 static TreeInfo *getTreeInfoCopy(TreeInfo *ti)
2646 TreeInfo *ti_copy = newTreeInfo();
2648 // copy all values from the original structure
2650 ti_copy->type = ti->type;
2652 ti_copy->node_top = ti->node_top;
2653 ti_copy->node_parent = ti->node_parent;
2654 ti_copy->node_group = ti->node_group;
2655 ti_copy->next = ti->next;
2657 ti_copy->cl_first = ti->cl_first;
2658 ti_copy->cl_cursor = ti->cl_cursor;
2660 ti_copy->subdir = getStringCopy(ti->subdir);
2661 ti_copy->fullpath = getStringCopy(ti->fullpath);
2662 ti_copy->basepath = getStringCopy(ti->basepath);
2663 ti_copy->identifier = getStringCopy(ti->identifier);
2664 ti_copy->name = getStringCopy(ti->name);
2665 ti_copy->name_sorting = getStringCopy(ti->name_sorting);
2666 ti_copy->author = getStringCopy(ti->author);
2667 ti_copy->year = getStringCopy(ti->year);
2669 ti_copy->program_title = getStringCopy(ti->program_title);
2670 ti_copy->program_copyright = getStringCopy(ti->program_copyright);
2671 ti_copy->program_company = getStringCopy(ti->program_company);
2673 ti_copy->imported_from = getStringCopy(ti->imported_from);
2674 ti_copy->imported_by = getStringCopy(ti->imported_by);
2675 ti_copy->tested_by = getStringCopy(ti->tested_by);
2677 ti_copy->graphics_set_ecs = getStringCopy(ti->graphics_set_ecs);
2678 ti_copy->graphics_set_aga = getStringCopy(ti->graphics_set_aga);
2679 ti_copy->graphics_set = getStringCopy(ti->graphics_set);
2680 ti_copy->sounds_set_default = getStringCopy(ti->sounds_set_default);
2681 ti_copy->sounds_set_lowpass = getStringCopy(ti->sounds_set_lowpass);
2682 ti_copy->sounds_set = getStringCopy(ti->sounds_set);
2683 ti_copy->music_set = getStringCopy(ti->music_set);
2684 ti_copy->graphics_path = getStringCopy(ti->graphics_path);
2685 ti_copy->sounds_path = getStringCopy(ti->sounds_path);
2686 ti_copy->music_path = getStringCopy(ti->music_path);
2688 ti_copy->level_filename = getStringCopy(ti->level_filename);
2689 ti_copy->level_filetype = getStringCopy(ti->level_filetype);
2691 ti_copy->special_flags = getStringCopy(ti->special_flags);
2693 ti_copy->levels = ti->levels;
2694 ti_copy->first_level = ti->first_level;
2695 ti_copy->last_level = ti->last_level;
2696 ti_copy->sort_priority = ti->sort_priority;
2698 ti_copy->latest_engine = ti->latest_engine;
2700 ti_copy->level_group = ti->level_group;
2701 ti_copy->parent_link = ti->parent_link;
2702 ti_copy->in_user_dir = ti->in_user_dir;
2703 ti_copy->user_defined = ti->user_defined;
2704 ti_copy->readonly = ti->readonly;
2705 ti_copy->handicap = ti->handicap;
2706 ti_copy->skip_levels = ti->skip_levels;
2708 ti_copy->use_emc_tiles = ti->use_emc_tiles;
2710 ti_copy->color = ti->color;
2711 ti_copy->class_desc = getStringCopy(ti->class_desc);
2712 ti_copy->handicap_level = ti->handicap_level;
2714 ti_copy->infotext = getStringCopy(ti->infotext);
2719 void freeTreeInfo(TreeInfo *ti)
2724 checked_free(ti->subdir);
2725 checked_free(ti->fullpath);
2726 checked_free(ti->basepath);
2727 checked_free(ti->identifier);
2729 checked_free(ti->name);
2730 checked_free(ti->name_sorting);
2731 checked_free(ti->author);
2732 checked_free(ti->year);
2734 checked_free(ti->program_title);
2735 checked_free(ti->program_copyright);
2736 checked_free(ti->program_company);
2738 checked_free(ti->class_desc);
2740 checked_free(ti->infotext);
2742 if (ti->type == TREE_TYPE_LEVEL_DIR)
2744 checked_free(ti->imported_from);
2745 checked_free(ti->imported_by);
2746 checked_free(ti->tested_by);
2748 checked_free(ti->graphics_set_ecs);
2749 checked_free(ti->graphics_set_aga);
2750 checked_free(ti->graphics_set);
2751 checked_free(ti->sounds_set_default);
2752 checked_free(ti->sounds_set_lowpass);
2753 checked_free(ti->sounds_set);
2754 checked_free(ti->music_set);
2756 checked_free(ti->graphics_path);
2757 checked_free(ti->sounds_path);
2758 checked_free(ti->music_path);
2760 checked_free(ti->level_filename);
2761 checked_free(ti->level_filetype);
2763 checked_free(ti->special_flags);
2766 // recursively free child node
2768 freeTreeInfo(ti->node_group);
2770 // recursively free next node
2772 freeTreeInfo(ti->next);
2777 void setSetupInfo(struct TokenInfo *token_info,
2778 int token_nr, char *token_value)
2780 int token_type = token_info[token_nr].type;
2781 void *setup_value = token_info[token_nr].value;
2783 if (token_value == NULL)
2786 // set setup field to corresponding token value
2791 *(boolean *)setup_value = get_boolean_from_string(token_value);
2795 *(int *)setup_value = get_switch3_from_string(token_value);
2799 *(Key *)setup_value = getKeyFromKeyName(token_value);
2803 *(Key *)setup_value = getKeyFromX11KeyName(token_value);
2807 *(int *)setup_value = get_integer_from_string(token_value);
2811 checked_free(*(char **)setup_value);
2812 *(char **)setup_value = getStringCopy(token_value);
2816 *(int *)setup_value = get_player_nr_from_string(token_value);
2824 static int compareTreeInfoEntries(const void *object1, const void *object2)
2826 const TreeInfo *entry1 = *((TreeInfo **)object1);
2827 const TreeInfo *entry2 = *((TreeInfo **)object2);
2828 int tree_sorting1 = TREE_SORTING(entry1);
2829 int tree_sorting2 = TREE_SORTING(entry2);
2831 if (tree_sorting1 != tree_sorting2)
2832 return (tree_sorting1 - tree_sorting2);
2834 return strcasecmp(entry1->name_sorting, entry2->name_sorting);
2837 static TreeInfo *createParentTreeInfoNode(TreeInfo *node_parent)
2841 if (node_parent == NULL)
2844 ti_new = newTreeInfo();
2845 setTreeInfoToDefaults(ti_new, node_parent->type);
2847 ti_new->node_parent = node_parent;
2848 ti_new->parent_link = TRUE;
2850 setString(&ti_new->identifier, node_parent->identifier);
2851 setString(&ti_new->name, BACKLINK_TEXT_PARENT);
2852 setString(&ti_new->name_sorting, ti_new->name);
2854 setString(&ti_new->subdir, STRING_PARENT_DIRECTORY);
2855 setString(&ti_new->fullpath, node_parent->fullpath);
2857 ti_new->sort_priority = node_parent->sort_priority;
2858 ti_new->latest_engine = node_parent->latest_engine;
2860 setString(&ti_new->class_desc, getLevelClassDescription(ti_new));
2862 pushTreeInfo(&node_parent->node_group, ti_new);
2867 static TreeInfo *createTopTreeInfoNode(TreeInfo *node_first)
2869 if (node_first == NULL)
2872 TreeInfo *ti_new = newTreeInfo();
2873 int type = node_first->type;
2875 setTreeInfoToDefaults(ti_new, type);
2877 ti_new->node_parent = NULL;
2878 ti_new->parent_link = FALSE;
2880 setString(&ti_new->identifier, node_first->identifier);
2881 setString(&ti_new->name, TREE_INFOTEXT(type));
2882 setString(&ti_new->name_sorting, ti_new->name);
2884 setString(&ti_new->subdir, STRING_TOP_DIRECTORY);
2885 setString(&ti_new->fullpath, ".");
2887 ti_new->sort_priority = node_first->sort_priority;;
2888 ti_new->latest_engine = node_first->latest_engine;
2890 setString(&ti_new->class_desc, TREE_INFOTEXT(type));
2892 ti_new->node_group = node_first;
2893 ti_new->level_group = TRUE;
2895 TreeInfo *ti_new2 = createParentTreeInfoNode(ti_new);
2897 setString(&ti_new2->name, TREE_BACKLINK_TEXT(type));
2898 setString(&ti_new2->name_sorting, ti_new2->name);
2903 static void setTreeInfoParentNodes(TreeInfo *node, TreeInfo *node_parent)
2907 if (node->node_group)
2908 setTreeInfoParentNodes(node->node_group, node);
2910 node->node_parent = node_parent;
2917 // ----------------------------------------------------------------------------
2918 // functions for handling level and custom artwork info cache
2919 // ----------------------------------------------------------------------------
2921 static void LoadArtworkInfoCache(void)
2923 InitCacheDirectory();
2925 if (artworkinfo_cache_old == NULL)
2927 char *filename = getPath2(getCacheDir(), ARTWORKINFO_CACHE_FILE);
2929 // try to load artwork info hash from already existing cache file
2930 artworkinfo_cache_old = loadSetupFileHash(filename);
2932 // try to get program version that artwork info cache was written with
2933 char *version = getHashEntry(artworkinfo_cache_old, "program.version");
2935 // check program version of artwork info cache against current version
2936 if (!strEqual(version, program.version_string))
2938 freeSetupFileHash(artworkinfo_cache_old);
2940 artworkinfo_cache_old = NULL;
2943 // if no artwork info cache file was found, start with empty hash
2944 if (artworkinfo_cache_old == NULL)
2945 artworkinfo_cache_old = newSetupFileHash();
2950 if (artworkinfo_cache_new == NULL)
2951 artworkinfo_cache_new = newSetupFileHash();
2953 update_artworkinfo_cache = FALSE;
2956 static void SaveArtworkInfoCache(void)
2958 if (!update_artworkinfo_cache)
2961 char *filename = getPath2(getCacheDir(), ARTWORKINFO_CACHE_FILE);
2963 InitCacheDirectory();
2965 saveSetupFileHash(artworkinfo_cache_new, filename);
2970 static char *getCacheTokenPrefix(char *prefix1, char *prefix2)
2972 static char *prefix = NULL;
2974 checked_free(prefix);
2976 prefix = getStringCat2WithSeparator(prefix1, prefix2, ".");
2981 // (identical to above function, but separate string buffer needed -- nasty)
2982 static char *getCacheToken(char *prefix, char *suffix)
2984 static char *token = NULL;
2986 checked_free(token);
2988 token = getStringCat2WithSeparator(prefix, suffix, ".");
2993 static char *getFileTimestampString(char *filename)
2995 return getStringCopy(i_to_a(getFileTimestampEpochSeconds(filename)));
2998 static boolean modifiedFileTimestamp(char *filename, char *timestamp_string)
3000 struct stat file_status;
3002 if (timestamp_string == NULL)
3005 if (!fileExists(filename)) // file does not exist
3006 return (atoi(timestamp_string) != 0);
3008 if (stat(filename, &file_status) != 0) // cannot stat file
3011 return (file_status.st_mtime != atoi(timestamp_string));
3014 static TreeInfo *getArtworkInfoCacheEntry(LevelDirTree *level_node, int type)
3016 char *identifier = level_node->subdir;
3017 char *type_string = ARTWORK_DIRECTORY(type);
3018 char *token_prefix = getCacheTokenPrefix(type_string, identifier);
3019 char *token_main = getCacheToken(token_prefix, "CACHED");
3020 char *cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
3021 boolean cached = (cache_entry != NULL && strEqual(cache_entry, "true"));
3022 TreeInfo *artwork_info = NULL;
3024 if (!use_artworkinfo_cache)
3027 if (optional_tokens_hash == NULL)
3031 // create hash from list of optional tokens (for quick access)
3032 optional_tokens_hash = newSetupFileHash();
3033 for (i = 0; optional_tokens[i] != NULL; i++)
3034 setHashEntry(optional_tokens_hash, optional_tokens[i], "");
3041 artwork_info = newTreeInfo();
3042 setTreeInfoToDefaults(artwork_info, type);
3044 // set all structure fields according to the token/value pairs
3045 ldi = *artwork_info;
3046 for (i = 0; artworkinfo_tokens[i].type != -1; i++)
3048 char *token_suffix = artworkinfo_tokens[i].text;
3049 char *token = getCacheToken(token_prefix, token_suffix);
3050 char *value = getHashEntry(artworkinfo_cache_old, token);
3052 (getHashEntry(optional_tokens_hash, token_suffix) != NULL);
3054 setSetupInfo(artworkinfo_tokens, i, value);
3056 // check if cache entry for this item is mandatory, but missing
3057 if (value == NULL && !optional)
3059 Warn("missing cache entry '%s'", token);
3065 *artwork_info = ldi;
3070 char *filename_levelinfo = getPath2(getLevelDirFromTreeInfo(level_node),
3071 LEVELINFO_FILENAME);
3072 char *filename_artworkinfo = getPath2(getSetupArtworkDir(artwork_info),
3073 ARTWORKINFO_FILENAME(type));
3075 // check if corresponding "levelinfo.conf" file has changed
3076 token_main = getCacheToken(token_prefix, "TIMESTAMP_LEVELINFO");
3077 cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
3079 if (modifiedFileTimestamp(filename_levelinfo, cache_entry))
3082 // check if corresponding "<artworkinfo>.conf" file has changed
3083 token_main = getCacheToken(token_prefix, "TIMESTAMP_ARTWORKINFO");
3084 cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
3086 if (modifiedFileTimestamp(filename_artworkinfo, cache_entry))
3089 checked_free(filename_levelinfo);
3090 checked_free(filename_artworkinfo);
3093 if (!cached && artwork_info != NULL)
3095 freeTreeInfo(artwork_info);
3100 return artwork_info;
3103 static void setArtworkInfoCacheEntry(TreeInfo *artwork_info,
3104 LevelDirTree *level_node, int type)
3106 char *identifier = level_node->subdir;
3107 char *type_string = ARTWORK_DIRECTORY(type);
3108 char *token_prefix = getCacheTokenPrefix(type_string, identifier);
3109 char *token_main = getCacheToken(token_prefix, "CACHED");
3110 boolean set_cache_timestamps = TRUE;
3113 setHashEntry(artworkinfo_cache_new, token_main, "true");
3115 if (set_cache_timestamps)
3117 char *filename_levelinfo = getPath2(getLevelDirFromTreeInfo(level_node),
3118 LEVELINFO_FILENAME);
3119 char *filename_artworkinfo = getPath2(getSetupArtworkDir(artwork_info),
3120 ARTWORKINFO_FILENAME(type));
3121 char *timestamp_levelinfo = getFileTimestampString(filename_levelinfo);
3122 char *timestamp_artworkinfo = getFileTimestampString(filename_artworkinfo);
3124 token_main = getCacheToken(token_prefix, "TIMESTAMP_LEVELINFO");
3125 setHashEntry(artworkinfo_cache_new, token_main, timestamp_levelinfo);
3127 token_main = getCacheToken(token_prefix, "TIMESTAMP_ARTWORKINFO");
3128 setHashEntry(artworkinfo_cache_new, token_main, timestamp_artworkinfo);
3130 checked_free(filename_levelinfo);
3131 checked_free(filename_artworkinfo);
3132 checked_free(timestamp_levelinfo);
3133 checked_free(timestamp_artworkinfo);
3136 ldi = *artwork_info;
3137 for (i = 0; artworkinfo_tokens[i].type != -1; i++)
3139 char *token = getCacheToken(token_prefix, artworkinfo_tokens[i].text);
3140 char *value = getSetupValue(artworkinfo_tokens[i].type,
3141 artworkinfo_tokens[i].value);
3143 setHashEntry(artworkinfo_cache_new, token, value);
3148 // ----------------------------------------------------------------------------
3149 // functions for loading level info and custom artwork info
3150 // ----------------------------------------------------------------------------
3152 int GetZipFileTreeType(char *zip_filename)
3154 static char *top_dir_path = NULL;
3155 static char *top_dir_conf_filename[NUM_BASE_TREE_TYPES] = { NULL };
3156 static char *conf_basename[NUM_BASE_TREE_TYPES] =
3158 GRAPHICSINFO_FILENAME,
3159 SOUNDSINFO_FILENAME,
3165 checked_free(top_dir_path);
3166 top_dir_path = NULL;
3168 for (j = 0; j < NUM_BASE_TREE_TYPES; j++)
3170 checked_free(top_dir_conf_filename[j]);
3171 top_dir_conf_filename[j] = NULL;
3174 char **zip_entries = zip_list(zip_filename);
3176 // check if zip file successfully opened
3177 if (zip_entries == NULL || zip_entries[0] == NULL)
3178 return TREE_TYPE_UNDEFINED;
3180 // first zip file entry is expected to be top level directory
3181 char *top_dir = zip_entries[0];
3183 // check if valid top level directory found in zip file
3184 if (!strSuffix(top_dir, "/"))
3185 return TREE_TYPE_UNDEFINED;
3187 // get filenames of valid configuration files in top level directory
3188 for (j = 0; j < NUM_BASE_TREE_TYPES; j++)
3189 top_dir_conf_filename[j] = getStringCat2(top_dir, conf_basename[j]);
3191 int tree_type = TREE_TYPE_UNDEFINED;
3194 while (zip_entries[e] != NULL)
3196 // check if every zip file entry is below top level directory
3197 if (!strPrefix(zip_entries[e], top_dir))
3198 return TREE_TYPE_UNDEFINED;
3200 // check if this zip file entry is a valid configuration filename
3201 for (j = 0; j < NUM_BASE_TREE_TYPES; j++)
3203 if (strEqual(zip_entries[e], top_dir_conf_filename[j]))
3205 // only exactly one valid configuration file allowed
3206 if (tree_type != TREE_TYPE_UNDEFINED)
3207 return TREE_TYPE_UNDEFINED;
3219 static boolean CheckZipFileForDirectory(char *zip_filename, char *directory,
3222 static char *top_dir_path = NULL;
3223 static char *top_dir_conf_filename = NULL;
3225 checked_free(top_dir_path);
3226 checked_free(top_dir_conf_filename);
3228 top_dir_path = NULL;
3229 top_dir_conf_filename = NULL;
3231 char *conf_basename = (tree_type == TREE_TYPE_LEVEL_DIR ? LEVELINFO_FILENAME :
3232 ARTWORKINFO_FILENAME(tree_type));
3234 // check if valid configuration filename determined
3235 if (conf_basename == NULL || strEqual(conf_basename, ""))
3238 char **zip_entries = zip_list(zip_filename);
3240 // check if zip file successfully opened
3241 if (zip_entries == NULL || zip_entries[0] == NULL)
3244 // first zip file entry is expected to be top level directory
3245 char *top_dir = zip_entries[0];
3247 // check if valid top level directory found in zip file
3248 if (!strSuffix(top_dir, "/"))
3251 // get path of extracted top level directory
3252 top_dir_path = getPath2(directory, top_dir);
3254 // remove trailing directory separator from top level directory path
3255 // (required to be able to check for file and directory in next step)
3256 top_dir_path[strlen(top_dir_path) - 1] = '\0';
3258 // check if zip file's top level directory already exists in target directory
3259 if (fileExists(top_dir_path)) // (checks for file and directory)
3262 // get filename of configuration file in top level directory
3263 top_dir_conf_filename = getStringCat2(top_dir, conf_basename);
3265 boolean found_top_dir_conf_filename = FALSE;
3268 while (zip_entries[i] != NULL)
3270 // check if every zip file entry is below top level directory
3271 if (!strPrefix(zip_entries[i], top_dir))
3274 // check if this zip file entry is the configuration filename
3275 if (strEqual(zip_entries[i], top_dir_conf_filename))
3276 found_top_dir_conf_filename = TRUE;
3281 // check if valid configuration filename was found in zip file
3282 if (!found_top_dir_conf_filename)
3288 char *ExtractZipFileIntoDirectory(char *zip_filename, char *directory,
3291 boolean zip_file_valid = CheckZipFileForDirectory(zip_filename, directory,
3294 if (!zip_file_valid)
3296 Warn("zip file '%s' rejected!", zip_filename);
3301 char **zip_entries = zip_extract(zip_filename, directory);
3303 if (zip_entries == NULL)
3305 Warn("zip file '%s' could not be extracted!", zip_filename);
3310 Info("zip file '%s' successfully extracted!", zip_filename);
3312 // first zip file entry contains top level directory
3313 char *top_dir = zip_entries[0];
3315 // remove trailing directory separator from top level directory
3316 top_dir[strlen(top_dir) - 1] = '\0';
3321 static void ProcessZipFilesInDirectory(char *directory, int tree_type)
3324 DirectoryEntry *dir_entry;
3326 if ((dir = openDirectory(directory)) == NULL)
3328 // display error if directory is main "options.graphics_directory" etc.
3329 if (tree_type == TREE_TYPE_LEVEL_DIR ||
3330 directory == OPTIONS_ARTWORK_DIRECTORY(tree_type))
3331 Warn("cannot read directory '%s'", directory);
3336 while ((dir_entry = readDirectory(dir)) != NULL) // loop all entries
3338 // skip non-zip files (and also directories with zip extension)
3339 if (!strSuffixLower(dir_entry->basename, ".zip") || dir_entry->is_directory)
3342 char *zip_filename = getPath2(directory, dir_entry->basename);
3343 char *zip_filename_extracted = getStringCat2(zip_filename, ".extracted");
3344 char *zip_filename_rejected = getStringCat2(zip_filename, ".rejected");
3346 // check if zip file hasn't already been extracted or rejected
3347 if (!fileExists(zip_filename_extracted) &&
3348 !fileExists(zip_filename_rejected))
3350 char *top_dir = ExtractZipFileIntoDirectory(zip_filename, directory,
3352 char *marker_filename = (top_dir != NULL ? zip_filename_extracted :
3353 zip_filename_rejected);
3356 // create empty file to mark zip file as extracted or rejected
3357 if ((marker_file = fopen(marker_filename, MODE_WRITE)))
3358 fclose(marker_file);
3361 free(zip_filename_extracted);
3362 free(zip_filename_rejected);
3366 closeDirectory(dir);
3369 // forward declaration for recursive call by "LoadLevelInfoFromLevelDir()"
3370 static void LoadLevelInfoFromLevelDir(TreeInfo **, TreeInfo *, char *);
3372 static boolean LoadLevelInfoFromLevelConf(TreeInfo **node_first,
3373 TreeInfo *node_parent,
3374 char *level_directory,
3375 char *directory_name)
3377 char *directory_path = getPath2(level_directory, directory_name);
3378 char *filename = getPath2(directory_path, LEVELINFO_FILENAME);
3379 SetupFileHash *setup_file_hash;
3380 LevelDirTree *leveldir_new = NULL;
3383 // unless debugging, silently ignore directories without "levelinfo.conf"
3384 if (!options.debug && !fileExists(filename))
3386 free(directory_path);
3392 setup_file_hash = loadSetupFileHash(filename);
3394 if (setup_file_hash == NULL)
3396 #if DEBUG_NO_CONFIG_FILE
3397 Debug("setup", "ignoring level directory '%s'", directory_path);
3400 free(directory_path);
3406 leveldir_new = newTreeInfo();
3409 setTreeInfoToDefaultsFromParent(leveldir_new, node_parent);
3411 setTreeInfoToDefaults(leveldir_new, TREE_TYPE_LEVEL_DIR);
3413 leveldir_new->subdir = getStringCopy(directory_name);
3415 // set all structure fields according to the token/value pairs
3416 ldi = *leveldir_new;
3417 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
3418 setSetupInfo(levelinfo_tokens, i,
3419 getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
3420 *leveldir_new = ldi;
3422 if (strEqual(leveldir_new->name, ANONYMOUS_NAME))
3423 setString(&leveldir_new->name, leveldir_new->subdir);
3425 if (leveldir_new->identifier == NULL)
3426 leveldir_new->identifier = getStringCopy(leveldir_new->subdir);
3428 if (leveldir_new->name_sorting == NULL)
3429 leveldir_new->name_sorting = getStringCopy(leveldir_new->name);
3431 if (node_parent == NULL) // top level group
3433 leveldir_new->basepath = getStringCopy(level_directory);
3434 leveldir_new->fullpath = getStringCopy(leveldir_new->subdir);
3436 else // sub level group
3438 leveldir_new->basepath = getStringCopy(node_parent->basepath);
3439 leveldir_new->fullpath = getPath2(node_parent->fullpath, directory_name);
3442 leveldir_new->last_level =
3443 leveldir_new->first_level + leveldir_new->levels - 1;
3445 leveldir_new->in_user_dir =
3446 (!strEqual(leveldir_new->basepath, options.level_directory));
3448 // adjust some settings if user's private level directory was detected
3449 if (leveldir_new->sort_priority == LEVELCLASS_UNDEFINED &&
3450 leveldir_new->in_user_dir &&
3451 (strEqual(leveldir_new->subdir, getLoginName()) ||
3452 strEqual(leveldir_new->name, getLoginName()) ||
3453 strEqual(leveldir_new->author, getRealName())))
3455 leveldir_new->sort_priority = LEVELCLASS_PRIVATE_START;
3456 leveldir_new->readonly = FALSE;
3459 leveldir_new->user_defined =
3460 (leveldir_new->in_user_dir && IS_LEVELCLASS_PRIVATE(leveldir_new));
3462 setString(&leveldir_new->class_desc, getLevelClassDescription(leveldir_new));
3464 leveldir_new->handicap_level = // set handicap to default value
3465 (leveldir_new->user_defined || !leveldir_new->handicap ?
3466 leveldir_new->last_level : leveldir_new->first_level);
3468 DrawInitText(leveldir_new->name, 150, FC_YELLOW);
3470 pushTreeInfo(node_first, leveldir_new);
3472 freeSetupFileHash(setup_file_hash);
3474 if (leveldir_new->level_group)
3476 // create node to link back to current level directory
3477 createParentTreeInfoNode(leveldir_new);
3479 // recursively step into sub-directory and look for more level series
3480 LoadLevelInfoFromLevelDir(&leveldir_new->node_group,
3481 leveldir_new, directory_path);
3484 free(directory_path);
3490 static void LoadLevelInfoFromLevelDir(TreeInfo **node_first,
3491 TreeInfo *node_parent,
3492 char *level_directory)
3494 // ---------- 1st stage: process any level set zip files ----------
3496 ProcessZipFilesInDirectory(level_directory, TREE_TYPE_LEVEL_DIR);
3498 // ---------- 2nd stage: check for level set directories ----------
3501 DirectoryEntry *dir_entry;
3502 boolean valid_entry_found = FALSE;
3504 if ((dir = openDirectory(level_directory)) == NULL)
3506 Warn("cannot read level directory '%s'", level_directory);
3511 while ((dir_entry = readDirectory(dir)) != NULL) // loop all entries
3513 char *directory_name = dir_entry->basename;
3514 char *directory_path = getPath2(level_directory, directory_name);
3516 // skip entries for current and parent directory
3517 if (strEqual(directory_name, ".") ||
3518 strEqual(directory_name, ".."))
3520 free(directory_path);
3525 // find out if directory entry is itself a directory
3526 if (!dir_entry->is_directory) // not a directory
3528 free(directory_path);
3533 free(directory_path);
3535 if (strEqual(directory_name, GRAPHICS_DIRECTORY) ||
3536 strEqual(directory_name, SOUNDS_DIRECTORY) ||
3537 strEqual(directory_name, MUSIC_DIRECTORY))
3540 valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
3545 closeDirectory(dir);
3547 // special case: top level directory may directly contain "levelinfo.conf"
3548 if (node_parent == NULL && !valid_entry_found)
3550 // check if this directory directly contains a file "levelinfo.conf"
3551 valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
3552 level_directory, ".");
3555 if (!valid_entry_found)
3556 Warn("cannot find any valid level series in directory '%s'",
3560 boolean AdjustGraphicsForEMC(void)
3562 boolean settings_changed = FALSE;
3564 settings_changed |= adjustTreeGraphicsForEMC(leveldir_first_all);
3565 settings_changed |= adjustTreeGraphicsForEMC(leveldir_first);
3567 return settings_changed;
3570 boolean AdjustSoundsForEMC(void)
3572 boolean settings_changed = FALSE;
3574 settings_changed |= adjustTreeSoundsForEMC(leveldir_first_all);
3575 settings_changed |= adjustTreeSoundsForEMC(leveldir_first);
3577 return settings_changed;
3580 void LoadLevelInfo(void)
3582 InitUserLevelDirectory(getLoginName());
3584 DrawInitText("Loading level series", 120, FC_GREEN);
3586 LoadLevelInfoFromLevelDir(&leveldir_first, NULL, options.level_directory);
3587 LoadLevelInfoFromLevelDir(&leveldir_first, NULL, getUserLevelDir(NULL));
3589 leveldir_first = createTopTreeInfoNode(leveldir_first);
3591 /* after loading all level set information, clone the level directory tree
3592 and remove all level sets without levels (these may still contain artwork
3593 to be offered in the setup menu as "custom artwork", and are therefore
3594 checked for existing artwork in the function "LoadLevelArtworkInfo()") */
3595 leveldir_first_all = leveldir_first;
3596 cloneTree(&leveldir_first, leveldir_first_all, TRUE);
3598 AdjustGraphicsForEMC();
3599 AdjustSoundsForEMC();
3601 // before sorting, the first entries will be from the user directory
3602 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
3604 if (leveldir_first == NULL)
3605 Fail("cannot find any valid level series in any directory");
3607 sortTreeInfo(&leveldir_first);
3609 #if ENABLE_UNUSED_CODE
3610 dumpTreeInfo(leveldir_first, 0);
3614 static boolean LoadArtworkInfoFromArtworkConf(TreeInfo **node_first,
3615 TreeInfo *node_parent,
3616 char *base_directory,
3617 char *directory_name, int type)
3619 char *directory_path = getPath2(base_directory, directory_name);
3620 char *filename = getPath2(directory_path, ARTWORKINFO_FILENAME(type));
3621 SetupFileHash *setup_file_hash = NULL;
3622 TreeInfo *artwork_new = NULL;
3625 if (fileExists(filename))
3626 setup_file_hash = loadSetupFileHash(filename);
3628 if (setup_file_hash == NULL) // no config file -- look for artwork files
3631 DirectoryEntry *dir_entry;
3632 boolean valid_file_found = FALSE;
3634 if ((dir = openDirectory(directory_path)) != NULL)
3636 while ((dir_entry = readDirectory(dir)) != NULL)
3638 if (FileIsArtworkType(dir_entry->filename, type))
3640 valid_file_found = TRUE;
3646 closeDirectory(dir);
3649 if (!valid_file_found)
3651 #if DEBUG_NO_CONFIG_FILE
3652 if (!strEqual(directory_name, "."))
3653 Debug("setup", "ignoring artwork directory '%s'", directory_path);
3656 free(directory_path);
3663 artwork_new = newTreeInfo();
3666 setTreeInfoToDefaultsFromParent(artwork_new, node_parent);
3668 setTreeInfoToDefaults(artwork_new, type);
3670 artwork_new->subdir = getStringCopy(directory_name);
3672 if (setup_file_hash) // (before defining ".color" and ".class_desc")
3674 // set all structure fields according to the token/value pairs
3676 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
3677 setSetupInfo(levelinfo_tokens, i,
3678 getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
3681 if (strEqual(artwork_new->name, ANONYMOUS_NAME))
3682 setString(&artwork_new->name, artwork_new->subdir);
3684 if (artwork_new->identifier == NULL)
3685 artwork_new->identifier = getStringCopy(artwork_new->subdir);
3687 if (artwork_new->name_sorting == NULL)
3688 artwork_new->name_sorting = getStringCopy(artwork_new->name);
3691 if (node_parent == NULL) // top level group
3693 artwork_new->basepath = getStringCopy(base_directory);
3694 artwork_new->fullpath = getStringCopy(artwork_new->subdir);
3696 else // sub level group
3698 artwork_new->basepath = getStringCopy(node_parent->basepath);
3699 artwork_new->fullpath = getPath2(node_parent->fullpath, directory_name);
3702 artwork_new->in_user_dir =
3703 (!strEqual(artwork_new->basepath, OPTIONS_ARTWORK_DIRECTORY(type)));
3705 setString(&artwork_new->class_desc, getLevelClassDescription(artwork_new));
3707 if (setup_file_hash == NULL) // (after determining ".user_defined")
3709 if (strEqual(artwork_new->subdir, "."))
3711 if (artwork_new->user_defined)
3713 setString(&artwork_new->identifier, "private");
3714 artwork_new->sort_priority = ARTWORKCLASS_PRIVATE;
3718 setString(&artwork_new->identifier, "classic");
3719 artwork_new->sort_priority = ARTWORKCLASS_CLASSICS;
3722 setString(&artwork_new->class_desc,
3723 getLevelClassDescription(artwork_new));
3727 setString(&artwork_new->identifier, artwork_new->subdir);
3730 setString(&artwork_new->name, artwork_new->identifier);
3731 setString(&artwork_new->name_sorting, artwork_new->name);
3734 pushTreeInfo(node_first, artwork_new);
3736 freeSetupFileHash(setup_file_hash);
3738 free(directory_path);
3744 static void LoadArtworkInfoFromArtworkDir(TreeInfo **node_first,
3745 TreeInfo *node_parent,
3746 char *base_directory, int type)
3748 // ---------- 1st stage: process any artwork set zip files ----------
3750 ProcessZipFilesInDirectory(base_directory, type);
3752 // ---------- 2nd stage: check for artwork set directories ----------
3755 DirectoryEntry *dir_entry;
3756 boolean valid_entry_found = FALSE;
3758 if ((dir = openDirectory(base_directory)) == NULL)
3760 // display error if directory is main "options.graphics_directory" etc.
3761 if (base_directory == OPTIONS_ARTWORK_DIRECTORY(type))
3762 Warn("cannot read directory '%s'", base_directory);
3767 while ((dir_entry = readDirectory(dir)) != NULL) // loop all entries
3769 char *directory_name = dir_entry->basename;
3770 char *directory_path = getPath2(base_directory, directory_name);
3772 // skip directory entries for current and parent directory
3773 if (strEqual(directory_name, ".") ||
3774 strEqual(directory_name, ".."))
3776 free(directory_path);
3781 // skip directory entries which are not a directory
3782 if (!dir_entry->is_directory) // not a directory
3784 free(directory_path);
3789 free(directory_path);
3791 // check if this directory contains artwork with or without config file
3792 valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
3794 directory_name, type);
3797 closeDirectory(dir);
3799 // check if this directory directly contains artwork itself
3800 valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
3801 base_directory, ".",
3803 if (!valid_entry_found)
3804 Warn("cannot find any valid artwork in directory '%s'", base_directory);
3807 static TreeInfo *getDummyArtworkInfo(int type)
3809 // this is only needed when there is completely no artwork available
3810 TreeInfo *artwork_new = newTreeInfo();
3812 setTreeInfoToDefaults(artwork_new, type);
3814 setString(&artwork_new->subdir, UNDEFINED_FILENAME);
3815 setString(&artwork_new->fullpath, UNDEFINED_FILENAME);
3816 setString(&artwork_new->basepath, UNDEFINED_FILENAME);
3818 setString(&artwork_new->identifier, UNDEFINED_FILENAME);
3819 setString(&artwork_new->name, UNDEFINED_FILENAME);
3820 setString(&artwork_new->name_sorting, UNDEFINED_FILENAME);
3825 void SetCurrentArtwork(int type)
3827 ArtworkDirTree **current_ptr = ARTWORK_CURRENT_PTR(artwork, type);
3828 ArtworkDirTree *first_node = ARTWORK_FIRST_NODE(artwork, type);
3829 char *setup_set = SETUP_ARTWORK_SET(setup, type);
3830 char *default_subdir = ARTWORK_DEFAULT_SUBDIR(type);
3832 // set current artwork to artwork configured in setup menu
3833 *current_ptr = getTreeInfoFromIdentifier(first_node, setup_set);
3835 // if not found, set current artwork to default artwork
3836 if (*current_ptr == NULL)
3837 *current_ptr = getTreeInfoFromIdentifier(first_node, default_subdir);
3839 // if not found, set current artwork to first artwork in tree
3840 if (*current_ptr == NULL)
3841 *current_ptr = getFirstValidTreeInfoEntry(first_node);
3844 void ChangeCurrentArtworkIfNeeded(int type)
3846 char *current_identifier = ARTWORK_CURRENT_IDENTIFIER(artwork, type);
3847 char *setup_set = SETUP_ARTWORK_SET(setup, type);
3849 if (!strEqual(current_identifier, setup_set))
3850 SetCurrentArtwork(type);
3853 void LoadArtworkInfo(void)
3855 LoadArtworkInfoCache();
3857 DrawInitText("Looking for custom artwork", 120, FC_GREEN);
3859 LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
3860 options.graphics_directory,
3861 TREE_TYPE_GRAPHICS_DIR);
3862 LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
3863 getUserGraphicsDir(),
3864 TREE_TYPE_GRAPHICS_DIR);
3866 LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
3867 options.sounds_directory,
3868 TREE_TYPE_SOUNDS_DIR);
3869 LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
3871 TREE_TYPE_SOUNDS_DIR);
3873 LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
3874 options.music_directory,
3875 TREE_TYPE_MUSIC_DIR);
3876 LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
3878 TREE_TYPE_MUSIC_DIR);
3880 if (artwork.gfx_first == NULL)
3881 artwork.gfx_first = getDummyArtworkInfo(TREE_TYPE_GRAPHICS_DIR);
3882 if (artwork.snd_first == NULL)
3883 artwork.snd_first = getDummyArtworkInfo(TREE_TYPE_SOUNDS_DIR);
3884 if (artwork.mus_first == NULL)
3885 artwork.mus_first = getDummyArtworkInfo(TREE_TYPE_MUSIC_DIR);
3887 // before sorting, the first entries will be from the user directory
3888 SetCurrentArtwork(ARTWORK_TYPE_GRAPHICS);
3889 SetCurrentArtwork(ARTWORK_TYPE_SOUNDS);
3890 SetCurrentArtwork(ARTWORK_TYPE_MUSIC);
3892 artwork.gfx_current_identifier = artwork.gfx_current->identifier;
3893 artwork.snd_current_identifier = artwork.snd_current->identifier;
3894 artwork.mus_current_identifier = artwork.mus_current->identifier;
3896 #if ENABLE_UNUSED_CODE
3897 Debug("setup:LoadArtworkInfo", "graphics set == %s",
3898 artwork.gfx_current_identifier);
3899 Debug("setup:LoadArtworkInfo", "sounds set == %s",
3900 artwork.snd_current_identifier);
3901 Debug("setup:LoadArtworkInfo", "music set == %s",
3902 artwork.mus_current_identifier);
3905 sortTreeInfo(&artwork.gfx_first);
3906 sortTreeInfo(&artwork.snd_first);
3907 sortTreeInfo(&artwork.mus_first);
3909 #if ENABLE_UNUSED_CODE
3910 dumpTreeInfo(artwork.gfx_first, 0);
3911 dumpTreeInfo(artwork.snd_first, 0);
3912 dumpTreeInfo(artwork.mus_first, 0);
3916 static void MoveArtworkInfoIntoSubTree(ArtworkDirTree **artwork_node)
3918 ArtworkDirTree *artwork_new = newTreeInfo();
3919 char *top_node_name = "standalone artwork";
3921 setTreeInfoToDefaults(artwork_new, (*artwork_node)->type);
3923 artwork_new->level_group = TRUE;
3925 setString(&artwork_new->identifier, top_node_name);
3926 setString(&artwork_new->name, top_node_name);
3927 setString(&artwork_new->name_sorting, top_node_name);
3929 // create node to link back to current custom artwork directory
3930 createParentTreeInfoNode(artwork_new);
3932 // move existing custom artwork tree into newly created sub-tree
3933 artwork_new->node_group->next = *artwork_node;
3935 // change custom artwork tree to contain only newly created node
3936 *artwork_node = artwork_new;
3939 static void LoadArtworkInfoFromLevelInfoExt(ArtworkDirTree **artwork_node,
3940 ArtworkDirTree *node_parent,
3941 LevelDirTree *level_node,
3942 boolean empty_level_set_mode)
3944 int type = (*artwork_node)->type;
3946 // recursively check all level directories for artwork sub-directories
3950 boolean empty_level_set = (level_node->levels == 0);
3952 // check all tree entries for artwork, but skip parent link entries
3953 if (!level_node->parent_link && empty_level_set == empty_level_set_mode)
3955 TreeInfo *artwork_new = getArtworkInfoCacheEntry(level_node, type);
3956 boolean cached = (artwork_new != NULL);
3960 pushTreeInfo(artwork_node, artwork_new);
3964 TreeInfo *topnode_last = *artwork_node;
3965 char *path = getPath2(getLevelDirFromTreeInfo(level_node),
3966 ARTWORK_DIRECTORY(type));
3968 LoadArtworkInfoFromArtworkDir(artwork_node, NULL, path, type);
3970 if (topnode_last != *artwork_node) // check for newly added node
3972 artwork_new = *artwork_node;
3974 setString(&artwork_new->identifier, level_node->subdir);
3975 setString(&artwork_new->name, level_node->name);
3976 setString(&artwork_new->name_sorting, level_node->name_sorting);
3978 artwork_new->sort_priority = level_node->sort_priority;
3979 artwork_new->in_user_dir = level_node->in_user_dir;
3981 update_artworkinfo_cache = TRUE;
3987 // insert artwork info (from old cache or filesystem) into new cache
3988 if (artwork_new != NULL)
3989 setArtworkInfoCacheEntry(artwork_new, level_node, type);
3992 DrawInitText(level_node->name, 150, FC_YELLOW);
3994 if (level_node->node_group != NULL)
3996 TreeInfo *artwork_new = newTreeInfo();
3999 setTreeInfoToDefaultsFromParent(artwork_new, node_parent);
4001 setTreeInfoToDefaults(artwork_new, type);
4003 artwork_new->level_group = TRUE;
4005 setString(&artwork_new->identifier, level_node->subdir);
4007 if (node_parent == NULL) // check for top tree node
4009 char *top_node_name = (empty_level_set_mode ?
4010 "artwork for certain level sets" :
4011 "artwork included in level sets");
4013 setString(&artwork_new->name, top_node_name);
4014 setString(&artwork_new->name_sorting, top_node_name);
4018 setString(&artwork_new->name, level_node->name);
4019 setString(&artwork_new->name_sorting, level_node->name_sorting);
4022 pushTreeInfo(artwork_node, artwork_new);
4024 // create node to link back to current custom artwork directory
4025 createParentTreeInfoNode(artwork_new);
4027 // recursively step into sub-directory and look for more custom artwork
4028 LoadArtworkInfoFromLevelInfoExt(&artwork_new->node_group, artwork_new,
4029 level_node->node_group,
4030 empty_level_set_mode);
4032 // if sub-tree has no custom artwork at all, remove it
4033 if (artwork_new->node_group->next == NULL)
4034 removeTreeInfo(artwork_node);
4037 level_node = level_node->next;
4041 static void LoadArtworkInfoFromLevelInfo(ArtworkDirTree **artwork_node)
4043 // move peviously loaded artwork tree into separate sub-tree
4044 MoveArtworkInfoIntoSubTree(artwork_node);
4046 // load artwork from level sets into separate sub-trees
4047 LoadArtworkInfoFromLevelInfoExt(artwork_node, NULL, leveldir_first_all, TRUE);
4048 LoadArtworkInfoFromLevelInfoExt(artwork_node, NULL, leveldir_first_all, FALSE);
4050 // add top tree node over all three separate sub-trees
4051 *artwork_node = createTopTreeInfoNode(*artwork_node);
4053 // set all parent links (back links) in complete artwork tree
4054 setTreeInfoParentNodes(*artwork_node, NULL);
4057 void LoadLevelArtworkInfo(void)
4059 print_timestamp_init("LoadLevelArtworkInfo");
4061 DrawInitText("Looking for custom level artwork", 120, FC_GREEN);
4063 print_timestamp_time("DrawTimeText");
4065 LoadArtworkInfoFromLevelInfo(&artwork.gfx_first);
4066 print_timestamp_time("LoadArtworkInfoFromLevelInfo (gfx)");
4067 LoadArtworkInfoFromLevelInfo(&artwork.snd_first);
4068 print_timestamp_time("LoadArtworkInfoFromLevelInfo (snd)");
4069 LoadArtworkInfoFromLevelInfo(&artwork.mus_first);
4070 print_timestamp_time("LoadArtworkInfoFromLevelInfo (mus)");
4072 SaveArtworkInfoCache();
4074 print_timestamp_time("SaveArtworkInfoCache");
4076 // needed for reloading level artwork not known at ealier stage
4077 ChangeCurrentArtworkIfNeeded(ARTWORK_TYPE_GRAPHICS);
4078 ChangeCurrentArtworkIfNeeded(ARTWORK_TYPE_SOUNDS);
4079 ChangeCurrentArtworkIfNeeded(ARTWORK_TYPE_MUSIC);
4081 print_timestamp_time("getTreeInfoFromIdentifier");
4083 sortTreeInfo(&artwork.gfx_first);
4084 sortTreeInfo(&artwork.snd_first);
4085 sortTreeInfo(&artwork.mus_first);
4087 print_timestamp_time("sortTreeInfo");
4089 #if ENABLE_UNUSED_CODE
4090 dumpTreeInfo(artwork.gfx_first, 0);
4091 dumpTreeInfo(artwork.snd_first, 0);
4092 dumpTreeInfo(artwork.mus_first, 0);
4095 print_timestamp_done("LoadLevelArtworkInfo");
4098 static boolean AddTreeSetToTreeInfoExt(TreeInfo *tree_node_old, char *tree_dir,
4099 char *tree_subdir_new, int type)
4101 if (tree_node_old == NULL)
4103 if (type == TREE_TYPE_LEVEL_DIR)
4105 // get level info tree node of personal user level set
4106 tree_node_old = getTreeInfoFromIdentifier(leveldir_first, getLoginName());
4108 // this may happen if "setup.internal.create_user_levelset" is FALSE
4109 // or if file "levelinfo.conf" is missing in personal user level set
4110 if (tree_node_old == NULL)
4111 tree_node_old = leveldir_first->node_group;
4115 // get artwork info tree node of first artwork set
4116 tree_node_old = ARTWORK_FIRST_NODE(artwork, type);
4120 if (tree_dir == NULL)
4121 tree_dir = TREE_USERDIR(type);
4123 if (tree_node_old == NULL ||
4125 tree_subdir_new == NULL) // should not happen
4128 int draw_deactivation_mask = GetDrawDeactivationMask();
4130 // override draw deactivation mask (temporarily disable drawing)
4131 SetDrawDeactivationMask(REDRAW_ALL);
4133 if (type == TREE_TYPE_LEVEL_DIR)
4135 // load new level set config and add it next to first user level set
4136 LoadLevelInfoFromLevelConf(&tree_node_old->next,
4137 tree_node_old->node_parent,
4138 tree_dir, tree_subdir_new);
4142 // load new artwork set config and add it next to first artwork set
4143 LoadArtworkInfoFromArtworkConf(&tree_node_old->next,
4144 tree_node_old->node_parent,
4145 tree_dir, tree_subdir_new, type);
4148 // set draw deactivation mask to previous value
4149 SetDrawDeactivationMask(draw_deactivation_mask);
4151 // get first node of level or artwork info tree
4152 TreeInfo **tree_node_first = TREE_FIRST_NODE_PTR(type);
4154 // get tree info node of newly added level or artwork set
4155 TreeInfo *tree_node_new = getTreeInfoFromIdentifier(*tree_node_first,
4158 if (tree_node_new == NULL) // should not happen
4161 // correct top link and parent node link of newly created tree node
4162 tree_node_new->node_top = tree_node_old->node_top;
4163 tree_node_new->node_parent = tree_node_old->node_parent;
4165 // sort tree info to adjust position of newly added tree set
4166 sortTreeInfo(tree_node_first);
4171 void AddTreeSetToTreeInfo(TreeInfo *tree_node, char *tree_dir,
4172 char *tree_subdir_new, int type)
4174 if (!AddTreeSetToTreeInfoExt(tree_node, tree_dir, tree_subdir_new, type))
4175 Fail("internal tree info structure corrupted -- aborting");
4178 void AddUserLevelSetToLevelInfo(char *level_subdir_new)
4180 AddTreeSetToTreeInfo(NULL, NULL, level_subdir_new, TREE_TYPE_LEVEL_DIR);
4183 char *getArtworkIdentifierForUserLevelSet(int type)
4185 char *classic_artwork_set = getClassicArtworkSet(type);
4187 // check for custom artwork configured in "levelinfo.conf"
4188 char *leveldir_artwork_set =
4189 *LEVELDIR_ARTWORK_SET_PTR(leveldir_current, type);
4190 boolean has_leveldir_artwork_set =
4191 (leveldir_artwork_set != NULL && !strEqual(leveldir_artwork_set,
4192 classic_artwork_set));
4194 // check for custom artwork in sub-directory "graphics" etc.
4195 TreeInfo *artwork_first_node = ARTWORK_FIRST_NODE(artwork, type);
4196 char *leveldir_identifier = leveldir_current->identifier;
4197 boolean has_artwork_subdir =
4198 (getTreeInfoFromIdentifier(artwork_first_node,
4199 leveldir_identifier) != NULL);
4201 return (has_leveldir_artwork_set ? leveldir_artwork_set :
4202 has_artwork_subdir ? leveldir_identifier :
4203 classic_artwork_set);
4206 TreeInfo *getArtworkTreeInfoForUserLevelSet(int type)
4208 char *artwork_set = getArtworkIdentifierForUserLevelSet(type);
4209 TreeInfo *artwork_first_node = ARTWORK_FIRST_NODE(artwork, type);
4210 TreeInfo *ti = getTreeInfoFromIdentifier(artwork_first_node, artwork_set);
4214 ti = getTreeInfoFromIdentifier(artwork_first_node,
4215 ARTWORK_DEFAULT_SUBDIR(type));
4217 Fail("cannot find default graphics -- should not happen");
4223 boolean checkIfCustomArtworkExistsForCurrentLevelSet(void)
4225 char *graphics_set =
4226 getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_GRAPHICS);
4228 getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_SOUNDS);
4230 getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_MUSIC);
4232 return (!strEqual(graphics_set, GFX_CLASSIC_SUBDIR) ||
4233 !strEqual(sounds_set, SND_CLASSIC_SUBDIR) ||
4234 !strEqual(music_set, MUS_CLASSIC_SUBDIR));
4237 boolean UpdateUserLevelSet(char *level_subdir, char *level_name,
4238 char *level_author, int num_levels)
4240 char *filename = getPath2(getUserLevelDir(level_subdir), LEVELINFO_FILENAME);
4241 char *filename_tmp = getStringCat2(filename, ".tmp");
4243 FILE *file_tmp = NULL;
4244 char line[MAX_LINE_LEN];
4245 boolean success = FALSE;
4246 LevelDirTree *leveldir = getTreeInfoFromIdentifier(leveldir_first,
4248 // update values in level directory tree
4250 if (level_name != NULL)
4251 setString(&leveldir->name, level_name);
4253 if (level_author != NULL)
4254 setString(&leveldir->author, level_author);
4256 if (num_levels != -1)
4257 leveldir->levels = num_levels;
4259 // update values that depend on other values
4261 setString(&leveldir->name_sorting, leveldir->name);
4263 leveldir->last_level = leveldir->first_level + leveldir->levels - 1;
4265 // sort order of level sets may have changed
4266 sortTreeInfo(&leveldir_first);
4268 if ((file = fopen(filename, MODE_READ)) &&
4269 (file_tmp = fopen(filename_tmp, MODE_WRITE)))
4271 while (fgets(line, MAX_LINE_LEN, file))
4273 if (strPrefix(line, "name:") && level_name != NULL)
4274 fprintf(file_tmp, "%-32s%s\n", "name:", level_name);
4275 else if (strPrefix(line, "author:") && level_author != NULL)
4276 fprintf(file_tmp, "%-32s%s\n", "author:", level_author);
4277 else if (strPrefix(line, "levels:") && num_levels != -1)
4278 fprintf(file_tmp, "%-32s%d\n", "levels:", num_levels);
4280 fputs(line, file_tmp);
4293 success = (rename(filename_tmp, filename) == 0);
4301 boolean CreateUserLevelSet(char *level_subdir, char *level_name,
4302 char *level_author, int num_levels,
4303 boolean use_artwork_set)
4305 LevelDirTree *level_info;
4310 // create user level sub-directory, if needed
4311 createDirectory(getUserLevelDir(level_subdir), "user level", PERMS_PRIVATE);
4313 filename = getPath2(getUserLevelDir(level_subdir), LEVELINFO_FILENAME);
4315 if (!(file = fopen(filename, MODE_WRITE)))
4317 Warn("cannot write level info file '%s'", filename);
4324 level_info = newTreeInfo();
4326 // always start with reliable default values
4327 setTreeInfoToDefaults(level_info, TREE_TYPE_LEVEL_DIR);
4329 setString(&level_info->name, level_name);
4330 setString(&level_info->author, level_author);
4331 level_info->levels = num_levels;
4332 level_info->first_level = 1;
4333 level_info->sort_priority = LEVELCLASS_PRIVATE_START;
4334 level_info->readonly = FALSE;
4336 if (use_artwork_set)
4338 level_info->graphics_set =
4339 getStringCopy(getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_GRAPHICS));
4340 level_info->sounds_set =
4341 getStringCopy(getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_SOUNDS));
4342 level_info->music_set =
4343 getStringCopy(getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_MUSIC));
4346 token_value_position = TOKEN_VALUE_POSITION_SHORT;
4348 fprintFileHeader(file, LEVELINFO_FILENAME);
4351 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
4353 if (i == LEVELINFO_TOKEN_NAME ||
4354 i == LEVELINFO_TOKEN_AUTHOR ||
4355 i == LEVELINFO_TOKEN_LEVELS ||
4356 i == LEVELINFO_TOKEN_FIRST_LEVEL ||
4357 i == LEVELINFO_TOKEN_SORT_PRIORITY ||
4358 i == LEVELINFO_TOKEN_READONLY ||
4359 (use_artwork_set && (i == LEVELINFO_TOKEN_GRAPHICS_SET ||
4360 i == LEVELINFO_TOKEN_SOUNDS_SET ||
4361 i == LEVELINFO_TOKEN_MUSIC_SET)))
4362 fprintf(file, "%s\n", getSetupLine(levelinfo_tokens, "", i));
4364 // just to make things nicer :)
4365 if (i == LEVELINFO_TOKEN_AUTHOR ||
4366 i == LEVELINFO_TOKEN_FIRST_LEVEL ||
4367 (use_artwork_set && i == LEVELINFO_TOKEN_READONLY))
4368 fprintf(file, "\n");
4371 token_value_position = TOKEN_VALUE_POSITION_DEFAULT;
4375 SetFilePermissions(filename, PERMS_PRIVATE);
4377 freeTreeInfo(level_info);
4383 static void SaveUserLevelInfo(void)
4385 CreateUserLevelSet(getLoginName(), getLoginName(), getRealName(), 100, FALSE);
4388 char *getSetupValue(int type, void *value)
4390 static char value_string[MAX_LINE_LEN];
4398 strcpy(value_string, (*(boolean *)value ? "true" : "false"));
4402 strcpy(value_string, (*(boolean *)value ? "on" : "off"));
4406 strcpy(value_string, (*(int *)value == AUTO ? "auto" :
4407 *(int *)value == FALSE ? "off" : "on"));
4411 strcpy(value_string, (*(boolean *)value ? "yes" : "no"));
4414 case TYPE_YES_NO_AUTO:
4415 strcpy(value_string, (*(int *)value == AUTO ? "auto" :
4416 *(int *)value == FALSE ? "no" : "yes"));
4420 strcpy(value_string, (*(boolean *)value ? "AGA" : "ECS"));
4424 strcpy(value_string, getKeyNameFromKey(*(Key *)value));
4428 strcpy(value_string, getX11KeyNameFromKey(*(Key *)value));
4432 sprintf(value_string, "%d", *(int *)value);
4436 if (*(char **)value == NULL)
4439 strcpy(value_string, *(char **)value);
4443 sprintf(value_string, "player_%d", *(int *)value + 1);
4447 value_string[0] = '\0';
4451 if (type & TYPE_GHOSTED)
4452 strcpy(value_string, "n/a");
4454 return value_string;
4457 char *getSetupLine(struct TokenInfo *token_info, char *prefix, int token_nr)
4461 static char token_string[MAX_LINE_LEN];
4462 int token_type = token_info[token_nr].type;
4463 void *setup_value = token_info[token_nr].value;
4464 char *token_text = token_info[token_nr].text;
4465 char *value_string = getSetupValue(token_type, setup_value);
4467 // build complete token string
4468 sprintf(token_string, "%s%s", prefix, token_text);
4470 // build setup entry line
4471 line = getFormattedSetupEntry(token_string, value_string);
4473 if (token_type == TYPE_KEY_X11)
4475 Key key = *(Key *)setup_value;
4476 char *keyname = getKeyNameFromKey(key);
4478 // add comment, if useful
4479 if (!strEqual(keyname, "(undefined)") &&
4480 !strEqual(keyname, "(unknown)"))
4482 // add at least one whitespace
4484 for (i = strlen(line); i < token_comment_position; i++)
4488 strcat(line, keyname);
4495 static void InitLastPlayedLevels_ParentNode(void)
4497 LevelDirTree **leveldir_top = &leveldir_first->node_group->next;
4498 LevelDirTree *leveldir_new = NULL;
4500 // check if parent node for last played levels already exists
4501 if (strEqual((*leveldir_top)->identifier, TOKEN_STR_LAST_LEVEL_SERIES))
4504 leveldir_new = newTreeInfo();
4506 setTreeInfoToDefaultsFromParent(leveldir_new, leveldir_first);
4508 leveldir_new->level_group = TRUE;
4510 setString(&leveldir_new->identifier, TOKEN_STR_LAST_LEVEL_SERIES);
4511 setString(&leveldir_new->name, "<< (last played level sets)");
4513 pushTreeInfo(leveldir_top, leveldir_new);
4515 // create node to link back to current level directory
4516 createParentTreeInfoNode(leveldir_new);
4519 void UpdateLastPlayedLevels_TreeInfo(void)
4521 char **last_level_series = setup.level_setup.last_level_series;
4522 boolean reset_leveldir_current = FALSE;
4523 LevelDirTree *leveldir_last;
4524 TreeInfo **node_new = NULL;
4527 if (last_level_series[0] == NULL)
4530 InitLastPlayedLevels_ParentNode();
4532 // check if current level set is from "last played" sub-tree to be rebuilt
4533 reset_leveldir_current = strEqual(leveldir_current->node_parent->identifier,
4534 TOKEN_STR_LAST_LEVEL_SERIES);
4536 leveldir_last = getTreeInfoFromIdentifierExt(leveldir_first,
4537 TOKEN_STR_LAST_LEVEL_SERIES,
4539 if (leveldir_last == NULL)
4542 node_new = &leveldir_last->node_group->next;
4544 freeTreeInfo(*node_new);
4546 for (i = 0; last_level_series[i] != NULL; i++)
4548 LevelDirTree *node_last = getTreeInfoFromIdentifier(leveldir_first,
4549 last_level_series[i]);
4551 *node_new = getTreeInfoCopy(node_last); // copy complete node
4553 (*node_new)->node_top = &leveldir_first; // correct top node link
4554 (*node_new)->node_parent = leveldir_last; // correct parent node link
4556 (*node_new)->node_group = NULL;
4557 (*node_new)->next = NULL;
4559 (*node_new)->cl_first = -1; // force setting tree cursor
4561 node_new = &((*node_new)->next);
4564 if (reset_leveldir_current)
4565 leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
4566 last_level_series[0]);
4569 static void UpdateLastPlayedLevels_List(void)
4571 char **last_level_series = setup.level_setup.last_level_series;
4572 int pos = MAX_LEVELDIR_HISTORY - 1;
4575 // search for potentially already existing entry in list of level sets
4576 for (i = 0; last_level_series[i] != NULL; i++)
4577 if (strEqual(last_level_series[i], leveldir_current->identifier))
4580 // move list of level sets one entry down (using potentially free entry)
4581 for (i = pos; i > 0; i--)
4582 setString(&last_level_series[i], last_level_series[i - 1]);
4584 // put last played level set at top position
4585 setString(&last_level_series[0], leveldir_current->identifier);
4588 void LoadLevelSetup_LastSeries(void)
4590 // --------------------------------------------------------------------------
4591 // ~/.<program>/levelsetup.conf
4592 // --------------------------------------------------------------------------
4594 char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
4595 SetupFileHash *level_setup_hash = NULL;
4599 // always start with reliable default values
4600 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
4602 // start with empty history of last played level sets
4603 setString(&setup.level_setup.last_level_series[0], NULL);
4605 if (!strEqual(DEFAULT_LEVELSET, UNDEFINED_LEVELSET))
4607 leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
4609 if (leveldir_current == NULL)
4610 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
4613 if ((level_setup_hash = loadSetupFileHash(filename)))
4615 char *last_level_series =
4616 getHashEntry(level_setup_hash, TOKEN_STR_LAST_LEVEL_SERIES);
4618 leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
4620 if (leveldir_current == NULL)
4621 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
4623 for (i = 0; i < MAX_LEVELDIR_HISTORY; i++)
4625 char token[strlen(TOKEN_STR_LAST_LEVEL_SERIES) + 10];
4626 LevelDirTree *leveldir_last;
4628 sprintf(token, "%s.%03d", TOKEN_STR_LAST_LEVEL_SERIES, i);
4630 last_level_series = getHashEntry(level_setup_hash, token);
4632 leveldir_last = getTreeInfoFromIdentifier(leveldir_first,
4634 if (leveldir_last != NULL)
4635 setString(&setup.level_setup.last_level_series[pos++],
4639 setString(&setup.level_setup.last_level_series[pos], NULL);
4641 freeSetupFileHash(level_setup_hash);
4645 Debug("setup", "using default setup values");
4651 static void SaveLevelSetup_LastSeries_Ext(boolean deactivate_last_level_series)
4653 // --------------------------------------------------------------------------
4654 // ~/.<program>/levelsetup.conf
4655 // --------------------------------------------------------------------------
4657 // check if the current level directory structure is available at this point
4658 if (leveldir_current == NULL)
4661 char **last_level_series = setup.level_setup.last_level_series;
4662 char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
4666 InitUserDataDirectory();
4668 UpdateLastPlayedLevels_List();
4670 if (!(file = fopen(filename, MODE_WRITE)))
4672 Warn("cannot write setup file '%s'", filename);
4679 fprintFileHeader(file, LEVELSETUP_FILENAME);
4681 if (deactivate_last_level_series)
4682 fprintf(file, "# %s\n# ", "the following level set may have caused a problem and was deactivated");
4684 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_LAST_LEVEL_SERIES,
4685 leveldir_current->identifier));
4687 for (i = 0; last_level_series[i] != NULL; i++)
4689 char token[strlen(TOKEN_STR_LAST_LEVEL_SERIES) + 10];
4691 sprintf(token, "%s.%03d", TOKEN_STR_LAST_LEVEL_SERIES, i);
4693 fprintf(file, "%s\n", getFormattedSetupEntry(token, last_level_series[i]));
4698 SetFilePermissions(filename, PERMS_PRIVATE);
4703 void SaveLevelSetup_LastSeries(void)
4705 SaveLevelSetup_LastSeries_Ext(FALSE);
4708 void SaveLevelSetup_LastSeries_Deactivate(void)
4710 SaveLevelSetup_LastSeries_Ext(TRUE);
4713 static void checkSeriesInfo(void)
4715 static char *level_directory = NULL;
4718 DirectoryEntry *dir_entry;
4721 checked_free(level_directory);
4723 // check for more levels besides the 'levels' field of 'levelinfo.conf'
4725 level_directory = getPath2((leveldir_current->in_user_dir ?
4726 getUserLevelDir(NULL) :
4727 options.level_directory),
4728 leveldir_current->fullpath);
4730 if ((dir = openDirectory(level_directory)) == NULL)
4732 Warn("cannot read level directory '%s'", level_directory);
4738 while ((dir_entry = readDirectory(dir)) != NULL) // loop all entries
4740 if (strlen(dir_entry->basename) > 4 &&
4741 dir_entry->basename[3] == '.' &&
4742 strEqual(&dir_entry->basename[4], LEVELFILE_EXTENSION))
4744 char levelnum_str[4];
4747 strncpy(levelnum_str, dir_entry->basename, 3);
4748 levelnum_str[3] = '\0';
4750 levelnum_value = atoi(levelnum_str);
4752 if (levelnum_value < leveldir_current->first_level)
4754 Warn("additional level %d found", levelnum_value);
4756 leveldir_current->first_level = levelnum_value;
4758 else if (levelnum_value > leveldir_current->last_level)
4760 Warn("additional level %d found", levelnum_value);
4762 leveldir_current->last_level = levelnum_value;
4768 closeDirectory(dir);
4771 void LoadLevelSetup_SeriesInfo(void)
4774 SetupFileHash *level_setup_hash = NULL;
4775 char *level_subdir = leveldir_current->subdir;
4778 // always start with reliable default values
4779 level_nr = leveldir_current->first_level;
4781 for (i = 0; i < MAX_LEVELS; i++)
4783 LevelStats_setPlayed(i, 0);
4784 LevelStats_setSolved(i, 0);
4789 // --------------------------------------------------------------------------
4790 // ~/.<program>/levelsetup/<level series>/levelsetup.conf
4791 // --------------------------------------------------------------------------
4793 level_subdir = leveldir_current->subdir;
4795 filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
4797 if ((level_setup_hash = loadSetupFileHash(filename)))
4801 // get last played level in this level set
4803 token_value = getHashEntry(level_setup_hash, TOKEN_STR_LAST_PLAYED_LEVEL);
4807 level_nr = atoi(token_value);
4809 if (level_nr < leveldir_current->first_level)
4810 level_nr = leveldir_current->first_level;
4811 if (level_nr > leveldir_current->last_level)
4812 level_nr = leveldir_current->last_level;
4815 // get handicap level in this level set
4817 token_value = getHashEntry(level_setup_hash, TOKEN_STR_HANDICAP_LEVEL);
4821 int level_nr = atoi(token_value);
4823 if (level_nr < leveldir_current->first_level)
4824 level_nr = leveldir_current->first_level;
4825 if (level_nr > leveldir_current->last_level + 1)
4826 level_nr = leveldir_current->last_level;
4828 if (leveldir_current->user_defined || !leveldir_current->handicap)
4829 level_nr = leveldir_current->last_level;
4831 leveldir_current->handicap_level = level_nr;
4834 // get number of played and solved levels in this level set
4836 BEGIN_HASH_ITERATION(level_setup_hash, itr)
4838 char *token = HASH_ITERATION_TOKEN(itr);
4839 char *value = HASH_ITERATION_VALUE(itr);
4841 if (strlen(token) == 3 &&
4842 token[0] >= '0' && token[0] <= '9' &&
4843 token[1] >= '0' && token[1] <= '9' &&
4844 token[2] >= '0' && token[2] <= '9')
4846 int level_nr = atoi(token);
4849 LevelStats_setPlayed(level_nr, atoi(value)); // read 1st column
4851 value = strchr(value, ' ');
4854 LevelStats_setSolved(level_nr, atoi(value)); // read 2nd column
4857 END_HASH_ITERATION(hash, itr)
4859 freeSetupFileHash(level_setup_hash);
4863 Debug("setup", "using default setup values");
4869 void SaveLevelSetup_SeriesInfo(void)
4872 char *level_subdir = leveldir_current->subdir;
4873 char *level_nr_str = int2str(level_nr, 0);
4874 char *handicap_level_str = int2str(leveldir_current->handicap_level, 0);
4878 // --------------------------------------------------------------------------
4879 // ~/.<program>/levelsetup/<level series>/levelsetup.conf
4880 // --------------------------------------------------------------------------
4882 InitLevelSetupDirectory(level_subdir);
4884 filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
4886 if (!(file = fopen(filename, MODE_WRITE)))
4888 Warn("cannot write setup file '%s'", filename);
4895 fprintFileHeader(file, LEVELSETUP_FILENAME);
4897 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_PLAYED_LEVEL,
4899 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_HANDICAP_LEVEL,
4900 handicap_level_str));
4902 for (i = leveldir_current->first_level; i <= leveldir_current->last_level;
4905 if (LevelStats_getPlayed(i) > 0 ||
4906 LevelStats_getSolved(i) > 0)
4911 sprintf(token, "%03d", i);
4912 sprintf(value, "%d %d", LevelStats_getPlayed(i), LevelStats_getSolved(i));
4914 fprintf(file, "%s\n", getFormattedSetupEntry(token, value));
4920 SetFilePermissions(filename, PERMS_PRIVATE);
4925 int LevelStats_getPlayed(int nr)
4927 return (nr >= 0 && nr < MAX_LEVELS ? level_stats[nr].played : 0);
4930 int LevelStats_getSolved(int nr)
4932 return (nr >= 0 && nr < MAX_LEVELS ? level_stats[nr].solved : 0);
4935 void LevelStats_setPlayed(int nr, int value)
4937 if (nr >= 0 && nr < MAX_LEVELS)
4938 level_stats[nr].played = value;
4941 void LevelStats_setSolved(int nr, int value)
4943 if (nr >= 0 && nr < MAX_LEVELS)
4944 level_stats[nr].solved = value;
4947 void LevelStats_incPlayed(int nr)
4949 if (nr >= 0 && nr < MAX_LEVELS)
4950 level_stats[nr].played++;
4953 void LevelStats_incSolved(int nr)
4955 if (nr >= 0 && nr < MAX_LEVELS)
4956 level_stats[nr].solved++;
4959 void LoadUserSetup(void)
4961 // --------------------------------------------------------------------------
4962 // ~/.<program>/usersetup.conf
4963 // --------------------------------------------------------------------------
4965 char *filename = getPath2(getMainUserGameDataDir(), USERSETUP_FILENAME);
4966 SetupFileHash *user_setup_hash = NULL;
4968 // always start with reliable default values
4971 if ((user_setup_hash = loadSetupFileHash(filename)))
4975 // get last selected user number
4976 token_value = getHashEntry(user_setup_hash, TOKEN_STR_LAST_USER);
4979 user.nr = MIN(MAX(0, atoi(token_value)), MAX_PLAYER_NAMES - 1);
4981 freeSetupFileHash(user_setup_hash);
4985 Debug("setup", "using default setup values");
4991 void SaveUserSetup(void)
4993 // --------------------------------------------------------------------------
4994 // ~/.<program>/usersetup.conf
4995 // --------------------------------------------------------------------------
4997 char *filename = getPath2(getMainUserGameDataDir(), USERSETUP_FILENAME);
5000 InitMainUserDataDirectory();
5002 if (!(file = fopen(filename, MODE_WRITE)))
5004 Warn("cannot write setup file '%s'", filename);
5011 fprintFileHeader(file, USERSETUP_FILENAME);
5013 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_USER,
5017 SetFilePermissions(filename, PERMS_PRIVATE);