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 // in a number of cases, tree node is no valid level set
1158 if (node == NULL || node->node_group || node->parent_link || node->is_copy)
1164 TreeInfo *getValidLevelSeries(TreeInfo *node, TreeInfo *default_node)
1166 if (validLevelSeries(node))
1168 else if (node->is_copy)
1169 return getTreeInfoFromIdentifier(leveldir_first, node->identifier);
1171 return getFirstValidTreeInfoEntry(default_node);
1174 TreeInfo *getFirstValidTreeInfoEntry(TreeInfo *node)
1179 if (node->node_group) // enter level group (step down into tree)
1180 return getFirstValidTreeInfoEntry(node->node_group);
1181 else if (node->parent_link) // skip start entry of level group
1183 if (node->next) // get first real level series entry
1184 return getFirstValidTreeInfoEntry(node->next);
1185 else // leave empty level group and go on
1186 return getFirstValidTreeInfoEntry(node->node_parent->next);
1188 else // this seems to be a regular level series
1192 TreeInfo *getTreeInfoFirstGroupEntry(TreeInfo *node)
1197 if (node->node_parent == NULL) // top level group
1198 return *node->node_top;
1199 else // sub level group
1200 return node->node_parent->node_group;
1203 int numTreeInfoInGroup(TreeInfo *node)
1205 return numTreeInfo(getTreeInfoFirstGroupEntry(node));
1208 int getPosFromTreeInfo(TreeInfo *node)
1210 TreeInfo *node_cmp = getTreeInfoFirstGroupEntry(node);
1215 if (node_cmp == node)
1219 node_cmp = node_cmp->next;
1225 TreeInfo *getTreeInfoFromPos(TreeInfo *node, int pos)
1227 TreeInfo *node_default = node;
1239 return node_default;
1242 static TreeInfo *getTreeInfoFromIdentifierExt(TreeInfo *node, char *identifier,
1243 boolean include_node_groups)
1245 if (identifier == NULL)
1250 if (node->node_group)
1252 if (include_node_groups && strEqual(identifier, node->identifier))
1255 TreeInfo *node_group = getTreeInfoFromIdentifierExt(node->node_group,
1257 include_node_groups);
1261 else if (!node->parent_link && !node->is_copy)
1263 if (strEqual(identifier, node->identifier))
1273 TreeInfo *getTreeInfoFromIdentifier(TreeInfo *node, char *identifier)
1275 return getTreeInfoFromIdentifierExt(node, identifier, FALSE);
1278 static TreeInfo *cloneTreeNode(TreeInfo **node_top, TreeInfo *node_parent,
1279 TreeInfo *node, boolean skip_sets_without_levels)
1286 if (!node->parent_link && !node->level_group &&
1287 skip_sets_without_levels && node->levels == 0)
1288 return cloneTreeNode(node_top, node_parent, node->next,
1289 skip_sets_without_levels);
1291 node_new = getTreeInfoCopy(node); // copy complete node
1293 node_new->node_top = node_top; // correct top node link
1294 node_new->node_parent = node_parent; // correct parent node link
1296 if (node->level_group)
1297 node_new->node_group = cloneTreeNode(node_top, node_new, node->node_group,
1298 skip_sets_without_levels);
1300 node_new->next = cloneTreeNode(node_top, node_parent, node->next,
1301 skip_sets_without_levels);
1306 static void cloneTree(TreeInfo **ti_new, TreeInfo *ti, boolean skip_empty_sets)
1308 TreeInfo *ti_cloned = cloneTreeNode(ti_new, NULL, ti, skip_empty_sets);
1310 *ti_new = ti_cloned;
1313 static boolean adjustTreeGraphicsForEMC(TreeInfo *node)
1315 boolean settings_changed = FALSE;
1319 boolean want_ecs = (setup.prefer_aga_graphics == FALSE);
1320 boolean want_aga = (setup.prefer_aga_graphics == TRUE);
1321 boolean has_only_ecs = (!node->graphics_set && !node->graphics_set_aga);
1322 boolean has_only_aga = (!node->graphics_set && !node->graphics_set_ecs);
1323 char *graphics_set = NULL;
1325 if (node->graphics_set_ecs && (want_ecs || has_only_ecs))
1326 graphics_set = node->graphics_set_ecs;
1328 if (node->graphics_set_aga && (want_aga || has_only_aga))
1329 graphics_set = node->graphics_set_aga;
1331 if (graphics_set && !strEqual(node->graphics_set, graphics_set))
1333 setString(&node->graphics_set, graphics_set);
1334 settings_changed = TRUE;
1337 if (node->node_group != NULL)
1338 settings_changed |= adjustTreeGraphicsForEMC(node->node_group);
1343 return settings_changed;
1346 static boolean adjustTreeSoundsForEMC(TreeInfo *node)
1348 boolean settings_changed = FALSE;
1352 boolean want_default = (setup.prefer_lowpass_sounds == FALSE);
1353 boolean want_lowpass = (setup.prefer_lowpass_sounds == TRUE);
1354 boolean has_only_default = (!node->sounds_set && !node->sounds_set_lowpass);
1355 boolean has_only_lowpass = (!node->sounds_set && !node->sounds_set_default);
1356 char *sounds_set = NULL;
1358 if (node->sounds_set_default && (want_default || has_only_default))
1359 sounds_set = node->sounds_set_default;
1361 if (node->sounds_set_lowpass && (want_lowpass || has_only_lowpass))
1362 sounds_set = node->sounds_set_lowpass;
1364 if (sounds_set && !strEqual(node->sounds_set, sounds_set))
1366 setString(&node->sounds_set, sounds_set);
1367 settings_changed = TRUE;
1370 if (node->node_group != NULL)
1371 settings_changed |= adjustTreeSoundsForEMC(node->node_group);
1376 return settings_changed;
1379 void dumpTreeInfo(TreeInfo *node, int depth)
1381 char bullet_list[] = { '-', '*', 'o' };
1385 Debug("tree", "Dumping TreeInfo:");
1389 char bullet = bullet_list[depth % ARRAY_SIZE(bullet_list)];
1391 for (i = 0; i < depth * 2; i++)
1392 DebugContinued("", " ");
1394 DebugContinued("tree", "%c '%s' ['%s] [PARENT: '%s'] %s\n",
1395 bullet, node->name, node->identifier,
1396 (node->node_parent ? node->node_parent->identifier : "-"),
1397 (node->node_group ? "[GROUP]" : ""));
1400 // use for dumping artwork info tree
1401 Debug("tree", "subdir == '%s' ['%s', '%s'] [%d])",
1402 node->subdir, node->fullpath, node->basepath, node->in_user_dir);
1405 if (node->node_group != NULL)
1406 dumpTreeInfo(node->node_group, depth + 1);
1412 void sortTreeInfoBySortFunction(TreeInfo **node_first,
1413 int (*compare_function)(const void *,
1416 int num_nodes = numTreeInfo(*node_first);
1417 TreeInfo **sort_array;
1418 TreeInfo *node = *node_first;
1424 // allocate array for sorting structure pointers
1425 sort_array = checked_calloc(num_nodes * sizeof(TreeInfo *));
1427 // writing structure pointers to sorting array
1428 while (i < num_nodes && node) // double boundary check...
1430 sort_array[i] = node;
1436 // sorting the structure pointers in the sorting array
1437 qsort(sort_array, num_nodes, sizeof(TreeInfo *),
1440 // update the linkage of list elements with the sorted node array
1441 for (i = 0; i < num_nodes - 1; i++)
1442 sort_array[i]->next = sort_array[i + 1];
1443 sort_array[num_nodes - 1]->next = NULL;
1445 // update the linkage of the main list anchor pointer
1446 *node_first = sort_array[0];
1450 // now recursively sort the level group structures
1454 if (node->node_group != NULL)
1455 sortTreeInfoBySortFunction(&node->node_group, compare_function);
1461 void sortTreeInfo(TreeInfo **node_first)
1463 sortTreeInfoBySortFunction(node_first, compareTreeInfoEntries);
1467 // ============================================================================
1468 // some stuff from "files.c"
1469 // ============================================================================
1471 #if defined(PLATFORM_WIN32)
1473 #define S_IRGRP S_IRUSR
1476 #define S_IROTH S_IRUSR
1479 #define S_IWGRP S_IWUSR
1482 #define S_IWOTH S_IWUSR
1485 #define S_IXGRP S_IXUSR
1488 #define S_IXOTH S_IXUSR
1491 #define S_IRWXG (S_IRGRP | S_IWGRP | S_IXGRP)
1496 #endif // PLATFORM_WIN32
1498 // file permissions for newly written files
1499 #define MODE_R_ALL (S_IRUSR | S_IRGRP | S_IROTH)
1500 #define MODE_W_ALL (S_IWUSR | S_IWGRP | S_IWOTH)
1501 #define MODE_X_ALL (S_IXUSR | S_IXGRP | S_IXOTH)
1503 #define MODE_W_PRIVATE (S_IWUSR)
1504 #define MODE_W_PUBLIC_FILE (S_IWUSR | S_IWGRP)
1505 #define MODE_W_PUBLIC_DIR (S_IWUSR | S_IWGRP | S_ISGID)
1507 #define DIR_PERMS_PRIVATE (MODE_R_ALL | MODE_X_ALL | MODE_W_PRIVATE)
1508 #define DIR_PERMS_PUBLIC (MODE_R_ALL | MODE_X_ALL | MODE_W_PUBLIC_DIR)
1509 #define DIR_PERMS_PUBLIC_ALL (MODE_R_ALL | MODE_X_ALL | MODE_W_ALL)
1511 #define FILE_PERMS_PRIVATE (MODE_R_ALL | MODE_W_PRIVATE)
1512 #define FILE_PERMS_PUBLIC (MODE_R_ALL | MODE_W_PUBLIC_FILE)
1513 #define FILE_PERMS_PUBLIC_ALL (MODE_R_ALL | MODE_W_ALL)
1516 char *getHomeDir(void)
1518 static char *dir = NULL;
1520 #if defined(PLATFORM_WIN32)
1523 dir = checked_malloc(MAX_PATH + 1);
1525 if (!SUCCEEDED(SHGetFolderPath(NULL, CSIDL_PERSONAL, NULL, 0, dir)))
1528 #elif defined(PLATFORM_EMSCRIPTEN)
1529 dir = "/persistent";
1530 #elif defined(PLATFORM_UNIX)
1533 if ((dir = getenv("HOME")) == NULL)
1535 dir = getUnixHomeDir();
1538 dir = getStringCopy(dir);
1550 char *getCommonDataDir(void)
1552 static char *common_data_dir = NULL;
1554 #if defined(PLATFORM_WIN32)
1555 if (common_data_dir == NULL)
1557 char *dir = checked_malloc(MAX_PATH + 1);
1559 if (SUCCEEDED(SHGetFolderPath(NULL, CSIDL_COMMON_DOCUMENTS, NULL, 0, dir))
1560 && !strEqual(dir, "")) // empty for Windows 95/98
1561 common_data_dir = getPath2(dir, program.userdata_subdir);
1563 common_data_dir = options.rw_base_directory;
1566 if (common_data_dir == NULL)
1567 common_data_dir = options.rw_base_directory;
1570 return common_data_dir;
1573 char *getPersonalDataDir(void)
1575 static char *personal_data_dir = NULL;
1577 #if defined(PLATFORM_MACOSX)
1578 if (personal_data_dir == NULL)
1579 personal_data_dir = getPath2(getHomeDir(), "Documents");
1581 if (personal_data_dir == NULL)
1582 personal_data_dir = getHomeDir();
1585 return personal_data_dir;
1588 char *getMainUserGameDataDir(void)
1590 static char *main_user_data_dir = NULL;
1592 #if defined(PLATFORM_ANDROID)
1593 if (main_user_data_dir == NULL)
1594 main_user_data_dir = (char *)(SDL_AndroidGetExternalStorageState() &
1595 SDL_ANDROID_EXTERNAL_STORAGE_WRITE ?
1596 SDL_AndroidGetExternalStoragePath() :
1597 SDL_AndroidGetInternalStoragePath());
1599 if (main_user_data_dir == NULL)
1600 main_user_data_dir = getPath2(getPersonalDataDir(),
1601 program.userdata_subdir);
1604 return main_user_data_dir;
1607 char *getUserGameDataDir(void)
1610 return getMainUserGameDataDir();
1612 return getUserDir(user.nr);
1615 char *getSetupDir(void)
1617 return getUserGameDataDir();
1620 static mode_t posix_umask(mode_t mask)
1622 #if defined(PLATFORM_UNIX)
1629 static int posix_mkdir(const char *pathname, mode_t mode)
1631 #if defined(PLATFORM_WIN32)
1632 return mkdir(pathname);
1634 return mkdir(pathname, mode);
1638 static boolean posix_process_running_setgid(void)
1640 #if defined(PLATFORM_UNIX)
1641 return (getgid() != getegid());
1647 void createDirectory(char *dir, char *text, int permission_class)
1649 if (directoryExists(dir))
1652 // leave "other" permissions in umask untouched, but ensure group parts
1653 // of USERDATA_DIR_MODE are not masked
1654 mode_t dir_mode = (permission_class == PERMS_PRIVATE ?
1655 DIR_PERMS_PRIVATE : DIR_PERMS_PUBLIC);
1656 mode_t last_umask = posix_umask(0);
1657 mode_t group_umask = ~(dir_mode & S_IRWXG);
1658 int running_setgid = posix_process_running_setgid();
1660 if (permission_class == PERMS_PUBLIC)
1662 // if we're setgid, protect files against "other"
1663 // else keep umask(0) to make the dir world-writable
1666 posix_umask(last_umask & group_umask);
1668 dir_mode = DIR_PERMS_PUBLIC_ALL;
1671 if (posix_mkdir(dir, dir_mode) != 0)
1672 Warn("cannot create %s directory '%s': %s", text, dir, strerror(errno));
1674 if (permission_class == PERMS_PUBLIC && !running_setgid)
1675 chmod(dir, dir_mode);
1677 posix_umask(last_umask); // restore previous umask
1680 void InitMainUserDataDirectory(void)
1682 createDirectory(getMainUserGameDataDir(), "main user data", PERMS_PRIVATE);
1685 void InitUserDataDirectory(void)
1687 createDirectory(getMainUserGameDataDir(), "main user data", PERMS_PRIVATE);
1691 createDirectory(getUserDir(-1), "users", PERMS_PRIVATE);
1692 createDirectory(getUserDir(user.nr), "user data", PERMS_PRIVATE);
1696 void SetFilePermissions(char *filename, int permission_class)
1698 int running_setgid = posix_process_running_setgid();
1699 int perms = (permission_class == PERMS_PRIVATE ?
1700 FILE_PERMS_PRIVATE : FILE_PERMS_PUBLIC);
1702 if (permission_class == PERMS_PUBLIC && !running_setgid)
1703 perms = FILE_PERMS_PUBLIC_ALL;
1705 chmod(filename, perms);
1708 char *getCookie(char *file_type)
1710 static char cookie[MAX_COOKIE_LEN + 1];
1712 if (strlen(program.cookie_prefix) + 1 +
1713 strlen(file_type) + strlen("_FILE_VERSION_x.x") > MAX_COOKIE_LEN)
1714 return "[COOKIE ERROR]"; // should never happen
1716 sprintf(cookie, "%s_%s_FILE_VERSION_%d.%d",
1717 program.cookie_prefix, file_type,
1718 program.version_super, program.version_major);
1723 void fprintFileHeader(FILE *file, char *basename)
1725 char *prefix = "# ";
1728 fprintf_line_with_prefix(file, prefix, sep1, 77);
1729 fprintf(file, "%s%s\n", prefix, basename);
1730 fprintf_line_with_prefix(file, prefix, sep1, 77);
1731 fprintf(file, "\n");
1734 int getFileVersionFromCookieString(const char *cookie)
1736 const char *ptr_cookie1, *ptr_cookie2;
1737 const char *pattern1 = "_FILE_VERSION_";
1738 const char *pattern2 = "?.?";
1739 const int len_cookie = strlen(cookie);
1740 const int len_pattern1 = strlen(pattern1);
1741 const int len_pattern2 = strlen(pattern2);
1742 const int len_pattern = len_pattern1 + len_pattern2;
1743 int version_super, version_major;
1745 if (len_cookie <= len_pattern)
1748 ptr_cookie1 = &cookie[len_cookie - len_pattern];
1749 ptr_cookie2 = &cookie[len_cookie - len_pattern2];
1751 if (strncmp(ptr_cookie1, pattern1, len_pattern1) != 0)
1754 if (ptr_cookie2[0] < '0' || ptr_cookie2[0] > '9' ||
1755 ptr_cookie2[1] != '.' ||
1756 ptr_cookie2[2] < '0' || ptr_cookie2[2] > '9')
1759 version_super = ptr_cookie2[0] - '0';
1760 version_major = ptr_cookie2[2] - '0';
1762 return VERSION_IDENT(version_super, version_major, 0, 0);
1765 boolean checkCookieString(const char *cookie, const char *template)
1767 const char *pattern = "_FILE_VERSION_?.?";
1768 const int len_cookie = strlen(cookie);
1769 const int len_template = strlen(template);
1770 const int len_pattern = strlen(pattern);
1772 if (len_cookie != len_template)
1775 if (strncmp(cookie, template, len_cookie - len_pattern) != 0)
1782 // ----------------------------------------------------------------------------
1783 // setup file list and hash handling functions
1784 // ----------------------------------------------------------------------------
1786 char *getFormattedSetupEntry(char *token, char *value)
1789 static char entry[MAX_LINE_LEN];
1791 // if value is an empty string, just return token without value
1795 // start with the token and some spaces to format output line
1796 sprintf(entry, "%s:", token);
1797 for (i = strlen(entry); i < token_value_position; i++)
1800 // continue with the token's value
1801 strcat(entry, value);
1806 SetupFileList *newSetupFileList(char *token, char *value)
1808 SetupFileList *new = checked_malloc(sizeof(SetupFileList));
1810 new->token = getStringCopy(token);
1811 new->value = getStringCopy(value);
1818 void freeSetupFileList(SetupFileList *list)
1823 checked_free(list->token);
1824 checked_free(list->value);
1827 freeSetupFileList(list->next);
1832 char *getListEntry(SetupFileList *list, char *token)
1837 if (strEqual(list->token, token))
1840 return getListEntry(list->next, token);
1843 SetupFileList *setListEntry(SetupFileList *list, char *token, char *value)
1848 if (strEqual(list->token, token))
1850 checked_free(list->value);
1852 list->value = getStringCopy(value);
1856 else if (list->next == NULL)
1857 return (list->next = newSetupFileList(token, value));
1859 return setListEntry(list->next, token, value);
1862 SetupFileList *addListEntry(SetupFileList *list, char *token, char *value)
1867 if (list->next == NULL)
1868 return (list->next = newSetupFileList(token, value));
1870 return addListEntry(list->next, token, value);
1873 #if ENABLE_UNUSED_CODE
1875 static void printSetupFileList(SetupFileList *list)
1880 Debug("setup:printSetupFileList", "token: '%s'", list->token);
1881 Debug("setup:printSetupFileList", "value: '%s'", list->value);
1883 printSetupFileList(list->next);
1889 DEFINE_HASHTABLE_INSERT(insert_hash_entry, char, char);
1890 DEFINE_HASHTABLE_SEARCH(search_hash_entry, char, char);
1891 DEFINE_HASHTABLE_CHANGE(change_hash_entry, char, char);
1892 DEFINE_HASHTABLE_REMOVE(remove_hash_entry, char, char);
1894 #define insert_hash_entry hashtable_insert
1895 #define search_hash_entry hashtable_search
1896 #define change_hash_entry hashtable_change
1897 #define remove_hash_entry hashtable_remove
1900 unsigned int get_hash_from_key(void *key)
1905 This algorithm (k=33) was first reported by Dan Bernstein many years ago in
1906 'comp.lang.c'. Another version of this algorithm (now favored by Bernstein)
1907 uses XOR: hash(i) = hash(i - 1) * 33 ^ str[i]; the magic of number 33 (why
1908 it works better than many other constants, prime or not) has never been
1909 adequately explained.
1911 If you just want to have a good hash function, and cannot wait, djb2
1912 is one of the best string hash functions i know. It has excellent
1913 distribution and speed on many different sets of keys and table sizes.
1914 You are not likely to do better with one of the "well known" functions
1915 such as PJW, K&R, etc.
1917 Ozan (oz) Yigit [http://www.cs.yorku.ca/~oz/hash.html]
1920 char *str = (char *)key;
1921 unsigned int hash = 5381;
1924 while ((c = *str++))
1925 hash = ((hash << 5) + hash) + c; // hash * 33 + c
1930 static int keys_are_equal(void *key1, void *key2)
1932 return (strEqual((char *)key1, (char *)key2));
1935 SetupFileHash *newSetupFileHash(void)
1937 SetupFileHash *new_hash =
1938 create_hashtable(16, 0.75, get_hash_from_key, keys_are_equal);
1940 if (new_hash == NULL)
1941 Fail("create_hashtable() failed -- out of memory");
1946 void freeSetupFileHash(SetupFileHash *hash)
1951 hashtable_destroy(hash, 1); // 1 == also free values stored in hash
1954 char *getHashEntry(SetupFileHash *hash, char *token)
1959 return search_hash_entry(hash, token);
1962 void setHashEntry(SetupFileHash *hash, char *token, char *value)
1969 value_copy = getStringCopy(value);
1971 // change value; if it does not exist, insert it as new
1972 if (!change_hash_entry(hash, token, value_copy))
1973 if (!insert_hash_entry(hash, getStringCopy(token), value_copy))
1974 Fail("cannot insert into hash -- aborting");
1977 char *removeHashEntry(SetupFileHash *hash, char *token)
1982 return remove_hash_entry(hash, token);
1985 #if ENABLE_UNUSED_CODE
1987 static void printSetupFileHash(SetupFileHash *hash)
1989 BEGIN_HASH_ITERATION(hash, itr)
1991 Debug("setup:printSetupFileHash", "token: '%s'", HASH_ITERATION_TOKEN(itr));
1992 Debug("setup:printSetupFileHash", "value: '%s'", HASH_ITERATION_VALUE(itr));
1994 END_HASH_ITERATION(hash, itr)
1999 #define ALLOW_TOKEN_VALUE_SEPARATOR_BEING_WHITESPACE 1
2000 #define CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING 0
2001 #define CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH 0
2003 static boolean token_value_separator_found = FALSE;
2004 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2005 static boolean token_value_separator_warning = FALSE;
2007 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2008 static boolean token_already_exists_warning = FALSE;
2011 static boolean getTokenValueFromSetupLineExt(char *line,
2012 char **token_ptr, char **value_ptr,
2013 char *filename, char *line_raw,
2015 boolean separator_required)
2017 static char line_copy[MAX_LINE_LEN + 1], line_raw_copy[MAX_LINE_LEN + 1];
2018 char *token, *value, *line_ptr;
2020 // when externally invoked via ReadTokenValueFromLine(), copy line buffers
2021 if (line_raw == NULL)
2023 strncpy(line_copy, line, MAX_LINE_LEN);
2024 line_copy[MAX_LINE_LEN] = '\0';
2027 strcpy(line_raw_copy, line_copy);
2028 line_raw = line_raw_copy;
2031 // cut trailing comment from input line
2032 for (line_ptr = line; *line_ptr; line_ptr++)
2034 if (*line_ptr == '#')
2041 // cut trailing whitespaces from input line
2042 for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
2043 if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
2046 // ignore empty lines
2050 // cut leading whitespaces from token
2051 for (token = line; *token; token++)
2052 if (*token != ' ' && *token != '\t')
2055 // start with empty value as reliable default
2058 token_value_separator_found = FALSE;
2060 // find end of token to determine start of value
2061 for (line_ptr = token; *line_ptr; line_ptr++)
2063 // first look for an explicit token/value separator, like ':' or '='
2064 if (*line_ptr == ':' || *line_ptr == '=')
2066 *line_ptr = '\0'; // terminate token string
2067 value = line_ptr + 1; // set beginning of value
2069 token_value_separator_found = TRUE;
2075 #if ALLOW_TOKEN_VALUE_SEPARATOR_BEING_WHITESPACE
2076 // fallback: if no token/value separator found, also allow whitespaces
2077 if (!token_value_separator_found && !separator_required)
2079 for (line_ptr = token; *line_ptr; line_ptr++)
2081 if (*line_ptr == ' ' || *line_ptr == '\t')
2083 *line_ptr = '\0'; // terminate token string
2084 value = line_ptr + 1; // set beginning of value
2086 token_value_separator_found = TRUE;
2092 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2093 if (token_value_separator_found)
2095 if (!token_value_separator_warning)
2097 Debug("setup", "---");
2099 if (filename != NULL)
2101 Debug("setup", "missing token/value separator(s) in config file:");
2102 Debug("setup", "- config file: '%s'", filename);
2106 Debug("setup", "missing token/value separator(s):");
2109 token_value_separator_warning = TRUE;
2112 if (filename != NULL)
2113 Debug("setup", "- line %d: '%s'", line_nr, line_raw);
2115 Debug("setup", "- line: '%s'", line_raw);
2121 // cut trailing whitespaces from token
2122 for (line_ptr = &token[strlen(token)]; line_ptr >= token; line_ptr--)
2123 if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
2126 // cut leading whitespaces from value
2127 for (; *value; value++)
2128 if (*value != ' ' && *value != '\t')
2137 boolean getTokenValueFromSetupLine(char *line, char **token, char **value)
2139 // while the internal (old) interface does not require a token/value
2140 // separator (for downwards compatibility with existing files which
2141 // don't use them), it is mandatory for the external (new) interface
2143 return getTokenValueFromSetupLineExt(line, token, value, NULL, NULL, 0, TRUE);
2146 static boolean loadSetupFileData(void *setup_file_data, char *filename,
2147 boolean top_recursion_level, boolean is_hash)
2149 static SetupFileHash *include_filename_hash = NULL;
2150 char line[MAX_LINE_LEN], line_raw[MAX_LINE_LEN], previous_line[MAX_LINE_LEN];
2151 char *token, *value, *line_ptr;
2152 void *insert_ptr = NULL;
2153 boolean read_continued_line = FALSE;
2155 int line_nr = 0, token_count = 0, include_count = 0;
2157 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2158 token_value_separator_warning = FALSE;
2161 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2162 token_already_exists_warning = FALSE;
2165 if (!(file = openFile(filename, MODE_READ)))
2167 #if DEBUG_NO_CONFIG_FILE
2168 Debug("setup", "cannot open configuration file '%s'", filename);
2174 // use "insert pointer" to store list end for constant insertion complexity
2176 insert_ptr = setup_file_data;
2178 // on top invocation, create hash to mark included files (to prevent loops)
2179 if (top_recursion_level)
2180 include_filename_hash = newSetupFileHash();
2182 // mark this file as already included (to prevent including it again)
2183 setHashEntry(include_filename_hash, getBaseNamePtr(filename), "true");
2185 while (!checkEndOfFile(file))
2187 // read next line of input file
2188 if (!getStringFromFile(file, line, MAX_LINE_LEN))
2191 // check if line was completely read and is terminated by line break
2192 if (strlen(line) > 0 && line[strlen(line) - 1] == '\n')
2195 // cut trailing line break (this can be newline and/or carriage return)
2196 for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
2197 if ((*line_ptr == '\n' || *line_ptr == '\r') && *(line_ptr + 1) == '\0')
2200 // copy raw input line for later use (mainly debugging output)
2201 strcpy(line_raw, line);
2203 if (read_continued_line)
2205 // append new line to existing line, if there is enough space
2206 if (strlen(previous_line) + strlen(line_ptr) < MAX_LINE_LEN)
2207 strcat(previous_line, line_ptr);
2209 strcpy(line, previous_line); // copy storage buffer to line
2211 read_continued_line = FALSE;
2214 // if the last character is '\', continue at next line
2215 if (strlen(line) > 0 && line[strlen(line) - 1] == '\\')
2217 line[strlen(line) - 1] = '\0'; // cut off trailing backslash
2218 strcpy(previous_line, line); // copy line to storage buffer
2220 read_continued_line = TRUE;
2225 if (!getTokenValueFromSetupLineExt(line, &token, &value, filename,
2226 line_raw, line_nr, FALSE))
2231 if (strEqual(token, "include"))
2233 if (getHashEntry(include_filename_hash, value) == NULL)
2235 char *basepath = getBasePath(filename);
2236 char *basename = getBaseName(value);
2237 char *filename_include = getPath2(basepath, basename);
2239 loadSetupFileData(setup_file_data, filename_include, FALSE, is_hash);
2243 free(filename_include);
2249 Warn("ignoring already processed file '%s'", value);
2256 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2258 getHashEntry((SetupFileHash *)setup_file_data, token);
2260 if (old_value != NULL)
2262 if (!token_already_exists_warning)
2264 Debug("setup", "---");
2265 Debug("setup", "duplicate token(s) found in config file:");
2266 Debug("setup", "- config file: '%s'", filename);
2268 token_already_exists_warning = TRUE;
2271 Debug("setup", "- token: '%s' (in line %d)", token, line_nr);
2272 Debug("setup", " old value: '%s'", old_value);
2273 Debug("setup", " new value: '%s'", value);
2277 setHashEntry((SetupFileHash *)setup_file_data, token, value);
2281 insert_ptr = addListEntry((SetupFileList *)insert_ptr, token, value);
2291 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2292 if (token_value_separator_warning)
2293 Debug("setup", "---");
2296 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2297 if (token_already_exists_warning)
2298 Debug("setup", "---");
2301 if (token_count == 0 && include_count == 0)
2302 Warn("configuration file '%s' is empty", filename);
2304 if (top_recursion_level)
2305 freeSetupFileHash(include_filename_hash);
2310 static int compareSetupFileData(const void *object1, const void *object2)
2312 const struct ConfigInfo *entry1 = (struct ConfigInfo *)object1;
2313 const struct ConfigInfo *entry2 = (struct ConfigInfo *)object2;
2315 return strcmp(entry1->token, entry2->token);
2318 static void saveSetupFileHash(SetupFileHash *hash, char *filename)
2320 int item_count = hashtable_count(hash);
2321 int item_size = sizeof(struct ConfigInfo);
2322 struct ConfigInfo *sort_array = checked_malloc(item_count * item_size);
2326 // copy string pointers from hash to array
2327 BEGIN_HASH_ITERATION(hash, itr)
2329 sort_array[i].token = HASH_ITERATION_TOKEN(itr);
2330 sort_array[i].value = HASH_ITERATION_VALUE(itr);
2334 if (i > item_count) // should never happen
2337 END_HASH_ITERATION(hash, itr)
2339 // sort string pointers from hash in array
2340 qsort(sort_array, item_count, item_size, compareSetupFileData);
2342 if (!(file = fopen(filename, MODE_WRITE)))
2344 Warn("cannot write configuration file '%s'", filename);
2349 fprintf(file, "%s\n\n", getFormattedSetupEntry("program.version",
2350 program.version_string));
2351 for (i = 0; i < item_count; i++)
2352 fprintf(file, "%s\n", getFormattedSetupEntry(sort_array[i].token,
2353 sort_array[i].value));
2356 checked_free(sort_array);
2359 SetupFileList *loadSetupFileList(char *filename)
2361 SetupFileList *setup_file_list = newSetupFileList("", "");
2362 SetupFileList *first_valid_list_entry;
2364 if (!loadSetupFileData(setup_file_list, filename, TRUE, FALSE))
2366 freeSetupFileList(setup_file_list);
2371 first_valid_list_entry = setup_file_list->next;
2373 // free empty list header
2374 setup_file_list->next = NULL;
2375 freeSetupFileList(setup_file_list);
2377 return first_valid_list_entry;
2380 SetupFileHash *loadSetupFileHash(char *filename)
2382 SetupFileHash *setup_file_hash = newSetupFileHash();
2384 if (!loadSetupFileData(setup_file_hash, filename, TRUE, TRUE))
2386 freeSetupFileHash(setup_file_hash);
2391 return setup_file_hash;
2395 // ============================================================================
2397 // ============================================================================
2399 #define TOKEN_STR_LAST_LEVEL_SERIES "last_level_series"
2400 #define TOKEN_STR_LAST_PLAYED_LEVEL "last_played_level"
2401 #define TOKEN_STR_HANDICAP_LEVEL "handicap_level"
2402 #define TOKEN_STR_LAST_USER "last_user"
2404 // level directory info
2405 #define LEVELINFO_TOKEN_IDENTIFIER 0
2406 #define LEVELINFO_TOKEN_NAME 1
2407 #define LEVELINFO_TOKEN_NAME_SORTING 2
2408 #define LEVELINFO_TOKEN_AUTHOR 3
2409 #define LEVELINFO_TOKEN_YEAR 4
2410 #define LEVELINFO_TOKEN_PROGRAM_TITLE 5
2411 #define LEVELINFO_TOKEN_PROGRAM_COPYRIGHT 6
2412 #define LEVELINFO_TOKEN_PROGRAM_COMPANY 7
2413 #define LEVELINFO_TOKEN_IMPORTED_FROM 8
2414 #define LEVELINFO_TOKEN_IMPORTED_BY 9
2415 #define LEVELINFO_TOKEN_TESTED_BY 10
2416 #define LEVELINFO_TOKEN_LEVELS 11
2417 #define LEVELINFO_TOKEN_FIRST_LEVEL 12
2418 #define LEVELINFO_TOKEN_SORT_PRIORITY 13
2419 #define LEVELINFO_TOKEN_LATEST_ENGINE 14
2420 #define LEVELINFO_TOKEN_LEVEL_GROUP 15
2421 #define LEVELINFO_TOKEN_READONLY 16
2422 #define LEVELINFO_TOKEN_GRAPHICS_SET_ECS 17
2423 #define LEVELINFO_TOKEN_GRAPHICS_SET_AGA 18
2424 #define LEVELINFO_TOKEN_GRAPHICS_SET 19
2425 #define LEVELINFO_TOKEN_SOUNDS_SET_DEFAULT 20
2426 #define LEVELINFO_TOKEN_SOUNDS_SET_LOWPASS 21
2427 #define LEVELINFO_TOKEN_SOUNDS_SET 22
2428 #define LEVELINFO_TOKEN_MUSIC_SET 23
2429 #define LEVELINFO_TOKEN_FILENAME 24
2430 #define LEVELINFO_TOKEN_FILETYPE 25
2431 #define LEVELINFO_TOKEN_SPECIAL_FLAGS 26
2432 #define LEVELINFO_TOKEN_HANDICAP 27
2433 #define LEVELINFO_TOKEN_SKIP_LEVELS 28
2434 #define LEVELINFO_TOKEN_USE_EMC_TILES 29
2436 #define NUM_LEVELINFO_TOKENS 30
2438 static LevelDirTree ldi;
2440 static struct TokenInfo levelinfo_tokens[] =
2442 // level directory info
2443 { TYPE_STRING, &ldi.identifier, "identifier" },
2444 { TYPE_STRING, &ldi.name, "name" },
2445 { TYPE_STRING, &ldi.name_sorting, "name_sorting" },
2446 { TYPE_STRING, &ldi.author, "author" },
2447 { TYPE_STRING, &ldi.year, "year" },
2448 { TYPE_STRING, &ldi.program_title, "program_title" },
2449 { TYPE_STRING, &ldi.program_copyright, "program_copyright" },
2450 { TYPE_STRING, &ldi.program_company, "program_company" },
2451 { TYPE_STRING, &ldi.imported_from, "imported_from" },
2452 { TYPE_STRING, &ldi.imported_by, "imported_by" },
2453 { TYPE_STRING, &ldi.tested_by, "tested_by" },
2454 { TYPE_INTEGER, &ldi.levels, "levels" },
2455 { TYPE_INTEGER, &ldi.first_level, "first_level" },
2456 { TYPE_INTEGER, &ldi.sort_priority, "sort_priority" },
2457 { TYPE_BOOLEAN, &ldi.latest_engine, "latest_engine" },
2458 { TYPE_BOOLEAN, &ldi.level_group, "level_group" },
2459 { TYPE_BOOLEAN, &ldi.readonly, "readonly" },
2460 { TYPE_STRING, &ldi.graphics_set_ecs, "graphics_set.ecs" },
2461 { TYPE_STRING, &ldi.graphics_set_aga, "graphics_set.aga" },
2462 { TYPE_STRING, &ldi.graphics_set, "graphics_set" },
2463 { TYPE_STRING, &ldi.sounds_set_default,"sounds_set.default" },
2464 { TYPE_STRING, &ldi.sounds_set_lowpass,"sounds_set.lowpass" },
2465 { TYPE_STRING, &ldi.sounds_set, "sounds_set" },
2466 { TYPE_STRING, &ldi.music_set, "music_set" },
2467 { TYPE_STRING, &ldi.level_filename, "filename" },
2468 { TYPE_STRING, &ldi.level_filetype, "filetype" },
2469 { TYPE_STRING, &ldi.special_flags, "special_flags" },
2470 { TYPE_BOOLEAN, &ldi.handicap, "handicap" },
2471 { TYPE_BOOLEAN, &ldi.skip_levels, "skip_levels" },
2472 { TYPE_BOOLEAN, &ldi.use_emc_tiles, "use_emc_tiles" }
2475 static struct TokenInfo artworkinfo_tokens[] =
2477 // artwork directory info
2478 { TYPE_STRING, &ldi.identifier, "identifier" },
2479 { TYPE_STRING, &ldi.subdir, "subdir" },
2480 { TYPE_STRING, &ldi.name, "name" },
2481 { TYPE_STRING, &ldi.name_sorting, "name_sorting" },
2482 { TYPE_STRING, &ldi.author, "author" },
2483 { TYPE_STRING, &ldi.program_title, "program_title" },
2484 { TYPE_STRING, &ldi.program_copyright, "program_copyright" },
2485 { TYPE_STRING, &ldi.program_company, "program_company" },
2486 { TYPE_INTEGER, &ldi.sort_priority, "sort_priority" },
2487 { TYPE_STRING, &ldi.basepath, "basepath" },
2488 { TYPE_STRING, &ldi.fullpath, "fullpath" },
2489 { TYPE_BOOLEAN, &ldi.in_user_dir, "in_user_dir" },
2490 { TYPE_STRING, &ldi.class_desc, "class_desc" },
2495 static char *optional_tokens[] =
2498 "program_copyright",
2504 static void setTreeInfoToDefaults(TreeInfo *ti, int type)
2508 ti->node_top = (ti->type == TREE_TYPE_LEVEL_DIR ? &leveldir_first :
2509 ti->type == TREE_TYPE_GRAPHICS_DIR ? &artwork.gfx_first :
2510 ti->type == TREE_TYPE_SOUNDS_DIR ? &artwork.snd_first :
2511 ti->type == TREE_TYPE_MUSIC_DIR ? &artwork.mus_first :
2514 ti->node_parent = NULL;
2515 ti->node_group = NULL;
2522 ti->fullpath = NULL;
2523 ti->basepath = NULL;
2524 ti->identifier = NULL;
2525 ti->name = getStringCopy(ANONYMOUS_NAME);
2526 ti->name_sorting = NULL;
2527 ti->author = getStringCopy(ANONYMOUS_NAME);
2530 ti->program_title = NULL;
2531 ti->program_copyright = NULL;
2532 ti->program_company = NULL;
2534 ti->sort_priority = LEVELCLASS_UNDEFINED; // default: least priority
2535 ti->latest_engine = FALSE; // default: get from level
2536 ti->parent_link = FALSE;
2537 ti->is_copy = FALSE;
2538 ti->in_user_dir = FALSE;
2539 ti->user_defined = FALSE;
2541 ti->class_desc = NULL;
2543 ti->infotext = getStringCopy(TREE_INFOTEXT(ti->type));
2545 if (ti->type == TREE_TYPE_LEVEL_DIR)
2547 ti->imported_from = NULL;
2548 ti->imported_by = NULL;
2549 ti->tested_by = NULL;
2551 ti->graphics_set_ecs = NULL;
2552 ti->graphics_set_aga = NULL;
2553 ti->graphics_set = NULL;
2554 ti->sounds_set_default = NULL;
2555 ti->sounds_set_lowpass = NULL;
2556 ti->sounds_set = NULL;
2557 ti->music_set = NULL;
2558 ti->graphics_path = getStringCopy(UNDEFINED_FILENAME);
2559 ti->sounds_path = getStringCopy(UNDEFINED_FILENAME);
2560 ti->music_path = getStringCopy(UNDEFINED_FILENAME);
2562 ti->level_filename = NULL;
2563 ti->level_filetype = NULL;
2565 ti->special_flags = NULL;
2568 ti->first_level = 0;
2570 ti->level_group = FALSE;
2571 ti->handicap_level = 0;
2572 ti->readonly = TRUE;
2573 ti->handicap = TRUE;
2574 ti->skip_levels = FALSE;
2576 ti->use_emc_tiles = FALSE;
2580 static void setTreeInfoToDefaultsFromParent(TreeInfo *ti, TreeInfo *parent)
2584 Warn("setTreeInfoToDefaultsFromParent(): parent == NULL");
2586 setTreeInfoToDefaults(ti, TREE_TYPE_UNDEFINED);
2591 // copy all values from the parent structure
2593 ti->type = parent->type;
2595 ti->node_top = parent->node_top;
2596 ti->node_parent = parent;
2597 ti->node_group = NULL;
2604 ti->fullpath = NULL;
2605 ti->basepath = NULL;
2606 ti->identifier = NULL;
2607 ti->name = getStringCopy(ANONYMOUS_NAME);
2608 ti->name_sorting = NULL;
2609 ti->author = getStringCopy(parent->author);
2610 ti->year = getStringCopy(parent->year);
2612 ti->program_title = getStringCopy(parent->program_title);
2613 ti->program_copyright = getStringCopy(parent->program_copyright);
2614 ti->program_company = getStringCopy(parent->program_company);
2616 ti->sort_priority = parent->sort_priority;
2617 ti->latest_engine = parent->latest_engine;
2618 ti->parent_link = FALSE;
2619 ti->is_copy = FALSE;
2620 ti->in_user_dir = parent->in_user_dir;
2621 ti->user_defined = parent->user_defined;
2622 ti->color = parent->color;
2623 ti->class_desc = getStringCopy(parent->class_desc);
2625 ti->infotext = getStringCopy(parent->infotext);
2627 if (ti->type == TREE_TYPE_LEVEL_DIR)
2629 ti->imported_from = getStringCopy(parent->imported_from);
2630 ti->imported_by = getStringCopy(parent->imported_by);
2631 ti->tested_by = getStringCopy(parent->tested_by);
2633 ti->graphics_set_ecs = getStringCopy(parent->graphics_set_ecs);
2634 ti->graphics_set_aga = getStringCopy(parent->graphics_set_aga);
2635 ti->graphics_set = getStringCopy(parent->graphics_set);
2636 ti->sounds_set_default = getStringCopy(parent->sounds_set_default);
2637 ti->sounds_set_lowpass = getStringCopy(parent->sounds_set_lowpass);
2638 ti->sounds_set = getStringCopy(parent->sounds_set);
2639 ti->music_set = getStringCopy(parent->music_set);
2640 ti->graphics_path = getStringCopy(UNDEFINED_FILENAME);
2641 ti->sounds_path = getStringCopy(UNDEFINED_FILENAME);
2642 ti->music_path = getStringCopy(UNDEFINED_FILENAME);
2644 ti->level_filename = getStringCopy(parent->level_filename);
2645 ti->level_filetype = getStringCopy(parent->level_filetype);
2647 ti->special_flags = getStringCopy(parent->special_flags);
2649 ti->levels = parent->levels;
2650 ti->first_level = parent->first_level;
2651 ti->last_level = parent->last_level;
2652 ti->level_group = FALSE;
2653 ti->handicap_level = parent->handicap_level;
2654 ti->readonly = parent->readonly;
2655 ti->handicap = parent->handicap;
2656 ti->skip_levels = parent->skip_levels;
2658 ti->use_emc_tiles = parent->use_emc_tiles;
2662 static TreeInfo *getTreeInfoCopy(TreeInfo *ti)
2664 TreeInfo *ti_copy = newTreeInfo();
2666 // copy all values from the original structure
2668 ti_copy->type = ti->type;
2670 ti_copy->node_top = ti->node_top;
2671 ti_copy->node_parent = ti->node_parent;
2672 ti_copy->node_group = ti->node_group;
2673 ti_copy->next = ti->next;
2675 ti_copy->cl_first = ti->cl_first;
2676 ti_copy->cl_cursor = ti->cl_cursor;
2678 ti_copy->subdir = getStringCopy(ti->subdir);
2679 ti_copy->fullpath = getStringCopy(ti->fullpath);
2680 ti_copy->basepath = getStringCopy(ti->basepath);
2681 ti_copy->identifier = getStringCopy(ti->identifier);
2682 ti_copy->name = getStringCopy(ti->name);
2683 ti_copy->name_sorting = getStringCopy(ti->name_sorting);
2684 ti_copy->author = getStringCopy(ti->author);
2685 ti_copy->year = getStringCopy(ti->year);
2687 ti_copy->program_title = getStringCopy(ti->program_title);
2688 ti_copy->program_copyright = getStringCopy(ti->program_copyright);
2689 ti_copy->program_company = getStringCopy(ti->program_company);
2691 ti_copy->imported_from = getStringCopy(ti->imported_from);
2692 ti_copy->imported_by = getStringCopy(ti->imported_by);
2693 ti_copy->tested_by = getStringCopy(ti->tested_by);
2695 ti_copy->graphics_set_ecs = getStringCopy(ti->graphics_set_ecs);
2696 ti_copy->graphics_set_aga = getStringCopy(ti->graphics_set_aga);
2697 ti_copy->graphics_set = getStringCopy(ti->graphics_set);
2698 ti_copy->sounds_set_default = getStringCopy(ti->sounds_set_default);
2699 ti_copy->sounds_set_lowpass = getStringCopy(ti->sounds_set_lowpass);
2700 ti_copy->sounds_set = getStringCopy(ti->sounds_set);
2701 ti_copy->music_set = getStringCopy(ti->music_set);
2702 ti_copy->graphics_path = getStringCopy(ti->graphics_path);
2703 ti_copy->sounds_path = getStringCopy(ti->sounds_path);
2704 ti_copy->music_path = getStringCopy(ti->music_path);
2706 ti_copy->level_filename = getStringCopy(ti->level_filename);
2707 ti_copy->level_filetype = getStringCopy(ti->level_filetype);
2709 ti_copy->special_flags = getStringCopy(ti->special_flags);
2711 ti_copy->levels = ti->levels;
2712 ti_copy->first_level = ti->first_level;
2713 ti_copy->last_level = ti->last_level;
2714 ti_copy->sort_priority = ti->sort_priority;
2716 ti_copy->latest_engine = ti->latest_engine;
2718 ti_copy->level_group = ti->level_group;
2719 ti_copy->parent_link = ti->parent_link;
2720 ti_copy->is_copy = ti->is_copy;
2721 ti_copy->in_user_dir = ti->in_user_dir;
2722 ti_copy->user_defined = ti->user_defined;
2723 ti_copy->readonly = ti->readonly;
2724 ti_copy->handicap = ti->handicap;
2725 ti_copy->skip_levels = ti->skip_levels;
2727 ti_copy->use_emc_tiles = ti->use_emc_tiles;
2729 ti_copy->color = ti->color;
2730 ti_copy->class_desc = getStringCopy(ti->class_desc);
2731 ti_copy->handicap_level = ti->handicap_level;
2733 ti_copy->infotext = getStringCopy(ti->infotext);
2738 void freeTreeInfo(TreeInfo *ti)
2743 checked_free(ti->subdir);
2744 checked_free(ti->fullpath);
2745 checked_free(ti->basepath);
2746 checked_free(ti->identifier);
2748 checked_free(ti->name);
2749 checked_free(ti->name_sorting);
2750 checked_free(ti->author);
2751 checked_free(ti->year);
2753 checked_free(ti->program_title);
2754 checked_free(ti->program_copyright);
2755 checked_free(ti->program_company);
2757 checked_free(ti->class_desc);
2759 checked_free(ti->infotext);
2761 if (ti->type == TREE_TYPE_LEVEL_DIR)
2763 checked_free(ti->imported_from);
2764 checked_free(ti->imported_by);
2765 checked_free(ti->tested_by);
2767 checked_free(ti->graphics_set_ecs);
2768 checked_free(ti->graphics_set_aga);
2769 checked_free(ti->graphics_set);
2770 checked_free(ti->sounds_set_default);
2771 checked_free(ti->sounds_set_lowpass);
2772 checked_free(ti->sounds_set);
2773 checked_free(ti->music_set);
2775 checked_free(ti->graphics_path);
2776 checked_free(ti->sounds_path);
2777 checked_free(ti->music_path);
2779 checked_free(ti->level_filename);
2780 checked_free(ti->level_filetype);
2782 checked_free(ti->special_flags);
2785 // recursively free child node
2787 freeTreeInfo(ti->node_group);
2789 // recursively free next node
2791 freeTreeInfo(ti->next);
2796 void setSetupInfo(struct TokenInfo *token_info,
2797 int token_nr, char *token_value)
2799 int token_type = token_info[token_nr].type;
2800 void *setup_value = token_info[token_nr].value;
2802 if (token_value == NULL)
2805 // set setup field to corresponding token value
2810 *(boolean *)setup_value = get_boolean_from_string(token_value);
2814 *(int *)setup_value = get_switch3_from_string(token_value);
2818 *(Key *)setup_value = getKeyFromKeyName(token_value);
2822 *(Key *)setup_value = getKeyFromX11KeyName(token_value);
2826 *(int *)setup_value = get_integer_from_string(token_value);
2830 checked_free(*(char **)setup_value);
2831 *(char **)setup_value = getStringCopy(token_value);
2835 *(int *)setup_value = get_player_nr_from_string(token_value);
2843 static int compareTreeInfoEntries(const void *object1, const void *object2)
2845 const TreeInfo *entry1 = *((TreeInfo **)object1);
2846 const TreeInfo *entry2 = *((TreeInfo **)object2);
2847 int tree_sorting1 = TREE_SORTING(entry1);
2848 int tree_sorting2 = TREE_SORTING(entry2);
2850 if (tree_sorting1 != tree_sorting2)
2851 return (tree_sorting1 - tree_sorting2);
2853 return strcasecmp(entry1->name_sorting, entry2->name_sorting);
2856 static TreeInfo *createParentTreeInfoNode(TreeInfo *node_parent)
2860 if (node_parent == NULL)
2863 ti_new = newTreeInfo();
2864 setTreeInfoToDefaults(ti_new, node_parent->type);
2866 ti_new->node_parent = node_parent;
2867 ti_new->parent_link = TRUE;
2869 setString(&ti_new->identifier, node_parent->identifier);
2870 setString(&ti_new->name, BACKLINK_TEXT_PARENT);
2871 setString(&ti_new->name_sorting, ti_new->name);
2873 setString(&ti_new->subdir, STRING_PARENT_DIRECTORY);
2874 setString(&ti_new->fullpath, node_parent->fullpath);
2876 ti_new->sort_priority = LEVELCLASS_PARENT;
2877 ti_new->latest_engine = node_parent->latest_engine;
2879 setString(&ti_new->class_desc, getLevelClassDescription(ti_new));
2881 pushTreeInfo(&node_parent->node_group, ti_new);
2886 static TreeInfo *createTopTreeInfoNode(TreeInfo *node_first)
2888 if (node_first == NULL)
2891 TreeInfo *ti_new = newTreeInfo();
2892 int type = node_first->type;
2894 setTreeInfoToDefaults(ti_new, type);
2896 ti_new->node_parent = NULL;
2897 ti_new->parent_link = FALSE;
2899 setString(&ti_new->identifier, "top_tree_node");
2900 setString(&ti_new->name, TREE_INFOTEXT(type));
2901 setString(&ti_new->name_sorting, ti_new->name);
2903 setString(&ti_new->subdir, STRING_TOP_DIRECTORY);
2904 setString(&ti_new->fullpath, ".");
2906 ti_new->sort_priority = LEVELCLASS_TOP;
2907 ti_new->latest_engine = node_first->latest_engine;
2909 setString(&ti_new->class_desc, TREE_INFOTEXT(type));
2911 ti_new->node_group = node_first;
2912 ti_new->level_group = TRUE;
2914 TreeInfo *ti_new2 = createParentTreeInfoNode(ti_new);
2916 setString(&ti_new2->name, TREE_BACKLINK_TEXT(type));
2917 setString(&ti_new2->name_sorting, ti_new2->name);
2922 static void setTreeInfoParentNodes(TreeInfo *node, TreeInfo *node_parent)
2926 if (node->node_group)
2927 setTreeInfoParentNodes(node->node_group, node);
2929 node->node_parent = node_parent;
2936 // ----------------------------------------------------------------------------
2937 // functions for handling level and custom artwork info cache
2938 // ----------------------------------------------------------------------------
2940 static void LoadArtworkInfoCache(void)
2942 InitCacheDirectory();
2944 if (artworkinfo_cache_old == NULL)
2946 char *filename = getPath2(getCacheDir(), ARTWORKINFO_CACHE_FILE);
2948 // try to load artwork info hash from already existing cache file
2949 artworkinfo_cache_old = loadSetupFileHash(filename);
2951 // try to get program version that artwork info cache was written with
2952 char *version = getHashEntry(artworkinfo_cache_old, "program.version");
2954 // check program version of artwork info cache against current version
2955 if (!strEqual(version, program.version_string))
2957 freeSetupFileHash(artworkinfo_cache_old);
2959 artworkinfo_cache_old = NULL;
2962 // if no artwork info cache file was found, start with empty hash
2963 if (artworkinfo_cache_old == NULL)
2964 artworkinfo_cache_old = newSetupFileHash();
2969 if (artworkinfo_cache_new == NULL)
2970 artworkinfo_cache_new = newSetupFileHash();
2972 update_artworkinfo_cache = FALSE;
2975 static void SaveArtworkInfoCache(void)
2977 if (!update_artworkinfo_cache)
2980 char *filename = getPath2(getCacheDir(), ARTWORKINFO_CACHE_FILE);
2982 InitCacheDirectory();
2984 saveSetupFileHash(artworkinfo_cache_new, filename);
2989 static char *getCacheTokenPrefix(char *prefix1, char *prefix2)
2991 static char *prefix = NULL;
2993 checked_free(prefix);
2995 prefix = getStringCat2WithSeparator(prefix1, prefix2, ".");
3000 // (identical to above function, but separate string buffer needed -- nasty)
3001 static char *getCacheToken(char *prefix, char *suffix)
3003 static char *token = NULL;
3005 checked_free(token);
3007 token = getStringCat2WithSeparator(prefix, suffix, ".");
3012 static char *getFileTimestampString(char *filename)
3014 return getStringCopy(i_to_a(getFileTimestampEpochSeconds(filename)));
3017 static boolean modifiedFileTimestamp(char *filename, char *timestamp_string)
3019 struct stat file_status;
3021 if (timestamp_string == NULL)
3024 if (!fileExists(filename)) // file does not exist
3025 return (atoi(timestamp_string) != 0);
3027 if (stat(filename, &file_status) != 0) // cannot stat file
3030 return (file_status.st_mtime != atoi(timestamp_string));
3033 static TreeInfo *getArtworkInfoCacheEntry(LevelDirTree *level_node, int type)
3035 char *identifier = level_node->subdir;
3036 char *type_string = ARTWORK_DIRECTORY(type);
3037 char *token_prefix = getCacheTokenPrefix(type_string, identifier);
3038 char *token_main = getCacheToken(token_prefix, "CACHED");
3039 char *cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
3040 boolean cached = (cache_entry != NULL && strEqual(cache_entry, "true"));
3041 TreeInfo *artwork_info = NULL;
3043 if (!use_artworkinfo_cache)
3046 if (optional_tokens_hash == NULL)
3050 // create hash from list of optional tokens (for quick access)
3051 optional_tokens_hash = newSetupFileHash();
3052 for (i = 0; optional_tokens[i] != NULL; i++)
3053 setHashEntry(optional_tokens_hash, optional_tokens[i], "");
3060 artwork_info = newTreeInfo();
3061 setTreeInfoToDefaults(artwork_info, type);
3063 // set all structure fields according to the token/value pairs
3064 ldi = *artwork_info;
3065 for (i = 0; artworkinfo_tokens[i].type != -1; i++)
3067 char *token_suffix = artworkinfo_tokens[i].text;
3068 char *token = getCacheToken(token_prefix, token_suffix);
3069 char *value = getHashEntry(artworkinfo_cache_old, token);
3071 (getHashEntry(optional_tokens_hash, token_suffix) != NULL);
3073 setSetupInfo(artworkinfo_tokens, i, value);
3075 // check if cache entry for this item is mandatory, but missing
3076 if (value == NULL && !optional)
3078 Warn("missing cache entry '%s'", token);
3084 *artwork_info = ldi;
3089 char *filename_levelinfo = getPath2(getLevelDirFromTreeInfo(level_node),
3090 LEVELINFO_FILENAME);
3091 char *filename_artworkinfo = getPath2(getSetupArtworkDir(artwork_info),
3092 ARTWORKINFO_FILENAME(type));
3094 // check if corresponding "levelinfo.conf" file has changed
3095 token_main = getCacheToken(token_prefix, "TIMESTAMP_LEVELINFO");
3096 cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
3098 if (modifiedFileTimestamp(filename_levelinfo, cache_entry))
3101 // check if corresponding "<artworkinfo>.conf" file has changed
3102 token_main = getCacheToken(token_prefix, "TIMESTAMP_ARTWORKINFO");
3103 cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
3105 if (modifiedFileTimestamp(filename_artworkinfo, cache_entry))
3108 checked_free(filename_levelinfo);
3109 checked_free(filename_artworkinfo);
3112 if (!cached && artwork_info != NULL)
3114 freeTreeInfo(artwork_info);
3119 return artwork_info;
3122 static void setArtworkInfoCacheEntry(TreeInfo *artwork_info,
3123 LevelDirTree *level_node, int type)
3125 char *identifier = level_node->subdir;
3126 char *type_string = ARTWORK_DIRECTORY(type);
3127 char *token_prefix = getCacheTokenPrefix(type_string, identifier);
3128 char *token_main = getCacheToken(token_prefix, "CACHED");
3129 boolean set_cache_timestamps = TRUE;
3132 setHashEntry(artworkinfo_cache_new, token_main, "true");
3134 if (set_cache_timestamps)
3136 char *filename_levelinfo = getPath2(getLevelDirFromTreeInfo(level_node),
3137 LEVELINFO_FILENAME);
3138 char *filename_artworkinfo = getPath2(getSetupArtworkDir(artwork_info),
3139 ARTWORKINFO_FILENAME(type));
3140 char *timestamp_levelinfo = getFileTimestampString(filename_levelinfo);
3141 char *timestamp_artworkinfo = getFileTimestampString(filename_artworkinfo);
3143 token_main = getCacheToken(token_prefix, "TIMESTAMP_LEVELINFO");
3144 setHashEntry(artworkinfo_cache_new, token_main, timestamp_levelinfo);
3146 token_main = getCacheToken(token_prefix, "TIMESTAMP_ARTWORKINFO");
3147 setHashEntry(artworkinfo_cache_new, token_main, timestamp_artworkinfo);
3149 checked_free(filename_levelinfo);
3150 checked_free(filename_artworkinfo);
3151 checked_free(timestamp_levelinfo);
3152 checked_free(timestamp_artworkinfo);
3155 ldi = *artwork_info;
3156 for (i = 0; artworkinfo_tokens[i].type != -1; i++)
3158 char *token = getCacheToken(token_prefix, artworkinfo_tokens[i].text);
3159 char *value = getSetupValue(artworkinfo_tokens[i].type,
3160 artworkinfo_tokens[i].value);
3162 setHashEntry(artworkinfo_cache_new, token, value);
3167 // ----------------------------------------------------------------------------
3168 // functions for loading level info and custom artwork info
3169 // ----------------------------------------------------------------------------
3171 int GetZipFileTreeType(char *zip_filename)
3173 static char *top_dir_path = NULL;
3174 static char *top_dir_conf_filename[NUM_BASE_TREE_TYPES] = { NULL };
3175 static char *conf_basename[NUM_BASE_TREE_TYPES] =
3177 GRAPHICSINFO_FILENAME,
3178 SOUNDSINFO_FILENAME,
3184 checked_free(top_dir_path);
3185 top_dir_path = NULL;
3187 for (j = 0; j < NUM_BASE_TREE_TYPES; j++)
3189 checked_free(top_dir_conf_filename[j]);
3190 top_dir_conf_filename[j] = NULL;
3193 char **zip_entries = zip_list(zip_filename);
3195 // check if zip file successfully opened
3196 if (zip_entries == NULL || zip_entries[0] == NULL)
3197 return TREE_TYPE_UNDEFINED;
3199 // first zip file entry is expected to be top level directory
3200 char *top_dir = zip_entries[0];
3202 // check if valid top level directory found in zip file
3203 if (!strSuffix(top_dir, "/"))
3204 return TREE_TYPE_UNDEFINED;
3206 // get filenames of valid configuration files in top level directory
3207 for (j = 0; j < NUM_BASE_TREE_TYPES; j++)
3208 top_dir_conf_filename[j] = getStringCat2(top_dir, conf_basename[j]);
3210 int tree_type = TREE_TYPE_UNDEFINED;
3213 while (zip_entries[e] != NULL)
3215 // check if every zip file entry is below top level directory
3216 if (!strPrefix(zip_entries[e], top_dir))
3217 return TREE_TYPE_UNDEFINED;
3219 // check if this zip file entry is a valid configuration filename
3220 for (j = 0; j < NUM_BASE_TREE_TYPES; j++)
3222 if (strEqual(zip_entries[e], top_dir_conf_filename[j]))
3224 // only exactly one valid configuration file allowed
3225 if (tree_type != TREE_TYPE_UNDEFINED)
3226 return TREE_TYPE_UNDEFINED;
3238 static boolean CheckZipFileForDirectory(char *zip_filename, char *directory,
3241 static char *top_dir_path = NULL;
3242 static char *top_dir_conf_filename = NULL;
3244 checked_free(top_dir_path);
3245 checked_free(top_dir_conf_filename);
3247 top_dir_path = NULL;
3248 top_dir_conf_filename = NULL;
3250 char *conf_basename = (tree_type == TREE_TYPE_LEVEL_DIR ? LEVELINFO_FILENAME :
3251 ARTWORKINFO_FILENAME(tree_type));
3253 // check if valid configuration filename determined
3254 if (conf_basename == NULL || strEqual(conf_basename, ""))
3257 char **zip_entries = zip_list(zip_filename);
3259 // check if zip file successfully opened
3260 if (zip_entries == NULL || zip_entries[0] == NULL)
3263 // first zip file entry is expected to be top level directory
3264 char *top_dir = zip_entries[0];
3266 // check if valid top level directory found in zip file
3267 if (!strSuffix(top_dir, "/"))
3270 // get path of extracted top level directory
3271 top_dir_path = getPath2(directory, top_dir);
3273 // remove trailing directory separator from top level directory path
3274 // (required to be able to check for file and directory in next step)
3275 top_dir_path[strlen(top_dir_path) - 1] = '\0';
3277 // check if zip file's top level directory already exists in target directory
3278 if (fileExists(top_dir_path)) // (checks for file and directory)
3281 // get filename of configuration file in top level directory
3282 top_dir_conf_filename = getStringCat2(top_dir, conf_basename);
3284 boolean found_top_dir_conf_filename = FALSE;
3287 while (zip_entries[i] != NULL)
3289 // check if every zip file entry is below top level directory
3290 if (!strPrefix(zip_entries[i], top_dir))
3293 // check if this zip file entry is the configuration filename
3294 if (strEqual(zip_entries[i], top_dir_conf_filename))
3295 found_top_dir_conf_filename = TRUE;
3300 // check if valid configuration filename was found in zip file
3301 if (!found_top_dir_conf_filename)
3307 char *ExtractZipFileIntoDirectory(char *zip_filename, char *directory,
3310 boolean zip_file_valid = CheckZipFileForDirectory(zip_filename, directory,
3313 if (!zip_file_valid)
3315 Warn("zip file '%s' rejected!", zip_filename);
3320 char **zip_entries = zip_extract(zip_filename, directory);
3322 if (zip_entries == NULL)
3324 Warn("zip file '%s' could not be extracted!", zip_filename);
3329 Info("zip file '%s' successfully extracted!", zip_filename);
3331 // first zip file entry contains top level directory
3332 char *top_dir = zip_entries[0];
3334 // remove trailing directory separator from top level directory
3335 top_dir[strlen(top_dir) - 1] = '\0';
3340 static void ProcessZipFilesInDirectory(char *directory, int tree_type)
3343 DirectoryEntry *dir_entry;
3345 if ((dir = openDirectory(directory)) == NULL)
3347 // display error if directory is main "options.graphics_directory" etc.
3348 if (tree_type == TREE_TYPE_LEVEL_DIR ||
3349 directory == OPTIONS_ARTWORK_DIRECTORY(tree_type))
3350 Warn("cannot read directory '%s'", directory);
3355 while ((dir_entry = readDirectory(dir)) != NULL) // loop all entries
3357 // skip non-zip files (and also directories with zip extension)
3358 if (!strSuffixLower(dir_entry->basename, ".zip") || dir_entry->is_directory)
3361 char *zip_filename = getPath2(directory, dir_entry->basename);
3362 char *zip_filename_extracted = getStringCat2(zip_filename, ".extracted");
3363 char *zip_filename_rejected = getStringCat2(zip_filename, ".rejected");
3365 // check if zip file hasn't already been extracted or rejected
3366 if (!fileExists(zip_filename_extracted) &&
3367 !fileExists(zip_filename_rejected))
3369 char *top_dir = ExtractZipFileIntoDirectory(zip_filename, directory,
3371 char *marker_filename = (top_dir != NULL ? zip_filename_extracted :
3372 zip_filename_rejected);
3375 // create empty file to mark zip file as extracted or rejected
3376 if ((marker_file = fopen(marker_filename, MODE_WRITE)))
3377 fclose(marker_file);
3380 free(zip_filename_extracted);
3381 free(zip_filename_rejected);
3385 closeDirectory(dir);
3388 // forward declaration for recursive call by "LoadLevelInfoFromLevelDir()"
3389 static void LoadLevelInfoFromLevelDir(TreeInfo **, TreeInfo *, char *);
3391 static boolean LoadLevelInfoFromLevelConf(TreeInfo **node_first,
3392 TreeInfo *node_parent,
3393 char *level_directory,
3394 char *directory_name)
3396 char *directory_path = getPath2(level_directory, directory_name);
3397 char *filename = getPath2(directory_path, LEVELINFO_FILENAME);
3398 SetupFileHash *setup_file_hash;
3399 LevelDirTree *leveldir_new = NULL;
3402 // unless debugging, silently ignore directories without "levelinfo.conf"
3403 if (!options.debug && !fileExists(filename))
3405 free(directory_path);
3411 setup_file_hash = loadSetupFileHash(filename);
3413 if (setup_file_hash == NULL)
3415 #if DEBUG_NO_CONFIG_FILE
3416 Debug("setup", "ignoring level directory '%s'", directory_path);
3419 free(directory_path);
3425 leveldir_new = newTreeInfo();
3428 setTreeInfoToDefaultsFromParent(leveldir_new, node_parent);
3430 setTreeInfoToDefaults(leveldir_new, TREE_TYPE_LEVEL_DIR);
3432 leveldir_new->subdir = getStringCopy(directory_name);
3434 // set all structure fields according to the token/value pairs
3435 ldi = *leveldir_new;
3436 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
3437 setSetupInfo(levelinfo_tokens, i,
3438 getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
3439 *leveldir_new = ldi;
3441 if (strEqual(leveldir_new->name, ANONYMOUS_NAME))
3442 setString(&leveldir_new->name, leveldir_new->subdir);
3444 if (leveldir_new->identifier == NULL)
3445 leveldir_new->identifier = getStringCopy(leveldir_new->subdir);
3447 if (leveldir_new->name_sorting == NULL)
3448 leveldir_new->name_sorting = getStringCopy(leveldir_new->name);
3450 if (node_parent == NULL) // top level group
3452 leveldir_new->basepath = getStringCopy(level_directory);
3453 leveldir_new->fullpath = getStringCopy(leveldir_new->subdir);
3455 else // sub level group
3457 leveldir_new->basepath = getStringCopy(node_parent->basepath);
3458 leveldir_new->fullpath = getPath2(node_parent->fullpath, directory_name);
3461 leveldir_new->last_level =
3462 leveldir_new->first_level + leveldir_new->levels - 1;
3464 leveldir_new->in_user_dir =
3465 (!strEqual(leveldir_new->basepath, options.level_directory));
3467 // adjust some settings if user's private level directory was detected
3468 if (leveldir_new->sort_priority == LEVELCLASS_UNDEFINED &&
3469 leveldir_new->in_user_dir &&
3470 (strEqual(leveldir_new->subdir, getLoginName()) ||
3471 strEqual(leveldir_new->name, getLoginName()) ||
3472 strEqual(leveldir_new->author, getRealName())))
3474 leveldir_new->sort_priority = LEVELCLASS_PRIVATE_START;
3475 leveldir_new->readonly = FALSE;
3478 leveldir_new->user_defined =
3479 (leveldir_new->in_user_dir && IS_LEVELCLASS_PRIVATE(leveldir_new));
3481 setString(&leveldir_new->class_desc, getLevelClassDescription(leveldir_new));
3483 leveldir_new->handicap_level = // set handicap to default value
3484 (leveldir_new->user_defined || !leveldir_new->handicap ?
3485 leveldir_new->last_level : leveldir_new->first_level);
3487 DrawInitText(leveldir_new->name, 150, FC_YELLOW);
3489 pushTreeInfo(node_first, leveldir_new);
3491 freeSetupFileHash(setup_file_hash);
3493 if (leveldir_new->level_group)
3495 // create node to link back to current level directory
3496 createParentTreeInfoNode(leveldir_new);
3498 // recursively step into sub-directory and look for more level series
3499 LoadLevelInfoFromLevelDir(&leveldir_new->node_group,
3500 leveldir_new, directory_path);
3503 free(directory_path);
3509 static void LoadLevelInfoFromLevelDir(TreeInfo **node_first,
3510 TreeInfo *node_parent,
3511 char *level_directory)
3513 // ---------- 1st stage: process any level set zip files ----------
3515 ProcessZipFilesInDirectory(level_directory, TREE_TYPE_LEVEL_DIR);
3517 // ---------- 2nd stage: check for level set directories ----------
3520 DirectoryEntry *dir_entry;
3521 boolean valid_entry_found = FALSE;
3523 if ((dir = openDirectory(level_directory)) == NULL)
3525 Warn("cannot read level directory '%s'", level_directory);
3530 while ((dir_entry = readDirectory(dir)) != NULL) // loop all entries
3532 char *directory_name = dir_entry->basename;
3533 char *directory_path = getPath2(level_directory, directory_name);
3535 // skip entries for current and parent directory
3536 if (strEqual(directory_name, ".") ||
3537 strEqual(directory_name, ".."))
3539 free(directory_path);
3544 // find out if directory entry is itself a directory
3545 if (!dir_entry->is_directory) // not a directory
3547 free(directory_path);
3552 free(directory_path);
3554 if (strEqual(directory_name, GRAPHICS_DIRECTORY) ||
3555 strEqual(directory_name, SOUNDS_DIRECTORY) ||
3556 strEqual(directory_name, MUSIC_DIRECTORY))
3559 valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
3564 closeDirectory(dir);
3566 // special case: top level directory may directly contain "levelinfo.conf"
3567 if (node_parent == NULL && !valid_entry_found)
3569 // check if this directory directly contains a file "levelinfo.conf"
3570 valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
3571 level_directory, ".");
3574 if (!valid_entry_found)
3575 Warn("cannot find any valid level series in directory '%s'",
3579 boolean AdjustGraphicsForEMC(void)
3581 boolean settings_changed = FALSE;
3583 settings_changed |= adjustTreeGraphicsForEMC(leveldir_first_all);
3584 settings_changed |= adjustTreeGraphicsForEMC(leveldir_first);
3586 return settings_changed;
3589 boolean AdjustSoundsForEMC(void)
3591 boolean settings_changed = FALSE;
3593 settings_changed |= adjustTreeSoundsForEMC(leveldir_first_all);
3594 settings_changed |= adjustTreeSoundsForEMC(leveldir_first);
3596 return settings_changed;
3599 void LoadLevelInfo(void)
3601 InitUserLevelDirectory(getLoginName());
3603 DrawInitText("Loading level series", 120, FC_GREEN);
3605 LoadLevelInfoFromLevelDir(&leveldir_first, NULL, options.level_directory);
3606 LoadLevelInfoFromLevelDir(&leveldir_first, NULL, getUserLevelDir(NULL));
3608 leveldir_first = createTopTreeInfoNode(leveldir_first);
3610 /* after loading all level set information, clone the level directory tree
3611 and remove all level sets without levels (these may still contain artwork
3612 to be offered in the setup menu as "custom artwork", and are therefore
3613 checked for existing artwork in the function "LoadLevelArtworkInfo()") */
3614 leveldir_first_all = leveldir_first;
3615 cloneTree(&leveldir_first, leveldir_first_all, TRUE);
3617 AdjustGraphicsForEMC();
3618 AdjustSoundsForEMC();
3620 // before sorting, the first entries will be from the user directory
3621 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
3623 if (leveldir_first == NULL)
3624 Fail("cannot find any valid level series in any directory");
3626 sortTreeInfo(&leveldir_first);
3628 #if ENABLE_UNUSED_CODE
3629 dumpTreeInfo(leveldir_first, 0);
3633 static boolean LoadArtworkInfoFromArtworkConf(TreeInfo **node_first,
3634 TreeInfo *node_parent,
3635 char *base_directory,
3636 char *directory_name, int type)
3638 char *directory_path = getPath2(base_directory, directory_name);
3639 char *filename = getPath2(directory_path, ARTWORKINFO_FILENAME(type));
3640 SetupFileHash *setup_file_hash = NULL;
3641 TreeInfo *artwork_new = NULL;
3644 if (fileExists(filename))
3645 setup_file_hash = loadSetupFileHash(filename);
3647 if (setup_file_hash == NULL) // no config file -- look for artwork files
3650 DirectoryEntry *dir_entry;
3651 boolean valid_file_found = FALSE;
3653 if ((dir = openDirectory(directory_path)) != NULL)
3655 while ((dir_entry = readDirectory(dir)) != NULL)
3657 if (FileIsArtworkType(dir_entry->filename, type))
3659 valid_file_found = TRUE;
3665 closeDirectory(dir);
3668 if (!valid_file_found)
3670 #if DEBUG_NO_CONFIG_FILE
3671 if (!strEqual(directory_name, "."))
3672 Debug("setup", "ignoring artwork directory '%s'", directory_path);
3675 free(directory_path);
3682 artwork_new = newTreeInfo();
3685 setTreeInfoToDefaultsFromParent(artwork_new, node_parent);
3687 setTreeInfoToDefaults(artwork_new, type);
3689 artwork_new->subdir = getStringCopy(directory_name);
3691 if (setup_file_hash) // (before defining ".color" and ".class_desc")
3693 // set all structure fields according to the token/value pairs
3695 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
3696 setSetupInfo(levelinfo_tokens, i,
3697 getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
3700 if (strEqual(artwork_new->name, ANONYMOUS_NAME))
3701 setString(&artwork_new->name, artwork_new->subdir);
3703 if (artwork_new->identifier == NULL)
3704 artwork_new->identifier = getStringCopy(artwork_new->subdir);
3706 if (artwork_new->name_sorting == NULL)
3707 artwork_new->name_sorting = getStringCopy(artwork_new->name);
3710 if (node_parent == NULL) // top level group
3712 artwork_new->basepath = getStringCopy(base_directory);
3713 artwork_new->fullpath = getStringCopy(artwork_new->subdir);
3715 else // sub level group
3717 artwork_new->basepath = getStringCopy(node_parent->basepath);
3718 artwork_new->fullpath = getPath2(node_parent->fullpath, directory_name);
3721 artwork_new->in_user_dir =
3722 (!strEqual(artwork_new->basepath, OPTIONS_ARTWORK_DIRECTORY(type)));
3724 setString(&artwork_new->class_desc, getLevelClassDescription(artwork_new));
3726 if (setup_file_hash == NULL) // (after determining ".user_defined")
3728 if (strEqual(artwork_new->subdir, "."))
3730 if (artwork_new->user_defined)
3732 setString(&artwork_new->identifier, "private");
3733 artwork_new->sort_priority = ARTWORKCLASS_PRIVATE;
3737 setString(&artwork_new->identifier, "classic");
3738 artwork_new->sort_priority = ARTWORKCLASS_CLASSICS;
3741 setString(&artwork_new->class_desc,
3742 getLevelClassDescription(artwork_new));
3746 setString(&artwork_new->identifier, artwork_new->subdir);
3749 setString(&artwork_new->name, artwork_new->identifier);
3750 setString(&artwork_new->name_sorting, artwork_new->name);
3753 pushTreeInfo(node_first, artwork_new);
3755 freeSetupFileHash(setup_file_hash);
3757 free(directory_path);
3763 static void LoadArtworkInfoFromArtworkDir(TreeInfo **node_first,
3764 TreeInfo *node_parent,
3765 char *base_directory, int type)
3767 // ---------- 1st stage: process any artwork set zip files ----------
3769 ProcessZipFilesInDirectory(base_directory, type);
3771 // ---------- 2nd stage: check for artwork set directories ----------
3774 DirectoryEntry *dir_entry;
3775 boolean valid_entry_found = FALSE;
3777 if ((dir = openDirectory(base_directory)) == NULL)
3779 // display error if directory is main "options.graphics_directory" etc.
3780 if (base_directory == OPTIONS_ARTWORK_DIRECTORY(type))
3781 Warn("cannot read directory '%s'", base_directory);
3786 while ((dir_entry = readDirectory(dir)) != NULL) // loop all entries
3788 char *directory_name = dir_entry->basename;
3789 char *directory_path = getPath2(base_directory, directory_name);
3791 // skip directory entries for current and parent directory
3792 if (strEqual(directory_name, ".") ||
3793 strEqual(directory_name, ".."))
3795 free(directory_path);
3800 // skip directory entries which are not a directory
3801 if (!dir_entry->is_directory) // not a directory
3803 free(directory_path);
3808 free(directory_path);
3810 // check if this directory contains artwork with or without config file
3811 valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
3813 directory_name, type);
3816 closeDirectory(dir);
3818 // check if this directory directly contains artwork itself
3819 valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
3820 base_directory, ".",
3822 if (!valid_entry_found)
3823 Warn("cannot find any valid artwork in directory '%s'", base_directory);
3826 static TreeInfo *getDummyArtworkInfo(int type)
3828 // this is only needed when there is completely no artwork available
3829 TreeInfo *artwork_new = newTreeInfo();
3831 setTreeInfoToDefaults(artwork_new, type);
3833 setString(&artwork_new->subdir, UNDEFINED_FILENAME);
3834 setString(&artwork_new->fullpath, UNDEFINED_FILENAME);
3835 setString(&artwork_new->basepath, UNDEFINED_FILENAME);
3837 setString(&artwork_new->identifier, UNDEFINED_FILENAME);
3838 setString(&artwork_new->name, UNDEFINED_FILENAME);
3839 setString(&artwork_new->name_sorting, UNDEFINED_FILENAME);
3844 void SetCurrentArtwork(int type)
3846 ArtworkDirTree **current_ptr = ARTWORK_CURRENT_PTR(artwork, type);
3847 ArtworkDirTree *first_node = ARTWORK_FIRST_NODE(artwork, type);
3848 char *setup_set = SETUP_ARTWORK_SET(setup, type);
3849 char *default_subdir = ARTWORK_DEFAULT_SUBDIR(type);
3851 // set current artwork to artwork configured in setup menu
3852 *current_ptr = getTreeInfoFromIdentifier(first_node, setup_set);
3854 // if not found, set current artwork to default artwork
3855 if (*current_ptr == NULL)
3856 *current_ptr = getTreeInfoFromIdentifier(first_node, default_subdir);
3858 // if not found, set current artwork to first artwork in tree
3859 if (*current_ptr == NULL)
3860 *current_ptr = getFirstValidTreeInfoEntry(first_node);
3863 void ChangeCurrentArtworkIfNeeded(int type)
3865 char *current_identifier = ARTWORK_CURRENT_IDENTIFIER(artwork, type);
3866 char *setup_set = SETUP_ARTWORK_SET(setup, type);
3868 if (!strEqual(current_identifier, setup_set))
3869 SetCurrentArtwork(type);
3872 void LoadArtworkInfo(void)
3874 LoadArtworkInfoCache();
3876 DrawInitText("Looking for custom artwork", 120, FC_GREEN);
3878 LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
3879 options.graphics_directory,
3880 TREE_TYPE_GRAPHICS_DIR);
3881 LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
3882 getUserGraphicsDir(),
3883 TREE_TYPE_GRAPHICS_DIR);
3885 LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
3886 options.sounds_directory,
3887 TREE_TYPE_SOUNDS_DIR);
3888 LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
3890 TREE_TYPE_SOUNDS_DIR);
3892 LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
3893 options.music_directory,
3894 TREE_TYPE_MUSIC_DIR);
3895 LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
3897 TREE_TYPE_MUSIC_DIR);
3899 if (artwork.gfx_first == NULL)
3900 artwork.gfx_first = getDummyArtworkInfo(TREE_TYPE_GRAPHICS_DIR);
3901 if (artwork.snd_first == NULL)
3902 artwork.snd_first = getDummyArtworkInfo(TREE_TYPE_SOUNDS_DIR);
3903 if (artwork.mus_first == NULL)
3904 artwork.mus_first = getDummyArtworkInfo(TREE_TYPE_MUSIC_DIR);
3906 // before sorting, the first entries will be from the user directory
3907 SetCurrentArtwork(ARTWORK_TYPE_GRAPHICS);
3908 SetCurrentArtwork(ARTWORK_TYPE_SOUNDS);
3909 SetCurrentArtwork(ARTWORK_TYPE_MUSIC);
3911 artwork.gfx_current_identifier = artwork.gfx_current->identifier;
3912 artwork.snd_current_identifier = artwork.snd_current->identifier;
3913 artwork.mus_current_identifier = artwork.mus_current->identifier;
3915 #if ENABLE_UNUSED_CODE
3916 Debug("setup:LoadArtworkInfo", "graphics set == %s",
3917 artwork.gfx_current_identifier);
3918 Debug("setup:LoadArtworkInfo", "sounds set == %s",
3919 artwork.snd_current_identifier);
3920 Debug("setup:LoadArtworkInfo", "music set == %s",
3921 artwork.mus_current_identifier);
3924 sortTreeInfo(&artwork.gfx_first);
3925 sortTreeInfo(&artwork.snd_first);
3926 sortTreeInfo(&artwork.mus_first);
3928 #if ENABLE_UNUSED_CODE
3929 dumpTreeInfo(artwork.gfx_first, 0);
3930 dumpTreeInfo(artwork.snd_first, 0);
3931 dumpTreeInfo(artwork.mus_first, 0);
3935 static void MoveArtworkInfoIntoSubTree(ArtworkDirTree **artwork_node)
3937 ArtworkDirTree *artwork_new = newTreeInfo();
3938 char *top_node_name = "standalone artwork";
3940 setTreeInfoToDefaults(artwork_new, (*artwork_node)->type);
3942 artwork_new->level_group = TRUE;
3944 setString(&artwork_new->identifier, top_node_name);
3945 setString(&artwork_new->name, top_node_name);
3946 setString(&artwork_new->name_sorting, top_node_name);
3948 // create node to link back to current custom artwork directory
3949 createParentTreeInfoNode(artwork_new);
3951 // move existing custom artwork tree into newly created sub-tree
3952 artwork_new->node_group->next = *artwork_node;
3954 // change custom artwork tree to contain only newly created node
3955 *artwork_node = artwork_new;
3958 static void LoadArtworkInfoFromLevelInfoExt(ArtworkDirTree **artwork_node,
3959 ArtworkDirTree *node_parent,
3960 LevelDirTree *level_node,
3961 boolean empty_level_set_mode)
3963 int type = (*artwork_node)->type;
3965 // recursively check all level directories for artwork sub-directories
3969 boolean empty_level_set = (level_node->levels == 0);
3971 // check all tree entries for artwork, but skip parent link entries
3972 if (!level_node->parent_link && empty_level_set == empty_level_set_mode)
3974 TreeInfo *artwork_new = getArtworkInfoCacheEntry(level_node, type);
3975 boolean cached = (artwork_new != NULL);
3979 pushTreeInfo(artwork_node, artwork_new);
3983 TreeInfo *topnode_last = *artwork_node;
3984 char *path = getPath2(getLevelDirFromTreeInfo(level_node),
3985 ARTWORK_DIRECTORY(type));
3987 LoadArtworkInfoFromArtworkDir(artwork_node, NULL, path, type);
3989 if (topnode_last != *artwork_node) // check for newly added node
3991 artwork_new = *artwork_node;
3993 setString(&artwork_new->identifier, level_node->subdir);
3994 setString(&artwork_new->name, level_node->name);
3995 setString(&artwork_new->name_sorting, level_node->name_sorting);
3997 artwork_new->sort_priority = level_node->sort_priority;
3998 artwork_new->in_user_dir = level_node->in_user_dir;
4000 update_artworkinfo_cache = TRUE;
4006 // insert artwork info (from old cache or filesystem) into new cache
4007 if (artwork_new != NULL)
4008 setArtworkInfoCacheEntry(artwork_new, level_node, type);
4011 DrawInitText(level_node->name, 150, FC_YELLOW);
4013 if (level_node->node_group != NULL)
4015 TreeInfo *artwork_new = newTreeInfo();
4018 setTreeInfoToDefaultsFromParent(artwork_new, node_parent);
4020 setTreeInfoToDefaults(artwork_new, type);
4022 artwork_new->level_group = TRUE;
4024 setString(&artwork_new->identifier, level_node->subdir);
4026 if (node_parent == NULL) // check for top tree node
4028 char *top_node_name = (empty_level_set_mode ?
4029 "artwork for certain level sets" :
4030 "artwork included in level sets");
4032 setString(&artwork_new->name, top_node_name);
4033 setString(&artwork_new->name_sorting, top_node_name);
4037 setString(&artwork_new->name, level_node->name);
4038 setString(&artwork_new->name_sorting, level_node->name_sorting);
4041 pushTreeInfo(artwork_node, artwork_new);
4043 // create node to link back to current custom artwork directory
4044 createParentTreeInfoNode(artwork_new);
4046 // recursively step into sub-directory and look for more custom artwork
4047 LoadArtworkInfoFromLevelInfoExt(&artwork_new->node_group, artwork_new,
4048 level_node->node_group,
4049 empty_level_set_mode);
4051 // if sub-tree has no custom artwork at all, remove it
4052 if (artwork_new->node_group->next == NULL)
4053 removeTreeInfo(artwork_node);
4056 level_node = level_node->next;
4060 static void LoadArtworkInfoFromLevelInfo(ArtworkDirTree **artwork_node)
4062 // move peviously loaded artwork tree into separate sub-tree
4063 MoveArtworkInfoIntoSubTree(artwork_node);
4065 // load artwork from level sets into separate sub-trees
4066 LoadArtworkInfoFromLevelInfoExt(artwork_node, NULL, leveldir_first_all, TRUE);
4067 LoadArtworkInfoFromLevelInfoExt(artwork_node, NULL, leveldir_first_all, FALSE);
4069 // add top tree node over all three separate sub-trees
4070 *artwork_node = createTopTreeInfoNode(*artwork_node);
4072 // set all parent links (back links) in complete artwork tree
4073 setTreeInfoParentNodes(*artwork_node, NULL);
4076 void LoadLevelArtworkInfo(void)
4078 print_timestamp_init("LoadLevelArtworkInfo");
4080 DrawInitText("Looking for custom level artwork", 120, FC_GREEN);
4082 print_timestamp_time("DrawTimeText");
4084 LoadArtworkInfoFromLevelInfo(&artwork.gfx_first);
4085 print_timestamp_time("LoadArtworkInfoFromLevelInfo (gfx)");
4086 LoadArtworkInfoFromLevelInfo(&artwork.snd_first);
4087 print_timestamp_time("LoadArtworkInfoFromLevelInfo (snd)");
4088 LoadArtworkInfoFromLevelInfo(&artwork.mus_first);
4089 print_timestamp_time("LoadArtworkInfoFromLevelInfo (mus)");
4091 SaveArtworkInfoCache();
4093 print_timestamp_time("SaveArtworkInfoCache");
4095 // needed for reloading level artwork not known at ealier stage
4096 ChangeCurrentArtworkIfNeeded(ARTWORK_TYPE_GRAPHICS);
4097 ChangeCurrentArtworkIfNeeded(ARTWORK_TYPE_SOUNDS);
4098 ChangeCurrentArtworkIfNeeded(ARTWORK_TYPE_MUSIC);
4100 print_timestamp_time("getTreeInfoFromIdentifier");
4102 sortTreeInfo(&artwork.gfx_first);
4103 sortTreeInfo(&artwork.snd_first);
4104 sortTreeInfo(&artwork.mus_first);
4106 print_timestamp_time("sortTreeInfo");
4108 #if ENABLE_UNUSED_CODE
4109 dumpTreeInfo(artwork.gfx_first, 0);
4110 dumpTreeInfo(artwork.snd_first, 0);
4111 dumpTreeInfo(artwork.mus_first, 0);
4114 print_timestamp_done("LoadLevelArtworkInfo");
4117 static boolean AddTreeSetToTreeInfoExt(TreeInfo *tree_node_old, char *tree_dir,
4118 char *tree_subdir_new, int type)
4120 if (tree_node_old == NULL)
4122 if (type == TREE_TYPE_LEVEL_DIR)
4124 // get level info tree node of personal user level set
4125 tree_node_old = getTreeInfoFromIdentifier(leveldir_first, getLoginName());
4127 // this may happen if "setup.internal.create_user_levelset" is FALSE
4128 // or if file "levelinfo.conf" is missing in personal user level set
4129 if (tree_node_old == NULL)
4130 tree_node_old = leveldir_first->node_group;
4134 // get artwork info tree node of first artwork set
4135 tree_node_old = ARTWORK_FIRST_NODE(artwork, type);
4139 if (tree_dir == NULL)
4140 tree_dir = TREE_USERDIR(type);
4142 if (tree_node_old == NULL ||
4144 tree_subdir_new == NULL) // should not happen
4147 int draw_deactivation_mask = GetDrawDeactivationMask();
4149 // override draw deactivation mask (temporarily disable drawing)
4150 SetDrawDeactivationMask(REDRAW_ALL);
4152 if (type == TREE_TYPE_LEVEL_DIR)
4154 // load new level set config and add it next to first user level set
4155 LoadLevelInfoFromLevelConf(&tree_node_old->next,
4156 tree_node_old->node_parent,
4157 tree_dir, tree_subdir_new);
4161 // load new artwork set config and add it next to first artwork set
4162 LoadArtworkInfoFromArtworkConf(&tree_node_old->next,
4163 tree_node_old->node_parent,
4164 tree_dir, tree_subdir_new, type);
4167 // set draw deactivation mask to previous value
4168 SetDrawDeactivationMask(draw_deactivation_mask);
4170 // get first node of level or artwork info tree
4171 TreeInfo **tree_node_first = TREE_FIRST_NODE_PTR(type);
4173 // get tree info node of newly added level or artwork set
4174 TreeInfo *tree_node_new = getTreeInfoFromIdentifier(*tree_node_first,
4177 if (tree_node_new == NULL) // should not happen
4180 // correct top link and parent node link of newly created tree node
4181 tree_node_new->node_top = tree_node_old->node_top;
4182 tree_node_new->node_parent = tree_node_old->node_parent;
4184 // sort tree info to adjust position of newly added tree set
4185 sortTreeInfo(tree_node_first);
4190 void AddTreeSetToTreeInfo(TreeInfo *tree_node, char *tree_dir,
4191 char *tree_subdir_new, int type)
4193 if (!AddTreeSetToTreeInfoExt(tree_node, tree_dir, tree_subdir_new, type))
4194 Fail("internal tree info structure corrupted -- aborting");
4197 void AddUserLevelSetToLevelInfo(char *level_subdir_new)
4199 AddTreeSetToTreeInfo(NULL, NULL, level_subdir_new, TREE_TYPE_LEVEL_DIR);
4202 char *getArtworkIdentifierForUserLevelSet(int type)
4204 char *classic_artwork_set = getClassicArtworkSet(type);
4206 // check for custom artwork configured in "levelinfo.conf"
4207 char *leveldir_artwork_set =
4208 *LEVELDIR_ARTWORK_SET_PTR(leveldir_current, type);
4209 boolean has_leveldir_artwork_set =
4210 (leveldir_artwork_set != NULL && !strEqual(leveldir_artwork_set,
4211 classic_artwork_set));
4213 // check for custom artwork in sub-directory "graphics" etc.
4214 TreeInfo *artwork_first_node = ARTWORK_FIRST_NODE(artwork, type);
4215 char *leveldir_identifier = leveldir_current->identifier;
4216 boolean has_artwork_subdir =
4217 (getTreeInfoFromIdentifier(artwork_first_node,
4218 leveldir_identifier) != NULL);
4220 return (has_leveldir_artwork_set ? leveldir_artwork_set :
4221 has_artwork_subdir ? leveldir_identifier :
4222 classic_artwork_set);
4225 TreeInfo *getArtworkTreeInfoForUserLevelSet(int type)
4227 char *artwork_set = getArtworkIdentifierForUserLevelSet(type);
4228 TreeInfo *artwork_first_node = ARTWORK_FIRST_NODE(artwork, type);
4229 TreeInfo *ti = getTreeInfoFromIdentifier(artwork_first_node, artwork_set);
4233 ti = getTreeInfoFromIdentifier(artwork_first_node,
4234 ARTWORK_DEFAULT_SUBDIR(type));
4236 Fail("cannot find default graphics -- should not happen");
4242 boolean checkIfCustomArtworkExistsForCurrentLevelSet(void)
4244 char *graphics_set =
4245 getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_GRAPHICS);
4247 getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_SOUNDS);
4249 getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_MUSIC);
4251 return (!strEqual(graphics_set, GFX_CLASSIC_SUBDIR) ||
4252 !strEqual(sounds_set, SND_CLASSIC_SUBDIR) ||
4253 !strEqual(music_set, MUS_CLASSIC_SUBDIR));
4256 boolean UpdateUserLevelSet(char *level_subdir, char *level_name,
4257 char *level_author, int num_levels)
4259 char *filename = getPath2(getUserLevelDir(level_subdir), LEVELINFO_FILENAME);
4260 char *filename_tmp = getStringCat2(filename, ".tmp");
4262 FILE *file_tmp = NULL;
4263 char line[MAX_LINE_LEN];
4264 boolean success = FALSE;
4265 LevelDirTree *leveldir = getTreeInfoFromIdentifier(leveldir_first,
4267 // update values in level directory tree
4269 if (level_name != NULL)
4270 setString(&leveldir->name, level_name);
4272 if (level_author != NULL)
4273 setString(&leveldir->author, level_author);
4275 if (num_levels != -1)
4276 leveldir->levels = num_levels;
4278 // update values that depend on other values
4280 setString(&leveldir->name_sorting, leveldir->name);
4282 leveldir->last_level = leveldir->first_level + leveldir->levels - 1;
4284 // sort order of level sets may have changed
4285 sortTreeInfo(&leveldir_first);
4287 if ((file = fopen(filename, MODE_READ)) &&
4288 (file_tmp = fopen(filename_tmp, MODE_WRITE)))
4290 while (fgets(line, MAX_LINE_LEN, file))
4292 if (strPrefix(line, "name:") && level_name != NULL)
4293 fprintf(file_tmp, "%-32s%s\n", "name:", level_name);
4294 else if (strPrefix(line, "author:") && level_author != NULL)
4295 fprintf(file_tmp, "%-32s%s\n", "author:", level_author);
4296 else if (strPrefix(line, "levels:") && num_levels != -1)
4297 fprintf(file_tmp, "%-32s%d\n", "levels:", num_levels);
4299 fputs(line, file_tmp);
4312 success = (rename(filename_tmp, filename) == 0);
4320 boolean CreateUserLevelSet(char *level_subdir, char *level_name,
4321 char *level_author, int num_levels,
4322 boolean use_artwork_set)
4324 LevelDirTree *level_info;
4329 // create user level sub-directory, if needed
4330 createDirectory(getUserLevelDir(level_subdir), "user level", PERMS_PRIVATE);
4332 filename = getPath2(getUserLevelDir(level_subdir), LEVELINFO_FILENAME);
4334 if (!(file = fopen(filename, MODE_WRITE)))
4336 Warn("cannot write level info file '%s'", filename);
4343 level_info = newTreeInfo();
4345 // always start with reliable default values
4346 setTreeInfoToDefaults(level_info, TREE_TYPE_LEVEL_DIR);
4348 setString(&level_info->name, level_name);
4349 setString(&level_info->author, level_author);
4350 level_info->levels = num_levels;
4351 level_info->first_level = 1;
4352 level_info->sort_priority = LEVELCLASS_PRIVATE_START;
4353 level_info->readonly = FALSE;
4355 if (use_artwork_set)
4357 level_info->graphics_set =
4358 getStringCopy(getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_GRAPHICS));
4359 level_info->sounds_set =
4360 getStringCopy(getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_SOUNDS));
4361 level_info->music_set =
4362 getStringCopy(getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_MUSIC));
4365 token_value_position = TOKEN_VALUE_POSITION_SHORT;
4367 fprintFileHeader(file, LEVELINFO_FILENAME);
4370 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
4372 if (i == LEVELINFO_TOKEN_NAME ||
4373 i == LEVELINFO_TOKEN_AUTHOR ||
4374 i == LEVELINFO_TOKEN_LEVELS ||
4375 i == LEVELINFO_TOKEN_FIRST_LEVEL ||
4376 i == LEVELINFO_TOKEN_SORT_PRIORITY ||
4377 i == LEVELINFO_TOKEN_READONLY ||
4378 (use_artwork_set && (i == LEVELINFO_TOKEN_GRAPHICS_SET ||
4379 i == LEVELINFO_TOKEN_SOUNDS_SET ||
4380 i == LEVELINFO_TOKEN_MUSIC_SET)))
4381 fprintf(file, "%s\n", getSetupLine(levelinfo_tokens, "", i));
4383 // just to make things nicer :)
4384 if (i == LEVELINFO_TOKEN_AUTHOR ||
4385 i == LEVELINFO_TOKEN_FIRST_LEVEL ||
4386 (use_artwork_set && i == LEVELINFO_TOKEN_READONLY))
4387 fprintf(file, "\n");
4390 token_value_position = TOKEN_VALUE_POSITION_DEFAULT;
4394 SetFilePermissions(filename, PERMS_PRIVATE);
4396 freeTreeInfo(level_info);
4402 static void SaveUserLevelInfo(void)
4404 CreateUserLevelSet(getLoginName(), getLoginName(), getRealName(), 100, FALSE);
4407 char *getSetupValue(int type, void *value)
4409 static char value_string[MAX_LINE_LEN];
4417 strcpy(value_string, (*(boolean *)value ? "true" : "false"));
4421 strcpy(value_string, (*(boolean *)value ? "on" : "off"));
4425 strcpy(value_string, (*(int *)value == AUTO ? "auto" :
4426 *(int *)value == FALSE ? "off" : "on"));
4430 strcpy(value_string, (*(boolean *)value ? "yes" : "no"));
4433 case TYPE_YES_NO_AUTO:
4434 strcpy(value_string, (*(int *)value == AUTO ? "auto" :
4435 *(int *)value == FALSE ? "no" : "yes"));
4439 strcpy(value_string, (*(boolean *)value ? "AGA" : "ECS"));
4443 strcpy(value_string, getKeyNameFromKey(*(Key *)value));
4447 strcpy(value_string, getX11KeyNameFromKey(*(Key *)value));
4451 sprintf(value_string, "%d", *(int *)value);
4455 if (*(char **)value == NULL)
4458 strcpy(value_string, *(char **)value);
4462 sprintf(value_string, "player_%d", *(int *)value + 1);
4466 value_string[0] = '\0';
4470 if (type & TYPE_GHOSTED)
4471 strcpy(value_string, "n/a");
4473 return value_string;
4476 char *getSetupLine(struct TokenInfo *token_info, char *prefix, int token_nr)
4480 static char token_string[MAX_LINE_LEN];
4481 int token_type = token_info[token_nr].type;
4482 void *setup_value = token_info[token_nr].value;
4483 char *token_text = token_info[token_nr].text;
4484 char *value_string = getSetupValue(token_type, setup_value);
4486 // build complete token string
4487 sprintf(token_string, "%s%s", prefix, token_text);
4489 // build setup entry line
4490 line = getFormattedSetupEntry(token_string, value_string);
4492 if (token_type == TYPE_KEY_X11)
4494 Key key = *(Key *)setup_value;
4495 char *keyname = getKeyNameFromKey(key);
4497 // add comment, if useful
4498 if (!strEqual(keyname, "(undefined)") &&
4499 !strEqual(keyname, "(unknown)"))
4501 // add at least one whitespace
4503 for (i = strlen(line); i < token_comment_position; i++)
4507 strcat(line, keyname);
4514 static void InitLastPlayedLevels_ParentNode(void)
4516 LevelDirTree **leveldir_top = &leveldir_first->node_group->next;
4517 LevelDirTree *leveldir_new = NULL;
4519 // check if parent node for last played levels already exists
4520 if (strEqual((*leveldir_top)->identifier, TOKEN_STR_LAST_LEVEL_SERIES))
4523 leveldir_new = newTreeInfo();
4525 setTreeInfoToDefaultsFromParent(leveldir_new, leveldir_first);
4527 leveldir_new->level_group = TRUE;
4528 leveldir_new->sort_priority = LEVELCLASS_LAST_PLAYED_LEVEL;
4530 setString(&leveldir_new->identifier, TOKEN_STR_LAST_LEVEL_SERIES);
4531 setString(&leveldir_new->name, "<< (last played level sets)");
4532 setString(&leveldir_new->name_sorting, leveldir_new->name);
4534 pushTreeInfo(leveldir_top, leveldir_new);
4536 // create node to link back to current level directory
4537 createParentTreeInfoNode(leveldir_new);
4540 void UpdateLastPlayedLevels_TreeInfo(void)
4542 char **last_level_series = setup.level_setup.last_level_series;
4543 boolean reset_leveldir_current = FALSE;
4544 LevelDirTree *leveldir_last;
4545 TreeInfo **node_new = NULL;
4548 if (last_level_series[0] == NULL)
4551 InitLastPlayedLevels_ParentNode();
4553 // check if current level set is from "last played" sub-tree to be rebuilt
4554 reset_leveldir_current = strEqual(leveldir_current->node_parent->identifier,
4555 TOKEN_STR_LAST_LEVEL_SERIES);
4557 leveldir_last = getTreeInfoFromIdentifierExt(leveldir_first,
4558 TOKEN_STR_LAST_LEVEL_SERIES,
4560 if (leveldir_last == NULL)
4563 node_new = &leveldir_last->node_group->next;
4565 freeTreeInfo(*node_new);
4569 for (i = 0; last_level_series[i] != NULL; i++)
4571 LevelDirTree *node_last = getTreeInfoFromIdentifier(leveldir_first,
4572 last_level_series[i]);
4573 if (node_last == NULL)
4576 *node_new = getTreeInfoCopy(node_last); // copy complete node
4578 (*node_new)->node_top = &leveldir_first; // correct top node link
4579 (*node_new)->node_parent = leveldir_last; // correct parent node link
4581 (*node_new)->is_copy = TRUE; // mark entry as node copy
4583 (*node_new)->node_group = NULL;
4584 (*node_new)->next = NULL;
4586 (*node_new)->cl_first = -1; // force setting tree cursor
4588 node_new = &((*node_new)->next);
4591 if (reset_leveldir_current)
4592 leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
4593 last_level_series[0]);
4596 static void UpdateLastPlayedLevels_List(void)
4598 char **last_level_series = setup.level_setup.last_level_series;
4599 int pos = MAX_LEVELDIR_HISTORY - 1;
4602 // search for potentially already existing entry in list of level sets
4603 for (i = 0; last_level_series[i] != NULL; i++)
4604 if (strEqual(last_level_series[i], leveldir_current->identifier))
4607 // move list of level sets one entry down (using potentially free entry)
4608 for (i = pos; i > 0; i--)
4609 setString(&last_level_series[i], last_level_series[i - 1]);
4611 // put last played level set at top position
4612 setString(&last_level_series[0], leveldir_current->identifier);
4615 void LoadLevelSetup_LastSeries(void)
4617 // --------------------------------------------------------------------------
4618 // ~/.<program>/levelsetup.conf
4619 // --------------------------------------------------------------------------
4621 char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
4622 SetupFileHash *level_setup_hash = NULL;
4626 // always start with reliable default values
4627 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
4629 // start with empty history of last played level sets
4630 setString(&setup.level_setup.last_level_series[0], NULL);
4632 if (!strEqual(DEFAULT_LEVELSET, UNDEFINED_LEVELSET))
4634 leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
4636 if (leveldir_current == NULL)
4637 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
4640 if ((level_setup_hash = loadSetupFileHash(filename)))
4642 char *last_level_series =
4643 getHashEntry(level_setup_hash, TOKEN_STR_LAST_LEVEL_SERIES);
4645 leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
4647 if (leveldir_current == NULL)
4648 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
4650 for (i = 0; i < MAX_LEVELDIR_HISTORY; i++)
4652 char token[strlen(TOKEN_STR_LAST_LEVEL_SERIES) + 10];
4653 LevelDirTree *leveldir_last;
4655 sprintf(token, "%s.%03d", TOKEN_STR_LAST_LEVEL_SERIES, i);
4657 last_level_series = getHashEntry(level_setup_hash, token);
4659 leveldir_last = getTreeInfoFromIdentifier(leveldir_first,
4661 if (leveldir_last != NULL)
4662 setString(&setup.level_setup.last_level_series[pos++],
4666 setString(&setup.level_setup.last_level_series[pos], NULL);
4668 freeSetupFileHash(level_setup_hash);
4672 Debug("setup", "using default setup values");
4678 static void SaveLevelSetup_LastSeries_Ext(boolean deactivate_last_level_series)
4680 // --------------------------------------------------------------------------
4681 // ~/.<program>/levelsetup.conf
4682 // --------------------------------------------------------------------------
4684 // check if the current level directory structure is available at this point
4685 if (leveldir_current == NULL)
4688 char **last_level_series = setup.level_setup.last_level_series;
4689 char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
4693 InitUserDataDirectory();
4695 UpdateLastPlayedLevels_List();
4697 if (!(file = fopen(filename, MODE_WRITE)))
4699 Warn("cannot write setup file '%s'", filename);
4706 fprintFileHeader(file, LEVELSETUP_FILENAME);
4708 if (deactivate_last_level_series)
4709 fprintf(file, "# %s\n# ", "the following level set may have caused a problem and was deactivated");
4711 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_LAST_LEVEL_SERIES,
4712 leveldir_current->identifier));
4714 for (i = 0; last_level_series[i] != NULL; i++)
4716 char token[strlen(TOKEN_STR_LAST_LEVEL_SERIES) + 10];
4718 sprintf(token, "%s.%03d", TOKEN_STR_LAST_LEVEL_SERIES, i);
4720 fprintf(file, "%s\n", getFormattedSetupEntry(token, last_level_series[i]));
4725 SetFilePermissions(filename, PERMS_PRIVATE);
4730 void SaveLevelSetup_LastSeries(void)
4732 SaveLevelSetup_LastSeries_Ext(FALSE);
4735 void SaveLevelSetup_LastSeries_Deactivate(void)
4737 SaveLevelSetup_LastSeries_Ext(TRUE);
4740 static void checkSeriesInfo(void)
4742 static char *level_directory = NULL;
4745 DirectoryEntry *dir_entry;
4748 checked_free(level_directory);
4750 // check for more levels besides the 'levels' field of 'levelinfo.conf'
4752 level_directory = getPath2((leveldir_current->in_user_dir ?
4753 getUserLevelDir(NULL) :
4754 options.level_directory),
4755 leveldir_current->fullpath);
4757 if ((dir = openDirectory(level_directory)) == NULL)
4759 Warn("cannot read level directory '%s'", level_directory);
4765 while ((dir_entry = readDirectory(dir)) != NULL) // loop all entries
4767 if (strlen(dir_entry->basename) > 4 &&
4768 dir_entry->basename[3] == '.' &&
4769 strEqual(&dir_entry->basename[4], LEVELFILE_EXTENSION))
4771 char levelnum_str[4];
4774 strncpy(levelnum_str, dir_entry->basename, 3);
4775 levelnum_str[3] = '\0';
4777 levelnum_value = atoi(levelnum_str);
4779 if (levelnum_value < leveldir_current->first_level)
4781 Warn("additional level %d found", levelnum_value);
4783 leveldir_current->first_level = levelnum_value;
4785 else if (levelnum_value > leveldir_current->last_level)
4787 Warn("additional level %d found", levelnum_value);
4789 leveldir_current->last_level = levelnum_value;
4795 closeDirectory(dir);
4798 void LoadLevelSetup_SeriesInfo(void)
4801 SetupFileHash *level_setup_hash = NULL;
4802 char *level_subdir = leveldir_current->subdir;
4805 // always start with reliable default values
4806 level_nr = leveldir_current->first_level;
4808 for (i = 0; i < MAX_LEVELS; i++)
4810 LevelStats_setPlayed(i, 0);
4811 LevelStats_setSolved(i, 0);
4816 // --------------------------------------------------------------------------
4817 // ~/.<program>/levelsetup/<level series>/levelsetup.conf
4818 // --------------------------------------------------------------------------
4820 level_subdir = leveldir_current->subdir;
4822 filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
4824 if ((level_setup_hash = loadSetupFileHash(filename)))
4828 // get last played level in this level set
4830 token_value = getHashEntry(level_setup_hash, TOKEN_STR_LAST_PLAYED_LEVEL);
4834 level_nr = atoi(token_value);
4836 if (level_nr < leveldir_current->first_level)
4837 level_nr = leveldir_current->first_level;
4838 if (level_nr > leveldir_current->last_level)
4839 level_nr = leveldir_current->last_level;
4842 // get handicap level in this level set
4844 token_value = getHashEntry(level_setup_hash, TOKEN_STR_HANDICAP_LEVEL);
4848 int level_nr = atoi(token_value);
4850 if (level_nr < leveldir_current->first_level)
4851 level_nr = leveldir_current->first_level;
4852 if (level_nr > leveldir_current->last_level + 1)
4853 level_nr = leveldir_current->last_level;
4855 if (leveldir_current->user_defined || !leveldir_current->handicap)
4856 level_nr = leveldir_current->last_level;
4858 leveldir_current->handicap_level = level_nr;
4861 // get number of played and solved levels in this level set
4863 BEGIN_HASH_ITERATION(level_setup_hash, itr)
4865 char *token = HASH_ITERATION_TOKEN(itr);
4866 char *value = HASH_ITERATION_VALUE(itr);
4868 if (strlen(token) == 3 &&
4869 token[0] >= '0' && token[0] <= '9' &&
4870 token[1] >= '0' && token[1] <= '9' &&
4871 token[2] >= '0' && token[2] <= '9')
4873 int level_nr = atoi(token);
4876 LevelStats_setPlayed(level_nr, atoi(value)); // read 1st column
4878 value = strchr(value, ' ');
4881 LevelStats_setSolved(level_nr, atoi(value)); // read 2nd column
4884 END_HASH_ITERATION(hash, itr)
4886 freeSetupFileHash(level_setup_hash);
4890 Debug("setup", "using default setup values");
4896 void SaveLevelSetup_SeriesInfo(void)
4899 char *level_subdir = leveldir_current->subdir;
4900 char *level_nr_str = int2str(level_nr, 0);
4901 char *handicap_level_str = int2str(leveldir_current->handicap_level, 0);
4905 // --------------------------------------------------------------------------
4906 // ~/.<program>/levelsetup/<level series>/levelsetup.conf
4907 // --------------------------------------------------------------------------
4909 InitLevelSetupDirectory(level_subdir);
4911 filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
4913 if (!(file = fopen(filename, MODE_WRITE)))
4915 Warn("cannot write setup file '%s'", filename);
4922 fprintFileHeader(file, LEVELSETUP_FILENAME);
4924 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_PLAYED_LEVEL,
4926 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_HANDICAP_LEVEL,
4927 handicap_level_str));
4929 for (i = leveldir_current->first_level; i <= leveldir_current->last_level;
4932 if (LevelStats_getPlayed(i) > 0 ||
4933 LevelStats_getSolved(i) > 0)
4938 sprintf(token, "%03d", i);
4939 sprintf(value, "%d %d", LevelStats_getPlayed(i), LevelStats_getSolved(i));
4941 fprintf(file, "%s\n", getFormattedSetupEntry(token, value));
4947 SetFilePermissions(filename, PERMS_PRIVATE);
4952 int LevelStats_getPlayed(int nr)
4954 return (nr >= 0 && nr < MAX_LEVELS ? level_stats[nr].played : 0);
4957 int LevelStats_getSolved(int nr)
4959 return (nr >= 0 && nr < MAX_LEVELS ? level_stats[nr].solved : 0);
4962 void LevelStats_setPlayed(int nr, int value)
4964 if (nr >= 0 && nr < MAX_LEVELS)
4965 level_stats[nr].played = value;
4968 void LevelStats_setSolved(int nr, int value)
4970 if (nr >= 0 && nr < MAX_LEVELS)
4971 level_stats[nr].solved = value;
4974 void LevelStats_incPlayed(int nr)
4976 if (nr >= 0 && nr < MAX_LEVELS)
4977 level_stats[nr].played++;
4980 void LevelStats_incSolved(int nr)
4982 if (nr >= 0 && nr < MAX_LEVELS)
4983 level_stats[nr].solved++;
4986 void LoadUserSetup(void)
4988 // --------------------------------------------------------------------------
4989 // ~/.<program>/usersetup.conf
4990 // --------------------------------------------------------------------------
4992 char *filename = getPath2(getMainUserGameDataDir(), USERSETUP_FILENAME);
4993 SetupFileHash *user_setup_hash = NULL;
4995 // always start with reliable default values
4998 if ((user_setup_hash = loadSetupFileHash(filename)))
5002 // get last selected user number
5003 token_value = getHashEntry(user_setup_hash, TOKEN_STR_LAST_USER);
5006 user.nr = MIN(MAX(0, atoi(token_value)), MAX_PLAYER_NAMES - 1);
5008 freeSetupFileHash(user_setup_hash);
5012 Debug("setup", "using default setup values");
5018 void SaveUserSetup(void)
5020 // --------------------------------------------------------------------------
5021 // ~/.<program>/usersetup.conf
5022 // --------------------------------------------------------------------------
5024 char *filename = getPath2(getMainUserGameDataDir(), USERSETUP_FILENAME);
5027 InitMainUserDataDirectory();
5029 if (!(file = fopen(filename, MODE_WRITE)))
5031 Warn("cannot write setup file '%s'", filename);
5038 fprintFileHeader(file, USERSETUP_FILENAME);
5040 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_USER,
5044 SetFilePermissions(filename, PERMS_PRIVATE);