1 // ============================================================================
2 // Artsoft Retro-Game Library
3 // ----------------------------------------------------------------------------
4 // (c) 1995-2014 by Artsoft Entertainment
7 // https://www.artsoft.org/
8 // ----------------------------------------------------------------------------
10 // ============================================================================
12 #include <sys/types.h>
26 #include "zip/miniunz.h"
29 #define ENABLE_UNUSED_CODE FALSE // for currently unused functions
30 #define DEBUG_NO_CONFIG_FILE FALSE // for extra-verbose debug output
32 #define NUM_LEVELCLASS_DESC 8
34 static char *levelclass_desc[NUM_LEVELCLASS_DESC] =
46 #define TOKEN_VALUE_POSITION_SHORT 32
47 #define TOKEN_VALUE_POSITION_DEFAULT 40
48 #define TOKEN_COMMENT_POSITION_DEFAULT 60
50 #define MAX_COOKIE_LEN 256
53 static void setTreeInfoToDefaults(TreeInfo *, int);
54 static TreeInfo *getTreeInfoCopy(TreeInfo *ti);
55 static int compareTreeInfoEntries(const void *, const void *);
57 static int token_value_position = TOKEN_VALUE_POSITION_DEFAULT;
58 static int token_comment_position = TOKEN_COMMENT_POSITION_DEFAULT;
60 static SetupFileHash *artworkinfo_cache_old = NULL;
61 static SetupFileHash *artworkinfo_cache_new = NULL;
62 static SetupFileHash *optional_tokens_hash = NULL;
63 static boolean use_artworkinfo_cache = TRUE;
64 static boolean update_artworkinfo_cache = FALSE;
67 // ----------------------------------------------------------------------------
69 // ----------------------------------------------------------------------------
71 static char *getLevelClassDescription(TreeInfo *ti)
73 int position = ti->sort_priority / 100;
75 if (position >= 0 && position < NUM_LEVELCLASS_DESC)
76 return levelclass_desc[position];
78 return "Unknown Level Class";
81 static char *getScoreDir(char *level_subdir)
83 static char *score_dir = NULL;
84 static char *score_level_dir = NULL;
85 char *score_subdir = SCORES_DIRECTORY;
87 if (score_dir == NULL)
89 if (program.global_scores)
90 score_dir = getPath2(getCommonDataDir(), score_subdir);
92 score_dir = getPath2(getMainUserGameDataDir(), score_subdir);
95 if (level_subdir != NULL)
97 checked_free(score_level_dir);
99 score_level_dir = getPath2(score_dir, level_subdir);
101 return score_level_dir;
107 static char *getUserSubdir(int nr)
109 static char user_subdir[16] = { 0 };
111 sprintf(user_subdir, "%03d", nr);
116 static char *getUserDir(int nr)
118 static char *user_dir = NULL;
119 char *main_data_dir = getMainUserGameDataDir();
120 char *users_subdir = USERS_DIRECTORY;
121 char *user_subdir = getUserSubdir(nr);
123 checked_free(user_dir);
126 user_dir = getPath3(main_data_dir, users_subdir, user_subdir);
128 user_dir = getPath2(main_data_dir, users_subdir);
133 static char *getLevelSetupDir(char *level_subdir)
135 static char *levelsetup_dir = NULL;
136 char *data_dir = getUserGameDataDir();
137 char *levelsetup_subdir = LEVELSETUP_DIRECTORY;
139 checked_free(levelsetup_dir);
141 if (level_subdir != NULL)
142 levelsetup_dir = getPath3(data_dir, levelsetup_subdir, level_subdir);
144 levelsetup_dir = getPath2(data_dir, levelsetup_subdir);
146 return levelsetup_dir;
149 static char *getCacheDir(void)
151 static char *cache_dir = NULL;
153 if (cache_dir == NULL)
154 cache_dir = getPath2(getMainUserGameDataDir(), CACHE_DIRECTORY);
159 static char *getNetworkDir(void)
161 static char *network_dir = NULL;
163 if (network_dir == NULL)
164 network_dir = getPath2(getMainUserGameDataDir(), NETWORK_DIRECTORY);
169 char *getLevelDirFromTreeInfo(TreeInfo *node)
171 static char *level_dir = NULL;
174 return options.level_directory;
176 checked_free(level_dir);
178 level_dir = getPath2((node->in_user_dir ? getUserLevelDir(NULL) :
179 options.level_directory), node->fullpath);
184 char *getUserLevelDir(char *level_subdir)
186 static char *userlevel_dir = NULL;
187 char *data_dir = getMainUserGameDataDir();
188 char *userlevel_subdir = LEVELS_DIRECTORY;
190 checked_free(userlevel_dir);
192 if (level_subdir != NULL)
193 userlevel_dir = getPath3(data_dir, userlevel_subdir, level_subdir);
195 userlevel_dir = getPath2(data_dir, userlevel_subdir);
197 return userlevel_dir;
200 char *getNetworkLevelDir(char *level_subdir)
202 static char *network_level_dir = NULL;
203 char *data_dir = getNetworkDir();
204 char *networklevel_subdir = LEVELS_DIRECTORY;
206 checked_free(network_level_dir);
208 if (level_subdir != NULL)
209 network_level_dir = getPath3(data_dir, networklevel_subdir, level_subdir);
211 network_level_dir = getPath2(data_dir, networklevel_subdir);
213 return network_level_dir;
216 char *getCurrentLevelDir(void)
218 return getLevelDirFromTreeInfo(leveldir_current);
221 char *getNewUserLevelSubdir(void)
223 static char *new_level_subdir = NULL;
224 char *subdir_prefix = getLoginName();
225 char subdir_suffix[10];
226 int max_suffix_number = 1000;
229 while (++i < max_suffix_number)
231 sprintf(subdir_suffix, "_%d", i);
233 checked_free(new_level_subdir);
234 new_level_subdir = getStringCat2(subdir_prefix, subdir_suffix);
236 if (!directoryExists(getUserLevelDir(new_level_subdir)))
240 return new_level_subdir;
243 static char *getTapeDir(char *level_subdir)
245 static char *tape_dir = NULL;
246 char *data_dir = getUserGameDataDir();
247 char *tape_subdir = TAPES_DIRECTORY;
249 checked_free(tape_dir);
251 if (level_subdir != NULL)
252 tape_dir = getPath3(data_dir, tape_subdir, level_subdir);
254 tape_dir = getPath2(data_dir, tape_subdir);
259 static char *getSolutionTapeDir(void)
261 static char *tape_dir = NULL;
262 char *data_dir = getCurrentLevelDir();
263 char *tape_subdir = TAPES_DIRECTORY;
265 checked_free(tape_dir);
267 tape_dir = getPath2(data_dir, tape_subdir);
272 static char *getDefaultGraphicsDir(char *graphics_subdir)
274 static char *graphics_dir = NULL;
276 if (graphics_subdir == NULL)
277 return options.graphics_directory;
279 checked_free(graphics_dir);
281 graphics_dir = getPath2(options.graphics_directory, graphics_subdir);
286 static char *getDefaultSoundsDir(char *sounds_subdir)
288 static char *sounds_dir = NULL;
290 if (sounds_subdir == NULL)
291 return options.sounds_directory;
293 checked_free(sounds_dir);
295 sounds_dir = getPath2(options.sounds_directory, sounds_subdir);
300 static char *getDefaultMusicDir(char *music_subdir)
302 static char *music_dir = NULL;
304 if (music_subdir == NULL)
305 return options.music_directory;
307 checked_free(music_dir);
309 music_dir = getPath2(options.music_directory, music_subdir);
314 static char *getClassicArtworkSet(int type)
316 return (type == TREE_TYPE_GRAPHICS_DIR ? GFX_CLASSIC_SUBDIR :
317 type == TREE_TYPE_SOUNDS_DIR ? SND_CLASSIC_SUBDIR :
318 type == TREE_TYPE_MUSIC_DIR ? MUS_CLASSIC_SUBDIR : "");
321 static char *getClassicArtworkDir(int type)
323 return (type == TREE_TYPE_GRAPHICS_DIR ?
324 getDefaultGraphicsDir(GFX_CLASSIC_SUBDIR) :
325 type == TREE_TYPE_SOUNDS_DIR ?
326 getDefaultSoundsDir(SND_CLASSIC_SUBDIR) :
327 type == TREE_TYPE_MUSIC_DIR ?
328 getDefaultMusicDir(MUS_CLASSIC_SUBDIR) : "");
331 char *getUserGraphicsDir(void)
333 static char *usergraphics_dir = NULL;
335 if (usergraphics_dir == NULL)
336 usergraphics_dir = getPath2(getMainUserGameDataDir(), GRAPHICS_DIRECTORY);
338 return usergraphics_dir;
341 char *getUserSoundsDir(void)
343 static char *usersounds_dir = NULL;
345 if (usersounds_dir == NULL)
346 usersounds_dir = getPath2(getMainUserGameDataDir(), SOUNDS_DIRECTORY);
348 return usersounds_dir;
351 char *getUserMusicDir(void)
353 static char *usermusic_dir = NULL;
355 if (usermusic_dir == NULL)
356 usermusic_dir = getPath2(getMainUserGameDataDir(), MUSIC_DIRECTORY);
358 return usermusic_dir;
361 static char *getSetupArtworkDir(TreeInfo *ti)
363 static char *artwork_dir = NULL;
368 checked_free(artwork_dir);
370 artwork_dir = getPath2(ti->basepath, ti->fullpath);
375 char *setLevelArtworkDir(TreeInfo *ti)
377 char **artwork_path_ptr, **artwork_set_ptr;
378 TreeInfo *level_artwork;
380 if (ti == NULL || leveldir_current == NULL)
383 artwork_path_ptr = LEVELDIR_ARTWORK_PATH_PTR(leveldir_current, ti->type);
384 artwork_set_ptr = LEVELDIR_ARTWORK_SET_PTR( leveldir_current, ti->type);
386 checked_free(*artwork_path_ptr);
388 if ((level_artwork = getTreeInfoFromIdentifier(ti, *artwork_set_ptr)))
390 *artwork_path_ptr = getStringCopy(getSetupArtworkDir(level_artwork));
395 No (or non-existing) artwork configured in "levelinfo.conf". This would
396 normally result in using the artwork configured in the setup menu. But
397 if an artwork subdirectory exists (which might contain custom artwork
398 or an artwork configuration file), this level artwork must be treated
399 as relative to the default "classic" artwork, not to the artwork that
400 is currently configured in the setup menu.
402 Update: For "special" versions of R'n'D (like "R'n'D jue"), do not use
403 the "default" artwork (which would be "jue0" for "R'n'D jue"), but use
404 the real "classic" artwork from the original R'n'D (like "gfx_classic").
407 char *dir = getPath2(getCurrentLevelDir(), ARTWORK_DIRECTORY(ti->type));
409 checked_free(*artwork_set_ptr);
411 if (directoryExists(dir))
413 *artwork_path_ptr = getStringCopy(getClassicArtworkDir(ti->type));
414 *artwork_set_ptr = getStringCopy(getClassicArtworkSet(ti->type));
418 *artwork_path_ptr = getStringCopy(UNDEFINED_FILENAME);
419 *artwork_set_ptr = NULL;
425 return *artwork_set_ptr;
428 static char *getLevelArtworkSet(int type)
430 if (leveldir_current == NULL)
433 return LEVELDIR_ARTWORK_SET(leveldir_current, type);
436 static char *getLevelArtworkDir(int type)
438 if (leveldir_current == NULL)
439 return UNDEFINED_FILENAME;
441 return LEVELDIR_ARTWORK_PATH(leveldir_current, type);
444 char *getProgramMainDataPath(char *command_filename, char *base_path)
446 // check if the program's main data base directory is configured
447 if (!strEqual(base_path, "."))
448 return getStringCopy(base_path);
450 /* if the program is configured to start from current directory (default),
451 determine program package directory from program binary (some versions
452 of KDE/Konqueror and Mac OS X (especially "Mavericks") apparently do not
453 set the current working directory to the program package directory) */
454 char *main_data_path = getBasePath(command_filename);
456 #if defined(PLATFORM_MACOSX)
457 if (strSuffix(main_data_path, MAC_APP_BINARY_SUBDIR))
459 char *main_data_path_old = main_data_path;
461 // cut relative path to Mac OS X application binary directory from path
462 main_data_path[strlen(main_data_path) -
463 strlen(MAC_APP_BINARY_SUBDIR)] = '\0';
465 // cut trailing path separator from path (but not if path is root directory)
466 if (strSuffix(main_data_path, "/") && !strEqual(main_data_path, "/"))
467 main_data_path[strlen(main_data_path) - 1] = '\0';
469 // replace empty path with current directory
470 if (strEqual(main_data_path, ""))
471 main_data_path = ".";
473 // add relative path to Mac OS X application resources directory to path
474 main_data_path = getPath2(main_data_path, MAC_APP_FILES_SUBDIR);
476 free(main_data_path_old);
480 return main_data_path;
483 char *getProgramConfigFilename(char *command_filename)
485 static char *config_filename_1 = NULL;
486 static char *config_filename_2 = NULL;
487 static char *config_filename_3 = NULL;
488 static boolean initialized = FALSE;
492 char *command_filename_1 = getStringCopy(command_filename);
494 // strip trailing executable suffix from command filename
495 if (strSuffix(command_filename_1, ".exe"))
496 command_filename_1[strlen(command_filename_1) - 4] = '\0';
498 char *ro_base_path = getProgramMainDataPath(command_filename, RO_BASE_PATH);
499 char *conf_directory = getPath2(ro_base_path, CONF_DIRECTORY);
501 char *command_basepath = getBasePath(command_filename);
502 char *command_basename = getBaseNameNoSuffix(command_filename);
503 char *command_filename_2 = getPath2(command_basepath, command_basename);
505 config_filename_1 = getStringCat2(command_filename_1, ".conf");
506 config_filename_2 = getStringCat2(command_filename_2, ".conf");
507 config_filename_3 = getPath2(conf_directory, SETUP_FILENAME);
509 checked_free(ro_base_path);
510 checked_free(conf_directory);
512 checked_free(command_basepath);
513 checked_free(command_basename);
515 checked_free(command_filename_1);
516 checked_free(command_filename_2);
521 // 1st try: look for config file that exactly matches the binary filename
522 if (fileExists(config_filename_1))
523 return config_filename_1;
525 // 2nd try: look for config file that matches binary filename without suffix
526 if (fileExists(config_filename_2))
527 return config_filename_2;
529 // 3rd try: return setup config filename in global program config directory
530 return config_filename_3;
533 char *getTapeFilename(int nr)
535 static char *filename = NULL;
536 char basename[MAX_FILENAME_LEN];
538 checked_free(filename);
540 sprintf(basename, "%03d.%s", nr, TAPEFILE_EXTENSION);
541 filename = getPath2(getTapeDir(leveldir_current->subdir), basename);
546 char *getSolutionTapeFilename(int nr)
548 static char *filename = NULL;
549 char basename[MAX_FILENAME_LEN];
551 checked_free(filename);
553 sprintf(basename, "%03d.%s", nr, TAPEFILE_EXTENSION);
554 filename = getPath2(getSolutionTapeDir(), basename);
556 if (!fileExists(filename))
558 static char *filename_sln = NULL;
560 checked_free(filename_sln);
562 sprintf(basename, "%03d.sln", nr);
563 filename_sln = getPath2(getSolutionTapeDir(), basename);
565 if (fileExists(filename_sln))
572 char *getScoreFilename(int nr)
574 static char *filename = NULL;
575 char basename[MAX_FILENAME_LEN];
577 checked_free(filename);
579 sprintf(basename, "%03d.%s", nr, SCOREFILE_EXTENSION);
581 // used instead of "leveldir_current->subdir" (for network games)
582 filename = getPath2(getScoreDir(levelset.identifier), basename);
587 char *getSetupFilename(void)
589 static char *filename = NULL;
591 checked_free(filename);
593 filename = getPath2(getSetupDir(), SETUP_FILENAME);
598 char *getDefaultSetupFilename(void)
600 return program.config_filename;
603 char *getEditorSetupFilename(void)
605 static char *filename = NULL;
607 checked_free(filename);
608 filename = getPath2(getCurrentLevelDir(), EDITORSETUP_FILENAME);
610 if (fileExists(filename))
613 checked_free(filename);
614 filename = getPath2(getSetupDir(), EDITORSETUP_FILENAME);
619 char *getHelpAnimFilename(void)
621 static char *filename = NULL;
623 checked_free(filename);
625 filename = getPath2(getCurrentLevelDir(), HELPANIM_FILENAME);
630 char *getHelpTextFilename(void)
632 static char *filename = NULL;
634 checked_free(filename);
636 filename = getPath2(getCurrentLevelDir(), HELPTEXT_FILENAME);
641 char *getLevelSetInfoFilename(void)
643 static char *filename = NULL;
658 for (i = 0; basenames[i] != NULL; i++)
660 checked_free(filename);
661 filename = getPath2(getCurrentLevelDir(), basenames[i]);
663 if (fileExists(filename))
670 static char *getLevelSetTitleMessageBasename(int nr, boolean initial)
672 static char basename[32];
674 sprintf(basename, "%s_%d.txt",
675 (initial ? "titlemessage_initial" : "titlemessage"), nr + 1);
680 char *getLevelSetTitleMessageFilename(int nr, boolean initial)
682 static char *filename = NULL;
684 boolean skip_setup_artwork = FALSE;
686 checked_free(filename);
688 basename = getLevelSetTitleMessageBasename(nr, initial);
690 if (!gfx.override_level_graphics)
692 // 1st try: look for special artwork in current level series directory
693 filename = getPath3(getCurrentLevelDir(), GRAPHICS_DIRECTORY, basename);
694 if (fileExists(filename))
699 // 2nd try: look for message file in current level set directory
700 filename = getPath2(getCurrentLevelDir(), basename);
701 if (fileExists(filename))
706 // check if there is special artwork configured in level series config
707 if (getLevelArtworkSet(ARTWORK_TYPE_GRAPHICS) != NULL)
709 // 3rd try: look for special artwork configured in level series config
710 filename = getPath2(getLevelArtworkDir(ARTWORK_TYPE_GRAPHICS), basename);
711 if (fileExists(filename))
716 // take missing artwork configured in level set config from default
717 skip_setup_artwork = TRUE;
721 if (!skip_setup_artwork)
723 // 4th try: look for special artwork in configured artwork directory
724 filename = getPath2(getSetupArtworkDir(artwork.gfx_current), basename);
725 if (fileExists(filename))
731 // 5th try: look for default artwork in new default artwork directory
732 filename = getPath2(getDefaultGraphicsDir(GFX_DEFAULT_SUBDIR), basename);
733 if (fileExists(filename))
738 // 6th try: look for default artwork in old default artwork directory
739 filename = getPath2(options.graphics_directory, basename);
740 if (fileExists(filename))
743 return NULL; // cannot find specified artwork file anywhere
746 static char *getCorrectedArtworkBasename(char *basename)
751 char *getCustomImageFilename(char *basename)
753 static char *filename = NULL;
754 boolean skip_setup_artwork = FALSE;
756 checked_free(filename);
758 basename = getCorrectedArtworkBasename(basename);
760 if (!gfx.override_level_graphics)
762 // 1st try: look for special artwork in current level series directory
763 filename = getImg3(getCurrentLevelDir(), GRAPHICS_DIRECTORY, basename);
764 if (fileExists(filename))
769 // check if there is special artwork configured in level series config
770 if (getLevelArtworkSet(ARTWORK_TYPE_GRAPHICS) != NULL)
772 // 2nd try: look for special artwork configured in level series config
773 filename = getImg2(getLevelArtworkDir(ARTWORK_TYPE_GRAPHICS), basename);
774 if (fileExists(filename))
779 // take missing artwork configured in level set config from default
780 skip_setup_artwork = TRUE;
784 if (!skip_setup_artwork)
786 // 3rd try: look for special artwork in configured artwork directory
787 filename = getImg2(getSetupArtworkDir(artwork.gfx_current), basename);
788 if (fileExists(filename))
794 // 4th try: look for default artwork in new default artwork directory
795 filename = getImg2(getDefaultGraphicsDir(GFX_DEFAULT_SUBDIR), basename);
796 if (fileExists(filename))
801 // 5th try: look for default artwork in old default artwork directory
802 filename = getImg2(options.graphics_directory, basename);
803 if (fileExists(filename))
806 if (!strEqual(GFX_FALLBACK_FILENAME, UNDEFINED_FILENAME))
810 Warn("cannot find artwork file '%s' (using fallback)", basename);
812 // 6th try: look for fallback artwork in old default artwork directory
813 // (needed to prevent errors when trying to access unused artwork files)
814 filename = getImg2(options.graphics_directory, GFX_FALLBACK_FILENAME);
815 if (fileExists(filename))
819 return NULL; // cannot find specified artwork file anywhere
822 char *getCustomSoundFilename(char *basename)
824 static char *filename = NULL;
825 boolean skip_setup_artwork = FALSE;
827 checked_free(filename);
829 basename = getCorrectedArtworkBasename(basename);
831 if (!gfx.override_level_sounds)
833 // 1st try: look for special artwork in current level series directory
834 filename = getPath3(getCurrentLevelDir(), SOUNDS_DIRECTORY, basename);
835 if (fileExists(filename))
840 // check if there is special artwork configured in level series config
841 if (getLevelArtworkSet(ARTWORK_TYPE_SOUNDS) != NULL)
843 // 2nd try: look for special artwork configured in level series config
844 filename = getPath2(getLevelArtworkDir(TREE_TYPE_SOUNDS_DIR), basename);
845 if (fileExists(filename))
850 // take missing artwork configured in level set config from default
851 skip_setup_artwork = TRUE;
855 if (!skip_setup_artwork)
857 // 3rd try: look for special artwork in configured artwork directory
858 filename = getPath2(getSetupArtworkDir(artwork.snd_current), basename);
859 if (fileExists(filename))
865 // 4th try: look for default artwork in new default artwork directory
866 filename = getPath2(getDefaultSoundsDir(SND_DEFAULT_SUBDIR), basename);
867 if (fileExists(filename))
872 // 5th try: look for default artwork in old default artwork directory
873 filename = getPath2(options.sounds_directory, basename);
874 if (fileExists(filename))
877 if (!strEqual(SND_FALLBACK_FILENAME, UNDEFINED_FILENAME))
881 Warn("cannot find artwork file '%s' (using fallback)", basename);
883 // 6th try: look for fallback artwork in old default artwork directory
884 // (needed to prevent errors when trying to access unused artwork files)
885 filename = getPath2(options.sounds_directory, SND_FALLBACK_FILENAME);
886 if (fileExists(filename))
890 return NULL; // cannot find specified artwork file anywhere
893 char *getCustomMusicFilename(char *basename)
895 static char *filename = NULL;
896 boolean skip_setup_artwork = FALSE;
898 checked_free(filename);
900 basename = getCorrectedArtworkBasename(basename);
902 if (!gfx.override_level_music)
904 // 1st try: look for special artwork in current level series directory
905 filename = getPath3(getCurrentLevelDir(), MUSIC_DIRECTORY, basename);
906 if (fileExists(filename))
911 // check if there is special artwork configured in level series config
912 if (getLevelArtworkSet(ARTWORK_TYPE_MUSIC) != NULL)
914 // 2nd try: look for special artwork configured in level series config
915 filename = getPath2(getLevelArtworkDir(TREE_TYPE_MUSIC_DIR), basename);
916 if (fileExists(filename))
921 // take missing artwork configured in level set config from default
922 skip_setup_artwork = TRUE;
926 if (!skip_setup_artwork)
928 // 3rd try: look for special artwork in configured artwork directory
929 filename = getPath2(getSetupArtworkDir(artwork.mus_current), basename);
930 if (fileExists(filename))
936 // 4th try: look for default artwork in new default artwork directory
937 filename = getPath2(getDefaultMusicDir(MUS_DEFAULT_SUBDIR), basename);
938 if (fileExists(filename))
943 // 5th try: look for default artwork in old default artwork directory
944 filename = getPath2(options.music_directory, basename);
945 if (fileExists(filename))
948 if (!strEqual(MUS_FALLBACK_FILENAME, UNDEFINED_FILENAME))
952 Warn("cannot find artwork file '%s' (using fallback)", basename);
954 // 6th try: look for fallback artwork in old default artwork directory
955 // (needed to prevent errors when trying to access unused artwork files)
956 filename = getPath2(options.music_directory, MUS_FALLBACK_FILENAME);
957 if (fileExists(filename))
961 return NULL; // cannot find specified artwork file anywhere
964 char *getCustomArtworkFilename(char *basename, int type)
966 if (type == ARTWORK_TYPE_GRAPHICS)
967 return getCustomImageFilename(basename);
968 else if (type == ARTWORK_TYPE_SOUNDS)
969 return getCustomSoundFilename(basename);
970 else if (type == ARTWORK_TYPE_MUSIC)
971 return getCustomMusicFilename(basename);
973 return UNDEFINED_FILENAME;
976 char *getCustomArtworkConfigFilename(int type)
978 return getCustomArtworkFilename(ARTWORKINFO_FILENAME(type), type);
981 char *getCustomArtworkLevelConfigFilename(int type)
983 static char *filename = NULL;
985 checked_free(filename);
987 filename = getPath2(getLevelArtworkDir(type), ARTWORKINFO_FILENAME(type));
992 char *getCustomMusicDirectory(void)
994 static char *directory = NULL;
995 boolean skip_setup_artwork = FALSE;
997 checked_free(directory);
999 if (!gfx.override_level_music)
1001 // 1st try: look for special artwork in current level series directory
1002 directory = getPath2(getCurrentLevelDir(), MUSIC_DIRECTORY);
1003 if (directoryExists(directory))
1008 // check if there is special artwork configured in level series config
1009 if (getLevelArtworkSet(ARTWORK_TYPE_MUSIC) != NULL)
1011 // 2nd try: look for special artwork configured in level series config
1012 directory = getStringCopy(getLevelArtworkDir(TREE_TYPE_MUSIC_DIR));
1013 if (directoryExists(directory))
1018 // take missing artwork configured in level set config from default
1019 skip_setup_artwork = TRUE;
1023 if (!skip_setup_artwork)
1025 // 3rd try: look for special artwork in configured artwork directory
1026 directory = getStringCopy(getSetupArtworkDir(artwork.mus_current));
1027 if (directoryExists(directory))
1033 // 4th try: look for default artwork in new default artwork directory
1034 directory = getStringCopy(getDefaultMusicDir(MUS_DEFAULT_SUBDIR));
1035 if (directoryExists(directory))
1040 // 5th try: look for default artwork in old default artwork directory
1041 directory = getStringCopy(options.music_directory);
1042 if (directoryExists(directory))
1045 return NULL; // cannot find specified artwork file anywhere
1048 void InitTapeDirectory(char *level_subdir)
1050 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
1051 createDirectory(getTapeDir(NULL), "main tape", PERMS_PRIVATE);
1052 createDirectory(getTapeDir(level_subdir), "level tape", PERMS_PRIVATE);
1055 void InitScoreDirectory(char *level_subdir)
1057 int permissions = (program.global_scores ? PERMS_PUBLIC : PERMS_PRIVATE);
1059 if (program.global_scores)
1060 createDirectory(getCommonDataDir(), "common data", permissions);
1062 createDirectory(getMainUserGameDataDir(), "main user data", permissions);
1064 createDirectory(getScoreDir(NULL), "main score", permissions);
1065 createDirectory(getScoreDir(level_subdir), "level score", permissions);
1068 static void SaveUserLevelInfo(void);
1070 void InitUserLevelDirectory(char *level_subdir)
1072 if (!directoryExists(getUserLevelDir(level_subdir)))
1074 createDirectory(getMainUserGameDataDir(), "main user data", PERMS_PRIVATE);
1075 createDirectory(getUserLevelDir(NULL), "main user level", PERMS_PRIVATE);
1076 createDirectory(getUserLevelDir(level_subdir), "user level", PERMS_PRIVATE);
1078 if (setup.internal.create_user_levelset)
1079 SaveUserLevelInfo();
1083 void InitNetworkLevelDirectory(char *level_subdir)
1085 if (!directoryExists(getNetworkLevelDir(level_subdir)))
1087 createDirectory(getMainUserGameDataDir(), "main user data", PERMS_PRIVATE);
1088 createDirectory(getNetworkDir(), "network data", PERMS_PRIVATE);
1089 createDirectory(getNetworkLevelDir(NULL), "main network level", PERMS_PRIVATE);
1090 createDirectory(getNetworkLevelDir(level_subdir), "network level", PERMS_PRIVATE);
1094 void InitLevelSetupDirectory(char *level_subdir)
1096 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
1097 createDirectory(getLevelSetupDir(NULL), "main level setup", PERMS_PRIVATE);
1098 createDirectory(getLevelSetupDir(level_subdir), "level setup", PERMS_PRIVATE);
1101 static void InitCacheDirectory(void)
1103 createDirectory(getMainUserGameDataDir(), "main user data", PERMS_PRIVATE);
1104 createDirectory(getCacheDir(), "cache data", PERMS_PRIVATE);
1108 // ----------------------------------------------------------------------------
1109 // some functions to handle lists of level and artwork directories
1110 // ----------------------------------------------------------------------------
1112 TreeInfo *newTreeInfo(void)
1114 return checked_calloc(sizeof(TreeInfo));
1117 TreeInfo *newTreeInfo_setDefaults(int type)
1119 TreeInfo *ti = newTreeInfo();
1121 setTreeInfoToDefaults(ti, type);
1126 void pushTreeInfo(TreeInfo **node_first, TreeInfo *node_new)
1128 node_new->next = *node_first;
1129 *node_first = node_new;
1132 void removeTreeInfo(TreeInfo **node_first)
1134 TreeInfo *node_old = *node_first;
1136 *node_first = node_old->next;
1137 node_old->next = NULL;
1139 freeTreeInfo(node_old);
1142 int numTreeInfo(TreeInfo *node)
1155 boolean validLevelSeries(TreeInfo *node)
1157 return (node != NULL && !node->node_group && !node->parent_link);
1160 TreeInfo *getFirstValidTreeInfoEntry(TreeInfo *node)
1165 if (node->node_group) // enter level group (step down into tree)
1166 return getFirstValidTreeInfoEntry(node->node_group);
1167 else if (node->parent_link) // skip start entry of level group
1169 if (node->next) // get first real level series entry
1170 return getFirstValidTreeInfoEntry(node->next);
1171 else // leave empty level group and go on
1172 return getFirstValidTreeInfoEntry(node->node_parent->next);
1174 else // this seems to be a regular level series
1178 TreeInfo *getTreeInfoFirstGroupEntry(TreeInfo *node)
1183 if (node->node_parent == NULL) // top level group
1184 return *node->node_top;
1185 else // sub level group
1186 return node->node_parent->node_group;
1189 int numTreeInfoInGroup(TreeInfo *node)
1191 return numTreeInfo(getTreeInfoFirstGroupEntry(node));
1194 int getPosFromTreeInfo(TreeInfo *node)
1196 TreeInfo *node_cmp = getTreeInfoFirstGroupEntry(node);
1201 if (node_cmp == node)
1205 node_cmp = node_cmp->next;
1211 TreeInfo *getTreeInfoFromPos(TreeInfo *node, int pos)
1213 TreeInfo *node_default = node;
1225 return node_default;
1228 static TreeInfo *getTreeInfoFromIdentifierExt(TreeInfo *node, char *identifier,
1229 boolean include_node_groups)
1231 if (identifier == NULL)
1236 if (node->node_group)
1238 if (include_node_groups && strEqual(identifier, node->identifier))
1241 TreeInfo *node_group = getTreeInfoFromIdentifierExt(node->node_group,
1243 include_node_groups);
1247 else if (!node->parent_link)
1249 if (strEqual(identifier, node->identifier))
1259 TreeInfo *getTreeInfoFromIdentifier(TreeInfo *node, char *identifier)
1261 return getTreeInfoFromIdentifierExt(node, identifier, FALSE);
1264 static TreeInfo *cloneTreeNode(TreeInfo **node_top, TreeInfo *node_parent,
1265 TreeInfo *node, boolean skip_sets_without_levels)
1272 if (!node->parent_link && !node->level_group &&
1273 skip_sets_without_levels && node->levels == 0)
1274 return cloneTreeNode(node_top, node_parent, node->next,
1275 skip_sets_without_levels);
1277 node_new = getTreeInfoCopy(node); // copy complete node
1279 node_new->node_top = node_top; // correct top node link
1280 node_new->node_parent = node_parent; // correct parent node link
1282 if (node->level_group)
1283 node_new->node_group = cloneTreeNode(node_top, node_new, node->node_group,
1284 skip_sets_without_levels);
1286 node_new->next = cloneTreeNode(node_top, node_parent, node->next,
1287 skip_sets_without_levels);
1292 static void cloneTree(TreeInfo **ti_new, TreeInfo *ti, boolean skip_empty_sets)
1294 TreeInfo *ti_cloned = cloneTreeNode(ti_new, NULL, ti, skip_empty_sets);
1296 *ti_new = ti_cloned;
1299 static boolean adjustTreeGraphicsForEMC(TreeInfo *node)
1301 boolean settings_changed = FALSE;
1305 boolean want_ecs = (setup.prefer_aga_graphics == FALSE);
1306 boolean want_aga = (setup.prefer_aga_graphics == TRUE);
1307 boolean has_only_ecs = (!node->graphics_set && !node->graphics_set_aga);
1308 boolean has_only_aga = (!node->graphics_set && !node->graphics_set_ecs);
1309 char *graphics_set = NULL;
1311 if (node->graphics_set_ecs && (want_ecs || has_only_ecs))
1312 graphics_set = node->graphics_set_ecs;
1314 if (node->graphics_set_aga && (want_aga || has_only_aga))
1315 graphics_set = node->graphics_set_aga;
1317 if (graphics_set && !strEqual(node->graphics_set, graphics_set))
1319 setString(&node->graphics_set, graphics_set);
1320 settings_changed = TRUE;
1323 if (node->node_group != NULL)
1324 settings_changed |= adjustTreeGraphicsForEMC(node->node_group);
1329 return settings_changed;
1332 static boolean adjustTreeSoundsForEMC(TreeInfo *node)
1334 boolean settings_changed = FALSE;
1338 boolean want_default = (setup.prefer_lowpass_sounds == FALSE);
1339 boolean want_lowpass = (setup.prefer_lowpass_sounds == TRUE);
1340 boolean has_only_default = (!node->sounds_set && !node->sounds_set_lowpass);
1341 boolean has_only_lowpass = (!node->sounds_set && !node->sounds_set_default);
1342 char *sounds_set = NULL;
1344 if (node->sounds_set_default && (want_default || has_only_default))
1345 sounds_set = node->sounds_set_default;
1347 if (node->sounds_set_lowpass && (want_lowpass || has_only_lowpass))
1348 sounds_set = node->sounds_set_lowpass;
1350 if (sounds_set && !strEqual(node->sounds_set, sounds_set))
1352 setString(&node->sounds_set, sounds_set);
1353 settings_changed = TRUE;
1356 if (node->node_group != NULL)
1357 settings_changed |= adjustTreeSoundsForEMC(node->node_group);
1362 return settings_changed;
1365 void dumpTreeInfo(TreeInfo *node, int depth)
1367 char bullet_list[] = { '-', '*', 'o' };
1371 Debug("tree", "Dumping TreeInfo:");
1375 char bullet = bullet_list[depth % ARRAY_SIZE(bullet_list)];
1377 for (i = 0; i < depth * 2; i++)
1378 DebugContinued("", " ");
1380 DebugContinued("tree", "%c '%s' ['%s] [PARENT: '%s'] %s\n",
1381 bullet, node->name, node->identifier,
1382 (node->node_parent ? node->node_parent->identifier : "-"),
1383 (node->node_group ? "[GROUP]" : ""));
1386 // use for dumping artwork info tree
1387 Debug("tree", "subdir == '%s' ['%s', '%s'] [%d])",
1388 node->subdir, node->fullpath, node->basepath, node->in_user_dir);
1391 if (node->node_group != NULL)
1392 dumpTreeInfo(node->node_group, depth + 1);
1398 void sortTreeInfoBySortFunction(TreeInfo **node_first,
1399 int (*compare_function)(const void *,
1402 int num_nodes = numTreeInfo(*node_first);
1403 TreeInfo **sort_array;
1404 TreeInfo *node = *node_first;
1410 // allocate array for sorting structure pointers
1411 sort_array = checked_calloc(num_nodes * sizeof(TreeInfo *));
1413 // writing structure pointers to sorting array
1414 while (i < num_nodes && node) // double boundary check...
1416 sort_array[i] = node;
1422 // sorting the structure pointers in the sorting array
1423 qsort(sort_array, num_nodes, sizeof(TreeInfo *),
1426 // update the linkage of list elements with the sorted node array
1427 for (i = 0; i < num_nodes - 1; i++)
1428 sort_array[i]->next = sort_array[i + 1];
1429 sort_array[num_nodes - 1]->next = NULL;
1431 // update the linkage of the main list anchor pointer
1432 *node_first = sort_array[0];
1436 // now recursively sort the level group structures
1440 if (node->node_group != NULL)
1441 sortTreeInfoBySortFunction(&node->node_group, compare_function);
1447 void sortTreeInfo(TreeInfo **node_first)
1449 sortTreeInfoBySortFunction(node_first, compareTreeInfoEntries);
1453 // ============================================================================
1454 // some stuff from "files.c"
1455 // ============================================================================
1457 #if defined(PLATFORM_WIN32)
1459 #define S_IRGRP S_IRUSR
1462 #define S_IROTH S_IRUSR
1465 #define S_IWGRP S_IWUSR
1468 #define S_IWOTH S_IWUSR
1471 #define S_IXGRP S_IXUSR
1474 #define S_IXOTH S_IXUSR
1477 #define S_IRWXG (S_IRGRP | S_IWGRP | S_IXGRP)
1482 #endif // PLATFORM_WIN32
1484 // file permissions for newly written files
1485 #define MODE_R_ALL (S_IRUSR | S_IRGRP | S_IROTH)
1486 #define MODE_W_ALL (S_IWUSR | S_IWGRP | S_IWOTH)
1487 #define MODE_X_ALL (S_IXUSR | S_IXGRP | S_IXOTH)
1489 #define MODE_W_PRIVATE (S_IWUSR)
1490 #define MODE_W_PUBLIC_FILE (S_IWUSR | S_IWGRP)
1491 #define MODE_W_PUBLIC_DIR (S_IWUSR | S_IWGRP | S_ISGID)
1493 #define DIR_PERMS_PRIVATE (MODE_R_ALL | MODE_X_ALL | MODE_W_PRIVATE)
1494 #define DIR_PERMS_PUBLIC (MODE_R_ALL | MODE_X_ALL | MODE_W_PUBLIC_DIR)
1495 #define DIR_PERMS_PUBLIC_ALL (MODE_R_ALL | MODE_X_ALL | MODE_W_ALL)
1497 #define FILE_PERMS_PRIVATE (MODE_R_ALL | MODE_W_PRIVATE)
1498 #define FILE_PERMS_PUBLIC (MODE_R_ALL | MODE_W_PUBLIC_FILE)
1499 #define FILE_PERMS_PUBLIC_ALL (MODE_R_ALL | MODE_W_ALL)
1502 char *getHomeDir(void)
1504 static char *dir = NULL;
1506 #if defined(PLATFORM_WIN32)
1509 dir = checked_malloc(MAX_PATH + 1);
1511 if (!SUCCEEDED(SHGetFolderPath(NULL, CSIDL_PERSONAL, NULL, 0, dir)))
1514 #elif defined(PLATFORM_EMSCRIPTEN)
1515 dir = "/persistent";
1516 #elif defined(PLATFORM_UNIX)
1519 if ((dir = getenv("HOME")) == NULL)
1521 dir = getUnixHomeDir();
1524 dir = getStringCopy(dir);
1536 char *getCommonDataDir(void)
1538 static char *common_data_dir = NULL;
1540 #if defined(PLATFORM_WIN32)
1541 if (common_data_dir == NULL)
1543 char *dir = checked_malloc(MAX_PATH + 1);
1545 if (SUCCEEDED(SHGetFolderPath(NULL, CSIDL_COMMON_DOCUMENTS, NULL, 0, dir))
1546 && !strEqual(dir, "")) // empty for Windows 95/98
1547 common_data_dir = getPath2(dir, program.userdata_subdir);
1549 common_data_dir = options.rw_base_directory;
1552 if (common_data_dir == NULL)
1553 common_data_dir = options.rw_base_directory;
1556 return common_data_dir;
1559 char *getPersonalDataDir(void)
1561 static char *personal_data_dir = NULL;
1563 #if defined(PLATFORM_MACOSX)
1564 if (personal_data_dir == NULL)
1565 personal_data_dir = getPath2(getHomeDir(), "Documents");
1567 if (personal_data_dir == NULL)
1568 personal_data_dir = getHomeDir();
1571 return personal_data_dir;
1574 char *getMainUserGameDataDir(void)
1576 static char *main_user_data_dir = NULL;
1578 #if defined(PLATFORM_ANDROID)
1579 if (main_user_data_dir == NULL)
1580 main_user_data_dir = (char *)(SDL_AndroidGetExternalStorageState() &
1581 SDL_ANDROID_EXTERNAL_STORAGE_WRITE ?
1582 SDL_AndroidGetExternalStoragePath() :
1583 SDL_AndroidGetInternalStoragePath());
1585 if (main_user_data_dir == NULL)
1586 main_user_data_dir = getPath2(getPersonalDataDir(),
1587 program.userdata_subdir);
1590 return main_user_data_dir;
1593 char *getUserGameDataDir(void)
1596 return getMainUserGameDataDir();
1598 return getUserDir(user.nr);
1601 char *getSetupDir(void)
1603 return getUserGameDataDir();
1606 static mode_t posix_umask(mode_t mask)
1608 #if defined(PLATFORM_UNIX)
1615 static int posix_mkdir(const char *pathname, mode_t mode)
1617 #if defined(PLATFORM_WIN32)
1618 return mkdir(pathname);
1620 return mkdir(pathname, mode);
1624 static boolean posix_process_running_setgid(void)
1626 #if defined(PLATFORM_UNIX)
1627 return (getgid() != getegid());
1633 void createDirectory(char *dir, char *text, int permission_class)
1635 if (directoryExists(dir))
1638 // leave "other" permissions in umask untouched, but ensure group parts
1639 // of USERDATA_DIR_MODE are not masked
1640 mode_t dir_mode = (permission_class == PERMS_PRIVATE ?
1641 DIR_PERMS_PRIVATE : DIR_PERMS_PUBLIC);
1642 mode_t last_umask = posix_umask(0);
1643 mode_t group_umask = ~(dir_mode & S_IRWXG);
1644 int running_setgid = posix_process_running_setgid();
1646 if (permission_class == PERMS_PUBLIC)
1648 // if we're setgid, protect files against "other"
1649 // else keep umask(0) to make the dir world-writable
1652 posix_umask(last_umask & group_umask);
1654 dir_mode = DIR_PERMS_PUBLIC_ALL;
1657 if (posix_mkdir(dir, dir_mode) != 0)
1658 Warn("cannot create %s directory '%s': %s", text, dir, strerror(errno));
1660 if (permission_class == PERMS_PUBLIC && !running_setgid)
1661 chmod(dir, dir_mode);
1663 posix_umask(last_umask); // restore previous umask
1666 void InitMainUserDataDirectory(void)
1668 createDirectory(getMainUserGameDataDir(), "main user data", PERMS_PRIVATE);
1671 void InitUserDataDirectory(void)
1673 createDirectory(getMainUserGameDataDir(), "main user data", PERMS_PRIVATE);
1677 createDirectory(getUserDir(-1), "users", PERMS_PRIVATE);
1678 createDirectory(getUserDir(user.nr), "user data", PERMS_PRIVATE);
1682 void SetFilePermissions(char *filename, int permission_class)
1684 int running_setgid = posix_process_running_setgid();
1685 int perms = (permission_class == PERMS_PRIVATE ?
1686 FILE_PERMS_PRIVATE : FILE_PERMS_PUBLIC);
1688 if (permission_class == PERMS_PUBLIC && !running_setgid)
1689 perms = FILE_PERMS_PUBLIC_ALL;
1691 chmod(filename, perms);
1694 char *getCookie(char *file_type)
1696 static char cookie[MAX_COOKIE_LEN + 1];
1698 if (strlen(program.cookie_prefix) + 1 +
1699 strlen(file_type) + strlen("_FILE_VERSION_x.x") > MAX_COOKIE_LEN)
1700 return "[COOKIE ERROR]"; // should never happen
1702 sprintf(cookie, "%s_%s_FILE_VERSION_%d.%d",
1703 program.cookie_prefix, file_type,
1704 program.version_super, program.version_major);
1709 void fprintFileHeader(FILE *file, char *basename)
1711 char *prefix = "# ";
1714 fprintf_line_with_prefix(file, prefix, sep1, 77);
1715 fprintf(file, "%s%s\n", prefix, basename);
1716 fprintf_line_with_prefix(file, prefix, sep1, 77);
1717 fprintf(file, "\n");
1720 int getFileVersionFromCookieString(const char *cookie)
1722 const char *ptr_cookie1, *ptr_cookie2;
1723 const char *pattern1 = "_FILE_VERSION_";
1724 const char *pattern2 = "?.?";
1725 const int len_cookie = strlen(cookie);
1726 const int len_pattern1 = strlen(pattern1);
1727 const int len_pattern2 = strlen(pattern2);
1728 const int len_pattern = len_pattern1 + len_pattern2;
1729 int version_super, version_major;
1731 if (len_cookie <= len_pattern)
1734 ptr_cookie1 = &cookie[len_cookie - len_pattern];
1735 ptr_cookie2 = &cookie[len_cookie - len_pattern2];
1737 if (strncmp(ptr_cookie1, pattern1, len_pattern1) != 0)
1740 if (ptr_cookie2[0] < '0' || ptr_cookie2[0] > '9' ||
1741 ptr_cookie2[1] != '.' ||
1742 ptr_cookie2[2] < '0' || ptr_cookie2[2] > '9')
1745 version_super = ptr_cookie2[0] - '0';
1746 version_major = ptr_cookie2[2] - '0';
1748 return VERSION_IDENT(version_super, version_major, 0, 0);
1751 boolean checkCookieString(const char *cookie, const char *template)
1753 const char *pattern = "_FILE_VERSION_?.?";
1754 const int len_cookie = strlen(cookie);
1755 const int len_template = strlen(template);
1756 const int len_pattern = strlen(pattern);
1758 if (len_cookie != len_template)
1761 if (strncmp(cookie, template, len_cookie - len_pattern) != 0)
1768 // ----------------------------------------------------------------------------
1769 // setup file list and hash handling functions
1770 // ----------------------------------------------------------------------------
1772 char *getFormattedSetupEntry(char *token, char *value)
1775 static char entry[MAX_LINE_LEN];
1777 // if value is an empty string, just return token without value
1781 // start with the token and some spaces to format output line
1782 sprintf(entry, "%s:", token);
1783 for (i = strlen(entry); i < token_value_position; i++)
1786 // continue with the token's value
1787 strcat(entry, value);
1792 SetupFileList *newSetupFileList(char *token, char *value)
1794 SetupFileList *new = checked_malloc(sizeof(SetupFileList));
1796 new->token = getStringCopy(token);
1797 new->value = getStringCopy(value);
1804 void freeSetupFileList(SetupFileList *list)
1809 checked_free(list->token);
1810 checked_free(list->value);
1813 freeSetupFileList(list->next);
1818 char *getListEntry(SetupFileList *list, char *token)
1823 if (strEqual(list->token, token))
1826 return getListEntry(list->next, token);
1829 SetupFileList *setListEntry(SetupFileList *list, char *token, char *value)
1834 if (strEqual(list->token, token))
1836 checked_free(list->value);
1838 list->value = getStringCopy(value);
1842 else if (list->next == NULL)
1843 return (list->next = newSetupFileList(token, value));
1845 return setListEntry(list->next, token, value);
1848 SetupFileList *addListEntry(SetupFileList *list, char *token, char *value)
1853 if (list->next == NULL)
1854 return (list->next = newSetupFileList(token, value));
1856 return addListEntry(list->next, token, value);
1859 #if ENABLE_UNUSED_CODE
1861 static void printSetupFileList(SetupFileList *list)
1866 Debug("setup:printSetupFileList", "token: '%s'", list->token);
1867 Debug("setup:printSetupFileList", "value: '%s'", list->value);
1869 printSetupFileList(list->next);
1875 DEFINE_HASHTABLE_INSERT(insert_hash_entry, char, char);
1876 DEFINE_HASHTABLE_SEARCH(search_hash_entry, char, char);
1877 DEFINE_HASHTABLE_CHANGE(change_hash_entry, char, char);
1878 DEFINE_HASHTABLE_REMOVE(remove_hash_entry, char, char);
1880 #define insert_hash_entry hashtable_insert
1881 #define search_hash_entry hashtable_search
1882 #define change_hash_entry hashtable_change
1883 #define remove_hash_entry hashtable_remove
1886 unsigned int get_hash_from_key(void *key)
1891 This algorithm (k=33) was first reported by Dan Bernstein many years ago in
1892 'comp.lang.c'. Another version of this algorithm (now favored by Bernstein)
1893 uses XOR: hash(i) = hash(i - 1) * 33 ^ str[i]; the magic of number 33 (why
1894 it works better than many other constants, prime or not) has never been
1895 adequately explained.
1897 If you just want to have a good hash function, and cannot wait, djb2
1898 is one of the best string hash functions i know. It has excellent
1899 distribution and speed on many different sets of keys and table sizes.
1900 You are not likely to do better with one of the "well known" functions
1901 such as PJW, K&R, etc.
1903 Ozan (oz) Yigit [http://www.cs.yorku.ca/~oz/hash.html]
1906 char *str = (char *)key;
1907 unsigned int hash = 5381;
1910 while ((c = *str++))
1911 hash = ((hash << 5) + hash) + c; // hash * 33 + c
1916 static int keys_are_equal(void *key1, void *key2)
1918 return (strEqual((char *)key1, (char *)key2));
1921 SetupFileHash *newSetupFileHash(void)
1923 SetupFileHash *new_hash =
1924 create_hashtable(16, 0.75, get_hash_from_key, keys_are_equal);
1926 if (new_hash == NULL)
1927 Fail("create_hashtable() failed -- out of memory");
1932 void freeSetupFileHash(SetupFileHash *hash)
1937 hashtable_destroy(hash, 1); // 1 == also free values stored in hash
1940 char *getHashEntry(SetupFileHash *hash, char *token)
1945 return search_hash_entry(hash, token);
1948 void setHashEntry(SetupFileHash *hash, char *token, char *value)
1955 value_copy = getStringCopy(value);
1957 // change value; if it does not exist, insert it as new
1958 if (!change_hash_entry(hash, token, value_copy))
1959 if (!insert_hash_entry(hash, getStringCopy(token), value_copy))
1960 Fail("cannot insert into hash -- aborting");
1963 char *removeHashEntry(SetupFileHash *hash, char *token)
1968 return remove_hash_entry(hash, token);
1971 #if ENABLE_UNUSED_CODE
1973 static void printSetupFileHash(SetupFileHash *hash)
1975 BEGIN_HASH_ITERATION(hash, itr)
1977 Debug("setup:printSetupFileHash", "token: '%s'", HASH_ITERATION_TOKEN(itr));
1978 Debug("setup:printSetupFileHash", "value: '%s'", HASH_ITERATION_VALUE(itr));
1980 END_HASH_ITERATION(hash, itr)
1985 #define ALLOW_TOKEN_VALUE_SEPARATOR_BEING_WHITESPACE 1
1986 #define CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING 0
1987 #define CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH 0
1989 static boolean token_value_separator_found = FALSE;
1990 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
1991 static boolean token_value_separator_warning = FALSE;
1993 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
1994 static boolean token_already_exists_warning = FALSE;
1997 static boolean getTokenValueFromSetupLineExt(char *line,
1998 char **token_ptr, char **value_ptr,
1999 char *filename, char *line_raw,
2001 boolean separator_required)
2003 static char line_copy[MAX_LINE_LEN + 1], line_raw_copy[MAX_LINE_LEN + 1];
2004 char *token, *value, *line_ptr;
2006 // when externally invoked via ReadTokenValueFromLine(), copy line buffers
2007 if (line_raw == NULL)
2009 strncpy(line_copy, line, MAX_LINE_LEN);
2010 line_copy[MAX_LINE_LEN] = '\0';
2013 strcpy(line_raw_copy, line_copy);
2014 line_raw = line_raw_copy;
2017 // cut trailing comment from input line
2018 for (line_ptr = line; *line_ptr; line_ptr++)
2020 if (*line_ptr == '#')
2027 // cut trailing whitespaces from input line
2028 for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
2029 if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
2032 // ignore empty lines
2036 // cut leading whitespaces from token
2037 for (token = line; *token; token++)
2038 if (*token != ' ' && *token != '\t')
2041 // start with empty value as reliable default
2044 token_value_separator_found = FALSE;
2046 // find end of token to determine start of value
2047 for (line_ptr = token; *line_ptr; line_ptr++)
2049 // first look for an explicit token/value separator, like ':' or '='
2050 if (*line_ptr == ':' || *line_ptr == '=')
2052 *line_ptr = '\0'; // terminate token string
2053 value = line_ptr + 1; // set beginning of value
2055 token_value_separator_found = TRUE;
2061 #if ALLOW_TOKEN_VALUE_SEPARATOR_BEING_WHITESPACE
2062 // fallback: if no token/value separator found, also allow whitespaces
2063 if (!token_value_separator_found && !separator_required)
2065 for (line_ptr = token; *line_ptr; line_ptr++)
2067 if (*line_ptr == ' ' || *line_ptr == '\t')
2069 *line_ptr = '\0'; // terminate token string
2070 value = line_ptr + 1; // set beginning of value
2072 token_value_separator_found = TRUE;
2078 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2079 if (token_value_separator_found)
2081 if (!token_value_separator_warning)
2083 Debug("setup", "---");
2085 if (filename != NULL)
2087 Debug("setup", "missing token/value separator(s) in config file:");
2088 Debug("setup", "- config file: '%s'", filename);
2092 Debug("setup", "missing token/value separator(s):");
2095 token_value_separator_warning = TRUE;
2098 if (filename != NULL)
2099 Debug("setup", "- line %d: '%s'", line_nr, line_raw);
2101 Debug("setup", "- line: '%s'", line_raw);
2107 // cut trailing whitespaces from token
2108 for (line_ptr = &token[strlen(token)]; line_ptr >= token; line_ptr--)
2109 if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
2112 // cut leading whitespaces from value
2113 for (; *value; value++)
2114 if (*value != ' ' && *value != '\t')
2123 boolean getTokenValueFromSetupLine(char *line, char **token, char **value)
2125 // while the internal (old) interface does not require a token/value
2126 // separator (for downwards compatibility with existing files which
2127 // don't use them), it is mandatory for the external (new) interface
2129 return getTokenValueFromSetupLineExt(line, token, value, NULL, NULL, 0, TRUE);
2132 static boolean loadSetupFileData(void *setup_file_data, char *filename,
2133 boolean top_recursion_level, boolean is_hash)
2135 static SetupFileHash *include_filename_hash = NULL;
2136 char line[MAX_LINE_LEN], line_raw[MAX_LINE_LEN], previous_line[MAX_LINE_LEN];
2137 char *token, *value, *line_ptr;
2138 void *insert_ptr = NULL;
2139 boolean read_continued_line = FALSE;
2141 int line_nr = 0, token_count = 0, include_count = 0;
2143 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2144 token_value_separator_warning = FALSE;
2147 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2148 token_already_exists_warning = FALSE;
2151 if (!(file = openFile(filename, MODE_READ)))
2153 #if DEBUG_NO_CONFIG_FILE
2154 Debug("setup", "cannot open configuration file '%s'", filename);
2160 // use "insert pointer" to store list end for constant insertion complexity
2162 insert_ptr = setup_file_data;
2164 // on top invocation, create hash to mark included files (to prevent loops)
2165 if (top_recursion_level)
2166 include_filename_hash = newSetupFileHash();
2168 // mark this file as already included (to prevent including it again)
2169 setHashEntry(include_filename_hash, getBaseNamePtr(filename), "true");
2171 while (!checkEndOfFile(file))
2173 // read next line of input file
2174 if (!getStringFromFile(file, line, MAX_LINE_LEN))
2177 // check if line was completely read and is terminated by line break
2178 if (strlen(line) > 0 && line[strlen(line) - 1] == '\n')
2181 // cut trailing line break (this can be newline and/or carriage return)
2182 for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
2183 if ((*line_ptr == '\n' || *line_ptr == '\r') && *(line_ptr + 1) == '\0')
2186 // copy raw input line for later use (mainly debugging output)
2187 strcpy(line_raw, line);
2189 if (read_continued_line)
2191 // append new line to existing line, if there is enough space
2192 if (strlen(previous_line) + strlen(line_ptr) < MAX_LINE_LEN)
2193 strcat(previous_line, line_ptr);
2195 strcpy(line, previous_line); // copy storage buffer to line
2197 read_continued_line = FALSE;
2200 // if the last character is '\', continue at next line
2201 if (strlen(line) > 0 && line[strlen(line) - 1] == '\\')
2203 line[strlen(line) - 1] = '\0'; // cut off trailing backslash
2204 strcpy(previous_line, line); // copy line to storage buffer
2206 read_continued_line = TRUE;
2211 if (!getTokenValueFromSetupLineExt(line, &token, &value, filename,
2212 line_raw, line_nr, FALSE))
2217 if (strEqual(token, "include"))
2219 if (getHashEntry(include_filename_hash, value) == NULL)
2221 char *basepath = getBasePath(filename);
2222 char *basename = getBaseName(value);
2223 char *filename_include = getPath2(basepath, basename);
2225 loadSetupFileData(setup_file_data, filename_include, FALSE, is_hash);
2229 free(filename_include);
2235 Warn("ignoring already processed file '%s'", value);
2242 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2244 getHashEntry((SetupFileHash *)setup_file_data, token);
2246 if (old_value != NULL)
2248 if (!token_already_exists_warning)
2250 Debug("setup", "---");
2251 Debug("setup", "duplicate token(s) found in config file:");
2252 Debug("setup", "- config file: '%s'", filename);
2254 token_already_exists_warning = TRUE;
2257 Debug("setup", "- token: '%s' (in line %d)", token, line_nr);
2258 Debug("setup", " old value: '%s'", old_value);
2259 Debug("setup", " new value: '%s'", value);
2263 setHashEntry((SetupFileHash *)setup_file_data, token, value);
2267 insert_ptr = addListEntry((SetupFileList *)insert_ptr, token, value);
2277 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2278 if (token_value_separator_warning)
2279 Debug("setup", "---");
2282 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2283 if (token_already_exists_warning)
2284 Debug("setup", "---");
2287 if (token_count == 0 && include_count == 0)
2288 Warn("configuration file '%s' is empty", filename);
2290 if (top_recursion_level)
2291 freeSetupFileHash(include_filename_hash);
2296 static int compareSetupFileData(const void *object1, const void *object2)
2298 const struct ConfigInfo *entry1 = (struct ConfigInfo *)object1;
2299 const struct ConfigInfo *entry2 = (struct ConfigInfo *)object2;
2301 return strcmp(entry1->token, entry2->token);
2304 static void saveSetupFileHash(SetupFileHash *hash, char *filename)
2306 int item_count = hashtable_count(hash);
2307 int item_size = sizeof(struct ConfigInfo);
2308 struct ConfigInfo *sort_array = checked_malloc(item_count * item_size);
2312 // copy string pointers from hash to array
2313 BEGIN_HASH_ITERATION(hash, itr)
2315 sort_array[i].token = HASH_ITERATION_TOKEN(itr);
2316 sort_array[i].value = HASH_ITERATION_VALUE(itr);
2320 if (i > item_count) // should never happen
2323 END_HASH_ITERATION(hash, itr)
2325 // sort string pointers from hash in array
2326 qsort(sort_array, item_count, item_size, compareSetupFileData);
2328 if (!(file = fopen(filename, MODE_WRITE)))
2330 Warn("cannot write configuration file '%s'", filename);
2335 fprintf(file, "%s\n\n", getFormattedSetupEntry("program.version",
2336 program.version_string));
2337 for (i = 0; i < item_count; i++)
2338 fprintf(file, "%s\n", getFormattedSetupEntry(sort_array[i].token,
2339 sort_array[i].value));
2342 checked_free(sort_array);
2345 SetupFileList *loadSetupFileList(char *filename)
2347 SetupFileList *setup_file_list = newSetupFileList("", "");
2348 SetupFileList *first_valid_list_entry;
2350 if (!loadSetupFileData(setup_file_list, filename, TRUE, FALSE))
2352 freeSetupFileList(setup_file_list);
2357 first_valid_list_entry = setup_file_list->next;
2359 // free empty list header
2360 setup_file_list->next = NULL;
2361 freeSetupFileList(setup_file_list);
2363 return first_valid_list_entry;
2366 SetupFileHash *loadSetupFileHash(char *filename)
2368 SetupFileHash *setup_file_hash = newSetupFileHash();
2370 if (!loadSetupFileData(setup_file_hash, filename, TRUE, TRUE))
2372 freeSetupFileHash(setup_file_hash);
2377 return setup_file_hash;
2381 // ============================================================================
2383 // ============================================================================
2385 #define TOKEN_STR_LAST_LEVEL_SERIES "last_level_series"
2386 #define TOKEN_STR_LAST_PLAYED_LEVEL "last_played_level"
2387 #define TOKEN_STR_HANDICAP_LEVEL "handicap_level"
2388 #define TOKEN_STR_LAST_USER "last_user"
2390 // level directory info
2391 #define LEVELINFO_TOKEN_IDENTIFIER 0
2392 #define LEVELINFO_TOKEN_NAME 1
2393 #define LEVELINFO_TOKEN_NAME_SORTING 2
2394 #define LEVELINFO_TOKEN_AUTHOR 3
2395 #define LEVELINFO_TOKEN_YEAR 4
2396 #define LEVELINFO_TOKEN_PROGRAM_TITLE 5
2397 #define LEVELINFO_TOKEN_PROGRAM_COPYRIGHT 6
2398 #define LEVELINFO_TOKEN_PROGRAM_COMPANY 7
2399 #define LEVELINFO_TOKEN_IMPORTED_FROM 8
2400 #define LEVELINFO_TOKEN_IMPORTED_BY 9
2401 #define LEVELINFO_TOKEN_TESTED_BY 10
2402 #define LEVELINFO_TOKEN_LEVELS 11
2403 #define LEVELINFO_TOKEN_FIRST_LEVEL 12
2404 #define LEVELINFO_TOKEN_SORT_PRIORITY 13
2405 #define LEVELINFO_TOKEN_LATEST_ENGINE 14
2406 #define LEVELINFO_TOKEN_LEVEL_GROUP 15
2407 #define LEVELINFO_TOKEN_READONLY 16
2408 #define LEVELINFO_TOKEN_GRAPHICS_SET_ECS 17
2409 #define LEVELINFO_TOKEN_GRAPHICS_SET_AGA 18
2410 #define LEVELINFO_TOKEN_GRAPHICS_SET 19
2411 #define LEVELINFO_TOKEN_SOUNDS_SET_DEFAULT 20
2412 #define LEVELINFO_TOKEN_SOUNDS_SET_LOWPASS 21
2413 #define LEVELINFO_TOKEN_SOUNDS_SET 22
2414 #define LEVELINFO_TOKEN_MUSIC_SET 23
2415 #define LEVELINFO_TOKEN_FILENAME 24
2416 #define LEVELINFO_TOKEN_FILETYPE 25
2417 #define LEVELINFO_TOKEN_SPECIAL_FLAGS 26
2418 #define LEVELINFO_TOKEN_HANDICAP 27
2419 #define LEVELINFO_TOKEN_SKIP_LEVELS 28
2420 #define LEVELINFO_TOKEN_USE_EMC_TILES 29
2422 #define NUM_LEVELINFO_TOKENS 30
2424 static LevelDirTree ldi;
2426 static struct TokenInfo levelinfo_tokens[] =
2428 // level directory info
2429 { TYPE_STRING, &ldi.identifier, "identifier" },
2430 { TYPE_STRING, &ldi.name, "name" },
2431 { TYPE_STRING, &ldi.name_sorting, "name_sorting" },
2432 { TYPE_STRING, &ldi.author, "author" },
2433 { TYPE_STRING, &ldi.year, "year" },
2434 { TYPE_STRING, &ldi.program_title, "program_title" },
2435 { TYPE_STRING, &ldi.program_copyright, "program_copyright" },
2436 { TYPE_STRING, &ldi.program_company, "program_company" },
2437 { TYPE_STRING, &ldi.imported_from, "imported_from" },
2438 { TYPE_STRING, &ldi.imported_by, "imported_by" },
2439 { TYPE_STRING, &ldi.tested_by, "tested_by" },
2440 { TYPE_INTEGER, &ldi.levels, "levels" },
2441 { TYPE_INTEGER, &ldi.first_level, "first_level" },
2442 { TYPE_INTEGER, &ldi.sort_priority, "sort_priority" },
2443 { TYPE_BOOLEAN, &ldi.latest_engine, "latest_engine" },
2444 { TYPE_BOOLEAN, &ldi.level_group, "level_group" },
2445 { TYPE_BOOLEAN, &ldi.readonly, "readonly" },
2446 { TYPE_STRING, &ldi.graphics_set_ecs, "graphics_set.ecs" },
2447 { TYPE_STRING, &ldi.graphics_set_aga, "graphics_set.aga" },
2448 { TYPE_STRING, &ldi.graphics_set, "graphics_set" },
2449 { TYPE_STRING, &ldi.sounds_set_default,"sounds_set.default" },
2450 { TYPE_STRING, &ldi.sounds_set_lowpass,"sounds_set.lowpass" },
2451 { TYPE_STRING, &ldi.sounds_set, "sounds_set" },
2452 { TYPE_STRING, &ldi.music_set, "music_set" },
2453 { TYPE_STRING, &ldi.level_filename, "filename" },
2454 { TYPE_STRING, &ldi.level_filetype, "filetype" },
2455 { TYPE_STRING, &ldi.special_flags, "special_flags" },
2456 { TYPE_BOOLEAN, &ldi.handicap, "handicap" },
2457 { TYPE_BOOLEAN, &ldi.skip_levels, "skip_levels" },
2458 { TYPE_BOOLEAN, &ldi.use_emc_tiles, "use_emc_tiles" }
2461 static struct TokenInfo artworkinfo_tokens[] =
2463 // artwork directory info
2464 { TYPE_STRING, &ldi.identifier, "identifier" },
2465 { TYPE_STRING, &ldi.subdir, "subdir" },
2466 { TYPE_STRING, &ldi.name, "name" },
2467 { TYPE_STRING, &ldi.name_sorting, "name_sorting" },
2468 { TYPE_STRING, &ldi.author, "author" },
2469 { TYPE_STRING, &ldi.program_title, "program_title" },
2470 { TYPE_STRING, &ldi.program_copyright, "program_copyright" },
2471 { TYPE_STRING, &ldi.program_company, "program_company" },
2472 { TYPE_INTEGER, &ldi.sort_priority, "sort_priority" },
2473 { TYPE_STRING, &ldi.basepath, "basepath" },
2474 { TYPE_STRING, &ldi.fullpath, "fullpath" },
2475 { TYPE_BOOLEAN, &ldi.in_user_dir, "in_user_dir" },
2476 { TYPE_STRING, &ldi.class_desc, "class_desc" },
2481 static char *optional_tokens[] =
2484 "program_copyright",
2490 static void setTreeInfoToDefaults(TreeInfo *ti, int type)
2494 ti->node_top = (ti->type == TREE_TYPE_LEVEL_DIR ? &leveldir_first :
2495 ti->type == TREE_TYPE_GRAPHICS_DIR ? &artwork.gfx_first :
2496 ti->type == TREE_TYPE_SOUNDS_DIR ? &artwork.snd_first :
2497 ti->type == TREE_TYPE_MUSIC_DIR ? &artwork.mus_first :
2500 ti->node_parent = NULL;
2501 ti->node_group = NULL;
2508 ti->fullpath = NULL;
2509 ti->basepath = NULL;
2510 ti->identifier = NULL;
2511 ti->name = getStringCopy(ANONYMOUS_NAME);
2512 ti->name_sorting = NULL;
2513 ti->author = getStringCopy(ANONYMOUS_NAME);
2516 ti->program_title = NULL;
2517 ti->program_copyright = NULL;
2518 ti->program_company = NULL;
2520 ti->sort_priority = LEVELCLASS_UNDEFINED; // default: least priority
2521 ti->latest_engine = FALSE; // default: get from level
2522 ti->parent_link = FALSE;
2523 ti->is_copy = FALSE;
2524 ti->in_user_dir = FALSE;
2525 ti->user_defined = FALSE;
2527 ti->class_desc = NULL;
2529 ti->infotext = getStringCopy(TREE_INFOTEXT(ti->type));
2531 if (ti->type == TREE_TYPE_LEVEL_DIR)
2533 ti->imported_from = NULL;
2534 ti->imported_by = NULL;
2535 ti->tested_by = NULL;
2537 ti->graphics_set_ecs = NULL;
2538 ti->graphics_set_aga = NULL;
2539 ti->graphics_set = NULL;
2540 ti->sounds_set_default = NULL;
2541 ti->sounds_set_lowpass = NULL;
2542 ti->sounds_set = NULL;
2543 ti->music_set = NULL;
2544 ti->graphics_path = getStringCopy(UNDEFINED_FILENAME);
2545 ti->sounds_path = getStringCopy(UNDEFINED_FILENAME);
2546 ti->music_path = getStringCopy(UNDEFINED_FILENAME);
2548 ti->level_filename = NULL;
2549 ti->level_filetype = NULL;
2551 ti->special_flags = NULL;
2554 ti->first_level = 0;
2556 ti->level_group = FALSE;
2557 ti->handicap_level = 0;
2558 ti->readonly = TRUE;
2559 ti->handicap = TRUE;
2560 ti->skip_levels = FALSE;
2562 ti->use_emc_tiles = FALSE;
2566 static void setTreeInfoToDefaultsFromParent(TreeInfo *ti, TreeInfo *parent)
2570 Warn("setTreeInfoToDefaultsFromParent(): parent == NULL");
2572 setTreeInfoToDefaults(ti, TREE_TYPE_UNDEFINED);
2577 // copy all values from the parent structure
2579 ti->type = parent->type;
2581 ti->node_top = parent->node_top;
2582 ti->node_parent = parent;
2583 ti->node_group = NULL;
2590 ti->fullpath = NULL;
2591 ti->basepath = NULL;
2592 ti->identifier = NULL;
2593 ti->name = getStringCopy(ANONYMOUS_NAME);
2594 ti->name_sorting = NULL;
2595 ti->author = getStringCopy(parent->author);
2596 ti->year = getStringCopy(parent->year);
2598 ti->program_title = getStringCopy(parent->program_title);
2599 ti->program_copyright = getStringCopy(parent->program_copyright);
2600 ti->program_company = getStringCopy(parent->program_company);
2602 ti->sort_priority = parent->sort_priority;
2603 ti->latest_engine = parent->latest_engine;
2604 ti->parent_link = FALSE;
2605 ti->is_copy = FALSE;
2606 ti->in_user_dir = parent->in_user_dir;
2607 ti->user_defined = parent->user_defined;
2608 ti->color = parent->color;
2609 ti->class_desc = getStringCopy(parent->class_desc);
2611 ti->infotext = getStringCopy(parent->infotext);
2613 if (ti->type == TREE_TYPE_LEVEL_DIR)
2615 ti->imported_from = getStringCopy(parent->imported_from);
2616 ti->imported_by = getStringCopy(parent->imported_by);
2617 ti->tested_by = getStringCopy(parent->tested_by);
2619 ti->graphics_set_ecs = getStringCopy(parent->graphics_set_ecs);
2620 ti->graphics_set_aga = getStringCopy(parent->graphics_set_aga);
2621 ti->graphics_set = getStringCopy(parent->graphics_set);
2622 ti->sounds_set_default = getStringCopy(parent->sounds_set_default);
2623 ti->sounds_set_lowpass = getStringCopy(parent->sounds_set_lowpass);
2624 ti->sounds_set = getStringCopy(parent->sounds_set);
2625 ti->music_set = getStringCopy(parent->music_set);
2626 ti->graphics_path = getStringCopy(UNDEFINED_FILENAME);
2627 ti->sounds_path = getStringCopy(UNDEFINED_FILENAME);
2628 ti->music_path = getStringCopy(UNDEFINED_FILENAME);
2630 ti->level_filename = getStringCopy(parent->level_filename);
2631 ti->level_filetype = getStringCopy(parent->level_filetype);
2633 ti->special_flags = getStringCopy(parent->special_flags);
2635 ti->levels = parent->levels;
2636 ti->first_level = parent->first_level;
2637 ti->last_level = parent->last_level;
2638 ti->level_group = FALSE;
2639 ti->handicap_level = parent->handicap_level;
2640 ti->readonly = parent->readonly;
2641 ti->handicap = parent->handicap;
2642 ti->skip_levels = parent->skip_levels;
2644 ti->use_emc_tiles = parent->use_emc_tiles;
2648 static TreeInfo *getTreeInfoCopy(TreeInfo *ti)
2650 TreeInfo *ti_copy = newTreeInfo();
2652 // copy all values from the original structure
2654 ti_copy->type = ti->type;
2656 ti_copy->node_top = ti->node_top;
2657 ti_copy->node_parent = ti->node_parent;
2658 ti_copy->node_group = ti->node_group;
2659 ti_copy->next = ti->next;
2661 ti_copy->cl_first = ti->cl_first;
2662 ti_copy->cl_cursor = ti->cl_cursor;
2664 ti_copy->subdir = getStringCopy(ti->subdir);
2665 ti_copy->fullpath = getStringCopy(ti->fullpath);
2666 ti_copy->basepath = getStringCopy(ti->basepath);
2667 ti_copy->identifier = getStringCopy(ti->identifier);
2668 ti_copy->name = getStringCopy(ti->name);
2669 ti_copy->name_sorting = getStringCopy(ti->name_sorting);
2670 ti_copy->author = getStringCopy(ti->author);
2671 ti_copy->year = getStringCopy(ti->year);
2673 ti_copy->program_title = getStringCopy(ti->program_title);
2674 ti_copy->program_copyright = getStringCopy(ti->program_copyright);
2675 ti_copy->program_company = getStringCopy(ti->program_company);
2677 ti_copy->imported_from = getStringCopy(ti->imported_from);
2678 ti_copy->imported_by = getStringCopy(ti->imported_by);
2679 ti_copy->tested_by = getStringCopy(ti->tested_by);
2681 ti_copy->graphics_set_ecs = getStringCopy(ti->graphics_set_ecs);
2682 ti_copy->graphics_set_aga = getStringCopy(ti->graphics_set_aga);
2683 ti_copy->graphics_set = getStringCopy(ti->graphics_set);
2684 ti_copy->sounds_set_default = getStringCopy(ti->sounds_set_default);
2685 ti_copy->sounds_set_lowpass = getStringCopy(ti->sounds_set_lowpass);
2686 ti_copy->sounds_set = getStringCopy(ti->sounds_set);
2687 ti_copy->music_set = getStringCopy(ti->music_set);
2688 ti_copy->graphics_path = getStringCopy(ti->graphics_path);
2689 ti_copy->sounds_path = getStringCopy(ti->sounds_path);
2690 ti_copy->music_path = getStringCopy(ti->music_path);
2692 ti_copy->level_filename = getStringCopy(ti->level_filename);
2693 ti_copy->level_filetype = getStringCopy(ti->level_filetype);
2695 ti_copy->special_flags = getStringCopy(ti->special_flags);
2697 ti_copy->levels = ti->levels;
2698 ti_copy->first_level = ti->first_level;
2699 ti_copy->last_level = ti->last_level;
2700 ti_copy->sort_priority = ti->sort_priority;
2702 ti_copy->latest_engine = ti->latest_engine;
2704 ti_copy->level_group = ti->level_group;
2705 ti_copy->parent_link = ti->parent_link;
2706 ti_copy->is_copy = ti->is_copy;
2707 ti_copy->in_user_dir = ti->in_user_dir;
2708 ti_copy->user_defined = ti->user_defined;
2709 ti_copy->readonly = ti->readonly;
2710 ti_copy->handicap = ti->handicap;
2711 ti_copy->skip_levels = ti->skip_levels;
2713 ti_copy->use_emc_tiles = ti->use_emc_tiles;
2715 ti_copy->color = ti->color;
2716 ti_copy->class_desc = getStringCopy(ti->class_desc);
2717 ti_copy->handicap_level = ti->handicap_level;
2719 ti_copy->infotext = getStringCopy(ti->infotext);
2724 void freeTreeInfo(TreeInfo *ti)
2729 checked_free(ti->subdir);
2730 checked_free(ti->fullpath);
2731 checked_free(ti->basepath);
2732 checked_free(ti->identifier);
2734 checked_free(ti->name);
2735 checked_free(ti->name_sorting);
2736 checked_free(ti->author);
2737 checked_free(ti->year);
2739 checked_free(ti->program_title);
2740 checked_free(ti->program_copyright);
2741 checked_free(ti->program_company);
2743 checked_free(ti->class_desc);
2745 checked_free(ti->infotext);
2747 if (ti->type == TREE_TYPE_LEVEL_DIR)
2749 checked_free(ti->imported_from);
2750 checked_free(ti->imported_by);
2751 checked_free(ti->tested_by);
2753 checked_free(ti->graphics_set_ecs);
2754 checked_free(ti->graphics_set_aga);
2755 checked_free(ti->graphics_set);
2756 checked_free(ti->sounds_set_default);
2757 checked_free(ti->sounds_set_lowpass);
2758 checked_free(ti->sounds_set);
2759 checked_free(ti->music_set);
2761 checked_free(ti->graphics_path);
2762 checked_free(ti->sounds_path);
2763 checked_free(ti->music_path);
2765 checked_free(ti->level_filename);
2766 checked_free(ti->level_filetype);
2768 checked_free(ti->special_flags);
2771 // recursively free child node
2773 freeTreeInfo(ti->node_group);
2775 // recursively free next node
2777 freeTreeInfo(ti->next);
2782 void setSetupInfo(struct TokenInfo *token_info,
2783 int token_nr, char *token_value)
2785 int token_type = token_info[token_nr].type;
2786 void *setup_value = token_info[token_nr].value;
2788 if (token_value == NULL)
2791 // set setup field to corresponding token value
2796 *(boolean *)setup_value = get_boolean_from_string(token_value);
2800 *(int *)setup_value = get_switch3_from_string(token_value);
2804 *(Key *)setup_value = getKeyFromKeyName(token_value);
2808 *(Key *)setup_value = getKeyFromX11KeyName(token_value);
2812 *(int *)setup_value = get_integer_from_string(token_value);
2816 checked_free(*(char **)setup_value);
2817 *(char **)setup_value = getStringCopy(token_value);
2821 *(int *)setup_value = get_player_nr_from_string(token_value);
2829 static int compareTreeInfoEntries(const void *object1, const void *object2)
2831 const TreeInfo *entry1 = *((TreeInfo **)object1);
2832 const TreeInfo *entry2 = *((TreeInfo **)object2);
2833 int tree_sorting1 = TREE_SORTING(entry1);
2834 int tree_sorting2 = TREE_SORTING(entry2);
2836 if (tree_sorting1 != tree_sorting2)
2837 return (tree_sorting1 - tree_sorting2);
2839 return strcasecmp(entry1->name_sorting, entry2->name_sorting);
2842 static TreeInfo *createParentTreeInfoNode(TreeInfo *node_parent)
2846 if (node_parent == NULL)
2849 ti_new = newTreeInfo();
2850 setTreeInfoToDefaults(ti_new, node_parent->type);
2852 ti_new->node_parent = node_parent;
2853 ti_new->parent_link = TRUE;
2855 setString(&ti_new->identifier, node_parent->identifier);
2856 setString(&ti_new->name, BACKLINK_TEXT_PARENT);
2857 setString(&ti_new->name_sorting, ti_new->name);
2859 setString(&ti_new->subdir, STRING_PARENT_DIRECTORY);
2860 setString(&ti_new->fullpath, node_parent->fullpath);
2862 ti_new->sort_priority = LEVELCLASS_PARENT;
2863 ti_new->latest_engine = node_parent->latest_engine;
2865 setString(&ti_new->class_desc, getLevelClassDescription(ti_new));
2867 pushTreeInfo(&node_parent->node_group, ti_new);
2872 static TreeInfo *createTopTreeInfoNode(TreeInfo *node_first)
2874 if (node_first == NULL)
2877 TreeInfo *ti_new = newTreeInfo();
2878 int type = node_first->type;
2880 setTreeInfoToDefaults(ti_new, type);
2882 ti_new->node_parent = NULL;
2883 ti_new->parent_link = FALSE;
2885 setString(&ti_new->identifier, "top_tree_node");
2886 setString(&ti_new->name, TREE_INFOTEXT(type));
2887 setString(&ti_new->name_sorting, ti_new->name);
2889 setString(&ti_new->subdir, STRING_TOP_DIRECTORY);
2890 setString(&ti_new->fullpath, ".");
2892 ti_new->sort_priority = LEVELCLASS_TOP;
2893 ti_new->latest_engine = node_first->latest_engine;
2895 setString(&ti_new->class_desc, TREE_INFOTEXT(type));
2897 ti_new->node_group = node_first;
2898 ti_new->level_group = TRUE;
2900 TreeInfo *ti_new2 = createParentTreeInfoNode(ti_new);
2902 setString(&ti_new2->name, TREE_BACKLINK_TEXT(type));
2903 setString(&ti_new2->name_sorting, ti_new2->name);
2908 static void setTreeInfoParentNodes(TreeInfo *node, TreeInfo *node_parent)
2912 if (node->node_group)
2913 setTreeInfoParentNodes(node->node_group, node);
2915 node->node_parent = node_parent;
2922 // ----------------------------------------------------------------------------
2923 // functions for handling level and custom artwork info cache
2924 // ----------------------------------------------------------------------------
2926 static void LoadArtworkInfoCache(void)
2928 InitCacheDirectory();
2930 if (artworkinfo_cache_old == NULL)
2932 char *filename = getPath2(getCacheDir(), ARTWORKINFO_CACHE_FILE);
2934 // try to load artwork info hash from already existing cache file
2935 artworkinfo_cache_old = loadSetupFileHash(filename);
2937 // try to get program version that artwork info cache was written with
2938 char *version = getHashEntry(artworkinfo_cache_old, "program.version");
2940 // check program version of artwork info cache against current version
2941 if (!strEqual(version, program.version_string))
2943 freeSetupFileHash(artworkinfo_cache_old);
2945 artworkinfo_cache_old = NULL;
2948 // if no artwork info cache file was found, start with empty hash
2949 if (artworkinfo_cache_old == NULL)
2950 artworkinfo_cache_old = newSetupFileHash();
2955 if (artworkinfo_cache_new == NULL)
2956 artworkinfo_cache_new = newSetupFileHash();
2958 update_artworkinfo_cache = FALSE;
2961 static void SaveArtworkInfoCache(void)
2963 if (!update_artworkinfo_cache)
2966 char *filename = getPath2(getCacheDir(), ARTWORKINFO_CACHE_FILE);
2968 InitCacheDirectory();
2970 saveSetupFileHash(artworkinfo_cache_new, filename);
2975 static char *getCacheTokenPrefix(char *prefix1, char *prefix2)
2977 static char *prefix = NULL;
2979 checked_free(prefix);
2981 prefix = getStringCat2WithSeparator(prefix1, prefix2, ".");
2986 // (identical to above function, but separate string buffer needed -- nasty)
2987 static char *getCacheToken(char *prefix, char *suffix)
2989 static char *token = NULL;
2991 checked_free(token);
2993 token = getStringCat2WithSeparator(prefix, suffix, ".");
2998 static char *getFileTimestampString(char *filename)
3000 return getStringCopy(i_to_a(getFileTimestampEpochSeconds(filename)));
3003 static boolean modifiedFileTimestamp(char *filename, char *timestamp_string)
3005 struct stat file_status;
3007 if (timestamp_string == NULL)
3010 if (!fileExists(filename)) // file does not exist
3011 return (atoi(timestamp_string) != 0);
3013 if (stat(filename, &file_status) != 0) // cannot stat file
3016 return (file_status.st_mtime != atoi(timestamp_string));
3019 static TreeInfo *getArtworkInfoCacheEntry(LevelDirTree *level_node, int type)
3021 char *identifier = level_node->subdir;
3022 char *type_string = ARTWORK_DIRECTORY(type);
3023 char *token_prefix = getCacheTokenPrefix(type_string, identifier);
3024 char *token_main = getCacheToken(token_prefix, "CACHED");
3025 char *cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
3026 boolean cached = (cache_entry != NULL && strEqual(cache_entry, "true"));
3027 TreeInfo *artwork_info = NULL;
3029 if (!use_artworkinfo_cache)
3032 if (optional_tokens_hash == NULL)
3036 // create hash from list of optional tokens (for quick access)
3037 optional_tokens_hash = newSetupFileHash();
3038 for (i = 0; optional_tokens[i] != NULL; i++)
3039 setHashEntry(optional_tokens_hash, optional_tokens[i], "");
3046 artwork_info = newTreeInfo();
3047 setTreeInfoToDefaults(artwork_info, type);
3049 // set all structure fields according to the token/value pairs
3050 ldi = *artwork_info;
3051 for (i = 0; artworkinfo_tokens[i].type != -1; i++)
3053 char *token_suffix = artworkinfo_tokens[i].text;
3054 char *token = getCacheToken(token_prefix, token_suffix);
3055 char *value = getHashEntry(artworkinfo_cache_old, token);
3057 (getHashEntry(optional_tokens_hash, token_suffix) != NULL);
3059 setSetupInfo(artworkinfo_tokens, i, value);
3061 // check if cache entry for this item is mandatory, but missing
3062 if (value == NULL && !optional)
3064 Warn("missing cache entry '%s'", token);
3070 *artwork_info = ldi;
3075 char *filename_levelinfo = getPath2(getLevelDirFromTreeInfo(level_node),
3076 LEVELINFO_FILENAME);
3077 char *filename_artworkinfo = getPath2(getSetupArtworkDir(artwork_info),
3078 ARTWORKINFO_FILENAME(type));
3080 // check if corresponding "levelinfo.conf" file has changed
3081 token_main = getCacheToken(token_prefix, "TIMESTAMP_LEVELINFO");
3082 cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
3084 if (modifiedFileTimestamp(filename_levelinfo, cache_entry))
3087 // check if corresponding "<artworkinfo>.conf" file has changed
3088 token_main = getCacheToken(token_prefix, "TIMESTAMP_ARTWORKINFO");
3089 cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
3091 if (modifiedFileTimestamp(filename_artworkinfo, cache_entry))
3094 checked_free(filename_levelinfo);
3095 checked_free(filename_artworkinfo);
3098 if (!cached && artwork_info != NULL)
3100 freeTreeInfo(artwork_info);
3105 return artwork_info;
3108 static void setArtworkInfoCacheEntry(TreeInfo *artwork_info,
3109 LevelDirTree *level_node, int type)
3111 char *identifier = level_node->subdir;
3112 char *type_string = ARTWORK_DIRECTORY(type);
3113 char *token_prefix = getCacheTokenPrefix(type_string, identifier);
3114 char *token_main = getCacheToken(token_prefix, "CACHED");
3115 boolean set_cache_timestamps = TRUE;
3118 setHashEntry(artworkinfo_cache_new, token_main, "true");
3120 if (set_cache_timestamps)
3122 char *filename_levelinfo = getPath2(getLevelDirFromTreeInfo(level_node),
3123 LEVELINFO_FILENAME);
3124 char *filename_artworkinfo = getPath2(getSetupArtworkDir(artwork_info),
3125 ARTWORKINFO_FILENAME(type));
3126 char *timestamp_levelinfo = getFileTimestampString(filename_levelinfo);
3127 char *timestamp_artworkinfo = getFileTimestampString(filename_artworkinfo);
3129 token_main = getCacheToken(token_prefix, "TIMESTAMP_LEVELINFO");
3130 setHashEntry(artworkinfo_cache_new, token_main, timestamp_levelinfo);
3132 token_main = getCacheToken(token_prefix, "TIMESTAMP_ARTWORKINFO");
3133 setHashEntry(artworkinfo_cache_new, token_main, timestamp_artworkinfo);
3135 checked_free(filename_levelinfo);
3136 checked_free(filename_artworkinfo);
3137 checked_free(timestamp_levelinfo);
3138 checked_free(timestamp_artworkinfo);
3141 ldi = *artwork_info;
3142 for (i = 0; artworkinfo_tokens[i].type != -1; i++)
3144 char *token = getCacheToken(token_prefix, artworkinfo_tokens[i].text);
3145 char *value = getSetupValue(artworkinfo_tokens[i].type,
3146 artworkinfo_tokens[i].value);
3148 setHashEntry(artworkinfo_cache_new, token, value);
3153 // ----------------------------------------------------------------------------
3154 // functions for loading level info and custom artwork info
3155 // ----------------------------------------------------------------------------
3157 int GetZipFileTreeType(char *zip_filename)
3159 static char *top_dir_path = NULL;
3160 static char *top_dir_conf_filename[NUM_BASE_TREE_TYPES] = { NULL };
3161 static char *conf_basename[NUM_BASE_TREE_TYPES] =
3163 GRAPHICSINFO_FILENAME,
3164 SOUNDSINFO_FILENAME,
3170 checked_free(top_dir_path);
3171 top_dir_path = NULL;
3173 for (j = 0; j < NUM_BASE_TREE_TYPES; j++)
3175 checked_free(top_dir_conf_filename[j]);
3176 top_dir_conf_filename[j] = NULL;
3179 char **zip_entries = zip_list(zip_filename);
3181 // check if zip file successfully opened
3182 if (zip_entries == NULL || zip_entries[0] == NULL)
3183 return TREE_TYPE_UNDEFINED;
3185 // first zip file entry is expected to be top level directory
3186 char *top_dir = zip_entries[0];
3188 // check if valid top level directory found in zip file
3189 if (!strSuffix(top_dir, "/"))
3190 return TREE_TYPE_UNDEFINED;
3192 // get filenames of valid configuration files in top level directory
3193 for (j = 0; j < NUM_BASE_TREE_TYPES; j++)
3194 top_dir_conf_filename[j] = getStringCat2(top_dir, conf_basename[j]);
3196 int tree_type = TREE_TYPE_UNDEFINED;
3199 while (zip_entries[e] != NULL)
3201 // check if every zip file entry is below top level directory
3202 if (!strPrefix(zip_entries[e], top_dir))
3203 return TREE_TYPE_UNDEFINED;
3205 // check if this zip file entry is a valid configuration filename
3206 for (j = 0; j < NUM_BASE_TREE_TYPES; j++)
3208 if (strEqual(zip_entries[e], top_dir_conf_filename[j]))
3210 // only exactly one valid configuration file allowed
3211 if (tree_type != TREE_TYPE_UNDEFINED)
3212 return TREE_TYPE_UNDEFINED;
3224 static boolean CheckZipFileForDirectory(char *zip_filename, char *directory,
3227 static char *top_dir_path = NULL;
3228 static char *top_dir_conf_filename = NULL;
3230 checked_free(top_dir_path);
3231 checked_free(top_dir_conf_filename);
3233 top_dir_path = NULL;
3234 top_dir_conf_filename = NULL;
3236 char *conf_basename = (tree_type == TREE_TYPE_LEVEL_DIR ? LEVELINFO_FILENAME :
3237 ARTWORKINFO_FILENAME(tree_type));
3239 // check if valid configuration filename determined
3240 if (conf_basename == NULL || strEqual(conf_basename, ""))
3243 char **zip_entries = zip_list(zip_filename);
3245 // check if zip file successfully opened
3246 if (zip_entries == NULL || zip_entries[0] == NULL)
3249 // first zip file entry is expected to be top level directory
3250 char *top_dir = zip_entries[0];
3252 // check if valid top level directory found in zip file
3253 if (!strSuffix(top_dir, "/"))
3256 // get path of extracted top level directory
3257 top_dir_path = getPath2(directory, top_dir);
3259 // remove trailing directory separator from top level directory path
3260 // (required to be able to check for file and directory in next step)
3261 top_dir_path[strlen(top_dir_path) - 1] = '\0';
3263 // check if zip file's top level directory already exists in target directory
3264 if (fileExists(top_dir_path)) // (checks for file and directory)
3267 // get filename of configuration file in top level directory
3268 top_dir_conf_filename = getStringCat2(top_dir, conf_basename);
3270 boolean found_top_dir_conf_filename = FALSE;
3273 while (zip_entries[i] != NULL)
3275 // check if every zip file entry is below top level directory
3276 if (!strPrefix(zip_entries[i], top_dir))
3279 // check if this zip file entry is the configuration filename
3280 if (strEqual(zip_entries[i], top_dir_conf_filename))
3281 found_top_dir_conf_filename = TRUE;
3286 // check if valid configuration filename was found in zip file
3287 if (!found_top_dir_conf_filename)
3293 char *ExtractZipFileIntoDirectory(char *zip_filename, char *directory,
3296 boolean zip_file_valid = CheckZipFileForDirectory(zip_filename, directory,
3299 if (!zip_file_valid)
3301 Warn("zip file '%s' rejected!", zip_filename);
3306 char **zip_entries = zip_extract(zip_filename, directory);
3308 if (zip_entries == NULL)
3310 Warn("zip file '%s' could not be extracted!", zip_filename);
3315 Info("zip file '%s' successfully extracted!", zip_filename);
3317 // first zip file entry contains top level directory
3318 char *top_dir = zip_entries[0];
3320 // remove trailing directory separator from top level directory
3321 top_dir[strlen(top_dir) - 1] = '\0';
3326 static void ProcessZipFilesInDirectory(char *directory, int tree_type)
3329 DirectoryEntry *dir_entry;
3331 if ((dir = openDirectory(directory)) == NULL)
3333 // display error if directory is main "options.graphics_directory" etc.
3334 if (tree_type == TREE_TYPE_LEVEL_DIR ||
3335 directory == OPTIONS_ARTWORK_DIRECTORY(tree_type))
3336 Warn("cannot read directory '%s'", directory);
3341 while ((dir_entry = readDirectory(dir)) != NULL) // loop all entries
3343 // skip non-zip files (and also directories with zip extension)
3344 if (!strSuffixLower(dir_entry->basename, ".zip") || dir_entry->is_directory)
3347 char *zip_filename = getPath2(directory, dir_entry->basename);
3348 char *zip_filename_extracted = getStringCat2(zip_filename, ".extracted");
3349 char *zip_filename_rejected = getStringCat2(zip_filename, ".rejected");
3351 // check if zip file hasn't already been extracted or rejected
3352 if (!fileExists(zip_filename_extracted) &&
3353 !fileExists(zip_filename_rejected))
3355 char *top_dir = ExtractZipFileIntoDirectory(zip_filename, directory,
3357 char *marker_filename = (top_dir != NULL ? zip_filename_extracted :
3358 zip_filename_rejected);
3361 // create empty file to mark zip file as extracted or rejected
3362 if ((marker_file = fopen(marker_filename, MODE_WRITE)))
3363 fclose(marker_file);
3366 free(zip_filename_extracted);
3367 free(zip_filename_rejected);
3371 closeDirectory(dir);
3374 // forward declaration for recursive call by "LoadLevelInfoFromLevelDir()"
3375 static void LoadLevelInfoFromLevelDir(TreeInfo **, TreeInfo *, char *);
3377 static boolean LoadLevelInfoFromLevelConf(TreeInfo **node_first,
3378 TreeInfo *node_parent,
3379 char *level_directory,
3380 char *directory_name)
3382 char *directory_path = getPath2(level_directory, directory_name);
3383 char *filename = getPath2(directory_path, LEVELINFO_FILENAME);
3384 SetupFileHash *setup_file_hash;
3385 LevelDirTree *leveldir_new = NULL;
3388 // unless debugging, silently ignore directories without "levelinfo.conf"
3389 if (!options.debug && !fileExists(filename))
3391 free(directory_path);
3397 setup_file_hash = loadSetupFileHash(filename);
3399 if (setup_file_hash == NULL)
3401 #if DEBUG_NO_CONFIG_FILE
3402 Debug("setup", "ignoring level directory '%s'", directory_path);
3405 free(directory_path);
3411 leveldir_new = newTreeInfo();
3414 setTreeInfoToDefaultsFromParent(leveldir_new, node_parent);
3416 setTreeInfoToDefaults(leveldir_new, TREE_TYPE_LEVEL_DIR);
3418 leveldir_new->subdir = getStringCopy(directory_name);
3420 // set all structure fields according to the token/value pairs
3421 ldi = *leveldir_new;
3422 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
3423 setSetupInfo(levelinfo_tokens, i,
3424 getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
3425 *leveldir_new = ldi;
3427 if (strEqual(leveldir_new->name, ANONYMOUS_NAME))
3428 setString(&leveldir_new->name, leveldir_new->subdir);
3430 if (leveldir_new->identifier == NULL)
3431 leveldir_new->identifier = getStringCopy(leveldir_new->subdir);
3433 if (leveldir_new->name_sorting == NULL)
3434 leveldir_new->name_sorting = getStringCopy(leveldir_new->name);
3436 if (node_parent == NULL) // top level group
3438 leveldir_new->basepath = getStringCopy(level_directory);
3439 leveldir_new->fullpath = getStringCopy(leveldir_new->subdir);
3441 else // sub level group
3443 leveldir_new->basepath = getStringCopy(node_parent->basepath);
3444 leveldir_new->fullpath = getPath2(node_parent->fullpath, directory_name);
3447 leveldir_new->last_level =
3448 leveldir_new->first_level + leveldir_new->levels - 1;
3450 leveldir_new->in_user_dir =
3451 (!strEqual(leveldir_new->basepath, options.level_directory));
3453 // adjust some settings if user's private level directory was detected
3454 if (leveldir_new->sort_priority == LEVELCLASS_UNDEFINED &&
3455 leveldir_new->in_user_dir &&
3456 (strEqual(leveldir_new->subdir, getLoginName()) ||
3457 strEqual(leveldir_new->name, getLoginName()) ||
3458 strEqual(leveldir_new->author, getRealName())))
3460 leveldir_new->sort_priority = LEVELCLASS_PRIVATE_START;
3461 leveldir_new->readonly = FALSE;
3464 leveldir_new->user_defined =
3465 (leveldir_new->in_user_dir && IS_LEVELCLASS_PRIVATE(leveldir_new));
3467 setString(&leveldir_new->class_desc, getLevelClassDescription(leveldir_new));
3469 leveldir_new->handicap_level = // set handicap to default value
3470 (leveldir_new->user_defined || !leveldir_new->handicap ?
3471 leveldir_new->last_level : leveldir_new->first_level);
3473 DrawInitText(leveldir_new->name, 150, FC_YELLOW);
3475 pushTreeInfo(node_first, leveldir_new);
3477 freeSetupFileHash(setup_file_hash);
3479 if (leveldir_new->level_group)
3481 // create node to link back to current level directory
3482 createParentTreeInfoNode(leveldir_new);
3484 // recursively step into sub-directory and look for more level series
3485 LoadLevelInfoFromLevelDir(&leveldir_new->node_group,
3486 leveldir_new, directory_path);
3489 free(directory_path);
3495 static void LoadLevelInfoFromLevelDir(TreeInfo **node_first,
3496 TreeInfo *node_parent,
3497 char *level_directory)
3499 // ---------- 1st stage: process any level set zip files ----------
3501 ProcessZipFilesInDirectory(level_directory, TREE_TYPE_LEVEL_DIR);
3503 // ---------- 2nd stage: check for level set directories ----------
3506 DirectoryEntry *dir_entry;
3507 boolean valid_entry_found = FALSE;
3509 if ((dir = openDirectory(level_directory)) == NULL)
3511 Warn("cannot read level directory '%s'", level_directory);
3516 while ((dir_entry = readDirectory(dir)) != NULL) // loop all entries
3518 char *directory_name = dir_entry->basename;
3519 char *directory_path = getPath2(level_directory, directory_name);
3521 // skip entries for current and parent directory
3522 if (strEqual(directory_name, ".") ||
3523 strEqual(directory_name, ".."))
3525 free(directory_path);
3530 // find out if directory entry is itself a directory
3531 if (!dir_entry->is_directory) // not a directory
3533 free(directory_path);
3538 free(directory_path);
3540 if (strEqual(directory_name, GRAPHICS_DIRECTORY) ||
3541 strEqual(directory_name, SOUNDS_DIRECTORY) ||
3542 strEqual(directory_name, MUSIC_DIRECTORY))
3545 valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
3550 closeDirectory(dir);
3552 // special case: top level directory may directly contain "levelinfo.conf"
3553 if (node_parent == NULL && !valid_entry_found)
3555 // check if this directory directly contains a file "levelinfo.conf"
3556 valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
3557 level_directory, ".");
3560 if (!valid_entry_found)
3561 Warn("cannot find any valid level series in directory '%s'",
3565 boolean AdjustGraphicsForEMC(void)
3567 boolean settings_changed = FALSE;
3569 settings_changed |= adjustTreeGraphicsForEMC(leveldir_first_all);
3570 settings_changed |= adjustTreeGraphicsForEMC(leveldir_first);
3572 return settings_changed;
3575 boolean AdjustSoundsForEMC(void)
3577 boolean settings_changed = FALSE;
3579 settings_changed |= adjustTreeSoundsForEMC(leveldir_first_all);
3580 settings_changed |= adjustTreeSoundsForEMC(leveldir_first);
3582 return settings_changed;
3585 void LoadLevelInfo(void)
3587 InitUserLevelDirectory(getLoginName());
3589 DrawInitText("Loading level series", 120, FC_GREEN);
3591 LoadLevelInfoFromLevelDir(&leveldir_first, NULL, options.level_directory);
3592 LoadLevelInfoFromLevelDir(&leveldir_first, NULL, getUserLevelDir(NULL));
3594 leveldir_first = createTopTreeInfoNode(leveldir_first);
3596 /* after loading all level set information, clone the level directory tree
3597 and remove all level sets without levels (these may still contain artwork
3598 to be offered in the setup menu as "custom artwork", and are therefore
3599 checked for existing artwork in the function "LoadLevelArtworkInfo()") */
3600 leveldir_first_all = leveldir_first;
3601 cloneTree(&leveldir_first, leveldir_first_all, TRUE);
3603 AdjustGraphicsForEMC();
3604 AdjustSoundsForEMC();
3606 // before sorting, the first entries will be from the user directory
3607 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
3609 if (leveldir_first == NULL)
3610 Fail("cannot find any valid level series in any directory");
3612 sortTreeInfo(&leveldir_first);
3614 #if ENABLE_UNUSED_CODE
3615 dumpTreeInfo(leveldir_first, 0);
3619 static boolean LoadArtworkInfoFromArtworkConf(TreeInfo **node_first,
3620 TreeInfo *node_parent,
3621 char *base_directory,
3622 char *directory_name, int type)
3624 char *directory_path = getPath2(base_directory, directory_name);
3625 char *filename = getPath2(directory_path, ARTWORKINFO_FILENAME(type));
3626 SetupFileHash *setup_file_hash = NULL;
3627 TreeInfo *artwork_new = NULL;
3630 if (fileExists(filename))
3631 setup_file_hash = loadSetupFileHash(filename);
3633 if (setup_file_hash == NULL) // no config file -- look for artwork files
3636 DirectoryEntry *dir_entry;
3637 boolean valid_file_found = FALSE;
3639 if ((dir = openDirectory(directory_path)) != NULL)
3641 while ((dir_entry = readDirectory(dir)) != NULL)
3643 if (FileIsArtworkType(dir_entry->filename, type))
3645 valid_file_found = TRUE;
3651 closeDirectory(dir);
3654 if (!valid_file_found)
3656 #if DEBUG_NO_CONFIG_FILE
3657 if (!strEqual(directory_name, "."))
3658 Debug("setup", "ignoring artwork directory '%s'", directory_path);
3661 free(directory_path);
3668 artwork_new = newTreeInfo();
3671 setTreeInfoToDefaultsFromParent(artwork_new, node_parent);
3673 setTreeInfoToDefaults(artwork_new, type);
3675 artwork_new->subdir = getStringCopy(directory_name);
3677 if (setup_file_hash) // (before defining ".color" and ".class_desc")
3679 // set all structure fields according to the token/value pairs
3681 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
3682 setSetupInfo(levelinfo_tokens, i,
3683 getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
3686 if (strEqual(artwork_new->name, ANONYMOUS_NAME))
3687 setString(&artwork_new->name, artwork_new->subdir);
3689 if (artwork_new->identifier == NULL)
3690 artwork_new->identifier = getStringCopy(artwork_new->subdir);
3692 if (artwork_new->name_sorting == NULL)
3693 artwork_new->name_sorting = getStringCopy(artwork_new->name);
3696 if (node_parent == NULL) // top level group
3698 artwork_new->basepath = getStringCopy(base_directory);
3699 artwork_new->fullpath = getStringCopy(artwork_new->subdir);
3701 else // sub level group
3703 artwork_new->basepath = getStringCopy(node_parent->basepath);
3704 artwork_new->fullpath = getPath2(node_parent->fullpath, directory_name);
3707 artwork_new->in_user_dir =
3708 (!strEqual(artwork_new->basepath, OPTIONS_ARTWORK_DIRECTORY(type)));
3710 setString(&artwork_new->class_desc, getLevelClassDescription(artwork_new));
3712 if (setup_file_hash == NULL) // (after determining ".user_defined")
3714 if (strEqual(artwork_new->subdir, "."))
3716 if (artwork_new->user_defined)
3718 setString(&artwork_new->identifier, "private");
3719 artwork_new->sort_priority = ARTWORKCLASS_PRIVATE;
3723 setString(&artwork_new->identifier, "classic");
3724 artwork_new->sort_priority = ARTWORKCLASS_CLASSICS;
3727 setString(&artwork_new->class_desc,
3728 getLevelClassDescription(artwork_new));
3732 setString(&artwork_new->identifier, artwork_new->subdir);
3735 setString(&artwork_new->name, artwork_new->identifier);
3736 setString(&artwork_new->name_sorting, artwork_new->name);
3739 pushTreeInfo(node_first, artwork_new);
3741 freeSetupFileHash(setup_file_hash);
3743 free(directory_path);
3749 static void LoadArtworkInfoFromArtworkDir(TreeInfo **node_first,
3750 TreeInfo *node_parent,
3751 char *base_directory, int type)
3753 // ---------- 1st stage: process any artwork set zip files ----------
3755 ProcessZipFilesInDirectory(base_directory, type);
3757 // ---------- 2nd stage: check for artwork set directories ----------
3760 DirectoryEntry *dir_entry;
3761 boolean valid_entry_found = FALSE;
3763 if ((dir = openDirectory(base_directory)) == NULL)
3765 // display error if directory is main "options.graphics_directory" etc.
3766 if (base_directory == OPTIONS_ARTWORK_DIRECTORY(type))
3767 Warn("cannot read directory '%s'", base_directory);
3772 while ((dir_entry = readDirectory(dir)) != NULL) // loop all entries
3774 char *directory_name = dir_entry->basename;
3775 char *directory_path = getPath2(base_directory, directory_name);
3777 // skip directory entries for current and parent directory
3778 if (strEqual(directory_name, ".") ||
3779 strEqual(directory_name, ".."))
3781 free(directory_path);
3786 // skip directory entries which are not a directory
3787 if (!dir_entry->is_directory) // not a directory
3789 free(directory_path);
3794 free(directory_path);
3796 // check if this directory contains artwork with or without config file
3797 valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
3799 directory_name, type);
3802 closeDirectory(dir);
3804 // check if this directory directly contains artwork itself
3805 valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
3806 base_directory, ".",
3808 if (!valid_entry_found)
3809 Warn("cannot find any valid artwork in directory '%s'", base_directory);
3812 static TreeInfo *getDummyArtworkInfo(int type)
3814 // this is only needed when there is completely no artwork available
3815 TreeInfo *artwork_new = newTreeInfo();
3817 setTreeInfoToDefaults(artwork_new, type);
3819 setString(&artwork_new->subdir, UNDEFINED_FILENAME);
3820 setString(&artwork_new->fullpath, UNDEFINED_FILENAME);
3821 setString(&artwork_new->basepath, UNDEFINED_FILENAME);
3823 setString(&artwork_new->identifier, UNDEFINED_FILENAME);
3824 setString(&artwork_new->name, UNDEFINED_FILENAME);
3825 setString(&artwork_new->name_sorting, UNDEFINED_FILENAME);
3830 void SetCurrentArtwork(int type)
3832 ArtworkDirTree **current_ptr = ARTWORK_CURRENT_PTR(artwork, type);
3833 ArtworkDirTree *first_node = ARTWORK_FIRST_NODE(artwork, type);
3834 char *setup_set = SETUP_ARTWORK_SET(setup, type);
3835 char *default_subdir = ARTWORK_DEFAULT_SUBDIR(type);
3837 // set current artwork to artwork configured in setup menu
3838 *current_ptr = getTreeInfoFromIdentifier(first_node, setup_set);
3840 // if not found, set current artwork to default artwork
3841 if (*current_ptr == NULL)
3842 *current_ptr = getTreeInfoFromIdentifier(first_node, default_subdir);
3844 // if not found, set current artwork to first artwork in tree
3845 if (*current_ptr == NULL)
3846 *current_ptr = getFirstValidTreeInfoEntry(first_node);
3849 void ChangeCurrentArtworkIfNeeded(int type)
3851 char *current_identifier = ARTWORK_CURRENT_IDENTIFIER(artwork, type);
3852 char *setup_set = SETUP_ARTWORK_SET(setup, type);
3854 if (!strEqual(current_identifier, setup_set))
3855 SetCurrentArtwork(type);
3858 void LoadArtworkInfo(void)
3860 LoadArtworkInfoCache();
3862 DrawInitText("Looking for custom artwork", 120, FC_GREEN);
3864 LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
3865 options.graphics_directory,
3866 TREE_TYPE_GRAPHICS_DIR);
3867 LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
3868 getUserGraphicsDir(),
3869 TREE_TYPE_GRAPHICS_DIR);
3871 LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
3872 options.sounds_directory,
3873 TREE_TYPE_SOUNDS_DIR);
3874 LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
3876 TREE_TYPE_SOUNDS_DIR);
3878 LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
3879 options.music_directory,
3880 TREE_TYPE_MUSIC_DIR);
3881 LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
3883 TREE_TYPE_MUSIC_DIR);
3885 if (artwork.gfx_first == NULL)
3886 artwork.gfx_first = getDummyArtworkInfo(TREE_TYPE_GRAPHICS_DIR);
3887 if (artwork.snd_first == NULL)
3888 artwork.snd_first = getDummyArtworkInfo(TREE_TYPE_SOUNDS_DIR);
3889 if (artwork.mus_first == NULL)
3890 artwork.mus_first = getDummyArtworkInfo(TREE_TYPE_MUSIC_DIR);
3892 // before sorting, the first entries will be from the user directory
3893 SetCurrentArtwork(ARTWORK_TYPE_GRAPHICS);
3894 SetCurrentArtwork(ARTWORK_TYPE_SOUNDS);
3895 SetCurrentArtwork(ARTWORK_TYPE_MUSIC);
3897 artwork.gfx_current_identifier = artwork.gfx_current->identifier;
3898 artwork.snd_current_identifier = artwork.snd_current->identifier;
3899 artwork.mus_current_identifier = artwork.mus_current->identifier;
3901 #if ENABLE_UNUSED_CODE
3902 Debug("setup:LoadArtworkInfo", "graphics set == %s",
3903 artwork.gfx_current_identifier);
3904 Debug("setup:LoadArtworkInfo", "sounds set == %s",
3905 artwork.snd_current_identifier);
3906 Debug("setup:LoadArtworkInfo", "music set == %s",
3907 artwork.mus_current_identifier);
3910 sortTreeInfo(&artwork.gfx_first);
3911 sortTreeInfo(&artwork.snd_first);
3912 sortTreeInfo(&artwork.mus_first);
3914 #if ENABLE_UNUSED_CODE
3915 dumpTreeInfo(artwork.gfx_first, 0);
3916 dumpTreeInfo(artwork.snd_first, 0);
3917 dumpTreeInfo(artwork.mus_first, 0);
3921 static void MoveArtworkInfoIntoSubTree(ArtworkDirTree **artwork_node)
3923 ArtworkDirTree *artwork_new = newTreeInfo();
3924 char *top_node_name = "standalone artwork";
3926 setTreeInfoToDefaults(artwork_new, (*artwork_node)->type);
3928 artwork_new->level_group = TRUE;
3930 setString(&artwork_new->identifier, top_node_name);
3931 setString(&artwork_new->name, top_node_name);
3932 setString(&artwork_new->name_sorting, top_node_name);
3934 // create node to link back to current custom artwork directory
3935 createParentTreeInfoNode(artwork_new);
3937 // move existing custom artwork tree into newly created sub-tree
3938 artwork_new->node_group->next = *artwork_node;
3940 // change custom artwork tree to contain only newly created node
3941 *artwork_node = artwork_new;
3944 static void LoadArtworkInfoFromLevelInfoExt(ArtworkDirTree **artwork_node,
3945 ArtworkDirTree *node_parent,
3946 LevelDirTree *level_node,
3947 boolean empty_level_set_mode)
3949 int type = (*artwork_node)->type;
3951 // recursively check all level directories for artwork sub-directories
3955 boolean empty_level_set = (level_node->levels == 0);
3957 // check all tree entries for artwork, but skip parent link entries
3958 if (!level_node->parent_link && empty_level_set == empty_level_set_mode)
3960 TreeInfo *artwork_new = getArtworkInfoCacheEntry(level_node, type);
3961 boolean cached = (artwork_new != NULL);
3965 pushTreeInfo(artwork_node, artwork_new);
3969 TreeInfo *topnode_last = *artwork_node;
3970 char *path = getPath2(getLevelDirFromTreeInfo(level_node),
3971 ARTWORK_DIRECTORY(type));
3973 LoadArtworkInfoFromArtworkDir(artwork_node, NULL, path, type);
3975 if (topnode_last != *artwork_node) // check for newly added node
3977 artwork_new = *artwork_node;
3979 setString(&artwork_new->identifier, level_node->subdir);
3980 setString(&artwork_new->name, level_node->name);
3981 setString(&artwork_new->name_sorting, level_node->name_sorting);
3983 artwork_new->sort_priority = level_node->sort_priority;
3984 artwork_new->in_user_dir = level_node->in_user_dir;
3986 update_artworkinfo_cache = TRUE;
3992 // insert artwork info (from old cache or filesystem) into new cache
3993 if (artwork_new != NULL)
3994 setArtworkInfoCacheEntry(artwork_new, level_node, type);
3997 DrawInitText(level_node->name, 150, FC_YELLOW);
3999 if (level_node->node_group != NULL)
4001 TreeInfo *artwork_new = newTreeInfo();
4004 setTreeInfoToDefaultsFromParent(artwork_new, node_parent);
4006 setTreeInfoToDefaults(artwork_new, type);
4008 artwork_new->level_group = TRUE;
4010 setString(&artwork_new->identifier, level_node->subdir);
4012 if (node_parent == NULL) // check for top tree node
4014 char *top_node_name = (empty_level_set_mode ?
4015 "artwork for certain level sets" :
4016 "artwork included in level sets");
4018 setString(&artwork_new->name, top_node_name);
4019 setString(&artwork_new->name_sorting, top_node_name);
4023 setString(&artwork_new->name, level_node->name);
4024 setString(&artwork_new->name_sorting, level_node->name_sorting);
4027 pushTreeInfo(artwork_node, artwork_new);
4029 // create node to link back to current custom artwork directory
4030 createParentTreeInfoNode(artwork_new);
4032 // recursively step into sub-directory and look for more custom artwork
4033 LoadArtworkInfoFromLevelInfoExt(&artwork_new->node_group, artwork_new,
4034 level_node->node_group,
4035 empty_level_set_mode);
4037 // if sub-tree has no custom artwork at all, remove it
4038 if (artwork_new->node_group->next == NULL)
4039 removeTreeInfo(artwork_node);
4042 level_node = level_node->next;
4046 static void LoadArtworkInfoFromLevelInfo(ArtworkDirTree **artwork_node)
4048 // move peviously loaded artwork tree into separate sub-tree
4049 MoveArtworkInfoIntoSubTree(artwork_node);
4051 // load artwork from level sets into separate sub-trees
4052 LoadArtworkInfoFromLevelInfoExt(artwork_node, NULL, leveldir_first_all, TRUE);
4053 LoadArtworkInfoFromLevelInfoExt(artwork_node, NULL, leveldir_first_all, FALSE);
4055 // add top tree node over all three separate sub-trees
4056 *artwork_node = createTopTreeInfoNode(*artwork_node);
4058 // set all parent links (back links) in complete artwork tree
4059 setTreeInfoParentNodes(*artwork_node, NULL);
4062 void LoadLevelArtworkInfo(void)
4064 print_timestamp_init("LoadLevelArtworkInfo");
4066 DrawInitText("Looking for custom level artwork", 120, FC_GREEN);
4068 print_timestamp_time("DrawTimeText");
4070 LoadArtworkInfoFromLevelInfo(&artwork.gfx_first);
4071 print_timestamp_time("LoadArtworkInfoFromLevelInfo (gfx)");
4072 LoadArtworkInfoFromLevelInfo(&artwork.snd_first);
4073 print_timestamp_time("LoadArtworkInfoFromLevelInfo (snd)");
4074 LoadArtworkInfoFromLevelInfo(&artwork.mus_first);
4075 print_timestamp_time("LoadArtworkInfoFromLevelInfo (mus)");
4077 SaveArtworkInfoCache();
4079 print_timestamp_time("SaveArtworkInfoCache");
4081 // needed for reloading level artwork not known at ealier stage
4082 ChangeCurrentArtworkIfNeeded(ARTWORK_TYPE_GRAPHICS);
4083 ChangeCurrentArtworkIfNeeded(ARTWORK_TYPE_SOUNDS);
4084 ChangeCurrentArtworkIfNeeded(ARTWORK_TYPE_MUSIC);
4086 print_timestamp_time("getTreeInfoFromIdentifier");
4088 sortTreeInfo(&artwork.gfx_first);
4089 sortTreeInfo(&artwork.snd_first);
4090 sortTreeInfo(&artwork.mus_first);
4092 print_timestamp_time("sortTreeInfo");
4094 #if ENABLE_UNUSED_CODE
4095 dumpTreeInfo(artwork.gfx_first, 0);
4096 dumpTreeInfo(artwork.snd_first, 0);
4097 dumpTreeInfo(artwork.mus_first, 0);
4100 print_timestamp_done("LoadLevelArtworkInfo");
4103 static boolean AddTreeSetToTreeInfoExt(TreeInfo *tree_node_old, char *tree_dir,
4104 char *tree_subdir_new, int type)
4106 if (tree_node_old == NULL)
4108 if (type == TREE_TYPE_LEVEL_DIR)
4110 // get level info tree node of personal user level set
4111 tree_node_old = getTreeInfoFromIdentifier(leveldir_first, getLoginName());
4113 // this may happen if "setup.internal.create_user_levelset" is FALSE
4114 // or if file "levelinfo.conf" is missing in personal user level set
4115 if (tree_node_old == NULL)
4116 tree_node_old = leveldir_first->node_group;
4120 // get artwork info tree node of first artwork set
4121 tree_node_old = ARTWORK_FIRST_NODE(artwork, type);
4125 if (tree_dir == NULL)
4126 tree_dir = TREE_USERDIR(type);
4128 if (tree_node_old == NULL ||
4130 tree_subdir_new == NULL) // should not happen
4133 int draw_deactivation_mask = GetDrawDeactivationMask();
4135 // override draw deactivation mask (temporarily disable drawing)
4136 SetDrawDeactivationMask(REDRAW_ALL);
4138 if (type == TREE_TYPE_LEVEL_DIR)
4140 // load new level set config and add it next to first user level set
4141 LoadLevelInfoFromLevelConf(&tree_node_old->next,
4142 tree_node_old->node_parent,
4143 tree_dir, tree_subdir_new);
4147 // load new artwork set config and add it next to first artwork set
4148 LoadArtworkInfoFromArtworkConf(&tree_node_old->next,
4149 tree_node_old->node_parent,
4150 tree_dir, tree_subdir_new, type);
4153 // set draw deactivation mask to previous value
4154 SetDrawDeactivationMask(draw_deactivation_mask);
4156 // get first node of level or artwork info tree
4157 TreeInfo **tree_node_first = TREE_FIRST_NODE_PTR(type);
4159 // get tree info node of newly added level or artwork set
4160 TreeInfo *tree_node_new = getTreeInfoFromIdentifier(*tree_node_first,
4163 if (tree_node_new == NULL) // should not happen
4166 // correct top link and parent node link of newly created tree node
4167 tree_node_new->node_top = tree_node_old->node_top;
4168 tree_node_new->node_parent = tree_node_old->node_parent;
4170 // sort tree info to adjust position of newly added tree set
4171 sortTreeInfo(tree_node_first);
4176 void AddTreeSetToTreeInfo(TreeInfo *tree_node, char *tree_dir,
4177 char *tree_subdir_new, int type)
4179 if (!AddTreeSetToTreeInfoExt(tree_node, tree_dir, tree_subdir_new, type))
4180 Fail("internal tree info structure corrupted -- aborting");
4183 void AddUserLevelSetToLevelInfo(char *level_subdir_new)
4185 AddTreeSetToTreeInfo(NULL, NULL, level_subdir_new, TREE_TYPE_LEVEL_DIR);
4188 char *getArtworkIdentifierForUserLevelSet(int type)
4190 char *classic_artwork_set = getClassicArtworkSet(type);
4192 // check for custom artwork configured in "levelinfo.conf"
4193 char *leveldir_artwork_set =
4194 *LEVELDIR_ARTWORK_SET_PTR(leveldir_current, type);
4195 boolean has_leveldir_artwork_set =
4196 (leveldir_artwork_set != NULL && !strEqual(leveldir_artwork_set,
4197 classic_artwork_set));
4199 // check for custom artwork in sub-directory "graphics" etc.
4200 TreeInfo *artwork_first_node = ARTWORK_FIRST_NODE(artwork, type);
4201 char *leveldir_identifier = leveldir_current->identifier;
4202 boolean has_artwork_subdir =
4203 (getTreeInfoFromIdentifier(artwork_first_node,
4204 leveldir_identifier) != NULL);
4206 return (has_leveldir_artwork_set ? leveldir_artwork_set :
4207 has_artwork_subdir ? leveldir_identifier :
4208 classic_artwork_set);
4211 TreeInfo *getArtworkTreeInfoForUserLevelSet(int type)
4213 char *artwork_set = getArtworkIdentifierForUserLevelSet(type);
4214 TreeInfo *artwork_first_node = ARTWORK_FIRST_NODE(artwork, type);
4215 TreeInfo *ti = getTreeInfoFromIdentifier(artwork_first_node, artwork_set);
4219 ti = getTreeInfoFromIdentifier(artwork_first_node,
4220 ARTWORK_DEFAULT_SUBDIR(type));
4222 Fail("cannot find default graphics -- should not happen");
4228 boolean checkIfCustomArtworkExistsForCurrentLevelSet(void)
4230 char *graphics_set =
4231 getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_GRAPHICS);
4233 getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_SOUNDS);
4235 getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_MUSIC);
4237 return (!strEqual(graphics_set, GFX_CLASSIC_SUBDIR) ||
4238 !strEqual(sounds_set, SND_CLASSIC_SUBDIR) ||
4239 !strEqual(music_set, MUS_CLASSIC_SUBDIR));
4242 boolean UpdateUserLevelSet(char *level_subdir, char *level_name,
4243 char *level_author, int num_levels)
4245 char *filename = getPath2(getUserLevelDir(level_subdir), LEVELINFO_FILENAME);
4246 char *filename_tmp = getStringCat2(filename, ".tmp");
4248 FILE *file_tmp = NULL;
4249 char line[MAX_LINE_LEN];
4250 boolean success = FALSE;
4251 LevelDirTree *leveldir = getTreeInfoFromIdentifier(leveldir_first,
4253 // update values in level directory tree
4255 if (level_name != NULL)
4256 setString(&leveldir->name, level_name);
4258 if (level_author != NULL)
4259 setString(&leveldir->author, level_author);
4261 if (num_levels != -1)
4262 leveldir->levels = num_levels;
4264 // update values that depend on other values
4266 setString(&leveldir->name_sorting, leveldir->name);
4268 leveldir->last_level = leveldir->first_level + leveldir->levels - 1;
4270 // sort order of level sets may have changed
4271 sortTreeInfo(&leveldir_first);
4273 if ((file = fopen(filename, MODE_READ)) &&
4274 (file_tmp = fopen(filename_tmp, MODE_WRITE)))
4276 while (fgets(line, MAX_LINE_LEN, file))
4278 if (strPrefix(line, "name:") && level_name != NULL)
4279 fprintf(file_tmp, "%-32s%s\n", "name:", level_name);
4280 else if (strPrefix(line, "author:") && level_author != NULL)
4281 fprintf(file_tmp, "%-32s%s\n", "author:", level_author);
4282 else if (strPrefix(line, "levels:") && num_levels != -1)
4283 fprintf(file_tmp, "%-32s%d\n", "levels:", num_levels);
4285 fputs(line, file_tmp);
4298 success = (rename(filename_tmp, filename) == 0);
4306 boolean CreateUserLevelSet(char *level_subdir, char *level_name,
4307 char *level_author, int num_levels,
4308 boolean use_artwork_set)
4310 LevelDirTree *level_info;
4315 // create user level sub-directory, if needed
4316 createDirectory(getUserLevelDir(level_subdir), "user level", PERMS_PRIVATE);
4318 filename = getPath2(getUserLevelDir(level_subdir), LEVELINFO_FILENAME);
4320 if (!(file = fopen(filename, MODE_WRITE)))
4322 Warn("cannot write level info file '%s'", filename);
4329 level_info = newTreeInfo();
4331 // always start with reliable default values
4332 setTreeInfoToDefaults(level_info, TREE_TYPE_LEVEL_DIR);
4334 setString(&level_info->name, level_name);
4335 setString(&level_info->author, level_author);
4336 level_info->levels = num_levels;
4337 level_info->first_level = 1;
4338 level_info->sort_priority = LEVELCLASS_PRIVATE_START;
4339 level_info->readonly = FALSE;
4341 if (use_artwork_set)
4343 level_info->graphics_set =
4344 getStringCopy(getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_GRAPHICS));
4345 level_info->sounds_set =
4346 getStringCopy(getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_SOUNDS));
4347 level_info->music_set =
4348 getStringCopy(getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_MUSIC));
4351 token_value_position = TOKEN_VALUE_POSITION_SHORT;
4353 fprintFileHeader(file, LEVELINFO_FILENAME);
4356 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
4358 if (i == LEVELINFO_TOKEN_NAME ||
4359 i == LEVELINFO_TOKEN_AUTHOR ||
4360 i == LEVELINFO_TOKEN_LEVELS ||
4361 i == LEVELINFO_TOKEN_FIRST_LEVEL ||
4362 i == LEVELINFO_TOKEN_SORT_PRIORITY ||
4363 i == LEVELINFO_TOKEN_READONLY ||
4364 (use_artwork_set && (i == LEVELINFO_TOKEN_GRAPHICS_SET ||
4365 i == LEVELINFO_TOKEN_SOUNDS_SET ||
4366 i == LEVELINFO_TOKEN_MUSIC_SET)))
4367 fprintf(file, "%s\n", getSetupLine(levelinfo_tokens, "", i));
4369 // just to make things nicer :)
4370 if (i == LEVELINFO_TOKEN_AUTHOR ||
4371 i == LEVELINFO_TOKEN_FIRST_LEVEL ||
4372 (use_artwork_set && i == LEVELINFO_TOKEN_READONLY))
4373 fprintf(file, "\n");
4376 token_value_position = TOKEN_VALUE_POSITION_DEFAULT;
4380 SetFilePermissions(filename, PERMS_PRIVATE);
4382 freeTreeInfo(level_info);
4388 static void SaveUserLevelInfo(void)
4390 CreateUserLevelSet(getLoginName(), getLoginName(), getRealName(), 100, FALSE);
4393 char *getSetupValue(int type, void *value)
4395 static char value_string[MAX_LINE_LEN];
4403 strcpy(value_string, (*(boolean *)value ? "true" : "false"));
4407 strcpy(value_string, (*(boolean *)value ? "on" : "off"));
4411 strcpy(value_string, (*(int *)value == AUTO ? "auto" :
4412 *(int *)value == FALSE ? "off" : "on"));
4416 strcpy(value_string, (*(boolean *)value ? "yes" : "no"));
4419 case TYPE_YES_NO_AUTO:
4420 strcpy(value_string, (*(int *)value == AUTO ? "auto" :
4421 *(int *)value == FALSE ? "no" : "yes"));
4425 strcpy(value_string, (*(boolean *)value ? "AGA" : "ECS"));
4429 strcpy(value_string, getKeyNameFromKey(*(Key *)value));
4433 strcpy(value_string, getX11KeyNameFromKey(*(Key *)value));
4437 sprintf(value_string, "%d", *(int *)value);
4441 if (*(char **)value == NULL)
4444 strcpy(value_string, *(char **)value);
4448 sprintf(value_string, "player_%d", *(int *)value + 1);
4452 value_string[0] = '\0';
4456 if (type & TYPE_GHOSTED)
4457 strcpy(value_string, "n/a");
4459 return value_string;
4462 char *getSetupLine(struct TokenInfo *token_info, char *prefix, int token_nr)
4466 static char token_string[MAX_LINE_LEN];
4467 int token_type = token_info[token_nr].type;
4468 void *setup_value = token_info[token_nr].value;
4469 char *token_text = token_info[token_nr].text;
4470 char *value_string = getSetupValue(token_type, setup_value);
4472 // build complete token string
4473 sprintf(token_string, "%s%s", prefix, token_text);
4475 // build setup entry line
4476 line = getFormattedSetupEntry(token_string, value_string);
4478 if (token_type == TYPE_KEY_X11)
4480 Key key = *(Key *)setup_value;
4481 char *keyname = getKeyNameFromKey(key);
4483 // add comment, if useful
4484 if (!strEqual(keyname, "(undefined)") &&
4485 !strEqual(keyname, "(unknown)"))
4487 // add at least one whitespace
4489 for (i = strlen(line); i < token_comment_position; i++)
4493 strcat(line, keyname);
4500 static void InitLastPlayedLevels_ParentNode(void)
4502 LevelDirTree **leveldir_top = &leveldir_first->node_group->next;
4503 LevelDirTree *leveldir_new = NULL;
4505 // check if parent node for last played levels already exists
4506 if (strEqual((*leveldir_top)->identifier, TOKEN_STR_LAST_LEVEL_SERIES))
4509 leveldir_new = newTreeInfo();
4511 setTreeInfoToDefaultsFromParent(leveldir_new, leveldir_first);
4513 leveldir_new->level_group = TRUE;
4514 leveldir_new->sort_priority = LEVELCLASS_LAST_PLAYED_LEVEL;
4516 setString(&leveldir_new->identifier, TOKEN_STR_LAST_LEVEL_SERIES);
4517 setString(&leveldir_new->name, "<< (last played level sets)");
4518 setString(&leveldir_new->name_sorting, leveldir_new->name);
4520 pushTreeInfo(leveldir_top, leveldir_new);
4522 // create node to link back to current level directory
4523 createParentTreeInfoNode(leveldir_new);
4526 void UpdateLastPlayedLevels_TreeInfo(void)
4528 char **last_level_series = setup.level_setup.last_level_series;
4529 boolean reset_leveldir_current = FALSE;
4530 LevelDirTree *leveldir_last;
4531 TreeInfo **node_new = NULL;
4534 if (last_level_series[0] == NULL)
4537 InitLastPlayedLevels_ParentNode();
4539 // check if current level set is from "last played" sub-tree to be rebuilt
4540 reset_leveldir_current = strEqual(leveldir_current->node_parent->identifier,
4541 TOKEN_STR_LAST_LEVEL_SERIES);
4543 leveldir_last = getTreeInfoFromIdentifierExt(leveldir_first,
4544 TOKEN_STR_LAST_LEVEL_SERIES,
4546 if (leveldir_last == NULL)
4549 node_new = &leveldir_last->node_group->next;
4551 freeTreeInfo(*node_new);
4555 for (i = 0; last_level_series[i] != NULL; i++)
4557 LevelDirTree *node_last = getTreeInfoFromIdentifier(leveldir_first,
4558 last_level_series[i]);
4559 if (node_last == NULL)
4562 *node_new = getTreeInfoCopy(node_last); // copy complete node
4564 (*node_new)->node_top = &leveldir_first; // correct top node link
4565 (*node_new)->node_parent = leveldir_last; // correct parent node link
4567 (*node_new)->is_copy = TRUE; // mark entry as node copy
4569 (*node_new)->node_group = NULL;
4570 (*node_new)->next = NULL;
4572 (*node_new)->cl_first = -1; // force setting tree cursor
4574 node_new = &((*node_new)->next);
4577 if (reset_leveldir_current)
4578 leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
4579 last_level_series[0]);
4582 static void UpdateLastPlayedLevels_List(void)
4584 char **last_level_series = setup.level_setup.last_level_series;
4585 int pos = MAX_LEVELDIR_HISTORY - 1;
4588 // search for potentially already existing entry in list of level sets
4589 for (i = 0; last_level_series[i] != NULL; i++)
4590 if (strEqual(last_level_series[i], leveldir_current->identifier))
4593 // move list of level sets one entry down (using potentially free entry)
4594 for (i = pos; i > 0; i--)
4595 setString(&last_level_series[i], last_level_series[i - 1]);
4597 // put last played level set at top position
4598 setString(&last_level_series[0], leveldir_current->identifier);
4601 void LoadLevelSetup_LastSeries(void)
4603 // --------------------------------------------------------------------------
4604 // ~/.<program>/levelsetup.conf
4605 // --------------------------------------------------------------------------
4607 char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
4608 SetupFileHash *level_setup_hash = NULL;
4612 // always start with reliable default values
4613 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
4615 // start with empty history of last played level sets
4616 setString(&setup.level_setup.last_level_series[0], NULL);
4618 if (!strEqual(DEFAULT_LEVELSET, UNDEFINED_LEVELSET))
4620 leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
4622 if (leveldir_current == NULL)
4623 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
4626 if ((level_setup_hash = loadSetupFileHash(filename)))
4628 char *last_level_series =
4629 getHashEntry(level_setup_hash, TOKEN_STR_LAST_LEVEL_SERIES);
4631 leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
4633 if (leveldir_current == NULL)
4634 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
4636 for (i = 0; i < MAX_LEVELDIR_HISTORY; i++)
4638 char token[strlen(TOKEN_STR_LAST_LEVEL_SERIES) + 10];
4639 LevelDirTree *leveldir_last;
4641 sprintf(token, "%s.%03d", TOKEN_STR_LAST_LEVEL_SERIES, i);
4643 last_level_series = getHashEntry(level_setup_hash, token);
4645 leveldir_last = getTreeInfoFromIdentifier(leveldir_first,
4647 if (leveldir_last != NULL)
4648 setString(&setup.level_setup.last_level_series[pos++],
4652 setString(&setup.level_setup.last_level_series[pos], NULL);
4654 freeSetupFileHash(level_setup_hash);
4658 Debug("setup", "using default setup values");
4664 static void SaveLevelSetup_LastSeries_Ext(boolean deactivate_last_level_series)
4666 // --------------------------------------------------------------------------
4667 // ~/.<program>/levelsetup.conf
4668 // --------------------------------------------------------------------------
4670 // check if the current level directory structure is available at this point
4671 if (leveldir_current == NULL)
4674 char **last_level_series = setup.level_setup.last_level_series;
4675 char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
4679 InitUserDataDirectory();
4681 UpdateLastPlayedLevels_List();
4683 if (!(file = fopen(filename, MODE_WRITE)))
4685 Warn("cannot write setup file '%s'", filename);
4692 fprintFileHeader(file, LEVELSETUP_FILENAME);
4694 if (deactivate_last_level_series)
4695 fprintf(file, "# %s\n# ", "the following level set may have caused a problem and was deactivated");
4697 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_LAST_LEVEL_SERIES,
4698 leveldir_current->identifier));
4700 for (i = 0; last_level_series[i] != NULL; i++)
4702 char token[strlen(TOKEN_STR_LAST_LEVEL_SERIES) + 10];
4704 sprintf(token, "%s.%03d", TOKEN_STR_LAST_LEVEL_SERIES, i);
4706 fprintf(file, "%s\n", getFormattedSetupEntry(token, last_level_series[i]));
4711 SetFilePermissions(filename, PERMS_PRIVATE);
4716 void SaveLevelSetup_LastSeries(void)
4718 SaveLevelSetup_LastSeries_Ext(FALSE);
4721 void SaveLevelSetup_LastSeries_Deactivate(void)
4723 SaveLevelSetup_LastSeries_Ext(TRUE);
4726 static void checkSeriesInfo(void)
4728 static char *level_directory = NULL;
4731 DirectoryEntry *dir_entry;
4734 checked_free(level_directory);
4736 // check for more levels besides the 'levels' field of 'levelinfo.conf'
4738 level_directory = getPath2((leveldir_current->in_user_dir ?
4739 getUserLevelDir(NULL) :
4740 options.level_directory),
4741 leveldir_current->fullpath);
4743 if ((dir = openDirectory(level_directory)) == NULL)
4745 Warn("cannot read level directory '%s'", level_directory);
4751 while ((dir_entry = readDirectory(dir)) != NULL) // loop all entries
4753 if (strlen(dir_entry->basename) > 4 &&
4754 dir_entry->basename[3] == '.' &&
4755 strEqual(&dir_entry->basename[4], LEVELFILE_EXTENSION))
4757 char levelnum_str[4];
4760 strncpy(levelnum_str, dir_entry->basename, 3);
4761 levelnum_str[3] = '\0';
4763 levelnum_value = atoi(levelnum_str);
4765 if (levelnum_value < leveldir_current->first_level)
4767 Warn("additional level %d found", levelnum_value);
4769 leveldir_current->first_level = levelnum_value;
4771 else if (levelnum_value > leveldir_current->last_level)
4773 Warn("additional level %d found", levelnum_value);
4775 leveldir_current->last_level = levelnum_value;
4781 closeDirectory(dir);
4784 void LoadLevelSetup_SeriesInfo(void)
4787 SetupFileHash *level_setup_hash = NULL;
4788 char *level_subdir = leveldir_current->subdir;
4791 // always start with reliable default values
4792 level_nr = leveldir_current->first_level;
4794 for (i = 0; i < MAX_LEVELS; i++)
4796 LevelStats_setPlayed(i, 0);
4797 LevelStats_setSolved(i, 0);
4802 // --------------------------------------------------------------------------
4803 // ~/.<program>/levelsetup/<level series>/levelsetup.conf
4804 // --------------------------------------------------------------------------
4806 level_subdir = leveldir_current->subdir;
4808 filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
4810 if ((level_setup_hash = loadSetupFileHash(filename)))
4814 // get last played level in this level set
4816 token_value = getHashEntry(level_setup_hash, TOKEN_STR_LAST_PLAYED_LEVEL);
4820 level_nr = atoi(token_value);
4822 if (level_nr < leveldir_current->first_level)
4823 level_nr = leveldir_current->first_level;
4824 if (level_nr > leveldir_current->last_level)
4825 level_nr = leveldir_current->last_level;
4828 // get handicap level in this level set
4830 token_value = getHashEntry(level_setup_hash, TOKEN_STR_HANDICAP_LEVEL);
4834 int 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 + 1)
4839 level_nr = leveldir_current->last_level;
4841 if (leveldir_current->user_defined || !leveldir_current->handicap)
4842 level_nr = leveldir_current->last_level;
4844 leveldir_current->handicap_level = level_nr;
4847 // get number of played and solved levels in this level set
4849 BEGIN_HASH_ITERATION(level_setup_hash, itr)
4851 char *token = HASH_ITERATION_TOKEN(itr);
4852 char *value = HASH_ITERATION_VALUE(itr);
4854 if (strlen(token) == 3 &&
4855 token[0] >= '0' && token[0] <= '9' &&
4856 token[1] >= '0' && token[1] <= '9' &&
4857 token[2] >= '0' && token[2] <= '9')
4859 int level_nr = atoi(token);
4862 LevelStats_setPlayed(level_nr, atoi(value)); // read 1st column
4864 value = strchr(value, ' ');
4867 LevelStats_setSolved(level_nr, atoi(value)); // read 2nd column
4870 END_HASH_ITERATION(hash, itr)
4872 freeSetupFileHash(level_setup_hash);
4876 Debug("setup", "using default setup values");
4882 void SaveLevelSetup_SeriesInfo(void)
4885 char *level_subdir = leveldir_current->subdir;
4886 char *level_nr_str = int2str(level_nr, 0);
4887 char *handicap_level_str = int2str(leveldir_current->handicap_level, 0);
4891 // --------------------------------------------------------------------------
4892 // ~/.<program>/levelsetup/<level series>/levelsetup.conf
4893 // --------------------------------------------------------------------------
4895 InitLevelSetupDirectory(level_subdir);
4897 filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
4899 if (!(file = fopen(filename, MODE_WRITE)))
4901 Warn("cannot write setup file '%s'", filename);
4908 fprintFileHeader(file, LEVELSETUP_FILENAME);
4910 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_PLAYED_LEVEL,
4912 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_HANDICAP_LEVEL,
4913 handicap_level_str));
4915 for (i = leveldir_current->first_level; i <= leveldir_current->last_level;
4918 if (LevelStats_getPlayed(i) > 0 ||
4919 LevelStats_getSolved(i) > 0)
4924 sprintf(token, "%03d", i);
4925 sprintf(value, "%d %d", LevelStats_getPlayed(i), LevelStats_getSolved(i));
4927 fprintf(file, "%s\n", getFormattedSetupEntry(token, value));
4933 SetFilePermissions(filename, PERMS_PRIVATE);
4938 int LevelStats_getPlayed(int nr)
4940 return (nr >= 0 && nr < MAX_LEVELS ? level_stats[nr].played : 0);
4943 int LevelStats_getSolved(int nr)
4945 return (nr >= 0 && nr < MAX_LEVELS ? level_stats[nr].solved : 0);
4948 void LevelStats_setPlayed(int nr, int value)
4950 if (nr >= 0 && nr < MAX_LEVELS)
4951 level_stats[nr].played = value;
4954 void LevelStats_setSolved(int nr, int value)
4956 if (nr >= 0 && nr < MAX_LEVELS)
4957 level_stats[nr].solved = value;
4960 void LevelStats_incPlayed(int nr)
4962 if (nr >= 0 && nr < MAX_LEVELS)
4963 level_stats[nr].played++;
4966 void LevelStats_incSolved(int nr)
4968 if (nr >= 0 && nr < MAX_LEVELS)
4969 level_stats[nr].solved++;
4972 void LoadUserSetup(void)
4974 // --------------------------------------------------------------------------
4975 // ~/.<program>/usersetup.conf
4976 // --------------------------------------------------------------------------
4978 char *filename = getPath2(getMainUserGameDataDir(), USERSETUP_FILENAME);
4979 SetupFileHash *user_setup_hash = NULL;
4981 // always start with reliable default values
4984 if ((user_setup_hash = loadSetupFileHash(filename)))
4988 // get last selected user number
4989 token_value = getHashEntry(user_setup_hash, TOKEN_STR_LAST_USER);
4992 user.nr = MIN(MAX(0, atoi(token_value)), MAX_PLAYER_NAMES - 1);
4994 freeSetupFileHash(user_setup_hash);
4998 Debug("setup", "using default setup values");
5004 void SaveUserSetup(void)
5006 // --------------------------------------------------------------------------
5007 // ~/.<program>/usersetup.conf
5008 // --------------------------------------------------------------------------
5010 char *filename = getPath2(getMainUserGameDataDir(), USERSETUP_FILENAME);
5013 InitMainUserDataDirectory();
5015 if (!(file = fopen(filename, MODE_WRITE)))
5017 Warn("cannot write setup file '%s'", filename);
5024 fprintFileHeader(file, USERSETUP_FILENAME);
5026 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_USER,
5030 SetFilePermissions(filename, PERMS_PRIVATE);