1 // ============================================================================
2 // Artsoft Retro-Game Library
3 // ----------------------------------------------------------------------------
4 // (c) 1995-2014 by Artsoft Entertainment
7 // https://www.artsoft.org/
8 // ----------------------------------------------------------------------------
10 // ============================================================================
12 #include <sys/types.h>
26 #include "zip/miniunz.h"
29 #define ENABLE_UNUSED_CODE FALSE // for currently unused functions
30 #define DEBUG_NO_CONFIG_FILE FALSE // for extra-verbose debug output
32 #define NUM_LEVELCLASS_DESC 8
34 static char *levelclass_desc[NUM_LEVELCLASS_DESC] =
46 #define TOKEN_VALUE_POSITION_SHORT 32
47 #define TOKEN_VALUE_POSITION_DEFAULT 40
48 #define TOKEN_COMMENT_POSITION_DEFAULT 60
50 #define MAX_COOKIE_LEN 256
53 static void setTreeInfoToDefaults(TreeInfo *, int);
54 static TreeInfo *getTreeInfoCopy(TreeInfo *ti);
55 static int compareTreeInfoEntries(const void *, const void *);
57 static int token_value_position = TOKEN_VALUE_POSITION_DEFAULT;
58 static int token_comment_position = TOKEN_COMMENT_POSITION_DEFAULT;
60 static SetupFileHash *artworkinfo_cache_old = NULL;
61 static SetupFileHash *artworkinfo_cache_new = NULL;
62 static SetupFileHash *optional_tokens_hash = NULL;
63 static boolean use_artworkinfo_cache = TRUE;
64 static boolean update_artworkinfo_cache = FALSE;
67 // ----------------------------------------------------------------------------
69 // ----------------------------------------------------------------------------
71 static char *getLevelClassDescription(TreeInfo *ti)
73 int position = ti->sort_priority / 100;
75 if (position >= 0 && position < NUM_LEVELCLASS_DESC)
76 return levelclass_desc[position];
78 return "Unknown Level Class";
81 static char *getScoreDir(char *level_subdir)
83 static char *score_dir = NULL;
84 static char *score_level_dir = NULL;
85 char *score_subdir = SCORES_DIRECTORY;
87 if (score_dir == NULL)
89 if (program.global_scores)
90 score_dir = getPath2(getCommonDataDir(), score_subdir);
92 score_dir = getPath2(getMainUserGameDataDir(), score_subdir);
95 if (level_subdir != NULL)
97 checked_free(score_level_dir);
99 score_level_dir = getPath2(score_dir, level_subdir);
101 return score_level_dir;
107 static char *getUserSubdir(int nr)
109 static char user_subdir[16] = { 0 };
111 sprintf(user_subdir, "%03d", nr);
116 static char *getUserDir(int nr)
118 static char *user_dir = NULL;
119 char *main_data_dir = getMainUserGameDataDir();
120 char *users_subdir = USERS_DIRECTORY;
121 char *user_subdir = getUserSubdir(nr);
123 checked_free(user_dir);
126 user_dir = getPath3(main_data_dir, users_subdir, user_subdir);
128 user_dir = getPath2(main_data_dir, users_subdir);
133 static char *getLevelSetupDir(char *level_subdir)
135 static char *levelsetup_dir = NULL;
136 char *data_dir = getUserGameDataDir();
137 char *levelsetup_subdir = LEVELSETUP_DIRECTORY;
139 checked_free(levelsetup_dir);
141 if (level_subdir != NULL)
142 levelsetup_dir = getPath3(data_dir, levelsetup_subdir, level_subdir);
144 levelsetup_dir = getPath2(data_dir, levelsetup_subdir);
146 return levelsetup_dir;
149 static char *getCacheDir(void)
151 static char *cache_dir = NULL;
153 if (cache_dir == NULL)
154 cache_dir = getPath2(getMainUserGameDataDir(), CACHE_DIRECTORY);
159 static char *getNetworkDir(void)
161 static char *network_dir = NULL;
163 if (network_dir == NULL)
164 network_dir = getPath2(getMainUserGameDataDir(), NETWORK_DIRECTORY);
169 char *getLevelDirFromTreeInfo(TreeInfo *node)
171 static char *level_dir = NULL;
174 return options.level_directory;
176 checked_free(level_dir);
178 level_dir = getPath2((node->in_user_dir ? getUserLevelDir(NULL) :
179 options.level_directory), node->fullpath);
184 char *getUserLevelDir(char *level_subdir)
186 static char *userlevel_dir = NULL;
187 char *data_dir = getMainUserGameDataDir();
188 char *userlevel_subdir = LEVELS_DIRECTORY;
190 checked_free(userlevel_dir);
192 if (level_subdir != NULL)
193 userlevel_dir = getPath3(data_dir, userlevel_subdir, level_subdir);
195 userlevel_dir = getPath2(data_dir, userlevel_subdir);
197 return userlevel_dir;
200 char *getNetworkLevelDir(char *level_subdir)
202 static char *network_level_dir = NULL;
203 char *data_dir = getNetworkDir();
204 char *networklevel_subdir = LEVELS_DIRECTORY;
206 checked_free(network_level_dir);
208 if (level_subdir != NULL)
209 network_level_dir = getPath3(data_dir, networklevel_subdir, level_subdir);
211 network_level_dir = getPath2(data_dir, networklevel_subdir);
213 return network_level_dir;
216 char *getCurrentLevelDir(void)
218 return getLevelDirFromTreeInfo(leveldir_current);
221 char *getNewUserLevelSubdir(void)
223 static char *new_level_subdir = NULL;
224 char *subdir_prefix = getLoginName();
225 char subdir_suffix[10];
226 int max_suffix_number = 1000;
229 while (++i < max_suffix_number)
231 sprintf(subdir_suffix, "_%d", i);
233 checked_free(new_level_subdir);
234 new_level_subdir = getStringCat2(subdir_prefix, subdir_suffix);
236 if (!directoryExists(getUserLevelDir(new_level_subdir)))
240 return new_level_subdir;
243 static char *getTapeDir(char *level_subdir)
245 static char *tape_dir = NULL;
246 char *data_dir = getUserGameDataDir();
247 char *tape_subdir = TAPES_DIRECTORY;
249 checked_free(tape_dir);
251 if (level_subdir != NULL)
252 tape_dir = getPath3(data_dir, tape_subdir, level_subdir);
254 tape_dir = getPath2(data_dir, tape_subdir);
259 static char *getSolutionTapeDir(void)
261 static char *tape_dir = NULL;
262 char *data_dir = getCurrentLevelDir();
263 char *tape_subdir = TAPES_DIRECTORY;
265 checked_free(tape_dir);
267 tape_dir = getPath2(data_dir, tape_subdir);
272 static char *getDefaultGraphicsDir(char *graphics_subdir)
274 static char *graphics_dir = NULL;
276 if (graphics_subdir == NULL)
277 return options.graphics_directory;
279 checked_free(graphics_dir);
281 graphics_dir = getPath2(options.graphics_directory, graphics_subdir);
286 static char *getDefaultSoundsDir(char *sounds_subdir)
288 static char *sounds_dir = NULL;
290 if (sounds_subdir == NULL)
291 return options.sounds_directory;
293 checked_free(sounds_dir);
295 sounds_dir = getPath2(options.sounds_directory, sounds_subdir);
300 static char *getDefaultMusicDir(char *music_subdir)
302 static char *music_dir = NULL;
304 if (music_subdir == NULL)
305 return options.music_directory;
307 checked_free(music_dir);
309 music_dir = getPath2(options.music_directory, music_subdir);
314 static char *getClassicArtworkSet(int type)
316 return (type == TREE_TYPE_GRAPHICS_DIR ? GFX_CLASSIC_SUBDIR :
317 type == TREE_TYPE_SOUNDS_DIR ? SND_CLASSIC_SUBDIR :
318 type == TREE_TYPE_MUSIC_DIR ? MUS_CLASSIC_SUBDIR : "");
321 static char *getClassicArtworkDir(int type)
323 return (type == TREE_TYPE_GRAPHICS_DIR ?
324 getDefaultGraphicsDir(GFX_CLASSIC_SUBDIR) :
325 type == TREE_TYPE_SOUNDS_DIR ?
326 getDefaultSoundsDir(SND_CLASSIC_SUBDIR) :
327 type == TREE_TYPE_MUSIC_DIR ?
328 getDefaultMusicDir(MUS_CLASSIC_SUBDIR) : "");
331 char *getUserGraphicsDir(void)
333 static char *usergraphics_dir = NULL;
335 if (usergraphics_dir == NULL)
336 usergraphics_dir = getPath2(getMainUserGameDataDir(), GRAPHICS_DIRECTORY);
338 return usergraphics_dir;
341 char *getUserSoundsDir(void)
343 static char *usersounds_dir = NULL;
345 if (usersounds_dir == NULL)
346 usersounds_dir = getPath2(getMainUserGameDataDir(), SOUNDS_DIRECTORY);
348 return usersounds_dir;
351 char *getUserMusicDir(void)
353 static char *usermusic_dir = NULL;
355 if (usermusic_dir == NULL)
356 usermusic_dir = getPath2(getMainUserGameDataDir(), MUSIC_DIRECTORY);
358 return usermusic_dir;
361 static char *getSetupArtworkDir(TreeInfo *ti)
363 static char *artwork_dir = NULL;
368 checked_free(artwork_dir);
370 artwork_dir = getPath2(ti->basepath, ti->fullpath);
375 char *setLevelArtworkDir(TreeInfo *ti)
377 char **artwork_path_ptr, **artwork_set_ptr;
378 TreeInfo *level_artwork;
380 if (ti == NULL || leveldir_current == NULL)
383 artwork_path_ptr = LEVELDIR_ARTWORK_PATH_PTR(leveldir_current, ti->type);
384 artwork_set_ptr = LEVELDIR_ARTWORK_SET_PTR( leveldir_current, ti->type);
386 checked_free(*artwork_path_ptr);
388 if ((level_artwork = getTreeInfoFromIdentifier(ti, *artwork_set_ptr)))
390 *artwork_path_ptr = getStringCopy(getSetupArtworkDir(level_artwork));
395 No (or non-existing) artwork configured in "levelinfo.conf". This would
396 normally result in using the artwork configured in the setup menu. But
397 if an artwork subdirectory exists (which might contain custom artwork
398 or an artwork configuration file), this level artwork must be treated
399 as relative to the default "classic" artwork, not to the artwork that
400 is currently configured in the setup menu.
402 Update: For "special" versions of R'n'D (like "R'n'D jue"), do not use
403 the "default" artwork (which would be "jue0" for "R'n'D jue"), but use
404 the real "classic" artwork from the original R'n'D (like "gfx_classic").
407 char *dir = getPath2(getCurrentLevelDir(), ARTWORK_DIRECTORY(ti->type));
409 checked_free(*artwork_set_ptr);
411 if (directoryExists(dir))
413 *artwork_path_ptr = getStringCopy(getClassicArtworkDir(ti->type));
414 *artwork_set_ptr = getStringCopy(getClassicArtworkSet(ti->type));
418 *artwork_path_ptr = getStringCopy(UNDEFINED_FILENAME);
419 *artwork_set_ptr = NULL;
425 return *artwork_set_ptr;
428 static char *getLevelArtworkSet(int type)
430 if (leveldir_current == NULL)
433 return LEVELDIR_ARTWORK_SET(leveldir_current, type);
436 static char *getLevelArtworkDir(int type)
438 if (leveldir_current == NULL)
439 return UNDEFINED_FILENAME;
441 return LEVELDIR_ARTWORK_PATH(leveldir_current, type);
444 char *getProgramMainDataPath(char *command_filename, char *base_path)
446 // check if the program's main data base directory is configured
447 if (!strEqual(base_path, "."))
448 return getStringCopy(base_path);
450 /* if the program is configured to start from current directory (default),
451 determine program package directory from program binary (some versions
452 of KDE/Konqueror and Mac OS X (especially "Mavericks") apparently do not
453 set the current working directory to the program package directory) */
454 char *main_data_path = getBasePath(command_filename);
456 #if defined(PLATFORM_MACOSX)
457 if (strSuffix(main_data_path, MAC_APP_BINARY_SUBDIR))
459 char *main_data_path_old = main_data_path;
461 // cut relative path to Mac OS X application binary directory from path
462 main_data_path[strlen(main_data_path) -
463 strlen(MAC_APP_BINARY_SUBDIR)] = '\0';
465 // cut trailing path separator from path (but not if path is root directory)
466 if (strSuffix(main_data_path, "/") && !strEqual(main_data_path, "/"))
467 main_data_path[strlen(main_data_path) - 1] = '\0';
469 // replace empty path with current directory
470 if (strEqual(main_data_path, ""))
471 main_data_path = ".";
473 // add relative path to Mac OS X application resources directory to path
474 main_data_path = getPath2(main_data_path, MAC_APP_FILES_SUBDIR);
476 free(main_data_path_old);
480 return main_data_path;
483 char *getProgramConfigFilename(char *command_filename)
485 static char *config_filename_1 = NULL;
486 static char *config_filename_2 = NULL;
487 static char *config_filename_3 = NULL;
488 static boolean initialized = FALSE;
492 char *command_filename_1 = getStringCopy(command_filename);
494 // strip trailing executable suffix from command filename
495 if (strSuffix(command_filename_1, ".exe"))
496 command_filename_1[strlen(command_filename_1) - 4] = '\0';
498 char *ro_base_path = getProgramMainDataPath(command_filename, RO_BASE_PATH);
499 char *conf_directory = getPath2(ro_base_path, CONF_DIRECTORY);
501 char *command_basepath = getBasePath(command_filename);
502 char *command_basename = getBaseNameNoSuffix(command_filename);
503 char *command_filename_2 = getPath2(command_basepath, command_basename);
505 config_filename_1 = getStringCat2(command_filename_1, ".conf");
506 config_filename_2 = getStringCat2(command_filename_2, ".conf");
507 config_filename_3 = getPath2(conf_directory, SETUP_FILENAME);
509 checked_free(ro_base_path);
510 checked_free(conf_directory);
512 checked_free(command_basepath);
513 checked_free(command_basename);
515 checked_free(command_filename_1);
516 checked_free(command_filename_2);
521 // 1st try: look for config file that exactly matches the binary filename
522 if (fileExists(config_filename_1))
523 return config_filename_1;
525 // 2nd try: look for config file that matches binary filename without suffix
526 if (fileExists(config_filename_2))
527 return config_filename_2;
529 // 3rd try: return setup config filename in global program config directory
530 return config_filename_3;
533 char *getTapeFilename(int nr)
535 static char *filename = NULL;
536 char basename[MAX_FILENAME_LEN];
538 checked_free(filename);
540 sprintf(basename, "%03d.%s", nr, TAPEFILE_EXTENSION);
541 filename = getPath2(getTapeDir(leveldir_current->subdir), basename);
546 char *getSolutionTapeFilename(int nr)
548 static char *filename = NULL;
549 char basename[MAX_FILENAME_LEN];
551 checked_free(filename);
553 sprintf(basename, "%03d.%s", nr, TAPEFILE_EXTENSION);
554 filename = getPath2(getSolutionTapeDir(), basename);
556 if (!fileExists(filename))
558 static char *filename_sln = NULL;
560 checked_free(filename_sln);
562 sprintf(basename, "%03d.sln", nr);
563 filename_sln = getPath2(getSolutionTapeDir(), basename);
565 if (fileExists(filename_sln))
572 char *getScoreFilename(int nr)
574 static char *filename = NULL;
575 char basename[MAX_FILENAME_LEN];
577 checked_free(filename);
579 sprintf(basename, "%03d.%s", nr, SCOREFILE_EXTENSION);
581 // used instead of "leveldir_current->subdir" (for network games)
582 filename = getPath2(getScoreDir(levelset.identifier), basename);
587 char *getSetupFilename(void)
589 static char *filename = NULL;
591 checked_free(filename);
593 filename = getPath2(getSetupDir(), SETUP_FILENAME);
598 char *getDefaultSetupFilename(void)
600 return program.config_filename;
603 char *getEditorSetupFilename(void)
605 static char *filename = NULL;
607 checked_free(filename);
608 filename = getPath2(getCurrentLevelDir(), EDITORSETUP_FILENAME);
610 if (fileExists(filename))
613 checked_free(filename);
614 filename = getPath2(getSetupDir(), EDITORSETUP_FILENAME);
619 char *getHelpAnimFilename(void)
621 static char *filename = NULL;
623 checked_free(filename);
625 filename = getPath2(getCurrentLevelDir(), HELPANIM_FILENAME);
630 char *getHelpTextFilename(void)
632 static char *filename = NULL;
634 checked_free(filename);
636 filename = getPath2(getCurrentLevelDir(), HELPTEXT_FILENAME);
641 char *getLevelSetInfoFilename(void)
643 static char *filename = NULL;
658 for (i = 0; basenames[i] != NULL; i++)
660 checked_free(filename);
661 filename = getPath2(getCurrentLevelDir(), basenames[i]);
663 if (fileExists(filename))
670 static char *getLevelSetTitleMessageBasename(int nr, boolean initial)
672 static char basename[32];
674 sprintf(basename, "%s_%d.txt",
675 (initial ? "titlemessage_initial" : "titlemessage"), nr + 1);
680 char *getLevelSetTitleMessageFilename(int nr, boolean initial)
682 static char *filename = NULL;
684 boolean skip_setup_artwork = FALSE;
686 checked_free(filename);
688 basename = getLevelSetTitleMessageBasename(nr, initial);
690 if (!gfx.override_level_graphics)
692 // 1st try: look for special artwork in current level series directory
693 filename = getPath3(getCurrentLevelDir(), GRAPHICS_DIRECTORY, basename);
694 if (fileExists(filename))
699 // 2nd try: look for message file in current level set directory
700 filename = getPath2(getCurrentLevelDir(), basename);
701 if (fileExists(filename))
706 // check if there is special artwork configured in level series config
707 if (getLevelArtworkSet(ARTWORK_TYPE_GRAPHICS) != NULL)
709 // 3rd try: look for special artwork configured in level series config
710 filename = getPath2(getLevelArtworkDir(ARTWORK_TYPE_GRAPHICS), basename);
711 if (fileExists(filename))
716 // take missing artwork configured in level set config from default
717 skip_setup_artwork = TRUE;
721 if (!skip_setup_artwork)
723 // 4th try: look for special artwork in configured artwork directory
724 filename = getPath2(getSetupArtworkDir(artwork.gfx_current), basename);
725 if (fileExists(filename))
731 // 5th try: look for default artwork in new default artwork directory
732 filename = getPath2(getDefaultGraphicsDir(GFX_DEFAULT_SUBDIR), basename);
733 if (fileExists(filename))
738 // 6th try: look for default artwork in old default artwork directory
739 filename = getPath2(options.graphics_directory, basename);
740 if (fileExists(filename))
743 return NULL; // cannot find specified artwork file anywhere
746 static char *getCorrectedArtworkBasename(char *basename)
751 char *getCustomImageFilename(char *basename)
753 static char *filename = NULL;
754 boolean skip_setup_artwork = FALSE;
756 checked_free(filename);
758 basename = getCorrectedArtworkBasename(basename);
760 if (!gfx.override_level_graphics)
762 // 1st try: look for special artwork in current level series directory
763 filename = getImg3(getCurrentLevelDir(), GRAPHICS_DIRECTORY, basename);
764 if (fileExists(filename))
769 // check if there is special artwork configured in level series config
770 if (getLevelArtworkSet(ARTWORK_TYPE_GRAPHICS) != NULL)
772 // 2nd try: look for special artwork configured in level series config
773 filename = getImg2(getLevelArtworkDir(ARTWORK_TYPE_GRAPHICS), basename);
774 if (fileExists(filename))
779 // take missing artwork configured in level set config from default
780 skip_setup_artwork = TRUE;
784 if (!skip_setup_artwork)
786 // 3rd try: look for special artwork in configured artwork directory
787 filename = getImg2(getSetupArtworkDir(artwork.gfx_current), basename);
788 if (fileExists(filename))
794 // 4th try: look for default artwork in new default artwork directory
795 filename = getImg2(getDefaultGraphicsDir(GFX_DEFAULT_SUBDIR), basename);
796 if (fileExists(filename))
801 // 5th try: look for default artwork in old default artwork directory
802 filename = getImg2(options.graphics_directory, basename);
803 if (fileExists(filename))
806 if (!strEqual(GFX_FALLBACK_FILENAME, UNDEFINED_FILENAME))
810 Warn("cannot find artwork file '%s' (using fallback)", basename);
812 // 6th try: look for fallback artwork in old default artwork directory
813 // (needed to prevent errors when trying to access unused artwork files)
814 filename = getImg2(options.graphics_directory, GFX_FALLBACK_FILENAME);
815 if (fileExists(filename))
819 return NULL; // cannot find specified artwork file anywhere
822 char *getCustomSoundFilename(char *basename)
824 static char *filename = NULL;
825 boolean skip_setup_artwork = FALSE;
827 checked_free(filename);
829 basename = getCorrectedArtworkBasename(basename);
831 if (!gfx.override_level_sounds)
833 // 1st try: look for special artwork in current level series directory
834 filename = getPath3(getCurrentLevelDir(), SOUNDS_DIRECTORY, basename);
835 if (fileExists(filename))
840 // check if there is special artwork configured in level series config
841 if (getLevelArtworkSet(ARTWORK_TYPE_SOUNDS) != NULL)
843 // 2nd try: look for special artwork configured in level series config
844 filename = getPath2(getLevelArtworkDir(TREE_TYPE_SOUNDS_DIR), basename);
845 if (fileExists(filename))
850 // take missing artwork configured in level set config from default
851 skip_setup_artwork = TRUE;
855 if (!skip_setup_artwork)
857 // 3rd try: look for special artwork in configured artwork directory
858 filename = getPath2(getSetupArtworkDir(artwork.snd_current), basename);
859 if (fileExists(filename))
865 // 4th try: look for default artwork in new default artwork directory
866 filename = getPath2(getDefaultSoundsDir(SND_DEFAULT_SUBDIR), basename);
867 if (fileExists(filename))
872 // 5th try: look for default artwork in old default artwork directory
873 filename = getPath2(options.sounds_directory, basename);
874 if (fileExists(filename))
877 if (!strEqual(SND_FALLBACK_FILENAME, UNDEFINED_FILENAME))
881 Warn("cannot find artwork file '%s' (using fallback)", basename);
883 // 6th try: look for fallback artwork in old default artwork directory
884 // (needed to prevent errors when trying to access unused artwork files)
885 filename = getPath2(options.sounds_directory, SND_FALLBACK_FILENAME);
886 if (fileExists(filename))
890 return NULL; // cannot find specified artwork file anywhere
893 char *getCustomMusicFilename(char *basename)
895 static char *filename = NULL;
896 boolean skip_setup_artwork = FALSE;
898 checked_free(filename);
900 basename = getCorrectedArtworkBasename(basename);
902 if (!gfx.override_level_music)
904 // 1st try: look for special artwork in current level series directory
905 filename = getPath3(getCurrentLevelDir(), MUSIC_DIRECTORY, basename);
906 if (fileExists(filename))
911 // check if there is special artwork configured in level series config
912 if (getLevelArtworkSet(ARTWORK_TYPE_MUSIC) != NULL)
914 // 2nd try: look for special artwork configured in level series config
915 filename = getPath2(getLevelArtworkDir(TREE_TYPE_MUSIC_DIR), basename);
916 if (fileExists(filename))
921 // take missing artwork configured in level set config from default
922 skip_setup_artwork = TRUE;
926 if (!skip_setup_artwork)
928 // 3rd try: look for special artwork in configured artwork directory
929 filename = getPath2(getSetupArtworkDir(artwork.mus_current), basename);
930 if (fileExists(filename))
936 // 4th try: look for default artwork in new default artwork directory
937 filename = getPath2(getDefaultMusicDir(MUS_DEFAULT_SUBDIR), basename);
938 if (fileExists(filename))
943 // 5th try: look for default artwork in old default artwork directory
944 filename = getPath2(options.music_directory, basename);
945 if (fileExists(filename))
948 if (!strEqual(MUS_FALLBACK_FILENAME, UNDEFINED_FILENAME))
952 Warn("cannot find artwork file '%s' (using fallback)", basename);
954 // 6th try: look for fallback artwork in old default artwork directory
955 // (needed to prevent errors when trying to access unused artwork files)
956 filename = getPath2(options.music_directory, MUS_FALLBACK_FILENAME);
957 if (fileExists(filename))
961 return NULL; // cannot find specified artwork file anywhere
964 char *getCustomArtworkFilename(char *basename, int type)
966 if (type == ARTWORK_TYPE_GRAPHICS)
967 return getCustomImageFilename(basename);
968 else if (type == ARTWORK_TYPE_SOUNDS)
969 return getCustomSoundFilename(basename);
970 else if (type == ARTWORK_TYPE_MUSIC)
971 return getCustomMusicFilename(basename);
973 return UNDEFINED_FILENAME;
976 char *getCustomArtworkConfigFilename(int type)
978 return getCustomArtworkFilename(ARTWORKINFO_FILENAME(type), type);
981 char *getCustomArtworkLevelConfigFilename(int type)
983 static char *filename = NULL;
985 checked_free(filename);
987 filename = getPath2(getLevelArtworkDir(type), ARTWORKINFO_FILENAME(type));
992 char *getCustomMusicDirectory(void)
994 static char *directory = NULL;
995 boolean skip_setup_artwork = FALSE;
997 checked_free(directory);
999 if (!gfx.override_level_music)
1001 // 1st try: look for special artwork in current level series directory
1002 directory = getPath2(getCurrentLevelDir(), MUSIC_DIRECTORY);
1003 if (directoryExists(directory))
1008 // check if there is special artwork configured in level series config
1009 if (getLevelArtworkSet(ARTWORK_TYPE_MUSIC) != NULL)
1011 // 2nd try: look for special artwork configured in level series config
1012 directory = getStringCopy(getLevelArtworkDir(TREE_TYPE_MUSIC_DIR));
1013 if (directoryExists(directory))
1018 // take missing artwork configured in level set config from default
1019 skip_setup_artwork = TRUE;
1023 if (!skip_setup_artwork)
1025 // 3rd try: look for special artwork in configured artwork directory
1026 directory = getStringCopy(getSetupArtworkDir(artwork.mus_current));
1027 if (directoryExists(directory))
1033 // 4th try: look for default artwork in new default artwork directory
1034 directory = getStringCopy(getDefaultMusicDir(MUS_DEFAULT_SUBDIR));
1035 if (directoryExists(directory))
1040 // 5th try: look for default artwork in old default artwork directory
1041 directory = getStringCopy(options.music_directory);
1042 if (directoryExists(directory))
1045 return NULL; // cannot find specified artwork file anywhere
1048 void InitTapeDirectory(char *level_subdir)
1050 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
1051 createDirectory(getTapeDir(NULL), "main tape", PERMS_PRIVATE);
1052 createDirectory(getTapeDir(level_subdir), "level tape", PERMS_PRIVATE);
1055 void InitScoreDirectory(char *level_subdir)
1057 int permissions = (program.global_scores ? PERMS_PUBLIC : PERMS_PRIVATE);
1059 if (program.global_scores)
1060 createDirectory(getCommonDataDir(), "common data", permissions);
1062 createDirectory(getMainUserGameDataDir(), "main user data", permissions);
1064 createDirectory(getScoreDir(NULL), "main score", permissions);
1065 createDirectory(getScoreDir(level_subdir), "level score", permissions);
1068 static void SaveUserLevelInfo(void);
1070 void InitUserLevelDirectory(char *level_subdir)
1072 if (!directoryExists(getUserLevelDir(level_subdir)))
1074 createDirectory(getMainUserGameDataDir(), "main user data", PERMS_PRIVATE);
1075 createDirectory(getUserLevelDir(NULL), "main user level", PERMS_PRIVATE);
1076 createDirectory(getUserLevelDir(level_subdir), "user level", PERMS_PRIVATE);
1078 if (setup.internal.create_user_levelset)
1079 SaveUserLevelInfo();
1083 void InitNetworkLevelDirectory(char *level_subdir)
1085 if (!directoryExists(getNetworkLevelDir(level_subdir)))
1087 createDirectory(getMainUserGameDataDir(), "main user data", PERMS_PRIVATE);
1088 createDirectory(getNetworkDir(), "network data", PERMS_PRIVATE);
1089 createDirectory(getNetworkLevelDir(NULL), "main network level", PERMS_PRIVATE);
1090 createDirectory(getNetworkLevelDir(level_subdir), "network level", PERMS_PRIVATE);
1094 void InitLevelSetupDirectory(char *level_subdir)
1096 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
1097 createDirectory(getLevelSetupDir(NULL), "main level setup", PERMS_PRIVATE);
1098 createDirectory(getLevelSetupDir(level_subdir), "level setup", PERMS_PRIVATE);
1101 static void InitCacheDirectory(void)
1103 createDirectory(getMainUserGameDataDir(), "main user data", PERMS_PRIVATE);
1104 createDirectory(getCacheDir(), "cache data", PERMS_PRIVATE);
1108 // ----------------------------------------------------------------------------
1109 // some functions to handle lists of level and artwork directories
1110 // ----------------------------------------------------------------------------
1112 TreeInfo *newTreeInfo(void)
1114 return checked_calloc(sizeof(TreeInfo));
1117 TreeInfo *newTreeInfo_setDefaults(int type)
1119 TreeInfo *ti = newTreeInfo();
1121 setTreeInfoToDefaults(ti, type);
1126 void pushTreeInfo(TreeInfo **node_first, TreeInfo *node_new)
1128 node_new->next = *node_first;
1129 *node_first = node_new;
1132 void removeTreeInfo(TreeInfo **node_first)
1134 TreeInfo *node_old = *node_first;
1136 *node_first = node_old->next;
1137 node_old->next = NULL;
1139 freeTreeInfo(node_old);
1142 int numTreeInfo(TreeInfo *node)
1155 boolean validLevelSeries(TreeInfo *node)
1157 return (node != NULL && !node->node_group && !node->parent_link);
1160 TreeInfo *getFirstValidTreeInfoEntry(TreeInfo *node)
1165 if (node->node_group) // enter level group (step down into tree)
1166 return getFirstValidTreeInfoEntry(node->node_group);
1167 else if (node->parent_link) // skip start entry of level group
1169 if (node->next) // get first real level series entry
1170 return getFirstValidTreeInfoEntry(node->next);
1171 else // leave empty level group and go on
1172 return getFirstValidTreeInfoEntry(node->node_parent->next);
1174 else // this seems to be a regular level series
1178 TreeInfo *getTreeInfoFirstGroupEntry(TreeInfo *node)
1183 if (node->node_parent == NULL) // top level group
1184 return *node->node_top;
1185 else // sub level group
1186 return node->node_parent->node_group;
1189 int numTreeInfoInGroup(TreeInfo *node)
1191 return numTreeInfo(getTreeInfoFirstGroupEntry(node));
1194 int getPosFromTreeInfo(TreeInfo *node)
1196 TreeInfo *node_cmp = getTreeInfoFirstGroupEntry(node);
1201 if (node_cmp == node)
1205 node_cmp = node_cmp->next;
1211 TreeInfo *getTreeInfoFromPos(TreeInfo *node, int pos)
1213 TreeInfo *node_default = node;
1225 return node_default;
1228 static TreeInfo *getTreeInfoFromIdentifierExt(TreeInfo *node, char *identifier,
1229 boolean include_node_groups)
1231 if (identifier == NULL)
1236 if (node->node_group)
1238 if (include_node_groups && strEqual(identifier, node->identifier))
1241 TreeInfo *node_group = getTreeInfoFromIdentifierExt(node->node_group,
1243 include_node_groups);
1247 else if (!node->parent_link)
1249 if (strEqual(identifier, node->identifier))
1259 TreeInfo *getTreeInfoFromIdentifier(TreeInfo *node, char *identifier)
1261 return getTreeInfoFromIdentifierExt(node, identifier, FALSE);
1264 static TreeInfo *cloneTreeNode(TreeInfo **node_top, TreeInfo *node_parent,
1265 TreeInfo *node, boolean skip_sets_without_levels)
1272 if (!node->parent_link && !node->level_group &&
1273 skip_sets_without_levels && node->levels == 0)
1274 return cloneTreeNode(node_top, node_parent, node->next,
1275 skip_sets_without_levels);
1277 node_new = getTreeInfoCopy(node); // copy complete node
1279 node_new->node_top = node_top; // correct top node link
1280 node_new->node_parent = node_parent; // correct parent node link
1282 if (node->level_group)
1283 node_new->node_group = cloneTreeNode(node_top, node_new, node->node_group,
1284 skip_sets_without_levels);
1286 node_new->next = cloneTreeNode(node_top, node_parent, node->next,
1287 skip_sets_without_levels);
1292 static void cloneTree(TreeInfo **ti_new, TreeInfo *ti, boolean skip_empty_sets)
1294 TreeInfo *ti_cloned = cloneTreeNode(ti_new, NULL, ti, skip_empty_sets);
1296 *ti_new = ti_cloned;
1299 static boolean adjustTreeGraphicsForEMC(TreeInfo *node)
1301 boolean settings_changed = FALSE;
1305 boolean want_ecs = (setup.prefer_aga_graphics == FALSE);
1306 boolean want_aga = (setup.prefer_aga_graphics == TRUE);
1307 boolean has_only_ecs = (!node->graphics_set && !node->graphics_set_aga);
1308 boolean has_only_aga = (!node->graphics_set && !node->graphics_set_ecs);
1309 char *graphics_set = NULL;
1311 if (node->graphics_set_ecs && (want_ecs || has_only_ecs))
1312 graphics_set = node->graphics_set_ecs;
1314 if (node->graphics_set_aga && (want_aga || has_only_aga))
1315 graphics_set = node->graphics_set_aga;
1317 if (graphics_set && !strEqual(node->graphics_set, graphics_set))
1319 setString(&node->graphics_set, graphics_set);
1320 settings_changed = TRUE;
1323 if (node->node_group != NULL)
1324 settings_changed |= adjustTreeGraphicsForEMC(node->node_group);
1329 return settings_changed;
1332 static boolean adjustTreeSoundsForEMC(TreeInfo *node)
1334 boolean settings_changed = FALSE;
1338 boolean want_default = (setup.prefer_lowpass_sounds == FALSE);
1339 boolean want_lowpass = (setup.prefer_lowpass_sounds == TRUE);
1340 boolean has_only_default = (!node->sounds_set && !node->sounds_set_lowpass);
1341 boolean has_only_lowpass = (!node->sounds_set && !node->sounds_set_default);
1342 char *sounds_set = NULL;
1344 if (node->sounds_set_default && (want_default || has_only_default))
1345 sounds_set = node->sounds_set_default;
1347 if (node->sounds_set_lowpass && (want_lowpass || has_only_lowpass))
1348 sounds_set = node->sounds_set_lowpass;
1350 if (sounds_set && !strEqual(node->sounds_set, sounds_set))
1352 setString(&node->sounds_set, sounds_set);
1353 settings_changed = TRUE;
1356 if (node->node_group != NULL)
1357 settings_changed |= adjustTreeSoundsForEMC(node->node_group);
1362 return settings_changed;
1365 void dumpTreeInfo(TreeInfo *node, int depth)
1367 char bullet_list[] = { '-', '*', 'o' };
1371 Debug("tree", "Dumping TreeInfo:");
1375 char bullet = bullet_list[depth % ARRAY_SIZE(bullet_list)];
1377 for (i = 0; i < depth * 2; i++)
1378 DebugContinued("", " ");
1380 DebugContinued("tree", "%c '%s' ['%s] [PARENT: '%s'] %s\n",
1381 bullet, node->name, node->identifier,
1382 (node->node_parent ? node->node_parent->identifier : "-"),
1383 (node->node_group ? "[GROUP]" : ""));
1386 // use for dumping artwork info tree
1387 Debug("tree", "subdir == '%s' ['%s', '%s'] [%d])",
1388 node->subdir, node->fullpath, node->basepath, node->in_user_dir);
1391 if (node->node_group != NULL)
1392 dumpTreeInfo(node->node_group, depth + 1);
1398 void sortTreeInfoBySortFunction(TreeInfo **node_first,
1399 int (*compare_function)(const void *,
1402 int num_nodes = numTreeInfo(*node_first);
1403 TreeInfo **sort_array;
1404 TreeInfo *node = *node_first;
1410 // allocate array for sorting structure pointers
1411 sort_array = checked_calloc(num_nodes * sizeof(TreeInfo *));
1413 // writing structure pointers to sorting array
1414 while (i < num_nodes && node) // double boundary check...
1416 sort_array[i] = node;
1422 // sorting the structure pointers in the sorting array
1423 qsort(sort_array, num_nodes, sizeof(TreeInfo *),
1426 // update the linkage of list elements with the sorted node array
1427 for (i = 0; i < num_nodes - 1; i++)
1428 sort_array[i]->next = sort_array[i + 1];
1429 sort_array[num_nodes - 1]->next = NULL;
1431 // update the linkage of the main list anchor pointer
1432 *node_first = sort_array[0];
1436 // now recursively sort the level group structures
1440 if (node->node_group != NULL)
1441 sortTreeInfoBySortFunction(&node->node_group, compare_function);
1447 void sortTreeInfo(TreeInfo **node_first)
1449 sortTreeInfoBySortFunction(node_first, compareTreeInfoEntries);
1453 // ============================================================================
1454 // some stuff from "files.c"
1455 // ============================================================================
1457 #if defined(PLATFORM_WIN32)
1459 #define S_IRGRP S_IRUSR
1462 #define S_IROTH S_IRUSR
1465 #define S_IWGRP S_IWUSR
1468 #define S_IWOTH S_IWUSR
1471 #define S_IXGRP S_IXUSR
1474 #define S_IXOTH S_IXUSR
1477 #define S_IRWXG (S_IRGRP | S_IWGRP | S_IXGRP)
1482 #endif // PLATFORM_WIN32
1484 // file permissions for newly written files
1485 #define MODE_R_ALL (S_IRUSR | S_IRGRP | S_IROTH)
1486 #define MODE_W_ALL (S_IWUSR | S_IWGRP | S_IWOTH)
1487 #define MODE_X_ALL (S_IXUSR | S_IXGRP | S_IXOTH)
1489 #define MODE_W_PRIVATE (S_IWUSR)
1490 #define MODE_W_PUBLIC_FILE (S_IWUSR | S_IWGRP)
1491 #define MODE_W_PUBLIC_DIR (S_IWUSR | S_IWGRP | S_ISGID)
1493 #define DIR_PERMS_PRIVATE (MODE_R_ALL | MODE_X_ALL | MODE_W_PRIVATE)
1494 #define DIR_PERMS_PUBLIC (MODE_R_ALL | MODE_X_ALL | MODE_W_PUBLIC_DIR)
1495 #define DIR_PERMS_PUBLIC_ALL (MODE_R_ALL | MODE_X_ALL | MODE_W_ALL)
1497 #define FILE_PERMS_PRIVATE (MODE_R_ALL | MODE_W_PRIVATE)
1498 #define FILE_PERMS_PUBLIC (MODE_R_ALL | MODE_W_PUBLIC_FILE)
1499 #define FILE_PERMS_PUBLIC_ALL (MODE_R_ALL | MODE_W_ALL)
1502 char *getHomeDir(void)
1504 static char *dir = NULL;
1506 #if defined(PLATFORM_WIN32)
1509 dir = checked_malloc(MAX_PATH + 1);
1511 if (!SUCCEEDED(SHGetFolderPath(NULL, CSIDL_PERSONAL, NULL, 0, dir)))
1514 #elif defined(PLATFORM_UNIX)
1517 if ((dir = getenv("HOME")) == NULL)
1519 dir = getUnixHomeDir();
1522 dir = getStringCopy(dir);
1534 char *getCommonDataDir(void)
1536 static char *common_data_dir = NULL;
1538 #if defined(PLATFORM_WIN32)
1539 if (common_data_dir == NULL)
1541 char *dir = checked_malloc(MAX_PATH + 1);
1543 if (SUCCEEDED(SHGetFolderPath(NULL, CSIDL_COMMON_DOCUMENTS, NULL, 0, dir))
1544 && !strEqual(dir, "")) // empty for Windows 95/98
1545 common_data_dir = getPath2(dir, program.userdata_subdir);
1547 common_data_dir = options.rw_base_directory;
1550 if (common_data_dir == NULL)
1551 common_data_dir = options.rw_base_directory;
1554 return common_data_dir;
1557 char *getPersonalDataDir(void)
1559 static char *personal_data_dir = NULL;
1561 #if defined(PLATFORM_MACOSX)
1562 if (personal_data_dir == NULL)
1563 personal_data_dir = getPath2(getHomeDir(), "Documents");
1565 if (personal_data_dir == NULL)
1566 personal_data_dir = getHomeDir();
1569 return personal_data_dir;
1572 char *getMainUserGameDataDir(void)
1574 static char *main_user_data_dir = NULL;
1576 #if defined(PLATFORM_ANDROID)
1577 if (main_user_data_dir == NULL)
1578 main_user_data_dir = (char *)(SDL_AndroidGetExternalStorageState() &
1579 SDL_ANDROID_EXTERNAL_STORAGE_WRITE ?
1580 SDL_AndroidGetExternalStoragePath() :
1581 SDL_AndroidGetInternalStoragePath());
1583 if (main_user_data_dir == NULL)
1584 main_user_data_dir = getPath2(getPersonalDataDir(),
1585 program.userdata_subdir);
1588 return main_user_data_dir;
1591 char *getUserGameDataDir(void)
1594 return getMainUserGameDataDir();
1596 return getUserDir(user.nr);
1599 char *getSetupDir(void)
1601 return getUserGameDataDir();
1604 static mode_t posix_umask(mode_t mask)
1606 #if defined(PLATFORM_UNIX)
1613 static int posix_mkdir(const char *pathname, mode_t mode)
1615 #if defined(PLATFORM_WIN32)
1616 return mkdir(pathname);
1618 return mkdir(pathname, mode);
1622 static boolean posix_process_running_setgid(void)
1624 #if defined(PLATFORM_UNIX)
1625 return (getgid() != getegid());
1631 void createDirectory(char *dir, char *text, int permission_class)
1633 if (directoryExists(dir))
1636 // leave "other" permissions in umask untouched, but ensure group parts
1637 // of USERDATA_DIR_MODE are not masked
1638 mode_t dir_mode = (permission_class == PERMS_PRIVATE ?
1639 DIR_PERMS_PRIVATE : DIR_PERMS_PUBLIC);
1640 mode_t last_umask = posix_umask(0);
1641 mode_t group_umask = ~(dir_mode & S_IRWXG);
1642 int running_setgid = posix_process_running_setgid();
1644 if (permission_class == PERMS_PUBLIC)
1646 // if we're setgid, protect files against "other"
1647 // else keep umask(0) to make the dir world-writable
1650 posix_umask(last_umask & group_umask);
1652 dir_mode = DIR_PERMS_PUBLIC_ALL;
1655 if (posix_mkdir(dir, dir_mode) != 0)
1656 Warn("cannot create %s directory '%s': %s", text, dir, strerror(errno));
1658 if (permission_class == PERMS_PUBLIC && !running_setgid)
1659 chmod(dir, dir_mode);
1661 posix_umask(last_umask); // restore previous umask
1664 void InitMainUserDataDirectory(void)
1666 createDirectory(getMainUserGameDataDir(), "main user data", PERMS_PRIVATE);
1669 void InitUserDataDirectory(void)
1671 createDirectory(getMainUserGameDataDir(), "main user data", PERMS_PRIVATE);
1675 createDirectory(getUserDir(-1), "users", PERMS_PRIVATE);
1676 createDirectory(getUserDir(user.nr), "user data", PERMS_PRIVATE);
1680 void SetFilePermissions(char *filename, int permission_class)
1682 int running_setgid = posix_process_running_setgid();
1683 int perms = (permission_class == PERMS_PRIVATE ?
1684 FILE_PERMS_PRIVATE : FILE_PERMS_PUBLIC);
1686 if (permission_class == PERMS_PUBLIC && !running_setgid)
1687 perms = FILE_PERMS_PUBLIC_ALL;
1689 chmod(filename, perms);
1692 char *getCookie(char *file_type)
1694 static char cookie[MAX_COOKIE_LEN + 1];
1696 if (strlen(program.cookie_prefix) + 1 +
1697 strlen(file_type) + strlen("_FILE_VERSION_x.x") > MAX_COOKIE_LEN)
1698 return "[COOKIE ERROR]"; // should never happen
1700 sprintf(cookie, "%s_%s_FILE_VERSION_%d.%d",
1701 program.cookie_prefix, file_type,
1702 program.version_super, program.version_major);
1707 void fprintFileHeader(FILE *file, char *basename)
1709 char *prefix = "# ";
1712 fprintf_line_with_prefix(file, prefix, sep1, 77);
1713 fprintf(file, "%s%s\n", prefix, basename);
1714 fprintf_line_with_prefix(file, prefix, sep1, 77);
1715 fprintf(file, "\n");
1718 int getFileVersionFromCookieString(const char *cookie)
1720 const char *ptr_cookie1, *ptr_cookie2;
1721 const char *pattern1 = "_FILE_VERSION_";
1722 const char *pattern2 = "?.?";
1723 const int len_cookie = strlen(cookie);
1724 const int len_pattern1 = strlen(pattern1);
1725 const int len_pattern2 = strlen(pattern2);
1726 const int len_pattern = len_pattern1 + len_pattern2;
1727 int version_super, version_major;
1729 if (len_cookie <= len_pattern)
1732 ptr_cookie1 = &cookie[len_cookie - len_pattern];
1733 ptr_cookie2 = &cookie[len_cookie - len_pattern2];
1735 if (strncmp(ptr_cookie1, pattern1, len_pattern1) != 0)
1738 if (ptr_cookie2[0] < '0' || ptr_cookie2[0] > '9' ||
1739 ptr_cookie2[1] != '.' ||
1740 ptr_cookie2[2] < '0' || ptr_cookie2[2] > '9')
1743 version_super = ptr_cookie2[0] - '0';
1744 version_major = ptr_cookie2[2] - '0';
1746 return VERSION_IDENT(version_super, version_major, 0, 0);
1749 boolean checkCookieString(const char *cookie, const char *template)
1751 const char *pattern = "_FILE_VERSION_?.?";
1752 const int len_cookie = strlen(cookie);
1753 const int len_template = strlen(template);
1754 const int len_pattern = strlen(pattern);
1756 if (len_cookie != len_template)
1759 if (strncmp(cookie, template, len_cookie - len_pattern) != 0)
1766 // ----------------------------------------------------------------------------
1767 // setup file list and hash handling functions
1768 // ----------------------------------------------------------------------------
1770 char *getFormattedSetupEntry(char *token, char *value)
1773 static char entry[MAX_LINE_LEN];
1775 // if value is an empty string, just return token without value
1779 // start with the token and some spaces to format output line
1780 sprintf(entry, "%s:", token);
1781 for (i = strlen(entry); i < token_value_position; i++)
1784 // continue with the token's value
1785 strcat(entry, value);
1790 SetupFileList *newSetupFileList(char *token, char *value)
1792 SetupFileList *new = checked_malloc(sizeof(SetupFileList));
1794 new->token = getStringCopy(token);
1795 new->value = getStringCopy(value);
1802 void freeSetupFileList(SetupFileList *list)
1807 checked_free(list->token);
1808 checked_free(list->value);
1811 freeSetupFileList(list->next);
1816 char *getListEntry(SetupFileList *list, char *token)
1821 if (strEqual(list->token, token))
1824 return getListEntry(list->next, token);
1827 SetupFileList *setListEntry(SetupFileList *list, char *token, char *value)
1832 if (strEqual(list->token, token))
1834 checked_free(list->value);
1836 list->value = getStringCopy(value);
1840 else if (list->next == NULL)
1841 return (list->next = newSetupFileList(token, value));
1843 return setListEntry(list->next, token, value);
1846 SetupFileList *addListEntry(SetupFileList *list, char *token, char *value)
1851 if (list->next == NULL)
1852 return (list->next = newSetupFileList(token, value));
1854 return addListEntry(list->next, token, value);
1857 #if ENABLE_UNUSED_CODE
1859 static void printSetupFileList(SetupFileList *list)
1864 Debug("setup:printSetupFileList", "token: '%s'", list->token);
1865 Debug("setup:printSetupFileList", "value: '%s'", list->value);
1867 printSetupFileList(list->next);
1873 DEFINE_HASHTABLE_INSERT(insert_hash_entry, char, char);
1874 DEFINE_HASHTABLE_SEARCH(search_hash_entry, char, char);
1875 DEFINE_HASHTABLE_CHANGE(change_hash_entry, char, char);
1876 DEFINE_HASHTABLE_REMOVE(remove_hash_entry, char, char);
1878 #define insert_hash_entry hashtable_insert
1879 #define search_hash_entry hashtable_search
1880 #define change_hash_entry hashtable_change
1881 #define remove_hash_entry hashtable_remove
1884 unsigned int get_hash_from_key(void *key)
1889 This algorithm (k=33) was first reported by Dan Bernstein many years ago in
1890 'comp.lang.c'. Another version of this algorithm (now favored by Bernstein)
1891 uses XOR: hash(i) = hash(i - 1) * 33 ^ str[i]; the magic of number 33 (why
1892 it works better than many other constants, prime or not) has never been
1893 adequately explained.
1895 If you just want to have a good hash function, and cannot wait, djb2
1896 is one of the best string hash functions i know. It has excellent
1897 distribution and speed on many different sets of keys and table sizes.
1898 You are not likely to do better with one of the "well known" functions
1899 such as PJW, K&R, etc.
1901 Ozan (oz) Yigit [http://www.cs.yorku.ca/~oz/hash.html]
1904 char *str = (char *)key;
1905 unsigned int hash = 5381;
1908 while ((c = *str++))
1909 hash = ((hash << 5) + hash) + c; // hash * 33 + c
1914 static int keys_are_equal(void *key1, void *key2)
1916 return (strEqual((char *)key1, (char *)key2));
1919 SetupFileHash *newSetupFileHash(void)
1921 SetupFileHash *new_hash =
1922 create_hashtable(16, 0.75, get_hash_from_key, keys_are_equal);
1924 if (new_hash == NULL)
1925 Fail("create_hashtable() failed -- out of memory");
1930 void freeSetupFileHash(SetupFileHash *hash)
1935 hashtable_destroy(hash, 1); // 1 == also free values stored in hash
1938 char *getHashEntry(SetupFileHash *hash, char *token)
1943 return search_hash_entry(hash, token);
1946 void setHashEntry(SetupFileHash *hash, char *token, char *value)
1953 value_copy = getStringCopy(value);
1955 // change value; if it does not exist, insert it as new
1956 if (!change_hash_entry(hash, token, value_copy))
1957 if (!insert_hash_entry(hash, getStringCopy(token), value_copy))
1958 Fail("cannot insert into hash -- aborting");
1961 char *removeHashEntry(SetupFileHash *hash, char *token)
1966 return remove_hash_entry(hash, token);
1969 #if ENABLE_UNUSED_CODE
1971 static void printSetupFileHash(SetupFileHash *hash)
1973 BEGIN_HASH_ITERATION(hash, itr)
1975 Debug("setup:printSetupFileHash", "token: '%s'", HASH_ITERATION_TOKEN(itr));
1976 Debug("setup:printSetupFileHash", "value: '%s'", HASH_ITERATION_VALUE(itr));
1978 END_HASH_ITERATION(hash, itr)
1983 #define ALLOW_TOKEN_VALUE_SEPARATOR_BEING_WHITESPACE 1
1984 #define CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING 0
1985 #define CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH 0
1987 static boolean token_value_separator_found = FALSE;
1988 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
1989 static boolean token_value_separator_warning = FALSE;
1991 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
1992 static boolean token_already_exists_warning = FALSE;
1995 static boolean getTokenValueFromSetupLineExt(char *line,
1996 char **token_ptr, char **value_ptr,
1997 char *filename, char *line_raw,
1999 boolean separator_required)
2001 static char line_copy[MAX_LINE_LEN + 1], line_raw_copy[MAX_LINE_LEN + 1];
2002 char *token, *value, *line_ptr;
2004 // when externally invoked via ReadTokenValueFromLine(), copy line buffers
2005 if (line_raw == NULL)
2007 strncpy(line_copy, line, MAX_LINE_LEN);
2008 line_copy[MAX_LINE_LEN] = '\0';
2011 strcpy(line_raw_copy, line_copy);
2012 line_raw = line_raw_copy;
2015 // cut trailing comment from input line
2016 for (line_ptr = line; *line_ptr; line_ptr++)
2018 if (*line_ptr == '#')
2025 // cut trailing whitespaces from input line
2026 for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
2027 if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
2030 // ignore empty lines
2034 // cut leading whitespaces from token
2035 for (token = line; *token; token++)
2036 if (*token != ' ' && *token != '\t')
2039 // start with empty value as reliable default
2042 token_value_separator_found = FALSE;
2044 // find end of token to determine start of value
2045 for (line_ptr = token; *line_ptr; line_ptr++)
2047 // first look for an explicit token/value separator, like ':' or '='
2048 if (*line_ptr == ':' || *line_ptr == '=')
2050 *line_ptr = '\0'; // terminate token string
2051 value = line_ptr + 1; // set beginning of value
2053 token_value_separator_found = TRUE;
2059 #if ALLOW_TOKEN_VALUE_SEPARATOR_BEING_WHITESPACE
2060 // fallback: if no token/value separator found, also allow whitespaces
2061 if (!token_value_separator_found && !separator_required)
2063 for (line_ptr = token; *line_ptr; line_ptr++)
2065 if (*line_ptr == ' ' || *line_ptr == '\t')
2067 *line_ptr = '\0'; // terminate token string
2068 value = line_ptr + 1; // set beginning of value
2070 token_value_separator_found = TRUE;
2076 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2077 if (token_value_separator_found)
2079 if (!token_value_separator_warning)
2081 Debug("setup", "---");
2083 if (filename != NULL)
2085 Debug("setup", "missing token/value separator(s) in config file:");
2086 Debug("setup", "- config file: '%s'", filename);
2090 Debug("setup", "missing token/value separator(s):");
2093 token_value_separator_warning = TRUE;
2096 if (filename != NULL)
2097 Debug("setup", "- line %d: '%s'", line_nr, line_raw);
2099 Debug("setup", "- line: '%s'", line_raw);
2105 // cut trailing whitespaces from token
2106 for (line_ptr = &token[strlen(token)]; line_ptr >= token; line_ptr--)
2107 if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
2110 // cut leading whitespaces from value
2111 for (; *value; value++)
2112 if (*value != ' ' && *value != '\t')
2121 boolean getTokenValueFromSetupLine(char *line, char **token, char **value)
2123 // while the internal (old) interface does not require a token/value
2124 // separator (for downwards compatibility with existing files which
2125 // don't use them), it is mandatory for the external (new) interface
2127 return getTokenValueFromSetupLineExt(line, token, value, NULL, NULL, 0, TRUE);
2130 static boolean loadSetupFileData(void *setup_file_data, char *filename,
2131 boolean top_recursion_level, boolean is_hash)
2133 static SetupFileHash *include_filename_hash = NULL;
2134 char line[MAX_LINE_LEN], line_raw[MAX_LINE_LEN], previous_line[MAX_LINE_LEN];
2135 char *token, *value, *line_ptr;
2136 void *insert_ptr = NULL;
2137 boolean read_continued_line = FALSE;
2139 int line_nr = 0, token_count = 0, include_count = 0;
2141 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2142 token_value_separator_warning = FALSE;
2145 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2146 token_already_exists_warning = FALSE;
2149 if (!(file = openFile(filename, MODE_READ)))
2151 #if DEBUG_NO_CONFIG_FILE
2152 Debug("setup", "cannot open configuration file '%s'", filename);
2158 // use "insert pointer" to store list end for constant insertion complexity
2160 insert_ptr = setup_file_data;
2162 // on top invocation, create hash to mark included files (to prevent loops)
2163 if (top_recursion_level)
2164 include_filename_hash = newSetupFileHash();
2166 // mark this file as already included (to prevent including it again)
2167 setHashEntry(include_filename_hash, getBaseNamePtr(filename), "true");
2169 while (!checkEndOfFile(file))
2171 // read next line of input file
2172 if (!getStringFromFile(file, line, MAX_LINE_LEN))
2175 // check if line was completely read and is terminated by line break
2176 if (strlen(line) > 0 && line[strlen(line) - 1] == '\n')
2179 // cut trailing line break (this can be newline and/or carriage return)
2180 for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
2181 if ((*line_ptr == '\n' || *line_ptr == '\r') && *(line_ptr + 1) == '\0')
2184 // copy raw input line for later use (mainly debugging output)
2185 strcpy(line_raw, line);
2187 if (read_continued_line)
2189 // append new line to existing line, if there is enough space
2190 if (strlen(previous_line) + strlen(line_ptr) < MAX_LINE_LEN)
2191 strcat(previous_line, line_ptr);
2193 strcpy(line, previous_line); // copy storage buffer to line
2195 read_continued_line = FALSE;
2198 // if the last character is '\', continue at next line
2199 if (strlen(line) > 0 && line[strlen(line) - 1] == '\\')
2201 line[strlen(line) - 1] = '\0'; // cut off trailing backslash
2202 strcpy(previous_line, line); // copy line to storage buffer
2204 read_continued_line = TRUE;
2209 if (!getTokenValueFromSetupLineExt(line, &token, &value, filename,
2210 line_raw, line_nr, FALSE))
2215 if (strEqual(token, "include"))
2217 if (getHashEntry(include_filename_hash, value) == NULL)
2219 char *basepath = getBasePath(filename);
2220 char *basename = getBaseName(value);
2221 char *filename_include = getPath2(basepath, basename);
2223 loadSetupFileData(setup_file_data, filename_include, FALSE, is_hash);
2227 free(filename_include);
2233 Warn("ignoring already processed file '%s'", value);
2240 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2242 getHashEntry((SetupFileHash *)setup_file_data, token);
2244 if (old_value != NULL)
2246 if (!token_already_exists_warning)
2248 Debug("setup", "---");
2249 Debug("setup", "duplicate token(s) found in config file:");
2250 Debug("setup", "- config file: '%s'", filename);
2252 token_already_exists_warning = TRUE;
2255 Debug("setup", "- token: '%s' (in line %d)", token, line_nr);
2256 Debug("setup", " old value: '%s'", old_value);
2257 Debug("setup", " new value: '%s'", value);
2261 setHashEntry((SetupFileHash *)setup_file_data, token, value);
2265 insert_ptr = addListEntry((SetupFileList *)insert_ptr, token, value);
2275 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2276 if (token_value_separator_warning)
2277 Debug("setup", "---");
2280 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2281 if (token_already_exists_warning)
2282 Debug("setup", "---");
2285 if (token_count == 0 && include_count == 0)
2286 Warn("configuration file '%s' is empty", filename);
2288 if (top_recursion_level)
2289 freeSetupFileHash(include_filename_hash);
2294 static int compareSetupFileData(const void *object1, const void *object2)
2296 const struct ConfigInfo *entry1 = (struct ConfigInfo *)object1;
2297 const struct ConfigInfo *entry2 = (struct ConfigInfo *)object2;
2299 return strcmp(entry1->token, entry2->token);
2302 static void saveSetupFileHash(SetupFileHash *hash, char *filename)
2304 int item_count = hashtable_count(hash);
2305 int item_size = sizeof(struct ConfigInfo);
2306 struct ConfigInfo *sort_array = checked_malloc(item_count * item_size);
2310 // copy string pointers from hash to array
2311 BEGIN_HASH_ITERATION(hash, itr)
2313 sort_array[i].token = HASH_ITERATION_TOKEN(itr);
2314 sort_array[i].value = HASH_ITERATION_VALUE(itr);
2318 if (i > item_count) // should never happen
2321 END_HASH_ITERATION(hash, itr)
2323 // sort string pointers from hash in array
2324 qsort(sort_array, item_count, item_size, compareSetupFileData);
2326 if (!(file = fopen(filename, MODE_WRITE)))
2328 Warn("cannot write configuration file '%s'", filename);
2333 for (i = 0; i < item_count; i++)
2334 fprintf(file, "%s\n", getFormattedSetupEntry(sort_array[i].token,
2335 sort_array[i].value));
2338 checked_free(sort_array);
2341 SetupFileList *loadSetupFileList(char *filename)
2343 SetupFileList *setup_file_list = newSetupFileList("", "");
2344 SetupFileList *first_valid_list_entry;
2346 if (!loadSetupFileData(setup_file_list, filename, TRUE, FALSE))
2348 freeSetupFileList(setup_file_list);
2353 first_valid_list_entry = setup_file_list->next;
2355 // free empty list header
2356 setup_file_list->next = NULL;
2357 freeSetupFileList(setup_file_list);
2359 return first_valid_list_entry;
2362 SetupFileHash *loadSetupFileHash(char *filename)
2364 SetupFileHash *setup_file_hash = newSetupFileHash();
2366 if (!loadSetupFileData(setup_file_hash, filename, TRUE, TRUE))
2368 freeSetupFileHash(setup_file_hash);
2373 return setup_file_hash;
2377 // ============================================================================
2379 // ============================================================================
2381 #define TOKEN_STR_LAST_LEVEL_SERIES "last_level_series"
2382 #define TOKEN_STR_LAST_PLAYED_LEVEL "last_played_level"
2383 #define TOKEN_STR_HANDICAP_LEVEL "handicap_level"
2384 #define TOKEN_STR_LAST_USER "last_user"
2386 // level directory info
2387 #define LEVELINFO_TOKEN_IDENTIFIER 0
2388 #define LEVELINFO_TOKEN_NAME 1
2389 #define LEVELINFO_TOKEN_NAME_SORTING 2
2390 #define LEVELINFO_TOKEN_AUTHOR 3
2391 #define LEVELINFO_TOKEN_YEAR 4
2392 #define LEVELINFO_TOKEN_PROGRAM_TITLE 5
2393 #define LEVELINFO_TOKEN_PROGRAM_COPYRIGHT 6
2394 #define LEVELINFO_TOKEN_PROGRAM_COMPANY 7
2395 #define LEVELINFO_TOKEN_IMPORTED_FROM 8
2396 #define LEVELINFO_TOKEN_IMPORTED_BY 9
2397 #define LEVELINFO_TOKEN_TESTED_BY 10
2398 #define LEVELINFO_TOKEN_LEVELS 11
2399 #define LEVELINFO_TOKEN_FIRST_LEVEL 12
2400 #define LEVELINFO_TOKEN_SORT_PRIORITY 13
2401 #define LEVELINFO_TOKEN_LATEST_ENGINE 14
2402 #define LEVELINFO_TOKEN_LEVEL_GROUP 15
2403 #define LEVELINFO_TOKEN_READONLY 16
2404 #define LEVELINFO_TOKEN_GRAPHICS_SET_ECS 17
2405 #define LEVELINFO_TOKEN_GRAPHICS_SET_AGA 18
2406 #define LEVELINFO_TOKEN_GRAPHICS_SET 19
2407 #define LEVELINFO_TOKEN_SOUNDS_SET_DEFAULT 20
2408 #define LEVELINFO_TOKEN_SOUNDS_SET_LOWPASS 21
2409 #define LEVELINFO_TOKEN_SOUNDS_SET 22
2410 #define LEVELINFO_TOKEN_MUSIC_SET 23
2411 #define LEVELINFO_TOKEN_FILENAME 24
2412 #define LEVELINFO_TOKEN_FILETYPE 25
2413 #define LEVELINFO_TOKEN_SPECIAL_FLAGS 26
2414 #define LEVELINFO_TOKEN_HANDICAP 27
2415 #define LEVELINFO_TOKEN_SKIP_LEVELS 28
2416 #define LEVELINFO_TOKEN_USE_EMC_TILES 29
2418 #define NUM_LEVELINFO_TOKENS 30
2420 static LevelDirTree ldi;
2422 static struct TokenInfo levelinfo_tokens[] =
2424 // level directory info
2425 { TYPE_STRING, &ldi.identifier, "identifier" },
2426 { TYPE_STRING, &ldi.name, "name" },
2427 { TYPE_STRING, &ldi.name_sorting, "name_sorting" },
2428 { TYPE_STRING, &ldi.author, "author" },
2429 { TYPE_STRING, &ldi.year, "year" },
2430 { TYPE_STRING, &ldi.program_title, "program_title" },
2431 { TYPE_STRING, &ldi.program_copyright, "program_copyright" },
2432 { TYPE_STRING, &ldi.program_company, "program_company" },
2433 { TYPE_STRING, &ldi.imported_from, "imported_from" },
2434 { TYPE_STRING, &ldi.imported_by, "imported_by" },
2435 { TYPE_STRING, &ldi.tested_by, "tested_by" },
2436 { TYPE_INTEGER, &ldi.levels, "levels" },
2437 { TYPE_INTEGER, &ldi.first_level, "first_level" },
2438 { TYPE_INTEGER, &ldi.sort_priority, "sort_priority" },
2439 { TYPE_BOOLEAN, &ldi.latest_engine, "latest_engine" },
2440 { TYPE_BOOLEAN, &ldi.level_group, "level_group" },
2441 { TYPE_BOOLEAN, &ldi.readonly, "readonly" },
2442 { TYPE_STRING, &ldi.graphics_set_ecs, "graphics_set.ecs" },
2443 { TYPE_STRING, &ldi.graphics_set_aga, "graphics_set.aga" },
2444 { TYPE_STRING, &ldi.graphics_set, "graphics_set" },
2445 { TYPE_STRING, &ldi.sounds_set_default,"sounds_set.default" },
2446 { TYPE_STRING, &ldi.sounds_set_lowpass,"sounds_set.lowpass" },
2447 { TYPE_STRING, &ldi.sounds_set, "sounds_set" },
2448 { TYPE_STRING, &ldi.music_set, "music_set" },
2449 { TYPE_STRING, &ldi.level_filename, "filename" },
2450 { TYPE_STRING, &ldi.level_filetype, "filetype" },
2451 { TYPE_STRING, &ldi.special_flags, "special_flags" },
2452 { TYPE_BOOLEAN, &ldi.handicap, "handicap" },
2453 { TYPE_BOOLEAN, &ldi.skip_levels, "skip_levels" },
2454 { TYPE_BOOLEAN, &ldi.use_emc_tiles, "use_emc_tiles" }
2457 static struct TokenInfo artworkinfo_tokens[] =
2459 // artwork directory info
2460 { TYPE_STRING, &ldi.identifier, "identifier" },
2461 { TYPE_STRING, &ldi.subdir, "subdir" },
2462 { TYPE_STRING, &ldi.name, "name" },
2463 { TYPE_STRING, &ldi.name_sorting, "name_sorting" },
2464 { TYPE_STRING, &ldi.author, "author" },
2465 { TYPE_STRING, &ldi.program_title, "program_title" },
2466 { TYPE_STRING, &ldi.program_copyright, "program_copyright" },
2467 { TYPE_STRING, &ldi.program_company, "program_company" },
2468 { TYPE_INTEGER, &ldi.sort_priority, "sort_priority" },
2469 { TYPE_STRING, &ldi.basepath, "basepath" },
2470 { TYPE_STRING, &ldi.fullpath, "fullpath" },
2471 { TYPE_BOOLEAN, &ldi.in_user_dir, "in_user_dir" },
2472 { TYPE_STRING, &ldi.class_desc, "class_desc" },
2477 static char *optional_tokens[] =
2480 "program_copyright",
2486 static void setTreeInfoToDefaults(TreeInfo *ti, int type)
2490 ti->node_top = (ti->type == TREE_TYPE_LEVEL_DIR ? &leveldir_first :
2491 ti->type == TREE_TYPE_GRAPHICS_DIR ? &artwork.gfx_first :
2492 ti->type == TREE_TYPE_SOUNDS_DIR ? &artwork.snd_first :
2493 ti->type == TREE_TYPE_MUSIC_DIR ? &artwork.mus_first :
2496 ti->node_parent = NULL;
2497 ti->node_group = NULL;
2504 ti->fullpath = NULL;
2505 ti->basepath = NULL;
2506 ti->identifier = NULL;
2507 ti->name = getStringCopy(ANONYMOUS_NAME);
2508 ti->name_sorting = NULL;
2509 ti->author = getStringCopy(ANONYMOUS_NAME);
2512 ti->program_title = NULL;
2513 ti->program_copyright = NULL;
2514 ti->program_company = NULL;
2516 ti->sort_priority = LEVELCLASS_UNDEFINED; // default: least priority
2517 ti->latest_engine = FALSE; // default: get from level
2518 ti->parent_link = FALSE;
2519 ti->in_user_dir = FALSE;
2520 ti->user_defined = FALSE;
2522 ti->class_desc = NULL;
2524 ti->infotext = getStringCopy(TREE_INFOTEXT(ti->type));
2526 if (ti->type == TREE_TYPE_LEVEL_DIR)
2528 ti->imported_from = NULL;
2529 ti->imported_by = NULL;
2530 ti->tested_by = NULL;
2532 ti->graphics_set_ecs = NULL;
2533 ti->graphics_set_aga = NULL;
2534 ti->graphics_set = NULL;
2535 ti->sounds_set_default = NULL;
2536 ti->sounds_set_lowpass = NULL;
2537 ti->sounds_set = NULL;
2538 ti->music_set = NULL;
2539 ti->graphics_path = getStringCopy(UNDEFINED_FILENAME);
2540 ti->sounds_path = getStringCopy(UNDEFINED_FILENAME);
2541 ti->music_path = getStringCopy(UNDEFINED_FILENAME);
2543 ti->level_filename = NULL;
2544 ti->level_filetype = NULL;
2546 ti->special_flags = NULL;
2549 ti->first_level = 0;
2551 ti->level_group = FALSE;
2552 ti->handicap_level = 0;
2553 ti->readonly = TRUE;
2554 ti->handicap = TRUE;
2555 ti->skip_levels = FALSE;
2557 ti->use_emc_tiles = FALSE;
2561 static void setTreeInfoToDefaultsFromParent(TreeInfo *ti, TreeInfo *parent)
2565 Warn("setTreeInfoToDefaultsFromParent(): parent == NULL");
2567 setTreeInfoToDefaults(ti, TREE_TYPE_UNDEFINED);
2572 // copy all values from the parent structure
2574 ti->type = parent->type;
2576 ti->node_top = parent->node_top;
2577 ti->node_parent = parent;
2578 ti->node_group = NULL;
2585 ti->fullpath = NULL;
2586 ti->basepath = NULL;
2587 ti->identifier = NULL;
2588 ti->name = getStringCopy(ANONYMOUS_NAME);
2589 ti->name_sorting = NULL;
2590 ti->author = getStringCopy(parent->author);
2591 ti->year = getStringCopy(parent->year);
2593 ti->program_title = getStringCopy(parent->program_title);
2594 ti->program_copyright = getStringCopy(parent->program_copyright);
2595 ti->program_company = getStringCopy(parent->program_company);
2597 ti->sort_priority = parent->sort_priority;
2598 ti->latest_engine = parent->latest_engine;
2599 ti->parent_link = FALSE;
2600 ti->in_user_dir = parent->in_user_dir;
2601 ti->user_defined = parent->user_defined;
2602 ti->color = parent->color;
2603 ti->class_desc = getStringCopy(parent->class_desc);
2605 ti->infotext = getStringCopy(parent->infotext);
2607 if (ti->type == TREE_TYPE_LEVEL_DIR)
2609 ti->imported_from = getStringCopy(parent->imported_from);
2610 ti->imported_by = getStringCopy(parent->imported_by);
2611 ti->tested_by = getStringCopy(parent->tested_by);
2613 ti->graphics_set_ecs = getStringCopy(parent->graphics_set_ecs);
2614 ti->graphics_set_aga = getStringCopy(parent->graphics_set_aga);
2615 ti->graphics_set = getStringCopy(parent->graphics_set);
2616 ti->sounds_set_default = getStringCopy(parent->sounds_set_default);
2617 ti->sounds_set_lowpass = getStringCopy(parent->sounds_set_lowpass);
2618 ti->sounds_set = getStringCopy(parent->sounds_set);
2619 ti->music_set = getStringCopy(parent->music_set);
2620 ti->graphics_path = getStringCopy(UNDEFINED_FILENAME);
2621 ti->sounds_path = getStringCopy(UNDEFINED_FILENAME);
2622 ti->music_path = getStringCopy(UNDEFINED_FILENAME);
2624 ti->level_filename = getStringCopy(parent->level_filename);
2625 ti->level_filetype = getStringCopy(parent->level_filetype);
2627 ti->special_flags = getStringCopy(parent->special_flags);
2629 ti->levels = parent->levels;
2630 ti->first_level = parent->first_level;
2631 ti->last_level = parent->last_level;
2632 ti->level_group = FALSE;
2633 ti->handicap_level = parent->handicap_level;
2634 ti->readonly = parent->readonly;
2635 ti->handicap = parent->handicap;
2636 ti->skip_levels = parent->skip_levels;
2638 ti->use_emc_tiles = parent->use_emc_tiles;
2642 static TreeInfo *getTreeInfoCopy(TreeInfo *ti)
2644 TreeInfo *ti_copy = newTreeInfo();
2646 // copy all values from the original structure
2648 ti_copy->type = ti->type;
2650 ti_copy->node_top = ti->node_top;
2651 ti_copy->node_parent = ti->node_parent;
2652 ti_copy->node_group = ti->node_group;
2653 ti_copy->next = ti->next;
2655 ti_copy->cl_first = ti->cl_first;
2656 ti_copy->cl_cursor = ti->cl_cursor;
2658 ti_copy->subdir = getStringCopy(ti->subdir);
2659 ti_copy->fullpath = getStringCopy(ti->fullpath);
2660 ti_copy->basepath = getStringCopy(ti->basepath);
2661 ti_copy->identifier = getStringCopy(ti->identifier);
2662 ti_copy->name = getStringCopy(ti->name);
2663 ti_copy->name_sorting = getStringCopy(ti->name_sorting);
2664 ti_copy->author = getStringCopy(ti->author);
2665 ti_copy->year = getStringCopy(ti->year);
2667 ti_copy->program_title = getStringCopy(ti->program_title);
2668 ti_copy->program_copyright = getStringCopy(ti->program_copyright);
2669 ti_copy->program_company = getStringCopy(ti->program_company);
2671 ti_copy->imported_from = getStringCopy(ti->imported_from);
2672 ti_copy->imported_by = getStringCopy(ti->imported_by);
2673 ti_copy->tested_by = getStringCopy(ti->tested_by);
2675 ti_copy->graphics_set_ecs = getStringCopy(ti->graphics_set_ecs);
2676 ti_copy->graphics_set_aga = getStringCopy(ti->graphics_set_aga);
2677 ti_copy->graphics_set = getStringCopy(ti->graphics_set);
2678 ti_copy->sounds_set_default = getStringCopy(ti->sounds_set_default);
2679 ti_copy->sounds_set_lowpass = getStringCopy(ti->sounds_set_lowpass);
2680 ti_copy->sounds_set = getStringCopy(ti->sounds_set);
2681 ti_copy->music_set = getStringCopy(ti->music_set);
2682 ti_copy->graphics_path = getStringCopy(ti->graphics_path);
2683 ti_copy->sounds_path = getStringCopy(ti->sounds_path);
2684 ti_copy->music_path = getStringCopy(ti->music_path);
2686 ti_copy->level_filename = getStringCopy(ti->level_filename);
2687 ti_copy->level_filetype = getStringCopy(ti->level_filetype);
2689 ti_copy->special_flags = getStringCopy(ti->special_flags);
2691 ti_copy->levels = ti->levels;
2692 ti_copy->first_level = ti->first_level;
2693 ti_copy->last_level = ti->last_level;
2694 ti_copy->sort_priority = ti->sort_priority;
2696 ti_copy->latest_engine = ti->latest_engine;
2698 ti_copy->level_group = ti->level_group;
2699 ti_copy->parent_link = ti->parent_link;
2700 ti_copy->in_user_dir = ti->in_user_dir;
2701 ti_copy->user_defined = ti->user_defined;
2702 ti_copy->readonly = ti->readonly;
2703 ti_copy->handicap = ti->handicap;
2704 ti_copy->skip_levels = ti->skip_levels;
2706 ti_copy->use_emc_tiles = ti->use_emc_tiles;
2708 ti_copy->color = ti->color;
2709 ti_copy->class_desc = getStringCopy(ti->class_desc);
2710 ti_copy->handicap_level = ti->handicap_level;
2712 ti_copy->infotext = getStringCopy(ti->infotext);
2717 void freeTreeInfo(TreeInfo *ti)
2722 checked_free(ti->subdir);
2723 checked_free(ti->fullpath);
2724 checked_free(ti->basepath);
2725 checked_free(ti->identifier);
2727 checked_free(ti->name);
2728 checked_free(ti->name_sorting);
2729 checked_free(ti->author);
2730 checked_free(ti->year);
2732 checked_free(ti->program_title);
2733 checked_free(ti->program_copyright);
2734 checked_free(ti->program_company);
2736 checked_free(ti->class_desc);
2738 checked_free(ti->infotext);
2740 if (ti->type == TREE_TYPE_LEVEL_DIR)
2742 checked_free(ti->imported_from);
2743 checked_free(ti->imported_by);
2744 checked_free(ti->tested_by);
2746 checked_free(ti->graphics_set_ecs);
2747 checked_free(ti->graphics_set_aga);
2748 checked_free(ti->graphics_set);
2749 checked_free(ti->sounds_set_default);
2750 checked_free(ti->sounds_set_lowpass);
2751 checked_free(ti->sounds_set);
2752 checked_free(ti->music_set);
2754 checked_free(ti->graphics_path);
2755 checked_free(ti->sounds_path);
2756 checked_free(ti->music_path);
2758 checked_free(ti->level_filename);
2759 checked_free(ti->level_filetype);
2761 checked_free(ti->special_flags);
2764 // recursively free child node
2766 freeTreeInfo(ti->node_group);
2768 // recursively free next node
2770 freeTreeInfo(ti->next);
2775 void setSetupInfo(struct TokenInfo *token_info,
2776 int token_nr, char *token_value)
2778 int token_type = token_info[token_nr].type;
2779 void *setup_value = token_info[token_nr].value;
2781 if (token_value == NULL)
2784 // set setup field to corresponding token value
2789 *(boolean *)setup_value = get_boolean_from_string(token_value);
2793 *(int *)setup_value = get_switch3_from_string(token_value);
2797 *(Key *)setup_value = getKeyFromKeyName(token_value);
2801 *(Key *)setup_value = getKeyFromX11KeyName(token_value);
2805 *(int *)setup_value = get_integer_from_string(token_value);
2809 checked_free(*(char **)setup_value);
2810 *(char **)setup_value = getStringCopy(token_value);
2814 *(int *)setup_value = get_player_nr_from_string(token_value);
2822 static int compareTreeInfoEntries(const void *object1, const void *object2)
2824 const TreeInfo *entry1 = *((TreeInfo **)object1);
2825 const TreeInfo *entry2 = *((TreeInfo **)object2);
2826 int tree_sorting1 = TREE_SORTING(entry1);
2827 int tree_sorting2 = TREE_SORTING(entry2);
2829 if (tree_sorting1 != tree_sorting2)
2830 return (tree_sorting1 - tree_sorting2);
2832 return strcasecmp(entry1->name_sorting, entry2->name_sorting);
2835 static TreeInfo *createParentTreeInfoNode(TreeInfo *node_parent)
2839 if (node_parent == NULL)
2842 ti_new = newTreeInfo();
2843 setTreeInfoToDefaults(ti_new, node_parent->type);
2845 ti_new->node_parent = node_parent;
2846 ti_new->parent_link = TRUE;
2848 setString(&ti_new->identifier, node_parent->identifier);
2849 setString(&ti_new->name, BACKLINK_TEXT_PARENT);
2850 setString(&ti_new->name_sorting, ti_new->name);
2852 setString(&ti_new->subdir, STRING_PARENT_DIRECTORY);
2853 setString(&ti_new->fullpath, node_parent->fullpath);
2855 ti_new->sort_priority = node_parent->sort_priority;
2856 ti_new->latest_engine = node_parent->latest_engine;
2858 setString(&ti_new->class_desc, getLevelClassDescription(ti_new));
2860 pushTreeInfo(&node_parent->node_group, ti_new);
2865 static TreeInfo *createTopTreeInfoNode(TreeInfo *node_first)
2867 if (node_first == NULL)
2870 TreeInfo *ti_new = newTreeInfo();
2871 int type = node_first->type;
2873 setTreeInfoToDefaults(ti_new, type);
2875 ti_new->node_parent = NULL;
2876 ti_new->parent_link = FALSE;
2878 setString(&ti_new->identifier, node_first->identifier);
2879 setString(&ti_new->name, TREE_INFOTEXT(type));
2880 setString(&ti_new->name_sorting, ti_new->name);
2882 setString(&ti_new->subdir, STRING_TOP_DIRECTORY);
2883 setString(&ti_new->fullpath, ".");
2885 ti_new->sort_priority = node_first->sort_priority;;
2886 ti_new->latest_engine = node_first->latest_engine;
2888 setString(&ti_new->class_desc, TREE_INFOTEXT(type));
2890 ti_new->node_group = node_first;
2891 ti_new->level_group = TRUE;
2893 TreeInfo *ti_new2 = createParentTreeInfoNode(ti_new);
2895 setString(&ti_new2->name, TREE_BACKLINK_TEXT(type));
2896 setString(&ti_new2->name_sorting, ti_new2->name);
2901 static void setTreeInfoParentNodes(TreeInfo *node, TreeInfo *node_parent)
2905 if (node->node_group)
2906 setTreeInfoParentNodes(node->node_group, node);
2908 node->node_parent = node_parent;
2915 // ----------------------------------------------------------------------------
2916 // functions for handling level and custom artwork info cache
2917 // ----------------------------------------------------------------------------
2919 static void LoadArtworkInfoCache(void)
2921 InitCacheDirectory();
2923 if (artworkinfo_cache_old == NULL)
2925 char *filename = getPath2(getCacheDir(), ARTWORKINFO_CACHE_FILE);
2927 // try to load artwork info hash from already existing cache file
2928 artworkinfo_cache_old = loadSetupFileHash(filename);
2930 // if no artwork info cache file was found, start with empty hash
2931 if (artworkinfo_cache_old == NULL)
2932 artworkinfo_cache_old = newSetupFileHash();
2937 if (artworkinfo_cache_new == NULL)
2938 artworkinfo_cache_new = newSetupFileHash();
2940 update_artworkinfo_cache = FALSE;
2943 static void SaveArtworkInfoCache(void)
2945 if (!update_artworkinfo_cache)
2948 char *filename = getPath2(getCacheDir(), ARTWORKINFO_CACHE_FILE);
2950 InitCacheDirectory();
2952 saveSetupFileHash(artworkinfo_cache_new, filename);
2957 static char *getCacheTokenPrefix(char *prefix1, char *prefix2)
2959 static char *prefix = NULL;
2961 checked_free(prefix);
2963 prefix = getStringCat2WithSeparator(prefix1, prefix2, ".");
2968 // (identical to above function, but separate string buffer needed -- nasty)
2969 static char *getCacheToken(char *prefix, char *suffix)
2971 static char *token = NULL;
2973 checked_free(token);
2975 token = getStringCat2WithSeparator(prefix, suffix, ".");
2980 static char *getFileTimestampString(char *filename)
2982 return getStringCopy(i_to_a(getFileTimestampEpochSeconds(filename)));
2985 static boolean modifiedFileTimestamp(char *filename, char *timestamp_string)
2987 struct stat file_status;
2989 if (timestamp_string == NULL)
2992 if (!fileExists(filename)) // file does not exist
2993 return (atoi(timestamp_string) != 0);
2995 if (stat(filename, &file_status) != 0) // cannot stat file
2998 return (file_status.st_mtime != atoi(timestamp_string));
3001 static TreeInfo *getArtworkInfoCacheEntry(LevelDirTree *level_node, int type)
3003 char *identifier = level_node->subdir;
3004 char *type_string = ARTWORK_DIRECTORY(type);
3005 char *token_prefix = getCacheTokenPrefix(type_string, identifier);
3006 char *token_main = getCacheToken(token_prefix, "CACHED");
3007 char *cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
3008 boolean cached = (cache_entry != NULL && strEqual(cache_entry, "true"));
3009 TreeInfo *artwork_info = NULL;
3011 if (!use_artworkinfo_cache)
3014 if (optional_tokens_hash == NULL)
3018 // create hash from list of optional tokens (for quick access)
3019 optional_tokens_hash = newSetupFileHash();
3020 for (i = 0; optional_tokens[i] != NULL; i++)
3021 setHashEntry(optional_tokens_hash, optional_tokens[i], "");
3028 artwork_info = newTreeInfo();
3029 setTreeInfoToDefaults(artwork_info, type);
3031 // set all structure fields according to the token/value pairs
3032 ldi = *artwork_info;
3033 for (i = 0; artworkinfo_tokens[i].type != -1; i++)
3035 char *token_suffix = artworkinfo_tokens[i].text;
3036 char *token = getCacheToken(token_prefix, token_suffix);
3037 char *value = getHashEntry(artworkinfo_cache_old, token);
3039 (getHashEntry(optional_tokens_hash, token_suffix) != NULL);
3041 setSetupInfo(artworkinfo_tokens, i, value);
3043 // check if cache entry for this item is mandatory, but missing
3044 if (value == NULL && !optional)
3046 Warn("missing cache entry '%s'", token);
3052 *artwork_info = ldi;
3057 char *filename_levelinfo = getPath2(getLevelDirFromTreeInfo(level_node),
3058 LEVELINFO_FILENAME);
3059 char *filename_artworkinfo = getPath2(getSetupArtworkDir(artwork_info),
3060 ARTWORKINFO_FILENAME(type));
3062 // check if corresponding "levelinfo.conf" file has changed
3063 token_main = getCacheToken(token_prefix, "TIMESTAMP_LEVELINFO");
3064 cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
3066 if (modifiedFileTimestamp(filename_levelinfo, cache_entry))
3069 // check if corresponding "<artworkinfo>.conf" file has changed
3070 token_main = getCacheToken(token_prefix, "TIMESTAMP_ARTWORKINFO");
3071 cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
3073 if (modifiedFileTimestamp(filename_artworkinfo, cache_entry))
3076 checked_free(filename_levelinfo);
3077 checked_free(filename_artworkinfo);
3080 if (!cached && artwork_info != NULL)
3082 freeTreeInfo(artwork_info);
3087 return artwork_info;
3090 static void setArtworkInfoCacheEntry(TreeInfo *artwork_info,
3091 LevelDirTree *level_node, int type)
3093 char *identifier = level_node->subdir;
3094 char *type_string = ARTWORK_DIRECTORY(type);
3095 char *token_prefix = getCacheTokenPrefix(type_string, identifier);
3096 char *token_main = getCacheToken(token_prefix, "CACHED");
3097 boolean set_cache_timestamps = TRUE;
3100 setHashEntry(artworkinfo_cache_new, token_main, "true");
3102 if (set_cache_timestamps)
3104 char *filename_levelinfo = getPath2(getLevelDirFromTreeInfo(level_node),
3105 LEVELINFO_FILENAME);
3106 char *filename_artworkinfo = getPath2(getSetupArtworkDir(artwork_info),
3107 ARTWORKINFO_FILENAME(type));
3108 char *timestamp_levelinfo = getFileTimestampString(filename_levelinfo);
3109 char *timestamp_artworkinfo = getFileTimestampString(filename_artworkinfo);
3111 token_main = getCacheToken(token_prefix, "TIMESTAMP_LEVELINFO");
3112 setHashEntry(artworkinfo_cache_new, token_main, timestamp_levelinfo);
3114 token_main = getCacheToken(token_prefix, "TIMESTAMP_ARTWORKINFO");
3115 setHashEntry(artworkinfo_cache_new, token_main, timestamp_artworkinfo);
3117 checked_free(filename_levelinfo);
3118 checked_free(filename_artworkinfo);
3119 checked_free(timestamp_levelinfo);
3120 checked_free(timestamp_artworkinfo);
3123 ldi = *artwork_info;
3124 for (i = 0; artworkinfo_tokens[i].type != -1; i++)
3126 char *token = getCacheToken(token_prefix, artworkinfo_tokens[i].text);
3127 char *value = getSetupValue(artworkinfo_tokens[i].type,
3128 artworkinfo_tokens[i].value);
3130 setHashEntry(artworkinfo_cache_new, token, value);
3135 // ----------------------------------------------------------------------------
3136 // functions for loading level info and custom artwork info
3137 // ----------------------------------------------------------------------------
3139 int GetZipFileTreeType(char *zip_filename)
3141 static char *top_dir_path = NULL;
3142 static char *top_dir_conf_filename[NUM_BASE_TREE_TYPES] = { NULL };
3143 static char *conf_basename[NUM_BASE_TREE_TYPES] =
3145 GRAPHICSINFO_FILENAME,
3146 SOUNDSINFO_FILENAME,
3152 checked_free(top_dir_path);
3153 top_dir_path = NULL;
3155 for (j = 0; j < NUM_BASE_TREE_TYPES; j++)
3157 checked_free(top_dir_conf_filename[j]);
3158 top_dir_conf_filename[j] = NULL;
3161 char **zip_entries = zip_list(zip_filename);
3163 // check if zip file successfully opened
3164 if (zip_entries == NULL || zip_entries[0] == NULL)
3165 return TREE_TYPE_UNDEFINED;
3167 // first zip file entry is expected to be top level directory
3168 char *top_dir = zip_entries[0];
3170 // check if valid top level directory found in zip file
3171 if (!strSuffix(top_dir, "/"))
3172 return TREE_TYPE_UNDEFINED;
3174 // get filenames of valid configuration files in top level directory
3175 for (j = 0; j < NUM_BASE_TREE_TYPES; j++)
3176 top_dir_conf_filename[j] = getStringCat2(top_dir, conf_basename[j]);
3178 int tree_type = TREE_TYPE_UNDEFINED;
3181 while (zip_entries[e] != NULL)
3183 // check if every zip file entry is below top level directory
3184 if (!strPrefix(zip_entries[e], top_dir))
3185 return TREE_TYPE_UNDEFINED;
3187 // check if this zip file entry is a valid configuration filename
3188 for (j = 0; j < NUM_BASE_TREE_TYPES; j++)
3190 if (strEqual(zip_entries[e], top_dir_conf_filename[j]))
3192 // only exactly one valid configuration file allowed
3193 if (tree_type != TREE_TYPE_UNDEFINED)
3194 return TREE_TYPE_UNDEFINED;
3206 static boolean CheckZipFileForDirectory(char *zip_filename, char *directory,
3209 static char *top_dir_path = NULL;
3210 static char *top_dir_conf_filename = NULL;
3212 checked_free(top_dir_path);
3213 checked_free(top_dir_conf_filename);
3215 top_dir_path = NULL;
3216 top_dir_conf_filename = NULL;
3218 char *conf_basename = (tree_type == TREE_TYPE_LEVEL_DIR ? LEVELINFO_FILENAME :
3219 ARTWORKINFO_FILENAME(tree_type));
3221 // check if valid configuration filename determined
3222 if (conf_basename == NULL || strEqual(conf_basename, ""))
3225 char **zip_entries = zip_list(zip_filename);
3227 // check if zip file successfully opened
3228 if (zip_entries == NULL || zip_entries[0] == NULL)
3231 // first zip file entry is expected to be top level directory
3232 char *top_dir = zip_entries[0];
3234 // check if valid top level directory found in zip file
3235 if (!strSuffix(top_dir, "/"))
3238 // get path of extracted top level directory
3239 top_dir_path = getPath2(directory, top_dir);
3241 // remove trailing directory separator from top level directory path
3242 // (required to be able to check for file and directory in next step)
3243 top_dir_path[strlen(top_dir_path) - 1] = '\0';
3245 // check if zip file's top level directory already exists in target directory
3246 if (fileExists(top_dir_path)) // (checks for file and directory)
3249 // get filename of configuration file in top level directory
3250 top_dir_conf_filename = getStringCat2(top_dir, conf_basename);
3252 boolean found_top_dir_conf_filename = FALSE;
3255 while (zip_entries[i] != NULL)
3257 // check if every zip file entry is below top level directory
3258 if (!strPrefix(zip_entries[i], top_dir))
3261 // check if this zip file entry is the configuration filename
3262 if (strEqual(zip_entries[i], top_dir_conf_filename))
3263 found_top_dir_conf_filename = TRUE;
3268 // check if valid configuration filename was found in zip file
3269 if (!found_top_dir_conf_filename)
3275 char *ExtractZipFileIntoDirectory(char *zip_filename, char *directory,
3278 boolean zip_file_valid = CheckZipFileForDirectory(zip_filename, directory,
3281 if (!zip_file_valid)
3283 Warn("zip file '%s' rejected!", zip_filename);
3288 char **zip_entries = zip_extract(zip_filename, directory);
3290 if (zip_entries == NULL)
3292 Warn("zip file '%s' could not be extracted!", zip_filename);
3297 Info("zip file '%s' successfully extracted!", zip_filename);
3299 // first zip file entry contains top level directory
3300 char *top_dir = zip_entries[0];
3302 // remove trailing directory separator from top level directory
3303 top_dir[strlen(top_dir) - 1] = '\0';
3308 static void ProcessZipFilesInDirectory(char *directory, int tree_type)
3311 DirectoryEntry *dir_entry;
3313 if ((dir = openDirectory(directory)) == NULL)
3315 // display error if directory is main "options.graphics_directory" etc.
3316 if (tree_type == TREE_TYPE_LEVEL_DIR ||
3317 directory == OPTIONS_ARTWORK_DIRECTORY(tree_type))
3318 Warn("cannot read directory '%s'", directory);
3323 while ((dir_entry = readDirectory(dir)) != NULL) // loop all entries
3325 // skip non-zip files (and also directories with zip extension)
3326 if (!strSuffixLower(dir_entry->basename, ".zip") || dir_entry->is_directory)
3329 char *zip_filename = getPath2(directory, dir_entry->basename);
3330 char *zip_filename_extracted = getStringCat2(zip_filename, ".extracted");
3331 char *zip_filename_rejected = getStringCat2(zip_filename, ".rejected");
3333 // check if zip file hasn't already been extracted or rejected
3334 if (!fileExists(zip_filename_extracted) &&
3335 !fileExists(zip_filename_rejected))
3337 char *top_dir = ExtractZipFileIntoDirectory(zip_filename, directory,
3339 char *marker_filename = (top_dir != NULL ? zip_filename_extracted :
3340 zip_filename_rejected);
3343 // create empty file to mark zip file as extracted or rejected
3344 if ((marker_file = fopen(marker_filename, MODE_WRITE)))
3345 fclose(marker_file);
3348 free(zip_filename_extracted);
3349 free(zip_filename_rejected);
3353 closeDirectory(dir);
3356 // forward declaration for recursive call by "LoadLevelInfoFromLevelDir()"
3357 static void LoadLevelInfoFromLevelDir(TreeInfo **, TreeInfo *, char *);
3359 static boolean LoadLevelInfoFromLevelConf(TreeInfo **node_first,
3360 TreeInfo *node_parent,
3361 char *level_directory,
3362 char *directory_name)
3364 char *directory_path = getPath2(level_directory, directory_name);
3365 char *filename = getPath2(directory_path, LEVELINFO_FILENAME);
3366 SetupFileHash *setup_file_hash;
3367 LevelDirTree *leveldir_new = NULL;
3370 // unless debugging, silently ignore directories without "levelinfo.conf"
3371 if (!options.debug && !fileExists(filename))
3373 free(directory_path);
3379 setup_file_hash = loadSetupFileHash(filename);
3381 if (setup_file_hash == NULL)
3383 #if DEBUG_NO_CONFIG_FILE
3384 Debug("setup", "ignoring level directory '%s'", directory_path);
3387 free(directory_path);
3393 leveldir_new = newTreeInfo();
3396 setTreeInfoToDefaultsFromParent(leveldir_new, node_parent);
3398 setTreeInfoToDefaults(leveldir_new, TREE_TYPE_LEVEL_DIR);
3400 leveldir_new->subdir = getStringCopy(directory_name);
3402 // set all structure fields according to the token/value pairs
3403 ldi = *leveldir_new;
3404 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
3405 setSetupInfo(levelinfo_tokens, i,
3406 getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
3407 *leveldir_new = ldi;
3409 if (strEqual(leveldir_new->name, ANONYMOUS_NAME))
3410 setString(&leveldir_new->name, leveldir_new->subdir);
3412 if (leveldir_new->identifier == NULL)
3413 leveldir_new->identifier = getStringCopy(leveldir_new->subdir);
3415 if (leveldir_new->name_sorting == NULL)
3416 leveldir_new->name_sorting = getStringCopy(leveldir_new->name);
3418 if (node_parent == NULL) // top level group
3420 leveldir_new->basepath = getStringCopy(level_directory);
3421 leveldir_new->fullpath = getStringCopy(leveldir_new->subdir);
3423 else // sub level group
3425 leveldir_new->basepath = getStringCopy(node_parent->basepath);
3426 leveldir_new->fullpath = getPath2(node_parent->fullpath, directory_name);
3429 leveldir_new->last_level =
3430 leveldir_new->first_level + leveldir_new->levels - 1;
3432 leveldir_new->in_user_dir =
3433 (!strEqual(leveldir_new->basepath, options.level_directory));
3435 // adjust some settings if user's private level directory was detected
3436 if (leveldir_new->sort_priority == LEVELCLASS_UNDEFINED &&
3437 leveldir_new->in_user_dir &&
3438 (strEqual(leveldir_new->subdir, getLoginName()) ||
3439 strEqual(leveldir_new->name, getLoginName()) ||
3440 strEqual(leveldir_new->author, getRealName())))
3442 leveldir_new->sort_priority = LEVELCLASS_PRIVATE_START;
3443 leveldir_new->readonly = FALSE;
3446 leveldir_new->user_defined =
3447 (leveldir_new->in_user_dir && IS_LEVELCLASS_PRIVATE(leveldir_new));
3449 setString(&leveldir_new->class_desc, getLevelClassDescription(leveldir_new));
3451 leveldir_new->handicap_level = // set handicap to default value
3452 (leveldir_new->user_defined || !leveldir_new->handicap ?
3453 leveldir_new->last_level : leveldir_new->first_level);
3455 DrawInitText(leveldir_new->name, 150, FC_YELLOW);
3457 pushTreeInfo(node_first, leveldir_new);
3459 freeSetupFileHash(setup_file_hash);
3461 if (leveldir_new->level_group)
3463 // create node to link back to current level directory
3464 createParentTreeInfoNode(leveldir_new);
3466 // recursively step into sub-directory and look for more level series
3467 LoadLevelInfoFromLevelDir(&leveldir_new->node_group,
3468 leveldir_new, directory_path);
3471 free(directory_path);
3477 static void LoadLevelInfoFromLevelDir(TreeInfo **node_first,
3478 TreeInfo *node_parent,
3479 char *level_directory)
3481 // ---------- 1st stage: process any level set zip files ----------
3483 ProcessZipFilesInDirectory(level_directory, TREE_TYPE_LEVEL_DIR);
3485 // ---------- 2nd stage: check for level set directories ----------
3488 DirectoryEntry *dir_entry;
3489 boolean valid_entry_found = FALSE;
3491 if ((dir = openDirectory(level_directory)) == NULL)
3493 Warn("cannot read level directory '%s'", level_directory);
3498 while ((dir_entry = readDirectory(dir)) != NULL) // loop all entries
3500 char *directory_name = dir_entry->basename;
3501 char *directory_path = getPath2(level_directory, directory_name);
3503 // skip entries for current and parent directory
3504 if (strEqual(directory_name, ".") ||
3505 strEqual(directory_name, ".."))
3507 free(directory_path);
3512 // find out if directory entry is itself a directory
3513 if (!dir_entry->is_directory) // not a directory
3515 free(directory_path);
3520 free(directory_path);
3522 if (strEqual(directory_name, GRAPHICS_DIRECTORY) ||
3523 strEqual(directory_name, SOUNDS_DIRECTORY) ||
3524 strEqual(directory_name, MUSIC_DIRECTORY))
3527 valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
3532 closeDirectory(dir);
3534 // special case: top level directory may directly contain "levelinfo.conf"
3535 if (node_parent == NULL && !valid_entry_found)
3537 // check if this directory directly contains a file "levelinfo.conf"
3538 valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
3539 level_directory, ".");
3542 if (!valid_entry_found)
3543 Warn("cannot find any valid level series in directory '%s'",
3547 boolean AdjustGraphicsForEMC(void)
3549 boolean settings_changed = FALSE;
3551 settings_changed |= adjustTreeGraphicsForEMC(leveldir_first_all);
3552 settings_changed |= adjustTreeGraphicsForEMC(leveldir_first);
3554 return settings_changed;
3557 boolean AdjustSoundsForEMC(void)
3559 boolean settings_changed = FALSE;
3561 settings_changed |= adjustTreeSoundsForEMC(leveldir_first_all);
3562 settings_changed |= adjustTreeSoundsForEMC(leveldir_first);
3564 return settings_changed;
3567 void LoadLevelInfo(void)
3569 InitUserLevelDirectory(getLoginName());
3571 DrawInitText("Loading level series", 120, FC_GREEN);
3573 LoadLevelInfoFromLevelDir(&leveldir_first, NULL, options.level_directory);
3574 LoadLevelInfoFromLevelDir(&leveldir_first, NULL, getUserLevelDir(NULL));
3576 leveldir_first = createTopTreeInfoNode(leveldir_first);
3578 /* after loading all level set information, clone the level directory tree
3579 and remove all level sets without levels (these may still contain artwork
3580 to be offered in the setup menu as "custom artwork", and are therefore
3581 checked for existing artwork in the function "LoadLevelArtworkInfo()") */
3582 leveldir_first_all = leveldir_first;
3583 cloneTree(&leveldir_first, leveldir_first_all, TRUE);
3585 AdjustGraphicsForEMC();
3586 AdjustSoundsForEMC();
3588 // before sorting, the first entries will be from the user directory
3589 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
3591 if (leveldir_first == NULL)
3592 Fail("cannot find any valid level series in any directory");
3594 sortTreeInfo(&leveldir_first);
3596 #if ENABLE_UNUSED_CODE
3597 dumpTreeInfo(leveldir_first, 0);
3601 static boolean LoadArtworkInfoFromArtworkConf(TreeInfo **node_first,
3602 TreeInfo *node_parent,
3603 char *base_directory,
3604 char *directory_name, int type)
3606 char *directory_path = getPath2(base_directory, directory_name);
3607 char *filename = getPath2(directory_path, ARTWORKINFO_FILENAME(type));
3608 SetupFileHash *setup_file_hash = NULL;
3609 TreeInfo *artwork_new = NULL;
3612 if (fileExists(filename))
3613 setup_file_hash = loadSetupFileHash(filename);
3615 if (setup_file_hash == NULL) // no config file -- look for artwork files
3618 DirectoryEntry *dir_entry;
3619 boolean valid_file_found = FALSE;
3621 if ((dir = openDirectory(directory_path)) != NULL)
3623 while ((dir_entry = readDirectory(dir)) != NULL)
3625 if (FileIsArtworkType(dir_entry->filename, type))
3627 valid_file_found = TRUE;
3633 closeDirectory(dir);
3636 if (!valid_file_found)
3638 #if DEBUG_NO_CONFIG_FILE
3639 if (!strEqual(directory_name, "."))
3640 Debug("setup", "ignoring artwork directory '%s'", directory_path);
3643 free(directory_path);
3650 artwork_new = newTreeInfo();
3653 setTreeInfoToDefaultsFromParent(artwork_new, node_parent);
3655 setTreeInfoToDefaults(artwork_new, type);
3657 artwork_new->subdir = getStringCopy(directory_name);
3659 if (setup_file_hash) // (before defining ".color" and ".class_desc")
3661 // set all structure fields according to the token/value pairs
3663 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
3664 setSetupInfo(levelinfo_tokens, i,
3665 getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
3668 if (strEqual(artwork_new->name, ANONYMOUS_NAME))
3669 setString(&artwork_new->name, artwork_new->subdir);
3671 if (artwork_new->identifier == NULL)
3672 artwork_new->identifier = getStringCopy(artwork_new->subdir);
3674 if (artwork_new->name_sorting == NULL)
3675 artwork_new->name_sorting = getStringCopy(artwork_new->name);
3678 if (node_parent == NULL) // top level group
3680 artwork_new->basepath = getStringCopy(base_directory);
3681 artwork_new->fullpath = getStringCopy(artwork_new->subdir);
3683 else // sub level group
3685 artwork_new->basepath = getStringCopy(node_parent->basepath);
3686 artwork_new->fullpath = getPath2(node_parent->fullpath, directory_name);
3689 artwork_new->in_user_dir =
3690 (!strEqual(artwork_new->basepath, OPTIONS_ARTWORK_DIRECTORY(type)));
3692 setString(&artwork_new->class_desc, getLevelClassDescription(artwork_new));
3694 if (setup_file_hash == NULL) // (after determining ".user_defined")
3696 if (strEqual(artwork_new->subdir, "."))
3698 if (artwork_new->user_defined)
3700 setString(&artwork_new->identifier, "private");
3701 artwork_new->sort_priority = ARTWORKCLASS_PRIVATE;
3705 setString(&artwork_new->identifier, "classic");
3706 artwork_new->sort_priority = ARTWORKCLASS_CLASSICS;
3709 setString(&artwork_new->class_desc,
3710 getLevelClassDescription(artwork_new));
3714 setString(&artwork_new->identifier, artwork_new->subdir);
3717 setString(&artwork_new->name, artwork_new->identifier);
3718 setString(&artwork_new->name_sorting, artwork_new->name);
3721 pushTreeInfo(node_first, artwork_new);
3723 freeSetupFileHash(setup_file_hash);
3725 free(directory_path);
3731 static void LoadArtworkInfoFromArtworkDir(TreeInfo **node_first,
3732 TreeInfo *node_parent,
3733 char *base_directory, int type)
3735 // ---------- 1st stage: process any artwork set zip files ----------
3737 ProcessZipFilesInDirectory(base_directory, type);
3739 // ---------- 2nd stage: check for artwork set directories ----------
3742 DirectoryEntry *dir_entry;
3743 boolean valid_entry_found = FALSE;
3745 if ((dir = openDirectory(base_directory)) == NULL)
3747 // display error if directory is main "options.graphics_directory" etc.
3748 if (base_directory == OPTIONS_ARTWORK_DIRECTORY(type))
3749 Warn("cannot read directory '%s'", base_directory);
3754 while ((dir_entry = readDirectory(dir)) != NULL) // loop all entries
3756 char *directory_name = dir_entry->basename;
3757 char *directory_path = getPath2(base_directory, directory_name);
3759 // skip directory entries for current and parent directory
3760 if (strEqual(directory_name, ".") ||
3761 strEqual(directory_name, ".."))
3763 free(directory_path);
3768 // skip directory entries which are not a directory
3769 if (!dir_entry->is_directory) // not a directory
3771 free(directory_path);
3776 free(directory_path);
3778 // check if this directory contains artwork with or without config file
3779 valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
3781 directory_name, type);
3784 closeDirectory(dir);
3786 // check if this directory directly contains artwork itself
3787 valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
3788 base_directory, ".",
3790 if (!valid_entry_found)
3791 Warn("cannot find any valid artwork in directory '%s'", base_directory);
3794 static TreeInfo *getDummyArtworkInfo(int type)
3796 // this is only needed when there is completely no artwork available
3797 TreeInfo *artwork_new = newTreeInfo();
3799 setTreeInfoToDefaults(artwork_new, type);
3801 setString(&artwork_new->subdir, UNDEFINED_FILENAME);
3802 setString(&artwork_new->fullpath, UNDEFINED_FILENAME);
3803 setString(&artwork_new->basepath, UNDEFINED_FILENAME);
3805 setString(&artwork_new->identifier, UNDEFINED_FILENAME);
3806 setString(&artwork_new->name, UNDEFINED_FILENAME);
3807 setString(&artwork_new->name_sorting, UNDEFINED_FILENAME);
3812 void SetCurrentArtwork(int type)
3814 ArtworkDirTree **current_ptr = ARTWORK_CURRENT_PTR(artwork, type);
3815 ArtworkDirTree *first_node = ARTWORK_FIRST_NODE(artwork, type);
3816 char *setup_set = SETUP_ARTWORK_SET(setup, type);
3817 char *default_subdir = ARTWORK_DEFAULT_SUBDIR(type);
3819 // set current artwork to artwork configured in setup menu
3820 *current_ptr = getTreeInfoFromIdentifier(first_node, setup_set);
3822 // if not found, set current artwork to default artwork
3823 if (*current_ptr == NULL)
3824 *current_ptr = getTreeInfoFromIdentifier(first_node, default_subdir);
3826 // if not found, set current artwork to first artwork in tree
3827 if (*current_ptr == NULL)
3828 *current_ptr = getFirstValidTreeInfoEntry(first_node);
3831 void ChangeCurrentArtworkIfNeeded(int type)
3833 char *current_identifier = ARTWORK_CURRENT_IDENTIFIER(artwork, type);
3834 char *setup_set = SETUP_ARTWORK_SET(setup, type);
3836 if (!strEqual(current_identifier, setup_set))
3837 SetCurrentArtwork(type);
3840 void LoadArtworkInfo(void)
3842 LoadArtworkInfoCache();
3844 DrawInitText("Looking for custom artwork", 120, FC_GREEN);
3846 LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
3847 options.graphics_directory,
3848 TREE_TYPE_GRAPHICS_DIR);
3849 LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
3850 getUserGraphicsDir(),
3851 TREE_TYPE_GRAPHICS_DIR);
3853 LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
3854 options.sounds_directory,
3855 TREE_TYPE_SOUNDS_DIR);
3856 LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
3858 TREE_TYPE_SOUNDS_DIR);
3860 LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
3861 options.music_directory,
3862 TREE_TYPE_MUSIC_DIR);
3863 LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
3865 TREE_TYPE_MUSIC_DIR);
3867 if (artwork.gfx_first == NULL)
3868 artwork.gfx_first = getDummyArtworkInfo(TREE_TYPE_GRAPHICS_DIR);
3869 if (artwork.snd_first == NULL)
3870 artwork.snd_first = getDummyArtworkInfo(TREE_TYPE_SOUNDS_DIR);
3871 if (artwork.mus_first == NULL)
3872 artwork.mus_first = getDummyArtworkInfo(TREE_TYPE_MUSIC_DIR);
3874 // before sorting, the first entries will be from the user directory
3875 SetCurrentArtwork(ARTWORK_TYPE_GRAPHICS);
3876 SetCurrentArtwork(ARTWORK_TYPE_SOUNDS);
3877 SetCurrentArtwork(ARTWORK_TYPE_MUSIC);
3879 artwork.gfx_current_identifier = artwork.gfx_current->identifier;
3880 artwork.snd_current_identifier = artwork.snd_current->identifier;
3881 artwork.mus_current_identifier = artwork.mus_current->identifier;
3883 #if ENABLE_UNUSED_CODE
3884 Debug("setup:LoadArtworkInfo", "graphics set == %s",
3885 artwork.gfx_current_identifier);
3886 Debug("setup:LoadArtworkInfo", "sounds set == %s",
3887 artwork.snd_current_identifier);
3888 Debug("setup:LoadArtworkInfo", "music set == %s",
3889 artwork.mus_current_identifier);
3892 sortTreeInfo(&artwork.gfx_first);
3893 sortTreeInfo(&artwork.snd_first);
3894 sortTreeInfo(&artwork.mus_first);
3896 #if ENABLE_UNUSED_CODE
3897 dumpTreeInfo(artwork.gfx_first, 0);
3898 dumpTreeInfo(artwork.snd_first, 0);
3899 dumpTreeInfo(artwork.mus_first, 0);
3903 static void MoveArtworkInfoIntoSubTree(ArtworkDirTree **artwork_node)
3905 ArtworkDirTree *artwork_new = newTreeInfo();
3906 char *top_node_name = "standalone artwork";
3908 setTreeInfoToDefaults(artwork_new, (*artwork_node)->type);
3910 artwork_new->level_group = TRUE;
3912 setString(&artwork_new->identifier, top_node_name);
3913 setString(&artwork_new->name, top_node_name);
3914 setString(&artwork_new->name_sorting, top_node_name);
3916 // create node to link back to current custom artwork directory
3917 createParentTreeInfoNode(artwork_new);
3919 // move existing custom artwork tree into newly created sub-tree
3920 artwork_new->node_group->next = *artwork_node;
3922 // change custom artwork tree to contain only newly created node
3923 *artwork_node = artwork_new;
3926 static void LoadArtworkInfoFromLevelInfoExt(ArtworkDirTree **artwork_node,
3927 ArtworkDirTree *node_parent,
3928 LevelDirTree *level_node,
3929 boolean empty_level_set_mode)
3931 int type = (*artwork_node)->type;
3933 // recursively check all level directories for artwork sub-directories
3937 boolean empty_level_set = (level_node->levels == 0);
3939 // check all tree entries for artwork, but skip parent link entries
3940 if (!level_node->parent_link && empty_level_set == empty_level_set_mode)
3942 TreeInfo *artwork_new = getArtworkInfoCacheEntry(level_node, type);
3943 boolean cached = (artwork_new != NULL);
3947 pushTreeInfo(artwork_node, artwork_new);
3951 TreeInfo *topnode_last = *artwork_node;
3952 char *path = getPath2(getLevelDirFromTreeInfo(level_node),
3953 ARTWORK_DIRECTORY(type));
3955 LoadArtworkInfoFromArtworkDir(artwork_node, NULL, path, type);
3957 if (topnode_last != *artwork_node) // check for newly added node
3959 artwork_new = *artwork_node;
3961 setString(&artwork_new->identifier, level_node->subdir);
3962 setString(&artwork_new->name, level_node->name);
3963 setString(&artwork_new->name_sorting, level_node->name_sorting);
3965 artwork_new->sort_priority = level_node->sort_priority;
3966 artwork_new->in_user_dir = level_node->in_user_dir;
3968 update_artworkinfo_cache = TRUE;
3974 // insert artwork info (from old cache or filesystem) into new cache
3975 if (artwork_new != NULL)
3976 setArtworkInfoCacheEntry(artwork_new, level_node, type);
3979 DrawInitText(level_node->name, 150, FC_YELLOW);
3981 if (level_node->node_group != NULL)
3983 TreeInfo *artwork_new = newTreeInfo();
3986 setTreeInfoToDefaultsFromParent(artwork_new, node_parent);
3988 setTreeInfoToDefaults(artwork_new, type);
3990 artwork_new->level_group = TRUE;
3992 setString(&artwork_new->identifier, level_node->subdir);
3994 if (node_parent == NULL) // check for top tree node
3996 char *top_node_name = (empty_level_set_mode ?
3997 "artwork for certain level sets" :
3998 "artwork included in level sets");
4000 setString(&artwork_new->name, top_node_name);
4001 setString(&artwork_new->name_sorting, top_node_name);
4005 setString(&artwork_new->name, level_node->name);
4006 setString(&artwork_new->name_sorting, level_node->name_sorting);
4009 pushTreeInfo(artwork_node, artwork_new);
4011 // create node to link back to current custom artwork directory
4012 createParentTreeInfoNode(artwork_new);
4014 // recursively step into sub-directory and look for more custom artwork
4015 LoadArtworkInfoFromLevelInfoExt(&artwork_new->node_group, artwork_new,
4016 level_node->node_group,
4017 empty_level_set_mode);
4019 // if sub-tree has no custom artwork at all, remove it
4020 if (artwork_new->node_group->next == NULL)
4021 removeTreeInfo(artwork_node);
4024 level_node = level_node->next;
4028 static void LoadArtworkInfoFromLevelInfo(ArtworkDirTree **artwork_node)
4030 // move peviously loaded artwork tree into separate sub-tree
4031 MoveArtworkInfoIntoSubTree(artwork_node);
4033 // load artwork from level sets into separate sub-trees
4034 LoadArtworkInfoFromLevelInfoExt(artwork_node, NULL, leveldir_first_all, TRUE);
4035 LoadArtworkInfoFromLevelInfoExt(artwork_node, NULL, leveldir_first_all, FALSE);
4037 // add top tree node over all three separate sub-trees
4038 *artwork_node = createTopTreeInfoNode(*artwork_node);
4040 // set all parent links (back links) in complete artwork tree
4041 setTreeInfoParentNodes(*artwork_node, NULL);
4044 void LoadLevelArtworkInfo(void)
4046 print_timestamp_init("LoadLevelArtworkInfo");
4048 DrawInitText("Looking for custom level artwork", 120, FC_GREEN);
4050 print_timestamp_time("DrawTimeText");
4052 LoadArtworkInfoFromLevelInfo(&artwork.gfx_first);
4053 print_timestamp_time("LoadArtworkInfoFromLevelInfo (gfx)");
4054 LoadArtworkInfoFromLevelInfo(&artwork.snd_first);
4055 print_timestamp_time("LoadArtworkInfoFromLevelInfo (snd)");
4056 LoadArtworkInfoFromLevelInfo(&artwork.mus_first);
4057 print_timestamp_time("LoadArtworkInfoFromLevelInfo (mus)");
4059 SaveArtworkInfoCache();
4061 print_timestamp_time("SaveArtworkInfoCache");
4063 // needed for reloading level artwork not known at ealier stage
4064 ChangeCurrentArtworkIfNeeded(ARTWORK_TYPE_GRAPHICS);
4065 ChangeCurrentArtworkIfNeeded(ARTWORK_TYPE_SOUNDS);
4066 ChangeCurrentArtworkIfNeeded(ARTWORK_TYPE_MUSIC);
4068 print_timestamp_time("getTreeInfoFromIdentifier");
4070 sortTreeInfo(&artwork.gfx_first);
4071 sortTreeInfo(&artwork.snd_first);
4072 sortTreeInfo(&artwork.mus_first);
4074 print_timestamp_time("sortTreeInfo");
4076 #if ENABLE_UNUSED_CODE
4077 dumpTreeInfo(artwork.gfx_first, 0);
4078 dumpTreeInfo(artwork.snd_first, 0);
4079 dumpTreeInfo(artwork.mus_first, 0);
4082 print_timestamp_done("LoadLevelArtworkInfo");
4085 static boolean AddTreeSetToTreeInfoExt(TreeInfo *tree_node_old, char *tree_dir,
4086 char *tree_subdir_new, int type)
4088 if (tree_node_old == NULL)
4090 if (type == TREE_TYPE_LEVEL_DIR)
4092 // get level info tree node of personal user level set
4093 tree_node_old = getTreeInfoFromIdentifier(leveldir_first, getLoginName());
4095 // this may happen if "setup.internal.create_user_levelset" is FALSE
4096 // or if file "levelinfo.conf" is missing in personal user level set
4097 if (tree_node_old == NULL)
4098 tree_node_old = leveldir_first->node_group;
4102 // get artwork info tree node of first artwork set
4103 tree_node_old = ARTWORK_FIRST_NODE(artwork, type);
4107 if (tree_dir == NULL)
4108 tree_dir = TREE_USERDIR(type);
4110 if (tree_node_old == NULL ||
4112 tree_subdir_new == NULL) // should not happen
4115 int draw_deactivation_mask = GetDrawDeactivationMask();
4117 // override draw deactivation mask (temporarily disable drawing)
4118 SetDrawDeactivationMask(REDRAW_ALL);
4120 if (type == TREE_TYPE_LEVEL_DIR)
4122 // load new level set config and add it next to first user level set
4123 LoadLevelInfoFromLevelConf(&tree_node_old->next,
4124 tree_node_old->node_parent,
4125 tree_dir, tree_subdir_new);
4129 // load new artwork set config and add it next to first artwork set
4130 LoadArtworkInfoFromArtworkConf(&tree_node_old->next,
4131 tree_node_old->node_parent,
4132 tree_dir, tree_subdir_new, type);
4135 // set draw deactivation mask to previous value
4136 SetDrawDeactivationMask(draw_deactivation_mask);
4138 // get first node of level or artwork info tree
4139 TreeInfo **tree_node_first = TREE_FIRST_NODE_PTR(type);
4141 // get tree info node of newly added level or artwork set
4142 TreeInfo *tree_node_new = getTreeInfoFromIdentifier(*tree_node_first,
4145 if (tree_node_new == NULL) // should not happen
4148 // correct top link and parent node link of newly created tree node
4149 tree_node_new->node_top = tree_node_old->node_top;
4150 tree_node_new->node_parent = tree_node_old->node_parent;
4152 // sort tree info to adjust position of newly added tree set
4153 sortTreeInfo(tree_node_first);
4158 void AddTreeSetToTreeInfo(TreeInfo *tree_node, char *tree_dir,
4159 char *tree_subdir_new, int type)
4161 if (!AddTreeSetToTreeInfoExt(tree_node, tree_dir, tree_subdir_new, type))
4162 Fail("internal tree info structure corrupted -- aborting");
4165 void AddUserLevelSetToLevelInfo(char *level_subdir_new)
4167 AddTreeSetToTreeInfo(NULL, NULL, level_subdir_new, TREE_TYPE_LEVEL_DIR);
4170 char *getArtworkIdentifierForUserLevelSet(int type)
4172 char *classic_artwork_set = getClassicArtworkSet(type);
4174 // check for custom artwork configured in "levelinfo.conf"
4175 char *leveldir_artwork_set =
4176 *LEVELDIR_ARTWORK_SET_PTR(leveldir_current, type);
4177 boolean has_leveldir_artwork_set =
4178 (leveldir_artwork_set != NULL && !strEqual(leveldir_artwork_set,
4179 classic_artwork_set));
4181 // check for custom artwork in sub-directory "graphics" etc.
4182 TreeInfo *artwork_first_node = ARTWORK_FIRST_NODE(artwork, type);
4183 char *leveldir_identifier = leveldir_current->identifier;
4184 boolean has_artwork_subdir =
4185 (getTreeInfoFromIdentifier(artwork_first_node,
4186 leveldir_identifier) != NULL);
4188 return (has_leveldir_artwork_set ? leveldir_artwork_set :
4189 has_artwork_subdir ? leveldir_identifier :
4190 classic_artwork_set);
4193 TreeInfo *getArtworkTreeInfoForUserLevelSet(int type)
4195 char *artwork_set = getArtworkIdentifierForUserLevelSet(type);
4196 TreeInfo *artwork_first_node = ARTWORK_FIRST_NODE(artwork, type);
4197 TreeInfo *ti = getTreeInfoFromIdentifier(artwork_first_node, artwork_set);
4201 ti = getTreeInfoFromIdentifier(artwork_first_node,
4202 ARTWORK_DEFAULT_SUBDIR(type));
4204 Fail("cannot find default graphics -- should not happen");
4210 boolean checkIfCustomArtworkExistsForCurrentLevelSet(void)
4212 char *graphics_set =
4213 getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_GRAPHICS);
4215 getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_SOUNDS);
4217 getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_MUSIC);
4219 return (!strEqual(graphics_set, GFX_CLASSIC_SUBDIR) ||
4220 !strEqual(sounds_set, SND_CLASSIC_SUBDIR) ||
4221 !strEqual(music_set, MUS_CLASSIC_SUBDIR));
4224 boolean UpdateUserLevelSet(char *level_subdir, char *level_name,
4225 char *level_author, int num_levels)
4227 char *filename = getPath2(getUserLevelDir(level_subdir), LEVELINFO_FILENAME);
4228 char *filename_tmp = getStringCat2(filename, ".tmp");
4230 FILE *file_tmp = NULL;
4231 char line[MAX_LINE_LEN];
4232 boolean success = FALSE;
4233 LevelDirTree *leveldir = getTreeInfoFromIdentifier(leveldir_first,
4235 // update values in level directory tree
4237 if (level_name != NULL)
4238 setString(&leveldir->name, level_name);
4240 if (level_author != NULL)
4241 setString(&leveldir->author, level_author);
4243 if (num_levels != -1)
4244 leveldir->levels = num_levels;
4246 // update values that depend on other values
4248 setString(&leveldir->name_sorting, leveldir->name);
4250 leveldir->last_level = leveldir->first_level + leveldir->levels - 1;
4252 // sort order of level sets may have changed
4253 sortTreeInfo(&leveldir_first);
4255 if ((file = fopen(filename, MODE_READ)) &&
4256 (file_tmp = fopen(filename_tmp, MODE_WRITE)))
4258 while (fgets(line, MAX_LINE_LEN, file))
4260 if (strPrefix(line, "name:") && level_name != NULL)
4261 fprintf(file_tmp, "%-32s%s\n", "name:", level_name);
4262 else if (strPrefix(line, "author:") && level_author != NULL)
4263 fprintf(file_tmp, "%-32s%s\n", "author:", level_author);
4264 else if (strPrefix(line, "levels:") && num_levels != -1)
4265 fprintf(file_tmp, "%-32s%d\n", "levels:", num_levels);
4267 fputs(line, file_tmp);
4280 success = (rename(filename_tmp, filename) == 0);
4288 boolean CreateUserLevelSet(char *level_subdir, char *level_name,
4289 char *level_author, int num_levels,
4290 boolean use_artwork_set)
4292 LevelDirTree *level_info;
4297 // create user level sub-directory, if needed
4298 createDirectory(getUserLevelDir(level_subdir), "user level", PERMS_PRIVATE);
4300 filename = getPath2(getUserLevelDir(level_subdir), LEVELINFO_FILENAME);
4302 if (!(file = fopen(filename, MODE_WRITE)))
4304 Warn("cannot write level info file '%s'", filename);
4311 level_info = newTreeInfo();
4313 // always start with reliable default values
4314 setTreeInfoToDefaults(level_info, TREE_TYPE_LEVEL_DIR);
4316 setString(&level_info->name, level_name);
4317 setString(&level_info->author, level_author);
4318 level_info->levels = num_levels;
4319 level_info->first_level = 1;
4320 level_info->sort_priority = LEVELCLASS_PRIVATE_START;
4321 level_info->readonly = FALSE;
4323 if (use_artwork_set)
4325 level_info->graphics_set =
4326 getStringCopy(getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_GRAPHICS));
4327 level_info->sounds_set =
4328 getStringCopy(getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_SOUNDS));
4329 level_info->music_set =
4330 getStringCopy(getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_MUSIC));
4333 token_value_position = TOKEN_VALUE_POSITION_SHORT;
4335 fprintFileHeader(file, LEVELINFO_FILENAME);
4338 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
4340 if (i == LEVELINFO_TOKEN_NAME ||
4341 i == LEVELINFO_TOKEN_AUTHOR ||
4342 i == LEVELINFO_TOKEN_LEVELS ||
4343 i == LEVELINFO_TOKEN_FIRST_LEVEL ||
4344 i == LEVELINFO_TOKEN_SORT_PRIORITY ||
4345 i == LEVELINFO_TOKEN_READONLY ||
4346 (use_artwork_set && (i == LEVELINFO_TOKEN_GRAPHICS_SET ||
4347 i == LEVELINFO_TOKEN_SOUNDS_SET ||
4348 i == LEVELINFO_TOKEN_MUSIC_SET)))
4349 fprintf(file, "%s\n", getSetupLine(levelinfo_tokens, "", i));
4351 // just to make things nicer :)
4352 if (i == LEVELINFO_TOKEN_AUTHOR ||
4353 i == LEVELINFO_TOKEN_FIRST_LEVEL ||
4354 (use_artwork_set && i == LEVELINFO_TOKEN_READONLY))
4355 fprintf(file, "\n");
4358 token_value_position = TOKEN_VALUE_POSITION_DEFAULT;
4362 SetFilePermissions(filename, PERMS_PRIVATE);
4364 freeTreeInfo(level_info);
4370 static void SaveUserLevelInfo(void)
4372 CreateUserLevelSet(getLoginName(), getLoginName(), getRealName(), 100, FALSE);
4375 char *getSetupValue(int type, void *value)
4377 static char value_string[MAX_LINE_LEN];
4385 strcpy(value_string, (*(boolean *)value ? "true" : "false"));
4389 strcpy(value_string, (*(boolean *)value ? "on" : "off"));
4393 strcpy(value_string, (*(int *)value == AUTO ? "auto" :
4394 *(int *)value == FALSE ? "off" : "on"));
4398 strcpy(value_string, (*(boolean *)value ? "yes" : "no"));
4401 case TYPE_YES_NO_AUTO:
4402 strcpy(value_string, (*(int *)value == AUTO ? "auto" :
4403 *(int *)value == FALSE ? "no" : "yes"));
4407 strcpy(value_string, (*(boolean *)value ? "AGA" : "ECS"));
4411 strcpy(value_string, getKeyNameFromKey(*(Key *)value));
4415 strcpy(value_string, getX11KeyNameFromKey(*(Key *)value));
4419 sprintf(value_string, "%d", *(int *)value);
4423 if (*(char **)value == NULL)
4426 strcpy(value_string, *(char **)value);
4430 sprintf(value_string, "player_%d", *(int *)value + 1);
4434 value_string[0] = '\0';
4438 if (type & TYPE_GHOSTED)
4439 strcpy(value_string, "n/a");
4441 return value_string;
4444 char *getSetupLine(struct TokenInfo *token_info, char *prefix, int token_nr)
4448 static char token_string[MAX_LINE_LEN];
4449 int token_type = token_info[token_nr].type;
4450 void *setup_value = token_info[token_nr].value;
4451 char *token_text = token_info[token_nr].text;
4452 char *value_string = getSetupValue(token_type, setup_value);
4454 // build complete token string
4455 sprintf(token_string, "%s%s", prefix, token_text);
4457 // build setup entry line
4458 line = getFormattedSetupEntry(token_string, value_string);
4460 if (token_type == TYPE_KEY_X11)
4462 Key key = *(Key *)setup_value;
4463 char *keyname = getKeyNameFromKey(key);
4465 // add comment, if useful
4466 if (!strEqual(keyname, "(undefined)") &&
4467 !strEqual(keyname, "(unknown)"))
4469 // add at least one whitespace
4471 for (i = strlen(line); i < token_comment_position; i++)
4475 strcat(line, keyname);
4482 static void InitLastPlayedLevels_ParentNode(void)
4484 LevelDirTree **leveldir_top = &leveldir_first->node_group->next;
4485 LevelDirTree *leveldir_new = NULL;
4487 // check if parent node for last played levels already exists
4488 if (strEqual((*leveldir_top)->identifier, TOKEN_STR_LAST_LEVEL_SERIES))
4491 leveldir_new = newTreeInfo();
4493 setTreeInfoToDefaultsFromParent(leveldir_new, leveldir_first);
4495 leveldir_new->level_group = TRUE;
4497 setString(&leveldir_new->identifier, TOKEN_STR_LAST_LEVEL_SERIES);
4498 setString(&leveldir_new->name, "<< (last played level sets)");
4500 pushTreeInfo(leveldir_top, leveldir_new);
4502 // create node to link back to current level directory
4503 createParentTreeInfoNode(leveldir_new);
4506 void UpdateLastPlayedLevels_TreeInfo(void)
4508 char **last_level_series = setup.level_setup.last_level_series;
4509 boolean reset_leveldir_current = FALSE;
4510 LevelDirTree *leveldir_last;
4511 TreeInfo **node_new = NULL;
4514 if (last_level_series[0] == NULL)
4517 InitLastPlayedLevels_ParentNode();
4519 // check if current level set is from "last played" sub-tree to be rebuilt
4520 reset_leveldir_current = strEqual(leveldir_current->node_parent->identifier,
4521 TOKEN_STR_LAST_LEVEL_SERIES);
4523 leveldir_last = getTreeInfoFromIdentifierExt(leveldir_first,
4524 TOKEN_STR_LAST_LEVEL_SERIES,
4526 if (leveldir_last == NULL)
4529 node_new = &leveldir_last->node_group->next;
4531 freeTreeInfo(*node_new);
4533 for (i = 0; last_level_series[i] != NULL; i++)
4535 LevelDirTree *node_last = getTreeInfoFromIdentifier(leveldir_first,
4536 last_level_series[i]);
4538 *node_new = getTreeInfoCopy(node_last); // copy complete node
4540 (*node_new)->node_top = &leveldir_first; // correct top node link
4541 (*node_new)->node_parent = leveldir_last; // correct parent node link
4543 (*node_new)->node_group = NULL;
4544 (*node_new)->next = NULL;
4546 (*node_new)->cl_first = -1; // force setting tree cursor
4548 node_new = &((*node_new)->next);
4551 if (reset_leveldir_current)
4552 leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
4553 last_level_series[0]);
4556 static void UpdateLastPlayedLevels_List(void)
4558 char **last_level_series = setup.level_setup.last_level_series;
4559 int pos = MAX_LEVELDIR_HISTORY - 1;
4562 // search for potentially already existing entry in list of level sets
4563 for (i = 0; last_level_series[i] != NULL; i++)
4564 if (strEqual(last_level_series[i], leveldir_current->identifier))
4567 // move list of level sets one entry down (using potentially free entry)
4568 for (i = pos; i > 0; i--)
4569 setString(&last_level_series[i], last_level_series[i - 1]);
4571 // put last played level set at top position
4572 setString(&last_level_series[0], leveldir_current->identifier);
4575 void LoadLevelSetup_LastSeries(void)
4577 // --------------------------------------------------------------------------
4578 // ~/.<program>/levelsetup.conf
4579 // --------------------------------------------------------------------------
4581 char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
4582 SetupFileHash *level_setup_hash = NULL;
4586 // always start with reliable default values
4587 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
4589 // start with empty history of last played level sets
4590 setString(&setup.level_setup.last_level_series[0], NULL);
4592 if (!strEqual(DEFAULT_LEVELSET, UNDEFINED_LEVELSET))
4594 leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
4596 if (leveldir_current == NULL)
4597 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
4600 if ((level_setup_hash = loadSetupFileHash(filename)))
4602 char *last_level_series =
4603 getHashEntry(level_setup_hash, TOKEN_STR_LAST_LEVEL_SERIES);
4605 leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
4607 if (leveldir_current == NULL)
4608 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
4610 for (i = 0; i < MAX_LEVELDIR_HISTORY; i++)
4612 char token[strlen(TOKEN_STR_LAST_LEVEL_SERIES) + 10];
4613 LevelDirTree *leveldir_last;
4615 sprintf(token, "%s.%03d", TOKEN_STR_LAST_LEVEL_SERIES, i);
4617 last_level_series = getHashEntry(level_setup_hash, token);
4619 leveldir_last = getTreeInfoFromIdentifier(leveldir_first,
4621 if (leveldir_last != NULL)
4622 setString(&setup.level_setup.last_level_series[pos++],
4626 setString(&setup.level_setup.last_level_series[pos], NULL);
4628 freeSetupFileHash(level_setup_hash);
4632 Debug("setup", "using default setup values");
4638 static void SaveLevelSetup_LastSeries_Ext(boolean deactivate_last_level_series)
4640 // --------------------------------------------------------------------------
4641 // ~/.<program>/levelsetup.conf
4642 // --------------------------------------------------------------------------
4644 // check if the current level directory structure is available at this point
4645 if (leveldir_current == NULL)
4648 char **last_level_series = setup.level_setup.last_level_series;
4649 char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
4653 InitUserDataDirectory();
4655 UpdateLastPlayedLevels_List();
4657 if (!(file = fopen(filename, MODE_WRITE)))
4659 Warn("cannot write setup file '%s'", filename);
4666 fprintFileHeader(file, LEVELSETUP_FILENAME);
4668 if (deactivate_last_level_series)
4669 fprintf(file, "# %s\n# ", "the following level set may have caused a problem and was deactivated");
4671 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_LAST_LEVEL_SERIES,
4672 leveldir_current->identifier));
4674 for (i = 0; last_level_series[i] != NULL; i++)
4676 char token[strlen(TOKEN_STR_LAST_LEVEL_SERIES) + 10];
4678 sprintf(token, "%s.%03d", TOKEN_STR_LAST_LEVEL_SERIES, i);
4680 fprintf(file, "%s\n", getFormattedSetupEntry(token, last_level_series[i]));
4685 SetFilePermissions(filename, PERMS_PRIVATE);
4690 void SaveLevelSetup_LastSeries(void)
4692 SaveLevelSetup_LastSeries_Ext(FALSE);
4695 void SaveLevelSetup_LastSeries_Deactivate(void)
4697 SaveLevelSetup_LastSeries_Ext(TRUE);
4700 static void checkSeriesInfo(void)
4702 static char *level_directory = NULL;
4705 DirectoryEntry *dir_entry;
4708 checked_free(level_directory);
4710 // check for more levels besides the 'levels' field of 'levelinfo.conf'
4712 level_directory = getPath2((leveldir_current->in_user_dir ?
4713 getUserLevelDir(NULL) :
4714 options.level_directory),
4715 leveldir_current->fullpath);
4717 if ((dir = openDirectory(level_directory)) == NULL)
4719 Warn("cannot read level directory '%s'", level_directory);
4725 while ((dir_entry = readDirectory(dir)) != NULL) // loop all entries
4727 if (strlen(dir_entry->basename) > 4 &&
4728 dir_entry->basename[3] == '.' &&
4729 strEqual(&dir_entry->basename[4], LEVELFILE_EXTENSION))
4731 char levelnum_str[4];
4734 strncpy(levelnum_str, dir_entry->basename, 3);
4735 levelnum_str[3] = '\0';
4737 levelnum_value = atoi(levelnum_str);
4739 if (levelnum_value < leveldir_current->first_level)
4741 Warn("additional level %d found", levelnum_value);
4743 leveldir_current->first_level = levelnum_value;
4745 else if (levelnum_value > leveldir_current->last_level)
4747 Warn("additional level %d found", levelnum_value);
4749 leveldir_current->last_level = levelnum_value;
4755 closeDirectory(dir);
4758 void LoadLevelSetup_SeriesInfo(void)
4761 SetupFileHash *level_setup_hash = NULL;
4762 char *level_subdir = leveldir_current->subdir;
4765 // always start with reliable default values
4766 level_nr = leveldir_current->first_level;
4768 for (i = 0; i < MAX_LEVELS; i++)
4770 LevelStats_setPlayed(i, 0);
4771 LevelStats_setSolved(i, 0);
4776 // --------------------------------------------------------------------------
4777 // ~/.<program>/levelsetup/<level series>/levelsetup.conf
4778 // --------------------------------------------------------------------------
4780 level_subdir = leveldir_current->subdir;
4782 filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
4784 if ((level_setup_hash = loadSetupFileHash(filename)))
4788 // get last played level in this level set
4790 token_value = getHashEntry(level_setup_hash, TOKEN_STR_LAST_PLAYED_LEVEL);
4794 level_nr = atoi(token_value);
4796 if (level_nr < leveldir_current->first_level)
4797 level_nr = leveldir_current->first_level;
4798 if (level_nr > leveldir_current->last_level)
4799 level_nr = leveldir_current->last_level;
4802 // get handicap level in this level set
4804 token_value = getHashEntry(level_setup_hash, TOKEN_STR_HANDICAP_LEVEL);
4808 int level_nr = atoi(token_value);
4810 if (level_nr < leveldir_current->first_level)
4811 level_nr = leveldir_current->first_level;
4812 if (level_nr > leveldir_current->last_level + 1)
4813 level_nr = leveldir_current->last_level;
4815 if (leveldir_current->user_defined || !leveldir_current->handicap)
4816 level_nr = leveldir_current->last_level;
4818 leveldir_current->handicap_level = level_nr;
4821 // get number of played and solved levels in this level set
4823 BEGIN_HASH_ITERATION(level_setup_hash, itr)
4825 char *token = HASH_ITERATION_TOKEN(itr);
4826 char *value = HASH_ITERATION_VALUE(itr);
4828 if (strlen(token) == 3 &&
4829 token[0] >= '0' && token[0] <= '9' &&
4830 token[1] >= '0' && token[1] <= '9' &&
4831 token[2] >= '0' && token[2] <= '9')
4833 int level_nr = atoi(token);
4836 LevelStats_setPlayed(level_nr, atoi(value)); // read 1st column
4838 value = strchr(value, ' ');
4841 LevelStats_setSolved(level_nr, atoi(value)); // read 2nd column
4844 END_HASH_ITERATION(hash, itr)
4846 freeSetupFileHash(level_setup_hash);
4850 Debug("setup", "using default setup values");
4856 void SaveLevelSetup_SeriesInfo(void)
4859 char *level_subdir = leveldir_current->subdir;
4860 char *level_nr_str = int2str(level_nr, 0);
4861 char *handicap_level_str = int2str(leveldir_current->handicap_level, 0);
4865 // --------------------------------------------------------------------------
4866 // ~/.<program>/levelsetup/<level series>/levelsetup.conf
4867 // --------------------------------------------------------------------------
4869 InitLevelSetupDirectory(level_subdir);
4871 filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
4873 if (!(file = fopen(filename, MODE_WRITE)))
4875 Warn("cannot write setup file '%s'", filename);
4882 fprintFileHeader(file, LEVELSETUP_FILENAME);
4884 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_PLAYED_LEVEL,
4886 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_HANDICAP_LEVEL,
4887 handicap_level_str));
4889 for (i = leveldir_current->first_level; i <= leveldir_current->last_level;
4892 if (LevelStats_getPlayed(i) > 0 ||
4893 LevelStats_getSolved(i) > 0)
4898 sprintf(token, "%03d", i);
4899 sprintf(value, "%d %d", LevelStats_getPlayed(i), LevelStats_getSolved(i));
4901 fprintf(file, "%s\n", getFormattedSetupEntry(token, value));
4907 SetFilePermissions(filename, PERMS_PRIVATE);
4912 int LevelStats_getPlayed(int nr)
4914 return (nr >= 0 && nr < MAX_LEVELS ? level_stats[nr].played : 0);
4917 int LevelStats_getSolved(int nr)
4919 return (nr >= 0 && nr < MAX_LEVELS ? level_stats[nr].solved : 0);
4922 void LevelStats_setPlayed(int nr, int value)
4924 if (nr >= 0 && nr < MAX_LEVELS)
4925 level_stats[nr].played = value;
4928 void LevelStats_setSolved(int nr, int value)
4930 if (nr >= 0 && nr < MAX_LEVELS)
4931 level_stats[nr].solved = value;
4934 void LevelStats_incPlayed(int nr)
4936 if (nr >= 0 && nr < MAX_LEVELS)
4937 level_stats[nr].played++;
4940 void LevelStats_incSolved(int nr)
4942 if (nr >= 0 && nr < MAX_LEVELS)
4943 level_stats[nr].solved++;
4946 void LoadUserSetup(void)
4948 // --------------------------------------------------------------------------
4949 // ~/.<program>/usersetup.conf
4950 // --------------------------------------------------------------------------
4952 char *filename = getPath2(getMainUserGameDataDir(), USERSETUP_FILENAME);
4953 SetupFileHash *user_setup_hash = NULL;
4955 // always start with reliable default values
4958 if ((user_setup_hash = loadSetupFileHash(filename)))
4962 // get last selected user number
4963 token_value = getHashEntry(user_setup_hash, TOKEN_STR_LAST_USER);
4966 user.nr = MIN(MAX(0, atoi(token_value)), MAX_PLAYER_NAMES - 1);
4968 freeSetupFileHash(user_setup_hash);
4972 Debug("setup", "using default setup values");
4978 void SaveUserSetup(void)
4980 // --------------------------------------------------------------------------
4981 // ~/.<program>/usersetup.conf
4982 // --------------------------------------------------------------------------
4984 char *filename = getPath2(getMainUserGameDataDir(), USERSETUP_FILENAME);
4987 InitMainUserDataDirectory();
4989 if (!(file = fopen(filename, MODE_WRITE)))
4991 Warn("cannot write setup file '%s'", filename);
4998 fprintFileHeader(file, USERSETUP_FILENAME);
5000 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_USER,
5004 SetFilePermissions(filename, PERMS_PRIVATE);