1 // ============================================================================
2 // Artsoft Retro-Game Library
3 // ----------------------------------------------------------------------------
4 // (c) 1995-2014 by Artsoft Entertainment
7 // http://www.artsoft.org/
8 // ----------------------------------------------------------------------------
10 // ============================================================================
12 #include <sys/types.h>
21 #if !defined(PLATFORM_WIN32)
23 #include <sys/param.h>
31 #include "zip/miniunz.h"
34 #define ENABLE_UNUSED_CODE FALSE // for currently unused functions
35 #define DEBUG_NO_CONFIG_FILE FALSE // for extra-verbose debug output
37 #define NUM_LEVELCLASS_DESC 8
39 static char *levelclass_desc[NUM_LEVELCLASS_DESC] =
52 #define LEVELCOLOR(n) (IS_LEVELCLASS_TUTORIAL(n) ? FC_BLUE : \
53 IS_LEVELCLASS_CLASSICS(n) ? FC_RED : \
54 IS_LEVELCLASS_BD(n) ? FC_YELLOW : \
55 IS_LEVELCLASS_EM(n) ? FC_YELLOW : \
56 IS_LEVELCLASS_SP(n) ? FC_YELLOW : \
57 IS_LEVELCLASS_DX(n) ? FC_YELLOW : \
58 IS_LEVELCLASS_SB(n) ? FC_YELLOW : \
59 IS_LEVELCLASS_CONTRIB(n) ? FC_GREEN : \
60 IS_LEVELCLASS_PRIVATE(n) ? FC_RED : \
63 #define LEVELSORTING(n) (IS_LEVELCLASS_TUTORIAL(n) ? 0 : \
64 IS_LEVELCLASS_CLASSICS(n) ? 1 : \
65 IS_LEVELCLASS_BD(n) ? 2 : \
66 IS_LEVELCLASS_EM(n) ? 3 : \
67 IS_LEVELCLASS_SP(n) ? 4 : \
68 IS_LEVELCLASS_DX(n) ? 5 : \
69 IS_LEVELCLASS_SB(n) ? 6 : \
70 IS_LEVELCLASS_CONTRIB(n) ? 7 : \
71 IS_LEVELCLASS_PRIVATE(n) ? 8 : \
74 #define ARTWORKCOLOR(n) (IS_ARTWORKCLASS_CLASSICS(n) ? FC_RED : \
75 IS_ARTWORKCLASS_CONTRIB(n) ? FC_GREEN : \
76 IS_ARTWORKCLASS_PRIVATE(n) ? FC_RED : \
77 IS_ARTWORKCLASS_LEVEL(n) ? FC_YELLOW : \
80 #define ARTWORKSORTING(n) (IS_ARTWORKCLASS_CLASSICS(n) ? 0 : \
81 IS_ARTWORKCLASS_LEVEL(n) ? 1 : \
82 IS_ARTWORKCLASS_CONTRIB(n) ? 2 : \
83 IS_ARTWORKCLASS_PRIVATE(n) ? 3 : \
86 #define TOKEN_VALUE_POSITION_SHORT 32
87 #define TOKEN_VALUE_POSITION_DEFAULT 40
88 #define TOKEN_COMMENT_POSITION_DEFAULT 60
90 #define MAX_COOKIE_LEN 256
93 static void setTreeInfoToDefaults(TreeInfo *, int);
94 static TreeInfo *getTreeInfoCopy(TreeInfo *ti);
95 static int compareTreeInfoEntries(const void *, const void *);
97 static int token_value_position = TOKEN_VALUE_POSITION_DEFAULT;
98 static int token_comment_position = TOKEN_COMMENT_POSITION_DEFAULT;
100 static SetupFileHash *artworkinfo_cache_old = NULL;
101 static SetupFileHash *artworkinfo_cache_new = NULL;
102 static boolean use_artworkinfo_cache = TRUE;
105 // ----------------------------------------------------------------------------
107 // ----------------------------------------------------------------------------
109 static char *getLevelClassDescription(TreeInfo *ti)
111 int position = ti->sort_priority / 100;
113 if (position >= 0 && position < NUM_LEVELCLASS_DESC)
114 return levelclass_desc[position];
116 return "Unknown Level Class";
119 static char *getScoreDir(char *level_subdir)
121 static char *score_dir = NULL;
122 static char *score_level_dir = NULL;
123 char *score_subdir = SCORES_DIRECTORY;
125 if (score_dir == NULL)
127 if (program.global_scores)
128 score_dir = getPath2(getCommonDataDir(), score_subdir);
130 score_dir = getPath2(getUserGameDataDir(), score_subdir);
133 if (level_subdir != NULL)
135 checked_free(score_level_dir);
137 score_level_dir = getPath2(score_dir, level_subdir);
139 return score_level_dir;
145 static char *getLevelSetupDir(char *level_subdir)
147 static char *levelsetup_dir = NULL;
148 char *data_dir = getUserGameDataDir();
149 char *levelsetup_subdir = LEVELSETUP_DIRECTORY;
151 checked_free(levelsetup_dir);
153 if (level_subdir != NULL)
154 levelsetup_dir = getPath3(data_dir, levelsetup_subdir, level_subdir);
156 levelsetup_dir = getPath2(data_dir, levelsetup_subdir);
158 return levelsetup_dir;
161 static char *getCacheDir(void)
163 static char *cache_dir = NULL;
165 if (cache_dir == NULL)
166 cache_dir = getPath2(getUserGameDataDir(), CACHE_DIRECTORY);
171 static char *getNetworkDir(void)
173 static char *network_dir = NULL;
175 if (network_dir == NULL)
176 network_dir = getPath2(getUserGameDataDir(), NETWORK_DIRECTORY);
181 char *getLevelDirFromTreeInfo(TreeInfo *node)
183 static char *level_dir = NULL;
186 return options.level_directory;
188 checked_free(level_dir);
190 level_dir = getPath2((node->in_user_dir ? getUserLevelDir(NULL) :
191 options.level_directory), node->fullpath);
196 char *getUserLevelDir(char *level_subdir)
198 static char *userlevel_dir = NULL;
199 char *data_dir = getUserGameDataDir();
200 char *userlevel_subdir = LEVELS_DIRECTORY;
202 checked_free(userlevel_dir);
204 if (level_subdir != NULL)
205 userlevel_dir = getPath3(data_dir, userlevel_subdir, level_subdir);
207 userlevel_dir = getPath2(data_dir, userlevel_subdir);
209 return userlevel_dir;
212 char *getNetworkLevelDir(char *level_subdir)
214 static char *network_level_dir = NULL;
215 char *data_dir = getNetworkDir();
216 char *networklevel_subdir = LEVELS_DIRECTORY;
218 checked_free(network_level_dir);
220 if (level_subdir != NULL)
221 network_level_dir = getPath3(data_dir, networklevel_subdir, level_subdir);
223 network_level_dir = getPath2(data_dir, networklevel_subdir);
225 return network_level_dir;
228 char *getCurrentLevelDir(void)
230 return getLevelDirFromTreeInfo(leveldir_current);
233 char *getNewUserLevelSubdir(void)
235 static char *new_level_subdir = NULL;
236 char *subdir_prefix = getLoginName();
237 char subdir_suffix[10];
238 int max_suffix_number = 1000;
241 while (++i < max_suffix_number)
243 sprintf(subdir_suffix, "_%d", i);
245 checked_free(new_level_subdir);
246 new_level_subdir = getStringCat2(subdir_prefix, subdir_suffix);
248 if (!directoryExists(getUserLevelDir(new_level_subdir)))
252 return new_level_subdir;
255 static char *getTapeDir(char *level_subdir)
257 static char *tape_dir = NULL;
258 char *data_dir = getUserGameDataDir();
259 char *tape_subdir = TAPES_DIRECTORY;
261 checked_free(tape_dir);
263 if (level_subdir != NULL)
264 tape_dir = getPath3(data_dir, tape_subdir, level_subdir);
266 tape_dir = getPath2(data_dir, tape_subdir);
271 static char *getSolutionTapeDir(void)
273 static char *tape_dir = NULL;
274 char *data_dir = getCurrentLevelDir();
275 char *tape_subdir = TAPES_DIRECTORY;
277 checked_free(tape_dir);
279 tape_dir = getPath2(data_dir, tape_subdir);
284 static char *getDefaultGraphicsDir(char *graphics_subdir)
286 static char *graphics_dir = NULL;
288 if (graphics_subdir == NULL)
289 return options.graphics_directory;
291 checked_free(graphics_dir);
293 graphics_dir = getPath2(options.graphics_directory, graphics_subdir);
298 static char *getDefaultSoundsDir(char *sounds_subdir)
300 static char *sounds_dir = NULL;
302 if (sounds_subdir == NULL)
303 return options.sounds_directory;
305 checked_free(sounds_dir);
307 sounds_dir = getPath2(options.sounds_directory, sounds_subdir);
312 static char *getDefaultMusicDir(char *music_subdir)
314 static char *music_dir = NULL;
316 if (music_subdir == NULL)
317 return options.music_directory;
319 checked_free(music_dir);
321 music_dir = getPath2(options.music_directory, music_subdir);
326 static char *getClassicArtworkSet(int type)
328 return (type == TREE_TYPE_GRAPHICS_DIR ? GFX_CLASSIC_SUBDIR :
329 type == TREE_TYPE_SOUNDS_DIR ? SND_CLASSIC_SUBDIR :
330 type == TREE_TYPE_MUSIC_DIR ? MUS_CLASSIC_SUBDIR : "");
333 static char *getClassicArtworkDir(int type)
335 return (type == TREE_TYPE_GRAPHICS_DIR ?
336 getDefaultGraphicsDir(GFX_CLASSIC_SUBDIR) :
337 type == TREE_TYPE_SOUNDS_DIR ?
338 getDefaultSoundsDir(SND_CLASSIC_SUBDIR) :
339 type == TREE_TYPE_MUSIC_DIR ?
340 getDefaultMusicDir(MUS_CLASSIC_SUBDIR) : "");
343 char *getUserGraphicsDir(void)
345 static char *usergraphics_dir = NULL;
347 if (usergraphics_dir == NULL)
348 usergraphics_dir = getPath2(getUserGameDataDir(), GRAPHICS_DIRECTORY);
350 return usergraphics_dir;
353 char *getUserSoundsDir(void)
355 static char *usersounds_dir = NULL;
357 if (usersounds_dir == NULL)
358 usersounds_dir = getPath2(getUserGameDataDir(), SOUNDS_DIRECTORY);
360 return usersounds_dir;
363 char *getUserMusicDir(void)
365 static char *usermusic_dir = NULL;
367 if (usermusic_dir == NULL)
368 usermusic_dir = getPath2(getUserGameDataDir(), MUSIC_DIRECTORY);
370 return usermusic_dir;
373 static char *getSetupArtworkDir(TreeInfo *ti)
375 static char *artwork_dir = NULL;
380 checked_free(artwork_dir);
382 artwork_dir = getPath2(ti->basepath, ti->fullpath);
387 char *setLevelArtworkDir(TreeInfo *ti)
389 char **artwork_path_ptr, **artwork_set_ptr;
390 TreeInfo *level_artwork;
392 if (ti == NULL || leveldir_current == NULL)
395 artwork_path_ptr = LEVELDIR_ARTWORK_PATH_PTR(leveldir_current, ti->type);
396 artwork_set_ptr = LEVELDIR_ARTWORK_SET_PTR( leveldir_current, ti->type);
398 checked_free(*artwork_path_ptr);
400 if ((level_artwork = getTreeInfoFromIdentifier(ti, *artwork_set_ptr)))
402 *artwork_path_ptr = getStringCopy(getSetupArtworkDir(level_artwork));
407 No (or non-existing) artwork configured in "levelinfo.conf". This would
408 normally result in using the artwork configured in the setup menu. But
409 if an artwork subdirectory exists (which might contain custom artwork
410 or an artwork configuration file), this level artwork must be treated
411 as relative to the default "classic" artwork, not to the artwork that
412 is currently configured in the setup menu.
414 Update: For "special" versions of R'n'D (like "R'n'D jue"), do not use
415 the "default" artwork (which would be "jue0" for "R'n'D jue"), but use
416 the real "classic" artwork from the original R'n'D (like "gfx_classic").
419 char *dir = getPath2(getCurrentLevelDir(), ARTWORK_DIRECTORY(ti->type));
421 checked_free(*artwork_set_ptr);
423 if (directoryExists(dir))
425 *artwork_path_ptr = getStringCopy(getClassicArtworkDir(ti->type));
426 *artwork_set_ptr = getStringCopy(getClassicArtworkSet(ti->type));
430 *artwork_path_ptr = getStringCopy(UNDEFINED_FILENAME);
431 *artwork_set_ptr = NULL;
437 return *artwork_set_ptr;
440 static char *getLevelArtworkSet(int type)
442 if (leveldir_current == NULL)
445 return LEVELDIR_ARTWORK_SET(leveldir_current, type);
448 static char *getLevelArtworkDir(int type)
450 if (leveldir_current == NULL)
451 return UNDEFINED_FILENAME;
453 return LEVELDIR_ARTWORK_PATH(leveldir_current, type);
456 char *getProgramMainDataPath(char *command_filename, char *base_path)
458 // check if the program's main data base directory is configured
459 if (!strEqual(base_path, "."))
460 return getStringCopy(base_path);
462 /* if the program is configured to start from current directory (default),
463 determine program package directory from program binary (some versions
464 of KDE/Konqueror and Mac OS X (especially "Mavericks") apparently do not
465 set the current working directory to the program package directory) */
466 char *main_data_path = getBasePath(command_filename);
468 #if defined(PLATFORM_MACOSX)
469 if (strSuffix(main_data_path, MAC_APP_BINARY_SUBDIR))
471 char *main_data_path_old = main_data_path;
473 // cut relative path to Mac OS X application binary directory from path
474 main_data_path[strlen(main_data_path) -
475 strlen(MAC_APP_BINARY_SUBDIR)] = '\0';
477 // cut trailing path separator from path (but not if path is root directory)
478 if (strSuffix(main_data_path, "/") && !strEqual(main_data_path, "/"))
479 main_data_path[strlen(main_data_path) - 1] = '\0';
481 // replace empty path with current directory
482 if (strEqual(main_data_path, ""))
483 main_data_path = ".";
485 // add relative path to Mac OS X application resources directory to path
486 main_data_path = getPath2(main_data_path, MAC_APP_FILES_SUBDIR);
488 free(main_data_path_old);
492 return main_data_path;
495 char *getProgramConfigFilename(char *command_filename)
497 static char *config_filename_1 = NULL;
498 static char *config_filename_2 = NULL;
499 static char *config_filename_3 = NULL;
500 static boolean initialized = FALSE;
504 char *command_filename_1 = getStringCopy(command_filename);
506 // strip trailing executable suffix from command filename
507 if (strSuffix(command_filename_1, ".exe"))
508 command_filename_1[strlen(command_filename_1) - 4] = '\0';
510 char *ro_base_path = getProgramMainDataPath(command_filename, RO_BASE_PATH);
511 char *conf_directory = getPath2(ro_base_path, CONF_DIRECTORY);
513 char *command_basepath = getBasePath(command_filename);
514 char *command_basename = getBaseNameNoSuffix(command_filename);
515 char *command_filename_2 = getPath2(command_basepath, command_basename);
517 config_filename_1 = getStringCat2(command_filename_1, ".conf");
518 config_filename_2 = getStringCat2(command_filename_2, ".conf");
519 config_filename_3 = getPath2(conf_directory, SETUP_FILENAME);
521 checked_free(ro_base_path);
522 checked_free(conf_directory);
524 checked_free(command_basepath);
525 checked_free(command_basename);
527 checked_free(command_filename_1);
528 checked_free(command_filename_2);
533 // 1st try: look for config file that exactly matches the binary filename
534 if (fileExists(config_filename_1))
535 return config_filename_1;
537 // 2nd try: look for config file that matches binary filename without suffix
538 if (fileExists(config_filename_2))
539 return config_filename_2;
541 // 3rd try: return setup config filename in global program config directory
542 return config_filename_3;
545 char *getTapeFilename(int nr)
547 static char *filename = NULL;
548 char basename[MAX_FILENAME_LEN];
550 checked_free(filename);
552 sprintf(basename, "%03d.%s", nr, TAPEFILE_EXTENSION);
553 filename = getPath2(getTapeDir(leveldir_current->subdir), basename);
558 char *getSolutionTapeFilename(int nr)
560 static char *filename = NULL;
561 char basename[MAX_FILENAME_LEN];
563 checked_free(filename);
565 sprintf(basename, "%03d.%s", nr, TAPEFILE_EXTENSION);
566 filename = getPath2(getSolutionTapeDir(), basename);
568 if (!fileExists(filename))
570 static char *filename_sln = NULL;
572 checked_free(filename_sln);
574 sprintf(basename, "%03d.sln", nr);
575 filename_sln = getPath2(getSolutionTapeDir(), basename);
577 if (fileExists(filename_sln))
584 char *getScoreFilename(int nr)
586 static char *filename = NULL;
587 char basename[MAX_FILENAME_LEN];
589 checked_free(filename);
591 sprintf(basename, "%03d.%s", nr, SCOREFILE_EXTENSION);
593 // used instead of "leveldir_current->subdir" (for network games)
594 filename = getPath2(getScoreDir(levelset.identifier), basename);
599 char *getSetupFilename(void)
601 static char *filename = NULL;
603 checked_free(filename);
605 filename = getPath2(getSetupDir(), SETUP_FILENAME);
610 char *getDefaultSetupFilename(void)
612 return program.config_filename;
615 char *getEditorSetupFilename(void)
617 static char *filename = NULL;
619 checked_free(filename);
620 filename = getPath2(getCurrentLevelDir(), EDITORSETUP_FILENAME);
622 if (fileExists(filename))
625 checked_free(filename);
626 filename = getPath2(getSetupDir(), EDITORSETUP_FILENAME);
631 char *getHelpAnimFilename(void)
633 static char *filename = NULL;
635 checked_free(filename);
637 filename = getPath2(getCurrentLevelDir(), HELPANIM_FILENAME);
642 char *getHelpTextFilename(void)
644 static char *filename = NULL;
646 checked_free(filename);
648 filename = getPath2(getCurrentLevelDir(), HELPTEXT_FILENAME);
653 char *getLevelSetInfoFilename(void)
655 static char *filename = NULL;
670 for (i = 0; basenames[i] != NULL; i++)
672 checked_free(filename);
673 filename = getPath2(getCurrentLevelDir(), basenames[i]);
675 if (fileExists(filename))
682 static char *getLevelSetTitleMessageBasename(int nr, boolean initial)
684 static char basename[32];
686 sprintf(basename, "%s_%d.txt",
687 (initial ? "titlemessage_initial" : "titlemessage"), nr + 1);
692 char *getLevelSetTitleMessageFilename(int nr, boolean initial)
694 static char *filename = NULL;
696 boolean skip_setup_artwork = FALSE;
698 checked_free(filename);
700 basename = getLevelSetTitleMessageBasename(nr, initial);
702 if (!gfx.override_level_graphics)
704 // 1st try: look for special artwork in current level series directory
705 filename = getPath3(getCurrentLevelDir(), GRAPHICS_DIRECTORY, basename);
706 if (fileExists(filename))
711 // 2nd try: look for message file in current level set directory
712 filename = getPath2(getCurrentLevelDir(), basename);
713 if (fileExists(filename))
718 // check if there is special artwork configured in level series config
719 if (getLevelArtworkSet(ARTWORK_TYPE_GRAPHICS) != NULL)
721 // 3rd try: look for special artwork configured in level series config
722 filename = getPath2(getLevelArtworkDir(ARTWORK_TYPE_GRAPHICS), basename);
723 if (fileExists(filename))
728 // take missing artwork configured in level set config from default
729 skip_setup_artwork = TRUE;
733 if (!skip_setup_artwork)
735 // 4th try: look for special artwork in configured artwork directory
736 filename = getPath2(getSetupArtworkDir(artwork.gfx_current), basename);
737 if (fileExists(filename))
743 // 5th try: look for default artwork in new default artwork directory
744 filename = getPath2(getDefaultGraphicsDir(GFX_DEFAULT_SUBDIR), basename);
745 if (fileExists(filename))
750 // 6th try: look for default artwork in old default artwork directory
751 filename = getPath2(options.graphics_directory, basename);
752 if (fileExists(filename))
755 return NULL; // cannot find specified artwork file anywhere
758 static char *getCorrectedArtworkBasename(char *basename)
763 char *getCustomImageFilename(char *basename)
765 static char *filename = NULL;
766 boolean skip_setup_artwork = FALSE;
768 checked_free(filename);
770 basename = getCorrectedArtworkBasename(basename);
772 if (!gfx.override_level_graphics)
774 // 1st try: look for special artwork in current level series directory
775 filename = getImg3(getCurrentLevelDir(), GRAPHICS_DIRECTORY, basename);
776 if (fileExists(filename))
781 // check if there is special artwork configured in level series config
782 if (getLevelArtworkSet(ARTWORK_TYPE_GRAPHICS) != NULL)
784 // 2nd try: look for special artwork configured in level series config
785 filename = getImg2(getLevelArtworkDir(ARTWORK_TYPE_GRAPHICS), basename);
786 if (fileExists(filename))
791 // take missing artwork configured in level set config from default
792 skip_setup_artwork = TRUE;
796 if (!skip_setup_artwork)
798 // 3rd try: look for special artwork in configured artwork directory
799 filename = getImg2(getSetupArtworkDir(artwork.gfx_current), basename);
800 if (fileExists(filename))
806 // 4th try: look for default artwork in new default artwork directory
807 filename = getImg2(getDefaultGraphicsDir(GFX_DEFAULT_SUBDIR), basename);
808 if (fileExists(filename))
813 // 5th try: look for default artwork in old default artwork directory
814 filename = getImg2(options.graphics_directory, basename);
815 if (fileExists(filename))
818 if (!strEqual(GFX_FALLBACK_FILENAME, UNDEFINED_FILENAME))
823 Error(ERR_WARN, "cannot find artwork file '%s' (using fallback)",
826 // 6th try: look for fallback artwork in old default artwork directory
827 // (needed to prevent errors when trying to access unused artwork files)
828 filename = getImg2(options.graphics_directory, GFX_FALLBACK_FILENAME);
829 if (fileExists(filename))
833 return NULL; // cannot find specified artwork file anywhere
836 char *getCustomSoundFilename(char *basename)
838 static char *filename = NULL;
839 boolean skip_setup_artwork = FALSE;
841 checked_free(filename);
843 basename = getCorrectedArtworkBasename(basename);
845 if (!gfx.override_level_sounds)
847 // 1st try: look for special artwork in current level series directory
848 filename = getPath3(getCurrentLevelDir(), SOUNDS_DIRECTORY, basename);
849 if (fileExists(filename))
854 // check if there is special artwork configured in level series config
855 if (getLevelArtworkSet(ARTWORK_TYPE_SOUNDS) != NULL)
857 // 2nd try: look for special artwork configured in level series config
858 filename = getPath2(getLevelArtworkDir(TREE_TYPE_SOUNDS_DIR), basename);
859 if (fileExists(filename))
864 // take missing artwork configured in level set config from default
865 skip_setup_artwork = TRUE;
869 if (!skip_setup_artwork)
871 // 3rd try: look for special artwork in configured artwork directory
872 filename = getPath2(getSetupArtworkDir(artwork.snd_current), basename);
873 if (fileExists(filename))
879 // 4th try: look for default artwork in new default artwork directory
880 filename = getPath2(getDefaultSoundsDir(SND_DEFAULT_SUBDIR), basename);
881 if (fileExists(filename))
886 // 5th try: look for default artwork in old default artwork directory
887 filename = getPath2(options.sounds_directory, basename);
888 if (fileExists(filename))
891 if (!strEqual(SND_FALLBACK_FILENAME, UNDEFINED_FILENAME))
896 Error(ERR_WARN, "cannot find artwork file '%s' (using fallback)",
899 // 6th try: look for fallback artwork in old default artwork directory
900 // (needed to prevent errors when trying to access unused artwork files)
901 filename = getPath2(options.sounds_directory, SND_FALLBACK_FILENAME);
902 if (fileExists(filename))
906 return NULL; // cannot find specified artwork file anywhere
909 char *getCustomMusicFilename(char *basename)
911 static char *filename = NULL;
912 boolean skip_setup_artwork = FALSE;
914 checked_free(filename);
916 basename = getCorrectedArtworkBasename(basename);
918 if (!gfx.override_level_music)
920 // 1st try: look for special artwork in current level series directory
921 filename = getPath3(getCurrentLevelDir(), MUSIC_DIRECTORY, basename);
922 if (fileExists(filename))
927 // check if there is special artwork configured in level series config
928 if (getLevelArtworkSet(ARTWORK_TYPE_MUSIC) != NULL)
930 // 2nd try: look for special artwork configured in level series config
931 filename = getPath2(getLevelArtworkDir(TREE_TYPE_MUSIC_DIR), basename);
932 if (fileExists(filename))
937 // take missing artwork configured in level set config from default
938 skip_setup_artwork = TRUE;
942 if (!skip_setup_artwork)
944 // 3rd try: look for special artwork in configured artwork directory
945 filename = getPath2(getSetupArtworkDir(artwork.mus_current), basename);
946 if (fileExists(filename))
952 // 4th try: look for default artwork in new default artwork directory
953 filename = getPath2(getDefaultMusicDir(MUS_DEFAULT_SUBDIR), basename);
954 if (fileExists(filename))
959 // 5th try: look for default artwork in old default artwork directory
960 filename = getPath2(options.music_directory, basename);
961 if (fileExists(filename))
964 if (!strEqual(MUS_FALLBACK_FILENAME, UNDEFINED_FILENAME))
969 Error(ERR_WARN, "cannot find artwork file '%s' (using fallback)",
972 // 6th try: look for fallback artwork in old default artwork directory
973 // (needed to prevent errors when trying to access unused artwork files)
974 filename = getPath2(options.music_directory, MUS_FALLBACK_FILENAME);
975 if (fileExists(filename))
979 return NULL; // cannot find specified artwork file anywhere
982 char *getCustomArtworkFilename(char *basename, int type)
984 if (type == ARTWORK_TYPE_GRAPHICS)
985 return getCustomImageFilename(basename);
986 else if (type == ARTWORK_TYPE_SOUNDS)
987 return getCustomSoundFilename(basename);
988 else if (type == ARTWORK_TYPE_MUSIC)
989 return getCustomMusicFilename(basename);
991 return UNDEFINED_FILENAME;
994 char *getCustomArtworkConfigFilename(int type)
996 return getCustomArtworkFilename(ARTWORKINFO_FILENAME(type), type);
999 char *getCustomArtworkLevelConfigFilename(int type)
1001 static char *filename = NULL;
1003 checked_free(filename);
1005 filename = getPath2(getLevelArtworkDir(type), ARTWORKINFO_FILENAME(type));
1010 char *getCustomMusicDirectory(void)
1012 static char *directory = NULL;
1013 boolean skip_setup_artwork = FALSE;
1015 checked_free(directory);
1017 if (!gfx.override_level_music)
1019 // 1st try: look for special artwork in current level series directory
1020 directory = getPath2(getCurrentLevelDir(), MUSIC_DIRECTORY);
1021 if (directoryExists(directory))
1026 // check if there is special artwork configured in level series config
1027 if (getLevelArtworkSet(ARTWORK_TYPE_MUSIC) != NULL)
1029 // 2nd try: look for special artwork configured in level series config
1030 directory = getStringCopy(getLevelArtworkDir(TREE_TYPE_MUSIC_DIR));
1031 if (directoryExists(directory))
1036 // take missing artwork configured in level set config from default
1037 skip_setup_artwork = TRUE;
1041 if (!skip_setup_artwork)
1043 // 3rd try: look for special artwork in configured artwork directory
1044 directory = getStringCopy(getSetupArtworkDir(artwork.mus_current));
1045 if (directoryExists(directory))
1051 // 4th try: look for default artwork in new default artwork directory
1052 directory = getStringCopy(getDefaultMusicDir(MUS_DEFAULT_SUBDIR));
1053 if (directoryExists(directory))
1058 // 5th try: look for default artwork in old default artwork directory
1059 directory = getStringCopy(options.music_directory);
1060 if (directoryExists(directory))
1063 return NULL; // cannot find specified artwork file anywhere
1066 void InitTapeDirectory(char *level_subdir)
1068 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
1069 createDirectory(getTapeDir(NULL), "main tape", PERMS_PRIVATE);
1070 createDirectory(getTapeDir(level_subdir), "level tape", PERMS_PRIVATE);
1073 void InitScoreDirectory(char *level_subdir)
1075 int permissions = (program.global_scores ? PERMS_PUBLIC : PERMS_PRIVATE);
1077 if (program.global_scores)
1078 createDirectory(getCommonDataDir(), "common data", permissions);
1080 createDirectory(getUserGameDataDir(), "user data", permissions);
1082 createDirectory(getScoreDir(NULL), "main score", permissions);
1083 createDirectory(getScoreDir(level_subdir), "level score", permissions);
1086 static void SaveUserLevelInfo(void);
1088 void InitUserLevelDirectory(char *level_subdir)
1090 if (!directoryExists(getUserLevelDir(level_subdir)))
1092 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
1093 createDirectory(getUserLevelDir(NULL), "main user level", PERMS_PRIVATE);
1094 createDirectory(getUserLevelDir(level_subdir), "user level", PERMS_PRIVATE);
1096 if (setup.internal.create_user_levelset)
1097 SaveUserLevelInfo();
1101 void InitNetworkLevelDirectory(char *level_subdir)
1103 if (!directoryExists(getNetworkLevelDir(level_subdir)))
1105 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
1106 createDirectory(getNetworkDir(), "network data", PERMS_PRIVATE);
1107 createDirectory(getNetworkLevelDir(NULL), "main network level", PERMS_PRIVATE);
1108 createDirectory(getNetworkLevelDir(level_subdir), "network level", PERMS_PRIVATE);
1112 void InitLevelSetupDirectory(char *level_subdir)
1114 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
1115 createDirectory(getLevelSetupDir(NULL), "main level setup", PERMS_PRIVATE);
1116 createDirectory(getLevelSetupDir(level_subdir), "level setup", PERMS_PRIVATE);
1119 static void InitCacheDirectory(void)
1121 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
1122 createDirectory(getCacheDir(), "cache data", PERMS_PRIVATE);
1126 // ----------------------------------------------------------------------------
1127 // some functions to handle lists of level and artwork directories
1128 // ----------------------------------------------------------------------------
1130 TreeInfo *newTreeInfo(void)
1132 return checked_calloc(sizeof(TreeInfo));
1135 TreeInfo *newTreeInfo_setDefaults(int type)
1137 TreeInfo *ti = newTreeInfo();
1139 setTreeInfoToDefaults(ti, type);
1144 void pushTreeInfo(TreeInfo **node_first, TreeInfo *node_new)
1146 node_new->next = *node_first;
1147 *node_first = node_new;
1150 int numTreeInfo(TreeInfo *node)
1163 boolean validLevelSeries(TreeInfo *node)
1165 return (node != NULL && !node->node_group && !node->parent_link);
1168 TreeInfo *getFirstValidTreeInfoEntry(TreeInfo *node)
1173 if (node->node_group) // enter level group (step down into tree)
1174 return getFirstValidTreeInfoEntry(node->node_group);
1175 else if (node->parent_link) // skip start entry of level group
1177 if (node->next) // get first real level series entry
1178 return getFirstValidTreeInfoEntry(node->next);
1179 else // leave empty level group and go on
1180 return getFirstValidTreeInfoEntry(node->node_parent->next);
1182 else // this seems to be a regular level series
1186 TreeInfo *getTreeInfoFirstGroupEntry(TreeInfo *node)
1191 if (node->node_parent == NULL) // top level group
1192 return *node->node_top;
1193 else // sub level group
1194 return node->node_parent->node_group;
1197 int numTreeInfoInGroup(TreeInfo *node)
1199 return numTreeInfo(getTreeInfoFirstGroupEntry(node));
1202 int posTreeInfo(TreeInfo *node)
1204 TreeInfo *node_cmp = getTreeInfoFirstGroupEntry(node);
1209 if (node_cmp == node)
1213 node_cmp = node_cmp->next;
1219 TreeInfo *getTreeInfoFromPos(TreeInfo *node, int pos)
1221 TreeInfo *node_default = node;
1233 return node_default;
1236 TreeInfo *getTreeInfoFromIdentifier(TreeInfo *node, char *identifier)
1238 if (identifier == NULL)
1243 if (node->node_group)
1245 TreeInfo *node_group;
1247 node_group = getTreeInfoFromIdentifier(node->node_group, identifier);
1252 else if (!node->parent_link)
1254 if (strEqual(identifier, node->identifier))
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)
1369 printf("Dumping TreeInfo:\n");
1373 for (i = 0; i < (depth + 1) * 3; i++)
1376 printf("'%s' / '%s'\n", node->identifier, node->name);
1379 // use for dumping artwork info tree
1380 printf("subdir == '%s' ['%s', '%s'] [%d])\n",
1381 node->subdir, node->fullpath, node->basepath, node->in_user_dir);
1384 if (node->node_group != NULL)
1385 dumpTreeInfo(node->node_group, depth + 1);
1391 void sortTreeInfoBySortFunction(TreeInfo **node_first,
1392 int (*compare_function)(const void *,
1395 int num_nodes = numTreeInfo(*node_first);
1396 TreeInfo **sort_array;
1397 TreeInfo *node = *node_first;
1403 // allocate array for sorting structure pointers
1404 sort_array = checked_calloc(num_nodes * sizeof(TreeInfo *));
1406 // writing structure pointers to sorting array
1407 while (i < num_nodes && node) // double boundary check...
1409 sort_array[i] = node;
1415 // sorting the structure pointers in the sorting array
1416 qsort(sort_array, num_nodes, sizeof(TreeInfo *),
1419 // update the linkage of list elements with the sorted node array
1420 for (i = 0; i < num_nodes - 1; i++)
1421 sort_array[i]->next = sort_array[i + 1];
1422 sort_array[num_nodes - 1]->next = NULL;
1424 // update the linkage of the main list anchor pointer
1425 *node_first = sort_array[0];
1429 // now recursively sort the level group structures
1433 if (node->node_group != NULL)
1434 sortTreeInfoBySortFunction(&node->node_group, compare_function);
1440 void sortTreeInfo(TreeInfo **node_first)
1442 sortTreeInfoBySortFunction(node_first, compareTreeInfoEntries);
1446 // ============================================================================
1447 // some stuff from "files.c"
1448 // ============================================================================
1450 #if defined(PLATFORM_WIN32)
1452 #define S_IRGRP S_IRUSR
1455 #define S_IROTH S_IRUSR
1458 #define S_IWGRP S_IWUSR
1461 #define S_IWOTH S_IWUSR
1464 #define S_IXGRP S_IXUSR
1467 #define S_IXOTH S_IXUSR
1470 #define S_IRWXG (S_IRGRP | S_IWGRP | S_IXGRP)
1475 #endif // PLATFORM_WIN32
1477 // file permissions for newly written files
1478 #define MODE_R_ALL (S_IRUSR | S_IRGRP | S_IROTH)
1479 #define MODE_W_ALL (S_IWUSR | S_IWGRP | S_IWOTH)
1480 #define MODE_X_ALL (S_IXUSR | S_IXGRP | S_IXOTH)
1482 #define MODE_W_PRIVATE (S_IWUSR)
1483 #define MODE_W_PUBLIC_FILE (S_IWUSR | S_IWGRP)
1484 #define MODE_W_PUBLIC_DIR (S_IWUSR | S_IWGRP | S_ISGID)
1486 #define DIR_PERMS_PRIVATE (MODE_R_ALL | MODE_X_ALL | MODE_W_PRIVATE)
1487 #define DIR_PERMS_PUBLIC (MODE_R_ALL | MODE_X_ALL | MODE_W_PUBLIC_DIR)
1488 #define DIR_PERMS_PUBLIC_ALL (MODE_R_ALL | MODE_X_ALL | MODE_W_ALL)
1490 #define FILE_PERMS_PRIVATE (MODE_R_ALL | MODE_W_PRIVATE)
1491 #define FILE_PERMS_PUBLIC (MODE_R_ALL | MODE_W_PUBLIC_FILE)
1492 #define FILE_PERMS_PUBLIC_ALL (MODE_R_ALL | MODE_W_ALL)
1495 char *getHomeDir(void)
1497 static char *dir = NULL;
1499 #if defined(PLATFORM_WIN32)
1502 dir = checked_malloc(MAX_PATH + 1);
1504 if (!SUCCEEDED(SHGetFolderPath(NULL, CSIDL_PERSONAL, NULL, 0, dir)))
1507 #elif defined(PLATFORM_UNIX)
1510 if ((dir = getenv("HOME")) == NULL)
1514 if ((pwd = getpwuid(getuid())) != NULL)
1515 dir = getStringCopy(pwd->pw_dir);
1527 char *getCommonDataDir(void)
1529 static char *common_data_dir = NULL;
1531 #if defined(PLATFORM_WIN32)
1532 if (common_data_dir == NULL)
1534 char *dir = checked_malloc(MAX_PATH + 1);
1536 if (SUCCEEDED(SHGetFolderPath(NULL, CSIDL_COMMON_DOCUMENTS, NULL, 0, dir))
1537 && !strEqual(dir, "")) // empty for Windows 95/98
1538 common_data_dir = getPath2(dir, program.userdata_subdir);
1540 common_data_dir = options.rw_base_directory;
1543 if (common_data_dir == NULL)
1544 common_data_dir = options.rw_base_directory;
1547 return common_data_dir;
1550 char *getPersonalDataDir(void)
1552 static char *personal_data_dir = NULL;
1554 #if defined(PLATFORM_MACOSX)
1555 if (personal_data_dir == NULL)
1556 personal_data_dir = getPath2(getHomeDir(), "Documents");
1558 if (personal_data_dir == NULL)
1559 personal_data_dir = getHomeDir();
1562 return personal_data_dir;
1565 char *getUserGameDataDir(void)
1567 static char *user_game_data_dir = NULL;
1569 #if defined(PLATFORM_ANDROID)
1570 if (user_game_data_dir == NULL)
1571 user_game_data_dir = (char *)(SDL_AndroidGetExternalStorageState() &
1572 SDL_ANDROID_EXTERNAL_STORAGE_WRITE ?
1573 SDL_AndroidGetExternalStoragePath() :
1574 SDL_AndroidGetInternalStoragePath());
1576 if (user_game_data_dir == NULL)
1577 user_game_data_dir = getPath2(getPersonalDataDir(),
1578 program.userdata_subdir);
1581 return user_game_data_dir;
1584 char *getSetupDir(void)
1586 return getUserGameDataDir();
1589 static mode_t posix_umask(mode_t mask)
1591 #if defined(PLATFORM_UNIX)
1598 static int posix_mkdir(const char *pathname, mode_t mode)
1600 #if defined(PLATFORM_WIN32)
1601 return mkdir(pathname);
1603 return mkdir(pathname, mode);
1607 static boolean posix_process_running_setgid(void)
1609 #if defined(PLATFORM_UNIX)
1610 return (getgid() != getegid());
1616 void createDirectory(char *dir, char *text, int permission_class)
1618 if (directoryExists(dir))
1621 // leave "other" permissions in umask untouched, but ensure group parts
1622 // of USERDATA_DIR_MODE are not masked
1623 mode_t dir_mode = (permission_class == PERMS_PRIVATE ?
1624 DIR_PERMS_PRIVATE : DIR_PERMS_PUBLIC);
1625 mode_t last_umask = posix_umask(0);
1626 mode_t group_umask = ~(dir_mode & S_IRWXG);
1627 int running_setgid = posix_process_running_setgid();
1629 if (permission_class == PERMS_PUBLIC)
1631 // if we're setgid, protect files against "other"
1632 // else keep umask(0) to make the dir world-writable
1635 posix_umask(last_umask & group_umask);
1637 dir_mode = DIR_PERMS_PUBLIC_ALL;
1640 if (posix_mkdir(dir, dir_mode) != 0)
1641 Error(ERR_WARN, "cannot create %s directory '%s': %s",
1642 text, dir, strerror(errno));
1644 if (permission_class == PERMS_PUBLIC && !running_setgid)
1645 chmod(dir, dir_mode);
1647 posix_umask(last_umask); // restore previous umask
1650 void InitUserDataDirectory(void)
1652 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
1655 void SetFilePermissions(char *filename, int permission_class)
1657 int running_setgid = posix_process_running_setgid();
1658 int perms = (permission_class == PERMS_PRIVATE ?
1659 FILE_PERMS_PRIVATE : FILE_PERMS_PUBLIC);
1661 if (permission_class == PERMS_PUBLIC && !running_setgid)
1662 perms = FILE_PERMS_PUBLIC_ALL;
1664 chmod(filename, perms);
1667 char *getCookie(char *file_type)
1669 static char cookie[MAX_COOKIE_LEN + 1];
1671 if (strlen(program.cookie_prefix) + 1 +
1672 strlen(file_type) + strlen("_FILE_VERSION_x.x") > MAX_COOKIE_LEN)
1673 return "[COOKIE ERROR]"; // should never happen
1675 sprintf(cookie, "%s_%s_FILE_VERSION_%d.%d",
1676 program.cookie_prefix, file_type,
1677 program.version_super, program.version_major);
1682 void fprintFileHeader(FILE *file, char *basename)
1684 char *prefix = "# ";
1687 fprintf_line_with_prefix(file, prefix, sep1, 77);
1688 fprintf(file, "%s%s\n", prefix, basename);
1689 fprintf_line_with_prefix(file, prefix, sep1, 77);
1690 fprintf(file, "\n");
1693 int getFileVersionFromCookieString(const char *cookie)
1695 const char *ptr_cookie1, *ptr_cookie2;
1696 const char *pattern1 = "_FILE_VERSION_";
1697 const char *pattern2 = "?.?";
1698 const int len_cookie = strlen(cookie);
1699 const int len_pattern1 = strlen(pattern1);
1700 const int len_pattern2 = strlen(pattern2);
1701 const int len_pattern = len_pattern1 + len_pattern2;
1702 int version_super, version_major;
1704 if (len_cookie <= len_pattern)
1707 ptr_cookie1 = &cookie[len_cookie - len_pattern];
1708 ptr_cookie2 = &cookie[len_cookie - len_pattern2];
1710 if (strncmp(ptr_cookie1, pattern1, len_pattern1) != 0)
1713 if (ptr_cookie2[0] < '0' || ptr_cookie2[0] > '9' ||
1714 ptr_cookie2[1] != '.' ||
1715 ptr_cookie2[2] < '0' || ptr_cookie2[2] > '9')
1718 version_super = ptr_cookie2[0] - '0';
1719 version_major = ptr_cookie2[2] - '0';
1721 return VERSION_IDENT(version_super, version_major, 0, 0);
1724 boolean checkCookieString(const char *cookie, const char *template)
1726 const char *pattern = "_FILE_VERSION_?.?";
1727 const int len_cookie = strlen(cookie);
1728 const int len_template = strlen(template);
1729 const int len_pattern = strlen(pattern);
1731 if (len_cookie != len_template)
1734 if (strncmp(cookie, template, len_cookie - len_pattern) != 0)
1741 // ----------------------------------------------------------------------------
1742 // setup file list and hash handling functions
1743 // ----------------------------------------------------------------------------
1745 char *getFormattedSetupEntry(char *token, char *value)
1748 static char entry[MAX_LINE_LEN];
1750 // if value is an empty string, just return token without value
1754 // start with the token and some spaces to format output line
1755 sprintf(entry, "%s:", token);
1756 for (i = strlen(entry); i < token_value_position; i++)
1759 // continue with the token's value
1760 strcat(entry, value);
1765 SetupFileList *newSetupFileList(char *token, char *value)
1767 SetupFileList *new = checked_malloc(sizeof(SetupFileList));
1769 new->token = getStringCopy(token);
1770 new->value = getStringCopy(value);
1777 void freeSetupFileList(SetupFileList *list)
1782 checked_free(list->token);
1783 checked_free(list->value);
1786 freeSetupFileList(list->next);
1791 char *getListEntry(SetupFileList *list, char *token)
1796 if (strEqual(list->token, token))
1799 return getListEntry(list->next, token);
1802 SetupFileList *setListEntry(SetupFileList *list, char *token, char *value)
1807 if (strEqual(list->token, token))
1809 checked_free(list->value);
1811 list->value = getStringCopy(value);
1815 else if (list->next == NULL)
1816 return (list->next = newSetupFileList(token, value));
1818 return setListEntry(list->next, token, value);
1821 SetupFileList *addListEntry(SetupFileList *list, char *token, char *value)
1826 if (list->next == NULL)
1827 return (list->next = newSetupFileList(token, value));
1829 return addListEntry(list->next, token, value);
1832 #if ENABLE_UNUSED_CODE
1834 static void printSetupFileList(SetupFileList *list)
1839 printf("token: '%s'\n", list->token);
1840 printf("value: '%s'\n", list->value);
1842 printSetupFileList(list->next);
1848 DEFINE_HASHTABLE_INSERT(insert_hash_entry, char, char);
1849 DEFINE_HASHTABLE_SEARCH(search_hash_entry, char, char);
1850 DEFINE_HASHTABLE_CHANGE(change_hash_entry, char, char);
1851 DEFINE_HASHTABLE_REMOVE(remove_hash_entry, char, char);
1853 #define insert_hash_entry hashtable_insert
1854 #define search_hash_entry hashtable_search
1855 #define change_hash_entry hashtable_change
1856 #define remove_hash_entry hashtable_remove
1859 unsigned int get_hash_from_key(void *key)
1864 This algorithm (k=33) was first reported by Dan Bernstein many years ago in
1865 'comp.lang.c'. Another version of this algorithm (now favored by Bernstein)
1866 uses XOR: hash(i) = hash(i - 1) * 33 ^ str[i]; the magic of number 33 (why
1867 it works better than many other constants, prime or not) has never been
1868 adequately explained.
1870 If you just want to have a good hash function, and cannot wait, djb2
1871 is one of the best string hash functions i know. It has excellent
1872 distribution and speed on many different sets of keys and table sizes.
1873 You are not likely to do better with one of the "well known" functions
1874 such as PJW, K&R, etc.
1876 Ozan (oz) Yigit [http://www.cs.yorku.ca/~oz/hash.html]
1879 char *str = (char *)key;
1880 unsigned int hash = 5381;
1883 while ((c = *str++))
1884 hash = ((hash << 5) + hash) + c; // hash * 33 + c
1889 static int keys_are_equal(void *key1, void *key2)
1891 return (strEqual((char *)key1, (char *)key2));
1894 SetupFileHash *newSetupFileHash(void)
1896 SetupFileHash *new_hash =
1897 create_hashtable(16, 0.75, get_hash_from_key, keys_are_equal);
1899 if (new_hash == NULL)
1900 Error(ERR_EXIT, "create_hashtable() failed -- out of memory");
1905 void freeSetupFileHash(SetupFileHash *hash)
1910 hashtable_destroy(hash, 1); // 1 == also free values stored in hash
1913 char *getHashEntry(SetupFileHash *hash, char *token)
1918 return search_hash_entry(hash, token);
1921 void setHashEntry(SetupFileHash *hash, char *token, char *value)
1928 value_copy = getStringCopy(value);
1930 // change value; if it does not exist, insert it as new
1931 if (!change_hash_entry(hash, token, value_copy))
1932 if (!insert_hash_entry(hash, getStringCopy(token), value_copy))
1933 Error(ERR_EXIT, "cannot insert into hash -- aborting");
1936 char *removeHashEntry(SetupFileHash *hash, char *token)
1941 return remove_hash_entry(hash, token);
1944 #if ENABLE_UNUSED_CODE
1946 static void printSetupFileHash(SetupFileHash *hash)
1948 BEGIN_HASH_ITERATION(hash, itr)
1950 printf("token: '%s'\n", HASH_ITERATION_TOKEN(itr));
1951 printf("value: '%s'\n", HASH_ITERATION_VALUE(itr));
1953 END_HASH_ITERATION(hash, itr)
1958 #define ALLOW_TOKEN_VALUE_SEPARATOR_BEING_WHITESPACE 1
1959 #define CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING 0
1960 #define CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH 0
1962 static boolean token_value_separator_found = FALSE;
1963 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
1964 static boolean token_value_separator_warning = FALSE;
1966 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
1967 static boolean token_already_exists_warning = FALSE;
1970 static boolean getTokenValueFromSetupLineExt(char *line,
1971 char **token_ptr, char **value_ptr,
1972 char *filename, char *line_raw,
1974 boolean separator_required)
1976 static char line_copy[MAX_LINE_LEN + 1], line_raw_copy[MAX_LINE_LEN + 1];
1977 char *token, *value, *line_ptr;
1979 // when externally invoked via ReadTokenValueFromLine(), copy line buffers
1980 if (line_raw == NULL)
1982 strncpy(line_copy, line, MAX_LINE_LEN);
1983 line_copy[MAX_LINE_LEN] = '\0';
1986 strcpy(line_raw_copy, line_copy);
1987 line_raw = line_raw_copy;
1990 // cut trailing comment from input line
1991 for (line_ptr = line; *line_ptr; line_ptr++)
1993 if (*line_ptr == '#')
2000 // cut trailing whitespaces from input line
2001 for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
2002 if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
2005 // ignore empty lines
2009 // cut leading whitespaces from token
2010 for (token = line; *token; token++)
2011 if (*token != ' ' && *token != '\t')
2014 // start with empty value as reliable default
2017 token_value_separator_found = FALSE;
2019 // find end of token to determine start of value
2020 for (line_ptr = token; *line_ptr; line_ptr++)
2022 // first look for an explicit token/value separator, like ':' or '='
2023 if (*line_ptr == ':' || *line_ptr == '=')
2025 *line_ptr = '\0'; // terminate token string
2026 value = line_ptr + 1; // set beginning of value
2028 token_value_separator_found = TRUE;
2034 #if ALLOW_TOKEN_VALUE_SEPARATOR_BEING_WHITESPACE
2035 // fallback: if no token/value separator found, also allow whitespaces
2036 if (!token_value_separator_found && !separator_required)
2038 for (line_ptr = token; *line_ptr; line_ptr++)
2040 if (*line_ptr == ' ' || *line_ptr == '\t')
2042 *line_ptr = '\0'; // terminate token string
2043 value = line_ptr + 1; // set beginning of value
2045 token_value_separator_found = TRUE;
2051 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2052 if (token_value_separator_found)
2054 if (!token_value_separator_warning)
2056 Error(ERR_INFO_LINE, "-");
2058 if (filename != NULL)
2060 Error(ERR_WARN, "missing token/value separator(s) in config file:");
2061 Error(ERR_INFO, "- config file: '%s'", filename);
2065 Error(ERR_WARN, "missing token/value separator(s):");
2068 token_value_separator_warning = TRUE;
2071 if (filename != NULL)
2072 Error(ERR_INFO, "- line %d: '%s'", line_nr, line_raw);
2074 Error(ERR_INFO, "- line: '%s'", line_raw);
2080 // cut trailing whitespaces from token
2081 for (line_ptr = &token[strlen(token)]; line_ptr >= token; line_ptr--)
2082 if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
2085 // cut leading whitespaces from value
2086 for (; *value; value++)
2087 if (*value != ' ' && *value != '\t')
2096 boolean getTokenValueFromSetupLine(char *line, char **token, char **value)
2098 // while the internal (old) interface does not require a token/value
2099 // separator (for downwards compatibility with existing files which
2100 // don't use them), it is mandatory for the external (new) interface
2102 return getTokenValueFromSetupLineExt(line, token, value, NULL, NULL, 0, TRUE);
2105 static boolean loadSetupFileData(void *setup_file_data, char *filename,
2106 boolean top_recursion_level, boolean is_hash)
2108 static SetupFileHash *include_filename_hash = NULL;
2109 char line[MAX_LINE_LEN], line_raw[MAX_LINE_LEN], previous_line[MAX_LINE_LEN];
2110 char *token, *value, *line_ptr;
2111 void *insert_ptr = NULL;
2112 boolean read_continued_line = FALSE;
2114 int line_nr = 0, token_count = 0, include_count = 0;
2116 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2117 token_value_separator_warning = FALSE;
2120 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2121 token_already_exists_warning = FALSE;
2124 if (!(file = openFile(filename, MODE_READ)))
2126 #if DEBUG_NO_CONFIG_FILE
2127 Error(ERR_DEBUG, "cannot open configuration file '%s'", filename);
2133 // use "insert pointer" to store list end for constant insertion complexity
2135 insert_ptr = setup_file_data;
2137 // on top invocation, create hash to mark included files (to prevent loops)
2138 if (top_recursion_level)
2139 include_filename_hash = newSetupFileHash();
2141 // mark this file as already included (to prevent including it again)
2142 setHashEntry(include_filename_hash, getBaseNamePtr(filename), "true");
2144 while (!checkEndOfFile(file))
2146 // read next line of input file
2147 if (!getStringFromFile(file, line, MAX_LINE_LEN))
2150 // check if line was completely read and is terminated by line break
2151 if (strlen(line) > 0 && line[strlen(line) - 1] == '\n')
2154 // cut trailing line break (this can be newline and/or carriage return)
2155 for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
2156 if ((*line_ptr == '\n' || *line_ptr == '\r') && *(line_ptr + 1) == '\0')
2159 // copy raw input line for later use (mainly debugging output)
2160 strcpy(line_raw, line);
2162 if (read_continued_line)
2164 // append new line to existing line, if there is enough space
2165 if (strlen(previous_line) + strlen(line_ptr) < MAX_LINE_LEN)
2166 strcat(previous_line, line_ptr);
2168 strcpy(line, previous_line); // copy storage buffer to line
2170 read_continued_line = FALSE;
2173 // if the last character is '\', continue at next line
2174 if (strlen(line) > 0 && line[strlen(line) - 1] == '\\')
2176 line[strlen(line) - 1] = '\0'; // cut off trailing backslash
2177 strcpy(previous_line, line); // copy line to storage buffer
2179 read_continued_line = TRUE;
2184 if (!getTokenValueFromSetupLineExt(line, &token, &value, filename,
2185 line_raw, line_nr, FALSE))
2190 if (strEqual(token, "include"))
2192 if (getHashEntry(include_filename_hash, value) == NULL)
2194 char *basepath = getBasePath(filename);
2195 char *basename = getBaseName(value);
2196 char *filename_include = getPath2(basepath, basename);
2198 loadSetupFileData(setup_file_data, filename_include, FALSE, is_hash);
2202 free(filename_include);
2208 Error(ERR_WARN, "ignoring already processed file '%s'", value);
2215 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2217 getHashEntry((SetupFileHash *)setup_file_data, token);
2219 if (old_value != NULL)
2221 if (!token_already_exists_warning)
2223 Error(ERR_INFO_LINE, "-");
2224 Error(ERR_WARN, "duplicate token(s) found in config file:");
2225 Error(ERR_INFO, "- config file: '%s'", filename);
2227 token_already_exists_warning = TRUE;
2230 Error(ERR_INFO, "- token: '%s' (in line %d)", token, line_nr);
2231 Error(ERR_INFO, " old value: '%s'", old_value);
2232 Error(ERR_INFO, " new value: '%s'", value);
2236 setHashEntry((SetupFileHash *)setup_file_data, token, value);
2240 insert_ptr = addListEntry((SetupFileList *)insert_ptr, token, value);
2250 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2251 if (token_value_separator_warning)
2252 Error(ERR_INFO_LINE, "-");
2255 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2256 if (token_already_exists_warning)
2257 Error(ERR_INFO_LINE, "-");
2260 if (token_count == 0 && include_count == 0)
2261 Error(ERR_WARN, "configuration file '%s' is empty", filename);
2263 if (top_recursion_level)
2264 freeSetupFileHash(include_filename_hash);
2269 static void saveSetupFileHash(SetupFileHash *hash, char *filename)
2273 if (!(file = fopen(filename, MODE_WRITE)))
2275 Error(ERR_WARN, "cannot write configuration file '%s'", filename);
2280 BEGIN_HASH_ITERATION(hash, itr)
2282 fprintf(file, "%s\n", getFormattedSetupEntry(HASH_ITERATION_TOKEN(itr),
2283 HASH_ITERATION_VALUE(itr)));
2285 END_HASH_ITERATION(hash, itr)
2290 SetupFileList *loadSetupFileList(char *filename)
2292 SetupFileList *setup_file_list = newSetupFileList("", "");
2293 SetupFileList *first_valid_list_entry;
2295 if (!loadSetupFileData(setup_file_list, filename, TRUE, FALSE))
2297 freeSetupFileList(setup_file_list);
2302 first_valid_list_entry = setup_file_list->next;
2304 // free empty list header
2305 setup_file_list->next = NULL;
2306 freeSetupFileList(setup_file_list);
2308 return first_valid_list_entry;
2311 SetupFileHash *loadSetupFileHash(char *filename)
2313 SetupFileHash *setup_file_hash = newSetupFileHash();
2315 if (!loadSetupFileData(setup_file_hash, filename, TRUE, TRUE))
2317 freeSetupFileHash(setup_file_hash);
2322 return setup_file_hash;
2326 // ============================================================================
2328 // ============================================================================
2330 #define TOKEN_STR_LAST_LEVEL_SERIES "last_level_series"
2331 #define TOKEN_STR_LAST_PLAYED_LEVEL "last_played_level"
2332 #define TOKEN_STR_HANDICAP_LEVEL "handicap_level"
2334 // level directory info
2335 #define LEVELINFO_TOKEN_IDENTIFIER 0
2336 #define LEVELINFO_TOKEN_NAME 1
2337 #define LEVELINFO_TOKEN_NAME_SORTING 2
2338 #define LEVELINFO_TOKEN_AUTHOR 3
2339 #define LEVELINFO_TOKEN_YEAR 4
2340 #define LEVELINFO_TOKEN_PROGRAM_TITLE 5
2341 #define LEVELINFO_TOKEN_PROGRAM_COPYRIGHT 6
2342 #define LEVELINFO_TOKEN_PROGRAM_COMPANY 7
2343 #define LEVELINFO_TOKEN_IMPORTED_FROM 8
2344 #define LEVELINFO_TOKEN_IMPORTED_BY 9
2345 #define LEVELINFO_TOKEN_TESTED_BY 10
2346 #define LEVELINFO_TOKEN_LEVELS 11
2347 #define LEVELINFO_TOKEN_FIRST_LEVEL 12
2348 #define LEVELINFO_TOKEN_SORT_PRIORITY 13
2349 #define LEVELINFO_TOKEN_LATEST_ENGINE 14
2350 #define LEVELINFO_TOKEN_LEVEL_GROUP 15
2351 #define LEVELINFO_TOKEN_READONLY 16
2352 #define LEVELINFO_TOKEN_GRAPHICS_SET_ECS 17
2353 #define LEVELINFO_TOKEN_GRAPHICS_SET_AGA 18
2354 #define LEVELINFO_TOKEN_GRAPHICS_SET 19
2355 #define LEVELINFO_TOKEN_SOUNDS_SET_DEFAULT 20
2356 #define LEVELINFO_TOKEN_SOUNDS_SET_LOWPASS 21
2357 #define LEVELINFO_TOKEN_SOUNDS_SET 22
2358 #define LEVELINFO_TOKEN_MUSIC_SET 23
2359 #define LEVELINFO_TOKEN_FILENAME 24
2360 #define LEVELINFO_TOKEN_FILETYPE 25
2361 #define LEVELINFO_TOKEN_SPECIAL_FLAGS 26
2362 #define LEVELINFO_TOKEN_HANDICAP 27
2363 #define LEVELINFO_TOKEN_SKIP_LEVELS 28
2364 #define LEVELINFO_TOKEN_USE_EMC_TILES 29
2366 #define NUM_LEVELINFO_TOKENS 30
2368 static LevelDirTree ldi;
2370 static struct TokenInfo levelinfo_tokens[] =
2372 // level directory info
2373 { TYPE_STRING, &ldi.identifier, "identifier" },
2374 { TYPE_STRING, &ldi.name, "name" },
2375 { TYPE_STRING, &ldi.name_sorting, "name_sorting" },
2376 { TYPE_STRING, &ldi.author, "author" },
2377 { TYPE_STRING, &ldi.year, "year" },
2378 { TYPE_STRING, &ldi.program_title, "program_title" },
2379 { TYPE_STRING, &ldi.program_copyright, "program_copyright" },
2380 { TYPE_STRING, &ldi.program_company, "program_company" },
2381 { TYPE_STRING, &ldi.imported_from, "imported_from" },
2382 { TYPE_STRING, &ldi.imported_by, "imported_by" },
2383 { TYPE_STRING, &ldi.tested_by, "tested_by" },
2384 { TYPE_INTEGER, &ldi.levels, "levels" },
2385 { TYPE_INTEGER, &ldi.first_level, "first_level" },
2386 { TYPE_INTEGER, &ldi.sort_priority, "sort_priority" },
2387 { TYPE_BOOLEAN, &ldi.latest_engine, "latest_engine" },
2388 { TYPE_BOOLEAN, &ldi.level_group, "level_group" },
2389 { TYPE_BOOLEAN, &ldi.readonly, "readonly" },
2390 { TYPE_STRING, &ldi.graphics_set_ecs, "graphics_set.ecs" },
2391 { TYPE_STRING, &ldi.graphics_set_aga, "graphics_set.aga" },
2392 { TYPE_STRING, &ldi.graphics_set, "graphics_set" },
2393 { TYPE_STRING, &ldi.sounds_set_default,"sounds_set.default" },
2394 { TYPE_STRING, &ldi.sounds_set_lowpass,"sounds_set.lowpass" },
2395 { TYPE_STRING, &ldi.sounds_set, "sounds_set" },
2396 { TYPE_STRING, &ldi.music_set, "music_set" },
2397 { TYPE_STRING, &ldi.level_filename, "filename" },
2398 { TYPE_STRING, &ldi.level_filetype, "filetype" },
2399 { TYPE_STRING, &ldi.special_flags, "special_flags" },
2400 { TYPE_BOOLEAN, &ldi.handicap, "handicap" },
2401 { TYPE_BOOLEAN, &ldi.skip_levels, "skip_levels" },
2402 { TYPE_BOOLEAN, &ldi.use_emc_tiles, "use_emc_tiles" }
2405 static struct TokenInfo artworkinfo_tokens[] =
2407 // artwork directory info
2408 { TYPE_STRING, &ldi.identifier, "identifier" },
2409 { TYPE_STRING, &ldi.subdir, "subdir" },
2410 { TYPE_STRING, &ldi.name, "name" },
2411 { TYPE_STRING, &ldi.name_sorting, "name_sorting" },
2412 { TYPE_STRING, &ldi.author, "author" },
2413 { TYPE_STRING, &ldi.program_title, "program_title" },
2414 { TYPE_STRING, &ldi.program_copyright, "program_copyright" },
2415 { TYPE_STRING, &ldi.program_company, "program_company" },
2416 { TYPE_INTEGER, &ldi.sort_priority, "sort_priority" },
2417 { TYPE_STRING, &ldi.basepath, "basepath" },
2418 { TYPE_STRING, &ldi.fullpath, "fullpath" },
2419 { TYPE_BOOLEAN, &ldi.in_user_dir, "in_user_dir" },
2420 { TYPE_INTEGER, &ldi.color, "color" },
2421 { TYPE_STRING, &ldi.class_desc, "class_desc" },
2426 static void setTreeInfoToDefaults(TreeInfo *ti, int type)
2430 ti->node_top = (ti->type == TREE_TYPE_LEVEL_DIR ? &leveldir_first :
2431 ti->type == TREE_TYPE_GRAPHICS_DIR ? &artwork.gfx_first :
2432 ti->type == TREE_TYPE_SOUNDS_DIR ? &artwork.snd_first :
2433 ti->type == TREE_TYPE_MUSIC_DIR ? &artwork.mus_first :
2436 ti->node_parent = NULL;
2437 ti->node_group = NULL;
2444 ti->fullpath = NULL;
2445 ti->basepath = NULL;
2446 ti->identifier = NULL;
2447 ti->name = getStringCopy(ANONYMOUS_NAME);
2448 ti->name_sorting = NULL;
2449 ti->author = getStringCopy(ANONYMOUS_NAME);
2452 ti->program_title = NULL;
2453 ti->program_copyright = NULL;
2454 ti->program_company = NULL;
2456 ti->sort_priority = LEVELCLASS_UNDEFINED; // default: least priority
2457 ti->latest_engine = FALSE; // default: get from level
2458 ti->parent_link = FALSE;
2459 ti->in_user_dir = FALSE;
2460 ti->user_defined = FALSE;
2462 ti->class_desc = NULL;
2464 ti->infotext = getStringCopy(TREE_INFOTEXT(ti->type));
2466 if (ti->type == TREE_TYPE_LEVEL_DIR)
2468 ti->imported_from = NULL;
2469 ti->imported_by = NULL;
2470 ti->tested_by = NULL;
2472 ti->graphics_set_ecs = NULL;
2473 ti->graphics_set_aga = NULL;
2474 ti->graphics_set = NULL;
2475 ti->sounds_set_default = NULL;
2476 ti->sounds_set_lowpass = NULL;
2477 ti->sounds_set = NULL;
2478 ti->music_set = NULL;
2479 ti->graphics_path = getStringCopy(UNDEFINED_FILENAME);
2480 ti->sounds_path = getStringCopy(UNDEFINED_FILENAME);
2481 ti->music_path = getStringCopy(UNDEFINED_FILENAME);
2483 ti->level_filename = NULL;
2484 ti->level_filetype = NULL;
2486 ti->special_flags = NULL;
2489 ti->first_level = 0;
2491 ti->level_group = FALSE;
2492 ti->handicap_level = 0;
2493 ti->readonly = TRUE;
2494 ti->handicap = TRUE;
2495 ti->skip_levels = FALSE;
2497 ti->use_emc_tiles = FALSE;
2501 static void setTreeInfoToDefaultsFromParent(TreeInfo *ti, TreeInfo *parent)
2505 Error(ERR_WARN, "setTreeInfoToDefaultsFromParent(): parent == NULL");
2507 setTreeInfoToDefaults(ti, TREE_TYPE_UNDEFINED);
2512 // copy all values from the parent structure
2514 ti->type = parent->type;
2516 ti->node_top = parent->node_top;
2517 ti->node_parent = parent;
2518 ti->node_group = NULL;
2525 ti->fullpath = NULL;
2526 ti->basepath = NULL;
2527 ti->identifier = NULL;
2528 ti->name = getStringCopy(ANONYMOUS_NAME);
2529 ti->name_sorting = NULL;
2530 ti->author = getStringCopy(parent->author);
2531 ti->year = getStringCopy(parent->year);
2533 ti->program_title = getStringCopy(parent->program_title);
2534 ti->program_copyright = getStringCopy(parent->program_copyright);
2535 ti->program_company = getStringCopy(parent->program_company);
2537 ti->sort_priority = parent->sort_priority;
2538 ti->latest_engine = parent->latest_engine;
2539 ti->parent_link = FALSE;
2540 ti->in_user_dir = parent->in_user_dir;
2541 ti->user_defined = parent->user_defined;
2542 ti->color = parent->color;
2543 ti->class_desc = getStringCopy(parent->class_desc);
2545 ti->infotext = getStringCopy(parent->infotext);
2547 if (ti->type == TREE_TYPE_LEVEL_DIR)
2549 ti->imported_from = getStringCopy(parent->imported_from);
2550 ti->imported_by = getStringCopy(parent->imported_by);
2551 ti->tested_by = getStringCopy(parent->tested_by);
2553 ti->graphics_set_ecs = getStringCopy(parent->graphics_set_ecs);
2554 ti->graphics_set_aga = getStringCopy(parent->graphics_set_aga);
2555 ti->graphics_set = getStringCopy(parent->graphics_set);
2556 ti->sounds_set_default = getStringCopy(parent->sounds_set_default);
2557 ti->sounds_set_lowpass = getStringCopy(parent->sounds_set_lowpass);
2558 ti->sounds_set = getStringCopy(parent->sounds_set);
2559 ti->music_set = getStringCopy(parent->music_set);
2560 ti->graphics_path = getStringCopy(UNDEFINED_FILENAME);
2561 ti->sounds_path = getStringCopy(UNDEFINED_FILENAME);
2562 ti->music_path = getStringCopy(UNDEFINED_FILENAME);
2564 ti->level_filename = getStringCopy(parent->level_filename);
2565 ti->level_filetype = getStringCopy(parent->level_filetype);
2567 ti->special_flags = getStringCopy(parent->special_flags);
2569 ti->levels = parent->levels;
2570 ti->first_level = parent->first_level;
2571 ti->last_level = parent->last_level;
2572 ti->level_group = FALSE;
2573 ti->handicap_level = parent->handicap_level;
2574 ti->readonly = parent->readonly;
2575 ti->handicap = parent->handicap;
2576 ti->skip_levels = parent->skip_levels;
2578 ti->use_emc_tiles = parent->use_emc_tiles;
2582 static TreeInfo *getTreeInfoCopy(TreeInfo *ti)
2584 TreeInfo *ti_copy = newTreeInfo();
2586 // copy all values from the original structure
2588 ti_copy->type = ti->type;
2590 ti_copy->node_top = ti->node_top;
2591 ti_copy->node_parent = ti->node_parent;
2592 ti_copy->node_group = ti->node_group;
2593 ti_copy->next = ti->next;
2595 ti_copy->cl_first = ti->cl_first;
2596 ti_copy->cl_cursor = ti->cl_cursor;
2598 ti_copy->subdir = getStringCopy(ti->subdir);
2599 ti_copy->fullpath = getStringCopy(ti->fullpath);
2600 ti_copy->basepath = getStringCopy(ti->basepath);
2601 ti_copy->identifier = getStringCopy(ti->identifier);
2602 ti_copy->name = getStringCopy(ti->name);
2603 ti_copy->name_sorting = getStringCopy(ti->name_sorting);
2604 ti_copy->author = getStringCopy(ti->author);
2605 ti_copy->year = getStringCopy(ti->year);
2607 ti_copy->program_title = getStringCopy(ti->program_title);
2608 ti_copy->program_copyright = getStringCopy(ti->program_copyright);
2609 ti_copy->program_company = getStringCopy(ti->program_company);
2611 ti_copy->imported_from = getStringCopy(ti->imported_from);
2612 ti_copy->imported_by = getStringCopy(ti->imported_by);
2613 ti_copy->tested_by = getStringCopy(ti->tested_by);
2615 ti_copy->graphics_set_ecs = getStringCopy(ti->graphics_set_ecs);
2616 ti_copy->graphics_set_aga = getStringCopy(ti->graphics_set_aga);
2617 ti_copy->graphics_set = getStringCopy(ti->graphics_set);
2618 ti_copy->sounds_set_default = getStringCopy(ti->sounds_set_default);
2619 ti_copy->sounds_set_lowpass = getStringCopy(ti->sounds_set_lowpass);
2620 ti_copy->sounds_set = getStringCopy(ti->sounds_set);
2621 ti_copy->music_set = getStringCopy(ti->music_set);
2622 ti_copy->graphics_path = getStringCopy(ti->graphics_path);
2623 ti_copy->sounds_path = getStringCopy(ti->sounds_path);
2624 ti_copy->music_path = getStringCopy(ti->music_path);
2626 ti_copy->level_filename = getStringCopy(ti->level_filename);
2627 ti_copy->level_filetype = getStringCopy(ti->level_filetype);
2629 ti_copy->special_flags = getStringCopy(ti->special_flags);
2631 ti_copy->levels = ti->levels;
2632 ti_copy->first_level = ti->first_level;
2633 ti_copy->last_level = ti->last_level;
2634 ti_copy->sort_priority = ti->sort_priority;
2636 ti_copy->latest_engine = ti->latest_engine;
2638 ti_copy->level_group = ti->level_group;
2639 ti_copy->parent_link = ti->parent_link;
2640 ti_copy->in_user_dir = ti->in_user_dir;
2641 ti_copy->user_defined = ti->user_defined;
2642 ti_copy->readonly = ti->readonly;
2643 ti_copy->handicap = ti->handicap;
2644 ti_copy->skip_levels = ti->skip_levels;
2646 ti_copy->use_emc_tiles = ti->use_emc_tiles;
2648 ti_copy->color = ti->color;
2649 ti_copy->class_desc = getStringCopy(ti->class_desc);
2650 ti_copy->handicap_level = ti->handicap_level;
2652 ti_copy->infotext = getStringCopy(ti->infotext);
2657 void freeTreeInfo(TreeInfo *ti)
2662 checked_free(ti->subdir);
2663 checked_free(ti->fullpath);
2664 checked_free(ti->basepath);
2665 checked_free(ti->identifier);
2667 checked_free(ti->name);
2668 checked_free(ti->name_sorting);
2669 checked_free(ti->author);
2670 checked_free(ti->year);
2672 checked_free(ti->program_title);
2673 checked_free(ti->program_copyright);
2674 checked_free(ti->program_company);
2676 checked_free(ti->class_desc);
2678 checked_free(ti->infotext);
2680 if (ti->type == TREE_TYPE_LEVEL_DIR)
2682 checked_free(ti->imported_from);
2683 checked_free(ti->imported_by);
2684 checked_free(ti->tested_by);
2686 checked_free(ti->graphics_set_ecs);
2687 checked_free(ti->graphics_set_aga);
2688 checked_free(ti->graphics_set);
2689 checked_free(ti->sounds_set_default);
2690 checked_free(ti->sounds_set_lowpass);
2691 checked_free(ti->sounds_set);
2692 checked_free(ti->music_set);
2694 checked_free(ti->graphics_path);
2695 checked_free(ti->sounds_path);
2696 checked_free(ti->music_path);
2698 checked_free(ti->level_filename);
2699 checked_free(ti->level_filetype);
2701 checked_free(ti->special_flags);
2704 // recursively free child node
2706 freeTreeInfo(ti->node_group);
2708 // recursively free next node
2710 freeTreeInfo(ti->next);
2715 void setSetupInfo(struct TokenInfo *token_info,
2716 int token_nr, char *token_value)
2718 int token_type = token_info[token_nr].type;
2719 void *setup_value = token_info[token_nr].value;
2721 if (token_value == NULL)
2724 // set setup field to corresponding token value
2729 *(boolean *)setup_value = get_boolean_from_string(token_value);
2733 *(int *)setup_value = get_switch3_from_string(token_value);
2737 *(Key *)setup_value = getKeyFromKeyName(token_value);
2741 *(Key *)setup_value = getKeyFromX11KeyName(token_value);
2745 *(int *)setup_value = get_integer_from_string(token_value);
2749 checked_free(*(char **)setup_value);
2750 *(char **)setup_value = getStringCopy(token_value);
2754 *(int *)setup_value = get_player_nr_from_string(token_value);
2762 static int compareTreeInfoEntries(const void *object1, const void *object2)
2764 const TreeInfo *entry1 = *((TreeInfo **)object1);
2765 const TreeInfo *entry2 = *((TreeInfo **)object2);
2766 int class_sorting1 = 0, class_sorting2 = 0;
2769 if (entry1->type == TREE_TYPE_LEVEL_DIR)
2771 class_sorting1 = LEVELSORTING(entry1);
2772 class_sorting2 = LEVELSORTING(entry2);
2774 else if (entry1->type == TREE_TYPE_GRAPHICS_DIR ||
2775 entry1->type == TREE_TYPE_SOUNDS_DIR ||
2776 entry1->type == TREE_TYPE_MUSIC_DIR)
2778 class_sorting1 = ARTWORKSORTING(entry1);
2779 class_sorting2 = ARTWORKSORTING(entry2);
2782 if (entry1->parent_link || entry2->parent_link)
2783 compare_result = (entry1->parent_link ? -1 : +1);
2784 else if (entry1->sort_priority == entry2->sort_priority)
2786 char *name1 = getStringToLower(entry1->name_sorting);
2787 char *name2 = getStringToLower(entry2->name_sorting);
2789 compare_result = strcmp(name1, name2);
2794 else if (class_sorting1 == class_sorting2)
2795 compare_result = entry1->sort_priority - entry2->sort_priority;
2797 compare_result = class_sorting1 - class_sorting2;
2799 return compare_result;
2802 static TreeInfo *createParentTreeInfoNode(TreeInfo *node_parent)
2806 if (node_parent == NULL)
2809 ti_new = newTreeInfo();
2810 setTreeInfoToDefaults(ti_new, node_parent->type);
2812 ti_new->node_parent = node_parent;
2813 ti_new->parent_link = TRUE;
2815 setString(&ti_new->identifier, node_parent->identifier);
2816 setString(&ti_new->name, ".. (parent directory)");
2817 setString(&ti_new->name_sorting, ti_new->name);
2819 setString(&ti_new->subdir, STRING_PARENT_DIRECTORY);
2820 setString(&ti_new->fullpath, node_parent->fullpath);
2822 ti_new->sort_priority = node_parent->sort_priority;
2823 ti_new->latest_engine = node_parent->latest_engine;
2825 setString(&ti_new->class_desc, getLevelClassDescription(ti_new));
2827 pushTreeInfo(&node_parent->node_group, ti_new);
2832 static TreeInfo *createTopTreeInfoNode(TreeInfo *node_first)
2834 TreeInfo *ti_new, *ti_new2;
2836 if (node_first == NULL)
2839 ti_new = newTreeInfo();
2840 setTreeInfoToDefaults(ti_new, TREE_TYPE_LEVEL_DIR);
2842 ti_new->node_parent = NULL;
2843 ti_new->parent_link = FALSE;
2845 setString(&ti_new->identifier, node_first->identifier);
2846 setString(&ti_new->name, "level sets");
2847 setString(&ti_new->name_sorting, ti_new->name);
2849 setString(&ti_new->subdir, STRING_TOP_DIRECTORY);
2850 setString(&ti_new->fullpath, ".");
2852 ti_new->sort_priority = node_first->sort_priority;;
2853 ti_new->latest_engine = node_first->latest_engine;
2855 setString(&ti_new->class_desc, "level sets");
2857 ti_new->node_group = node_first;
2858 ti_new->level_group = TRUE;
2860 ti_new2 = createParentTreeInfoNode(ti_new);
2862 setString(&ti_new2->name, ".. (main menu)");
2863 setString(&ti_new2->name_sorting, ti_new2->name);
2869 // ----------------------------------------------------------------------------
2870 // functions for handling level and custom artwork info cache
2871 // ----------------------------------------------------------------------------
2873 static void LoadArtworkInfoCache(void)
2875 InitCacheDirectory();
2877 if (artworkinfo_cache_old == NULL)
2879 char *filename = getPath2(getCacheDir(), ARTWORKINFO_CACHE_FILE);
2881 // try to load artwork info hash from already existing cache file
2882 artworkinfo_cache_old = loadSetupFileHash(filename);
2884 // if no artwork info cache file was found, start with empty hash
2885 if (artworkinfo_cache_old == NULL)
2886 artworkinfo_cache_old = newSetupFileHash();
2891 if (artworkinfo_cache_new == NULL)
2892 artworkinfo_cache_new = newSetupFileHash();
2895 static void SaveArtworkInfoCache(void)
2897 char *filename = getPath2(getCacheDir(), ARTWORKINFO_CACHE_FILE);
2899 InitCacheDirectory();
2901 saveSetupFileHash(artworkinfo_cache_new, filename);
2906 static char *getCacheTokenPrefix(char *prefix1, char *prefix2)
2908 static char *prefix = NULL;
2910 checked_free(prefix);
2912 prefix = getStringCat2WithSeparator(prefix1, prefix2, ".");
2917 // (identical to above function, but separate string buffer needed -- nasty)
2918 static char *getCacheToken(char *prefix, char *suffix)
2920 static char *token = NULL;
2922 checked_free(token);
2924 token = getStringCat2WithSeparator(prefix, suffix, ".");
2929 static char *getFileTimestampString(char *filename)
2931 return getStringCopy(i_to_a(getFileTimestampEpochSeconds(filename)));
2934 static boolean modifiedFileTimestamp(char *filename, char *timestamp_string)
2936 struct stat file_status;
2938 if (timestamp_string == NULL)
2941 if (stat(filename, &file_status) != 0) // cannot stat file
2944 return (file_status.st_mtime != atoi(timestamp_string));
2947 static TreeInfo *getArtworkInfoCacheEntry(LevelDirTree *level_node, int type)
2949 char *identifier = level_node->subdir;
2950 char *type_string = ARTWORK_DIRECTORY(type);
2951 char *token_prefix = getCacheTokenPrefix(type_string, identifier);
2952 char *token_main = getCacheToken(token_prefix, "CACHED");
2953 char *cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
2954 boolean cached = (cache_entry != NULL && strEqual(cache_entry, "true"));
2955 TreeInfo *artwork_info = NULL;
2957 if (!use_artworkinfo_cache)
2964 artwork_info = newTreeInfo();
2965 setTreeInfoToDefaults(artwork_info, type);
2967 // set all structure fields according to the token/value pairs
2968 ldi = *artwork_info;
2969 for (i = 0; artworkinfo_tokens[i].type != -1; i++)
2971 char *token = getCacheToken(token_prefix, artworkinfo_tokens[i].text);
2972 char *value = getHashEntry(artworkinfo_cache_old, token);
2974 // if defined, use value from cache, else keep default value
2976 setSetupInfo(artworkinfo_tokens, i, value);
2979 *artwork_info = ldi;
2981 char *filename_levelinfo = getPath2(getLevelDirFromTreeInfo(level_node),
2982 LEVELINFO_FILENAME);
2983 char *filename_artworkinfo = getPath2(getSetupArtworkDir(artwork_info),
2984 ARTWORKINFO_FILENAME(type));
2986 // check if corresponding "levelinfo.conf" file has changed
2987 token_main = getCacheToken(token_prefix, "TIMESTAMP_LEVELINFO");
2988 cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
2990 if (modifiedFileTimestamp(filename_levelinfo, cache_entry))
2993 // check if corresponding "<artworkinfo>.conf" file has changed
2994 token_main = getCacheToken(token_prefix, "TIMESTAMP_ARTWORKINFO");
2995 cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
2997 if (modifiedFileTimestamp(filename_artworkinfo, cache_entry))
3000 checked_free(filename_levelinfo);
3001 checked_free(filename_artworkinfo);
3004 if (!cached && artwork_info != NULL)
3006 freeTreeInfo(artwork_info);
3011 return artwork_info;
3014 static void setArtworkInfoCacheEntry(TreeInfo *artwork_info,
3015 LevelDirTree *level_node, int type)
3017 char *identifier = level_node->subdir;
3018 char *type_string = ARTWORK_DIRECTORY(type);
3019 char *token_prefix = getCacheTokenPrefix(type_string, identifier);
3020 char *token_main = getCacheToken(token_prefix, "CACHED");
3021 boolean set_cache_timestamps = TRUE;
3024 setHashEntry(artworkinfo_cache_new, token_main, "true");
3026 if (set_cache_timestamps)
3028 char *filename_levelinfo = getPath2(getLevelDirFromTreeInfo(level_node),
3029 LEVELINFO_FILENAME);
3030 char *filename_artworkinfo = getPath2(getSetupArtworkDir(artwork_info),
3031 ARTWORKINFO_FILENAME(type));
3032 char *timestamp_levelinfo = getFileTimestampString(filename_levelinfo);
3033 char *timestamp_artworkinfo = getFileTimestampString(filename_artworkinfo);
3035 token_main = getCacheToken(token_prefix, "TIMESTAMP_LEVELINFO");
3036 setHashEntry(artworkinfo_cache_new, token_main, timestamp_levelinfo);
3038 token_main = getCacheToken(token_prefix, "TIMESTAMP_ARTWORKINFO");
3039 setHashEntry(artworkinfo_cache_new, token_main, timestamp_artworkinfo);
3041 checked_free(filename_levelinfo);
3042 checked_free(filename_artworkinfo);
3043 checked_free(timestamp_levelinfo);
3044 checked_free(timestamp_artworkinfo);
3047 ldi = *artwork_info;
3048 for (i = 0; artworkinfo_tokens[i].type != -1; i++)
3050 char *token = getCacheToken(token_prefix, artworkinfo_tokens[i].text);
3051 char *value = getSetupValue(artworkinfo_tokens[i].type,
3052 artworkinfo_tokens[i].value);
3054 setHashEntry(artworkinfo_cache_new, token, value);
3059 // ----------------------------------------------------------------------------
3060 // functions for loading level info and custom artwork info
3061 // ----------------------------------------------------------------------------
3063 int GetZipFileTreeType(char *zip_filename)
3065 static char *top_dir_path = NULL;
3066 static char *top_dir_conf_filename[NUM_BASE_TREE_TYPES] = { NULL };
3067 static char *conf_basename[NUM_BASE_TREE_TYPES] =
3069 GRAPHICSINFO_FILENAME,
3070 SOUNDSINFO_FILENAME,
3076 checked_free(top_dir_path);
3077 top_dir_path = NULL;
3079 for (j = 0; j < NUM_BASE_TREE_TYPES; j++)
3081 checked_free(top_dir_conf_filename[j]);
3082 top_dir_conf_filename[j] = NULL;
3085 char **zip_entries = zip_list(zip_filename);
3087 // check if zip file successfully opened
3088 if (zip_entries == NULL || zip_entries[0] == NULL)
3089 return TREE_TYPE_UNDEFINED;
3091 // first zip file entry is expected to be top level directory
3092 char *top_dir = zip_entries[0];
3094 // check if valid top level directory found in zip file
3095 if (!strSuffix(top_dir, "/"))
3096 return TREE_TYPE_UNDEFINED;
3098 // get filenames of valid configuration files in top level directory
3099 for (j = 0; j < NUM_BASE_TREE_TYPES; j++)
3100 top_dir_conf_filename[j] = getStringCat2(top_dir, conf_basename[j]);
3102 int tree_type = TREE_TYPE_UNDEFINED;
3105 while (zip_entries[e] != NULL)
3107 // check if every zip file entry is below top level directory
3108 if (!strPrefix(zip_entries[e], top_dir))
3109 return TREE_TYPE_UNDEFINED;
3111 // check if this zip file entry is a valid configuration filename
3112 for (j = 0; j < NUM_BASE_TREE_TYPES; j++)
3114 if (strEqual(zip_entries[e], top_dir_conf_filename[j]))
3116 // only exactly one valid configuration file allowed
3117 if (tree_type != TREE_TYPE_UNDEFINED)
3118 return TREE_TYPE_UNDEFINED;
3130 static boolean CheckZipFileForDirectory(char *zip_filename, char *directory,
3133 static char *top_dir_path = NULL;
3134 static char *top_dir_conf_filename = NULL;
3136 checked_free(top_dir_path);
3137 checked_free(top_dir_conf_filename);
3139 top_dir_path = NULL;
3140 top_dir_conf_filename = NULL;
3142 char *conf_basename = (tree_type == TREE_TYPE_LEVEL_DIR ? LEVELINFO_FILENAME :
3143 ARTWORKINFO_FILENAME(tree_type));
3145 // check if valid configuration filename determined
3146 if (conf_basename == NULL || strEqual(conf_basename, ""))
3149 char **zip_entries = zip_list(zip_filename);
3151 // check if zip file successfully opened
3152 if (zip_entries == NULL || zip_entries[0] == NULL)
3155 // first zip file entry is expected to be top level directory
3156 char *top_dir = zip_entries[0];
3158 // check if valid top level directory found in zip file
3159 if (!strSuffix(top_dir, "/"))
3162 // get path of extracted top level directory
3163 top_dir_path = getPath2(directory, top_dir);
3165 // remove trailing directory separator from top level directory path
3166 // (required to be able to check for file and directory in next step)
3167 top_dir_path[strlen(top_dir_path) - 1] = '\0';
3169 // check if zip file's top level directory already exists in target directory
3170 if (fileExists(top_dir_path)) // (checks for file and directory)
3173 // get filename of configuration file in top level directory
3174 top_dir_conf_filename = getStringCat2(top_dir, conf_basename);
3176 boolean found_top_dir_conf_filename = FALSE;
3179 while (zip_entries[i] != NULL)
3181 // check if every zip file entry is below top level directory
3182 if (!strPrefix(zip_entries[i], top_dir))
3185 // check if this zip file entry is the configuration filename
3186 if (strEqual(zip_entries[i], top_dir_conf_filename))
3187 found_top_dir_conf_filename = TRUE;
3192 // check if valid configuration filename was found in zip file
3193 if (!found_top_dir_conf_filename)
3199 char *ExtractZipFileIntoDirectory(char *zip_filename, char *directory,
3202 boolean zip_file_valid = CheckZipFileForDirectory(zip_filename, directory,
3205 if (!zip_file_valid)
3207 Error(ERR_WARN, "zip file '%s' rejected!", zip_filename);
3212 char **zip_entries = zip_extract(zip_filename, directory);
3214 if (zip_entries == NULL)
3216 Error(ERR_WARN, "zip file '%s' could not be extracted!", zip_filename);
3221 Error(ERR_INFO, "zip file '%s' successfully extracted!", zip_filename);
3223 // first zip file entry contains top level directory
3224 char *top_dir = zip_entries[0];
3226 // remove trailing directory separator from top level directory
3227 top_dir[strlen(top_dir) - 1] = '\0';
3232 static void ProcessZipFilesInDirectory(char *directory, int tree_type)
3235 DirectoryEntry *dir_entry;
3237 if ((dir = openDirectory(directory)) == NULL)
3239 // display error if directory is main "options.graphics_directory" etc.
3240 if (tree_type == TREE_TYPE_LEVEL_DIR ||
3241 directory == OPTIONS_ARTWORK_DIRECTORY(tree_type))
3242 Error(ERR_WARN, "cannot read directory '%s'", directory);
3247 while ((dir_entry = readDirectory(dir)) != NULL) // loop all entries
3249 // skip non-zip files (and also directories with zip extension)
3250 if (!strSuffixLower(dir_entry->basename, ".zip") || dir_entry->is_directory)
3253 char *zip_filename = getPath2(directory, dir_entry->basename);
3254 char *zip_filename_extracted = getStringCat2(zip_filename, ".extracted");
3255 char *zip_filename_rejected = getStringCat2(zip_filename, ".rejected");
3257 // check if zip file hasn't already been extracted or rejected
3258 if (!fileExists(zip_filename_extracted) &&
3259 !fileExists(zip_filename_rejected))
3261 char *top_dir = ExtractZipFileIntoDirectory(zip_filename, directory,
3263 char *marker_filename = (top_dir != NULL ? zip_filename_extracted :
3264 zip_filename_rejected);
3267 // create empty file to mark zip file as extracted or rejected
3268 if ((marker_file = fopen(marker_filename, MODE_WRITE)))
3269 fclose(marker_file);
3272 free(zip_filename_extracted);
3273 free(zip_filename_rejected);
3277 closeDirectory(dir);
3280 // forward declaration for recursive call by "LoadLevelInfoFromLevelDir()"
3281 static void LoadLevelInfoFromLevelDir(TreeInfo **, TreeInfo *, char *);
3283 static boolean LoadLevelInfoFromLevelConf(TreeInfo **node_first,
3284 TreeInfo *node_parent,
3285 char *level_directory,
3286 char *directory_name)
3288 char *directory_path = getPath2(level_directory, directory_name);
3289 char *filename = getPath2(directory_path, LEVELINFO_FILENAME);
3290 SetupFileHash *setup_file_hash;
3291 LevelDirTree *leveldir_new = NULL;
3294 // unless debugging, silently ignore directories without "levelinfo.conf"
3295 if (!options.debug && !fileExists(filename))
3297 free(directory_path);
3303 setup_file_hash = loadSetupFileHash(filename);
3305 if (setup_file_hash == NULL)
3307 #if DEBUG_NO_CONFIG_FILE
3308 Error(ERR_WARN, "ignoring level directory '%s'", directory_path);
3311 free(directory_path);
3317 leveldir_new = newTreeInfo();
3320 setTreeInfoToDefaultsFromParent(leveldir_new, node_parent);
3322 setTreeInfoToDefaults(leveldir_new, TREE_TYPE_LEVEL_DIR);
3324 leveldir_new->subdir = getStringCopy(directory_name);
3326 // set all structure fields according to the token/value pairs
3327 ldi = *leveldir_new;
3328 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
3329 setSetupInfo(levelinfo_tokens, i,
3330 getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
3331 *leveldir_new = ldi;
3333 if (strEqual(leveldir_new->name, ANONYMOUS_NAME))
3334 setString(&leveldir_new->name, leveldir_new->subdir);
3336 if (leveldir_new->identifier == NULL)
3337 leveldir_new->identifier = getStringCopy(leveldir_new->subdir);
3339 if (leveldir_new->name_sorting == NULL)
3340 leveldir_new->name_sorting = getStringCopy(leveldir_new->name);
3342 if (node_parent == NULL) // top level group
3344 leveldir_new->basepath = getStringCopy(level_directory);
3345 leveldir_new->fullpath = getStringCopy(leveldir_new->subdir);
3347 else // sub level group
3349 leveldir_new->basepath = getStringCopy(node_parent->basepath);
3350 leveldir_new->fullpath = getPath2(node_parent->fullpath, directory_name);
3353 leveldir_new->last_level =
3354 leveldir_new->first_level + leveldir_new->levels - 1;
3356 leveldir_new->in_user_dir =
3357 (!strEqual(leveldir_new->basepath, options.level_directory));
3359 // adjust some settings if user's private level directory was detected
3360 if (leveldir_new->sort_priority == LEVELCLASS_UNDEFINED &&
3361 leveldir_new->in_user_dir &&
3362 (strEqual(leveldir_new->subdir, getLoginName()) ||
3363 strEqual(leveldir_new->name, getLoginName()) ||
3364 strEqual(leveldir_new->author, getRealName())))
3366 leveldir_new->sort_priority = LEVELCLASS_PRIVATE_START;
3367 leveldir_new->readonly = FALSE;
3370 leveldir_new->user_defined =
3371 (leveldir_new->in_user_dir && IS_LEVELCLASS_PRIVATE(leveldir_new));
3373 leveldir_new->color = LEVELCOLOR(leveldir_new);
3375 setString(&leveldir_new->class_desc, getLevelClassDescription(leveldir_new));
3377 leveldir_new->handicap_level = // set handicap to default value
3378 (leveldir_new->user_defined || !leveldir_new->handicap ?
3379 leveldir_new->last_level : leveldir_new->first_level);
3381 DrawInitText(leveldir_new->name, 150, FC_YELLOW);
3383 pushTreeInfo(node_first, leveldir_new);
3385 freeSetupFileHash(setup_file_hash);
3387 if (leveldir_new->level_group)
3389 // create node to link back to current level directory
3390 createParentTreeInfoNode(leveldir_new);
3392 // recursively step into sub-directory and look for more level series
3393 LoadLevelInfoFromLevelDir(&leveldir_new->node_group,
3394 leveldir_new, directory_path);
3397 free(directory_path);
3403 static void LoadLevelInfoFromLevelDir(TreeInfo **node_first,
3404 TreeInfo *node_parent,
3405 char *level_directory)
3407 // ---------- 1st stage: process any level set zip files ----------
3409 ProcessZipFilesInDirectory(level_directory, TREE_TYPE_LEVEL_DIR);
3411 // ---------- 2nd stage: check for level set directories ----------
3414 DirectoryEntry *dir_entry;
3415 boolean valid_entry_found = FALSE;
3417 if ((dir = openDirectory(level_directory)) == NULL)
3419 Error(ERR_WARN, "cannot read level directory '%s'", level_directory);
3424 while ((dir_entry = readDirectory(dir)) != NULL) // loop all entries
3426 char *directory_name = dir_entry->basename;
3427 char *directory_path = getPath2(level_directory, directory_name);
3429 // skip entries for current and parent directory
3430 if (strEqual(directory_name, ".") ||
3431 strEqual(directory_name, ".."))
3433 free(directory_path);
3438 // find out if directory entry is itself a directory
3439 if (!dir_entry->is_directory) // not a directory
3441 free(directory_path);
3446 free(directory_path);
3448 if (strEqual(directory_name, GRAPHICS_DIRECTORY) ||
3449 strEqual(directory_name, SOUNDS_DIRECTORY) ||
3450 strEqual(directory_name, MUSIC_DIRECTORY))
3453 valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
3458 closeDirectory(dir);
3460 // special case: top level directory may directly contain "levelinfo.conf"
3461 if (node_parent == NULL && !valid_entry_found)
3463 // check if this directory directly contains a file "levelinfo.conf"
3464 valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
3465 level_directory, ".");
3468 if (!valid_entry_found)
3469 Error(ERR_WARN, "cannot find any valid level series in directory '%s'",
3473 boolean AdjustGraphicsForEMC(void)
3475 boolean settings_changed = FALSE;
3477 settings_changed |= adjustTreeGraphicsForEMC(leveldir_first_all);
3478 settings_changed |= adjustTreeGraphicsForEMC(leveldir_first);
3480 return settings_changed;
3483 boolean AdjustSoundsForEMC(void)
3485 boolean settings_changed = FALSE;
3487 settings_changed |= adjustTreeSoundsForEMC(leveldir_first_all);
3488 settings_changed |= adjustTreeSoundsForEMC(leveldir_first);
3490 return settings_changed;
3493 void LoadLevelInfo(void)
3495 InitUserLevelDirectory(getLoginName());
3497 DrawInitText("Loading level series", 120, FC_GREEN);
3499 LoadLevelInfoFromLevelDir(&leveldir_first, NULL, options.level_directory);
3500 LoadLevelInfoFromLevelDir(&leveldir_first, NULL, getUserLevelDir(NULL));
3502 leveldir_first = createTopTreeInfoNode(leveldir_first);
3504 /* after loading all level set information, clone the level directory tree
3505 and remove all level sets without levels (these may still contain artwork
3506 to be offered in the setup menu as "custom artwork", and are therefore
3507 checked for existing artwork in the function "LoadLevelArtworkInfo()") */
3508 leveldir_first_all = leveldir_first;
3509 cloneTree(&leveldir_first, leveldir_first_all, TRUE);
3511 AdjustGraphicsForEMC();
3512 AdjustSoundsForEMC();
3514 // before sorting, the first entries will be from the user directory
3515 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
3517 if (leveldir_first == NULL)
3518 Error(ERR_EXIT, "cannot find any valid level series in any directory");
3520 sortTreeInfo(&leveldir_first);
3522 #if ENABLE_UNUSED_CODE
3523 dumpTreeInfo(leveldir_first, 0);
3527 static boolean LoadArtworkInfoFromArtworkConf(TreeInfo **node_first,
3528 TreeInfo *node_parent,
3529 char *base_directory,
3530 char *directory_name, int type)
3532 char *directory_path = getPath2(base_directory, directory_name);
3533 char *filename = getPath2(directory_path, ARTWORKINFO_FILENAME(type));
3534 SetupFileHash *setup_file_hash = NULL;
3535 TreeInfo *artwork_new = NULL;
3538 if (fileExists(filename))
3539 setup_file_hash = loadSetupFileHash(filename);
3541 if (setup_file_hash == NULL) // no config file -- look for artwork files
3544 DirectoryEntry *dir_entry;
3545 boolean valid_file_found = FALSE;
3547 if ((dir = openDirectory(directory_path)) != NULL)
3549 while ((dir_entry = readDirectory(dir)) != NULL)
3551 if (FileIsArtworkType(dir_entry->filename, type))
3553 valid_file_found = TRUE;
3559 closeDirectory(dir);
3562 if (!valid_file_found)
3564 #if DEBUG_NO_CONFIG_FILE
3565 if (!strEqual(directory_name, "."))
3566 Error(ERR_WARN, "ignoring artwork directory '%s'", directory_path);
3569 free(directory_path);
3576 artwork_new = newTreeInfo();
3579 setTreeInfoToDefaultsFromParent(artwork_new, node_parent);
3581 setTreeInfoToDefaults(artwork_new, type);
3583 artwork_new->subdir = getStringCopy(directory_name);
3585 if (setup_file_hash) // (before defining ".color" and ".class_desc")
3587 // set all structure fields according to the token/value pairs
3589 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
3590 setSetupInfo(levelinfo_tokens, i,
3591 getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
3594 if (strEqual(artwork_new->name, ANONYMOUS_NAME))
3595 setString(&artwork_new->name, artwork_new->subdir);
3597 if (artwork_new->identifier == NULL)
3598 artwork_new->identifier = getStringCopy(artwork_new->subdir);
3600 if (artwork_new->name_sorting == NULL)
3601 artwork_new->name_sorting = getStringCopy(artwork_new->name);
3604 if (node_parent == NULL) // top level group
3606 artwork_new->basepath = getStringCopy(base_directory);
3607 artwork_new->fullpath = getStringCopy(artwork_new->subdir);
3609 else // sub level group
3611 artwork_new->basepath = getStringCopy(node_parent->basepath);
3612 artwork_new->fullpath = getPath2(node_parent->fullpath, directory_name);
3615 artwork_new->in_user_dir =
3616 (!strEqual(artwork_new->basepath, OPTIONS_ARTWORK_DIRECTORY(type)));
3618 // (may use ".sort_priority" from "setup_file_hash" above)
3619 artwork_new->color = ARTWORKCOLOR(artwork_new);
3621 setString(&artwork_new->class_desc, getLevelClassDescription(artwork_new));
3623 if (setup_file_hash == NULL) // (after determining ".user_defined")
3625 if (strEqual(artwork_new->subdir, "."))
3627 if (artwork_new->user_defined)
3629 setString(&artwork_new->identifier, "private");
3630 artwork_new->sort_priority = ARTWORKCLASS_PRIVATE;
3634 setString(&artwork_new->identifier, "classic");
3635 artwork_new->sort_priority = ARTWORKCLASS_CLASSICS;
3638 // set to new values after changing ".sort_priority"
3639 artwork_new->color = ARTWORKCOLOR(artwork_new);
3641 setString(&artwork_new->class_desc,
3642 getLevelClassDescription(artwork_new));
3646 setString(&artwork_new->identifier, artwork_new->subdir);
3649 setString(&artwork_new->name, artwork_new->identifier);
3650 setString(&artwork_new->name_sorting, artwork_new->name);
3653 pushTreeInfo(node_first, artwork_new);
3655 freeSetupFileHash(setup_file_hash);
3657 free(directory_path);
3663 static void LoadArtworkInfoFromArtworkDir(TreeInfo **node_first,
3664 TreeInfo *node_parent,
3665 char *base_directory, int type)
3667 // ---------- 1st stage: process any artwork set zip files ----------
3669 ProcessZipFilesInDirectory(base_directory, type);
3671 // ---------- 2nd stage: check for artwork set directories ----------
3674 DirectoryEntry *dir_entry;
3675 boolean valid_entry_found = FALSE;
3677 if ((dir = openDirectory(base_directory)) == NULL)
3679 // display error if directory is main "options.graphics_directory" etc.
3680 if (base_directory == OPTIONS_ARTWORK_DIRECTORY(type))
3681 Error(ERR_WARN, "cannot read directory '%s'", base_directory);
3686 while ((dir_entry = readDirectory(dir)) != NULL) // loop all entries
3688 char *directory_name = dir_entry->basename;
3689 char *directory_path = getPath2(base_directory, directory_name);
3691 // skip directory entries for current and parent directory
3692 if (strEqual(directory_name, ".") ||
3693 strEqual(directory_name, ".."))
3695 free(directory_path);
3700 // skip directory entries which are not a directory
3701 if (!dir_entry->is_directory) // not a directory
3703 free(directory_path);
3708 free(directory_path);
3710 // check if this directory contains artwork with or without config file
3711 valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
3713 directory_name, type);
3716 closeDirectory(dir);
3718 // check if this directory directly contains artwork itself
3719 valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
3720 base_directory, ".",
3722 if (!valid_entry_found)
3723 Error(ERR_WARN, "cannot find any valid artwork in directory '%s'",
3727 static TreeInfo *getDummyArtworkInfo(int type)
3729 // this is only needed when there is completely no artwork available
3730 TreeInfo *artwork_new = newTreeInfo();
3732 setTreeInfoToDefaults(artwork_new, type);
3734 setString(&artwork_new->subdir, UNDEFINED_FILENAME);
3735 setString(&artwork_new->fullpath, UNDEFINED_FILENAME);
3736 setString(&artwork_new->basepath, UNDEFINED_FILENAME);
3738 setString(&artwork_new->identifier, UNDEFINED_FILENAME);
3739 setString(&artwork_new->name, UNDEFINED_FILENAME);
3740 setString(&artwork_new->name_sorting, UNDEFINED_FILENAME);
3745 void LoadArtworkInfo(void)
3747 LoadArtworkInfoCache();
3749 DrawInitText("Looking for custom artwork", 120, FC_GREEN);
3751 LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
3752 options.graphics_directory,
3753 TREE_TYPE_GRAPHICS_DIR);
3754 LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
3755 getUserGraphicsDir(),
3756 TREE_TYPE_GRAPHICS_DIR);
3758 LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
3759 options.sounds_directory,
3760 TREE_TYPE_SOUNDS_DIR);
3761 LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
3763 TREE_TYPE_SOUNDS_DIR);
3765 LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
3766 options.music_directory,
3767 TREE_TYPE_MUSIC_DIR);
3768 LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
3770 TREE_TYPE_MUSIC_DIR);
3772 if (artwork.gfx_first == NULL)
3773 artwork.gfx_first = getDummyArtworkInfo(TREE_TYPE_GRAPHICS_DIR);
3774 if (artwork.snd_first == NULL)
3775 artwork.snd_first = getDummyArtworkInfo(TREE_TYPE_SOUNDS_DIR);
3776 if (artwork.mus_first == NULL)
3777 artwork.mus_first = getDummyArtworkInfo(TREE_TYPE_MUSIC_DIR);
3779 // before sorting, the first entries will be from the user directory
3780 artwork.gfx_current =
3781 getTreeInfoFromIdentifier(artwork.gfx_first, setup.graphics_set);
3782 if (artwork.gfx_current == NULL)
3783 artwork.gfx_current =
3784 getTreeInfoFromIdentifier(artwork.gfx_first, GFX_DEFAULT_SUBDIR);
3785 if (artwork.gfx_current == NULL)
3786 artwork.gfx_current = getFirstValidTreeInfoEntry(artwork.gfx_first);
3788 artwork.snd_current =
3789 getTreeInfoFromIdentifier(artwork.snd_first, setup.sounds_set);
3790 if (artwork.snd_current == NULL)
3791 artwork.snd_current =
3792 getTreeInfoFromIdentifier(artwork.snd_first, SND_DEFAULT_SUBDIR);
3793 if (artwork.snd_current == NULL)
3794 artwork.snd_current = getFirstValidTreeInfoEntry(artwork.snd_first);
3796 artwork.mus_current =
3797 getTreeInfoFromIdentifier(artwork.mus_first, setup.music_set);
3798 if (artwork.mus_current == NULL)
3799 artwork.mus_current =
3800 getTreeInfoFromIdentifier(artwork.mus_first, MUS_DEFAULT_SUBDIR);
3801 if (artwork.mus_current == NULL)
3802 artwork.mus_current = getFirstValidTreeInfoEntry(artwork.mus_first);
3804 artwork.gfx_current_identifier = artwork.gfx_current->identifier;
3805 artwork.snd_current_identifier = artwork.snd_current->identifier;
3806 artwork.mus_current_identifier = artwork.mus_current->identifier;
3808 #if ENABLE_UNUSED_CODE
3809 printf("graphics set == %s\n\n", artwork.gfx_current_identifier);
3810 printf("sounds set == %s\n\n", artwork.snd_current_identifier);
3811 printf("music set == %s\n\n", artwork.mus_current_identifier);
3814 sortTreeInfo(&artwork.gfx_first);
3815 sortTreeInfo(&artwork.snd_first);
3816 sortTreeInfo(&artwork.mus_first);
3818 #if ENABLE_UNUSED_CODE
3819 dumpTreeInfo(artwork.gfx_first, 0);
3820 dumpTreeInfo(artwork.snd_first, 0);
3821 dumpTreeInfo(artwork.mus_first, 0);
3825 static void LoadArtworkInfoFromLevelInfo(ArtworkDirTree **artwork_node,
3826 LevelDirTree *level_node)
3828 int type = (*artwork_node)->type;
3830 // recursively check all level directories for artwork sub-directories
3834 // check all tree entries for artwork, but skip parent link entries
3835 if (!level_node->parent_link)
3837 TreeInfo *artwork_new = getArtworkInfoCacheEntry(level_node, type);
3838 boolean cached = (artwork_new != NULL);
3842 pushTreeInfo(artwork_node, artwork_new);
3846 TreeInfo *topnode_last = *artwork_node;
3847 char *path = getPath2(getLevelDirFromTreeInfo(level_node),
3848 ARTWORK_DIRECTORY(type));
3850 LoadArtworkInfoFromArtworkDir(artwork_node, NULL, path, type);
3852 if (topnode_last != *artwork_node) // check for newly added node
3854 artwork_new = *artwork_node;
3856 setString(&artwork_new->identifier, level_node->subdir);
3857 setString(&artwork_new->name, level_node->name);
3858 setString(&artwork_new->name_sorting, level_node->name_sorting);
3860 artwork_new->sort_priority = level_node->sort_priority;
3861 artwork_new->color = LEVELCOLOR(artwork_new);
3867 // insert artwork info (from old cache or filesystem) into new cache
3868 if (artwork_new != NULL)
3869 setArtworkInfoCacheEntry(artwork_new, level_node, type);
3872 DrawInitText(level_node->name, 150, FC_YELLOW);
3874 if (level_node->node_group != NULL)
3875 LoadArtworkInfoFromLevelInfo(artwork_node, level_node->node_group);
3877 level_node = level_node->next;
3881 void LoadLevelArtworkInfo(void)
3883 print_timestamp_init("LoadLevelArtworkInfo");
3885 DrawInitText("Looking for custom level artwork", 120, FC_GREEN);
3887 print_timestamp_time("DrawTimeText");
3889 LoadArtworkInfoFromLevelInfo(&artwork.gfx_first, leveldir_first_all);
3890 print_timestamp_time("LoadArtworkInfoFromLevelInfo (gfx)");
3891 LoadArtworkInfoFromLevelInfo(&artwork.snd_first, leveldir_first_all);
3892 print_timestamp_time("LoadArtworkInfoFromLevelInfo (snd)");
3893 LoadArtworkInfoFromLevelInfo(&artwork.mus_first, leveldir_first_all);
3894 print_timestamp_time("LoadArtworkInfoFromLevelInfo (mus)");
3896 SaveArtworkInfoCache();
3898 print_timestamp_time("SaveArtworkInfoCache");
3900 // needed for reloading level artwork not known at ealier stage
3902 if (!strEqual(artwork.gfx_current_identifier, setup.graphics_set))
3904 artwork.gfx_current =
3905 getTreeInfoFromIdentifier(artwork.gfx_first, setup.graphics_set);
3906 if (artwork.gfx_current == NULL)
3907 artwork.gfx_current =
3908 getTreeInfoFromIdentifier(artwork.gfx_first, GFX_DEFAULT_SUBDIR);
3909 if (artwork.gfx_current == NULL)
3910 artwork.gfx_current = getFirstValidTreeInfoEntry(artwork.gfx_first);
3913 if (!strEqual(artwork.snd_current_identifier, setup.sounds_set))
3915 artwork.snd_current =
3916 getTreeInfoFromIdentifier(artwork.snd_first, setup.sounds_set);
3917 if (artwork.snd_current == NULL)
3918 artwork.snd_current =
3919 getTreeInfoFromIdentifier(artwork.snd_first, SND_DEFAULT_SUBDIR);
3920 if (artwork.snd_current == NULL)
3921 artwork.snd_current = getFirstValidTreeInfoEntry(artwork.snd_first);
3924 if (!strEqual(artwork.mus_current_identifier, setup.music_set))
3926 artwork.mus_current =
3927 getTreeInfoFromIdentifier(artwork.mus_first, setup.music_set);
3928 if (artwork.mus_current == NULL)
3929 artwork.mus_current =
3930 getTreeInfoFromIdentifier(artwork.mus_first, MUS_DEFAULT_SUBDIR);
3931 if (artwork.mus_current == NULL)
3932 artwork.mus_current = getFirstValidTreeInfoEntry(artwork.mus_first);
3935 print_timestamp_time("getTreeInfoFromIdentifier");
3937 sortTreeInfo(&artwork.gfx_first);
3938 sortTreeInfo(&artwork.snd_first);
3939 sortTreeInfo(&artwork.mus_first);
3941 print_timestamp_time("sortTreeInfo");
3943 #if ENABLE_UNUSED_CODE
3944 dumpTreeInfo(artwork.gfx_first, 0);
3945 dumpTreeInfo(artwork.snd_first, 0);
3946 dumpTreeInfo(artwork.mus_first, 0);
3949 print_timestamp_done("LoadLevelArtworkInfo");
3952 static boolean AddTreeSetToTreeInfoExt(TreeInfo *tree_node_old, char *tree_dir,
3953 char *tree_subdir_new, int type)
3955 if (tree_node_old == NULL)
3957 if (type == TREE_TYPE_LEVEL_DIR)
3959 // get level info tree node of personal user level set
3960 tree_node_old = getTreeInfoFromIdentifier(leveldir_first, getLoginName());
3962 // this may happen if "setup.internal.create_user_levelset" is FALSE
3963 // or if file "levelinfo.conf" is missing in personal user level set
3964 if (tree_node_old == NULL)
3965 tree_node_old = leveldir_first->node_group;
3969 // get artwork info tree node of first artwork set
3970 tree_node_old = ARTWORK_FIRST_NODE(artwork, type);
3974 if (tree_dir == NULL)
3975 tree_dir = TREE_USERDIR(type);
3977 if (tree_node_old == NULL ||
3979 tree_subdir_new == NULL) // should not happen
3982 int draw_deactivation_mask = GetDrawDeactivationMask();
3984 // override draw deactivation mask (temporarily disable drawing)
3985 SetDrawDeactivationMask(REDRAW_ALL);
3987 if (type == TREE_TYPE_LEVEL_DIR)
3989 // load new level set config and add it next to first user level set
3990 LoadLevelInfoFromLevelConf(&tree_node_old->next,
3991 tree_node_old->node_parent,
3992 tree_dir, tree_subdir_new);
3996 // load new artwork set config and add it next to first artwork set
3997 LoadArtworkInfoFromArtworkConf(&tree_node_old->next,
3998 tree_node_old->node_parent,
3999 tree_dir, tree_subdir_new, type);
4002 // set draw deactivation mask to previous value
4003 SetDrawDeactivationMask(draw_deactivation_mask);
4005 // get first node of level or artwork info tree
4006 TreeInfo **tree_node_first = TREE_FIRST_NODE_PTR(type);
4008 // get tree info node of newly added level or artwork set
4009 TreeInfo *tree_node_new = getTreeInfoFromIdentifier(*tree_node_first,
4012 if (tree_node_new == NULL) // should not happen
4015 // correct top link and parent node link of newly created tree node
4016 tree_node_new->node_top = tree_node_old->node_top;
4017 tree_node_new->node_parent = tree_node_old->node_parent;
4019 // sort tree info to adjust position of newly added tree set
4020 sortTreeInfo(tree_node_first);
4025 void AddTreeSetToTreeInfo(TreeInfo *tree_node, char *tree_dir,
4026 char *tree_subdir_new, int type)
4028 if (!AddTreeSetToTreeInfoExt(tree_node, tree_dir, tree_subdir_new, type))
4029 Error(ERR_EXIT, "internal tree info structure corrupted -- aborting");
4032 void AddUserLevelSetToLevelInfo(char *level_subdir_new)
4034 AddTreeSetToTreeInfo(NULL, NULL, level_subdir_new, TREE_TYPE_LEVEL_DIR);
4037 char *getArtworkIdentifierForUserLevelSet(int type)
4039 char *classic_artwork_set = getClassicArtworkSet(type);
4041 // check for custom artwork configured in "levelinfo.conf"
4042 char *leveldir_artwork_set =
4043 *LEVELDIR_ARTWORK_SET_PTR(leveldir_current, type);
4044 boolean has_leveldir_artwork_set =
4045 (leveldir_artwork_set != NULL && !strEqual(leveldir_artwork_set,
4046 classic_artwork_set));
4048 // check for custom artwork in sub-directory "graphics" etc.
4049 TreeInfo *artwork_first_node = ARTWORK_FIRST_NODE(artwork, type);
4050 char *leveldir_identifier = leveldir_current->identifier;
4051 boolean has_artwork_subdir =
4052 (getTreeInfoFromIdentifier(artwork_first_node,
4053 leveldir_identifier) != NULL);
4055 return (has_leveldir_artwork_set ? leveldir_artwork_set :
4056 has_artwork_subdir ? leveldir_identifier :
4057 classic_artwork_set);
4060 TreeInfo *getArtworkTreeInfoForUserLevelSet(int type)
4062 char *artwork_set = getArtworkIdentifierForUserLevelSet(type);
4063 TreeInfo *artwork_first_node = ARTWORK_FIRST_NODE(artwork, type);
4064 TreeInfo *ti = getTreeInfoFromIdentifier(artwork_first_node, artwork_set);
4068 ti = getTreeInfoFromIdentifier(artwork_first_node,
4069 ARTWORK_DEFAULT_SUBDIR(type));
4071 Error(ERR_EXIT, "cannot find default graphics -- should not happen");
4077 boolean checkIfCustomArtworkExistsForCurrentLevelSet(void)
4079 char *graphics_set =
4080 getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_GRAPHICS);
4082 getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_SOUNDS);
4084 getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_MUSIC);
4086 return (!strEqual(graphics_set, GFX_CLASSIC_SUBDIR) ||
4087 !strEqual(sounds_set, SND_CLASSIC_SUBDIR) ||
4088 !strEqual(music_set, MUS_CLASSIC_SUBDIR));
4091 boolean UpdateUserLevelSet(char *level_subdir, char *level_name,
4092 char *level_author, int num_levels)
4094 char *filename = getPath2(getUserLevelDir(level_subdir), LEVELINFO_FILENAME);
4095 char *filename_tmp = getStringCat2(filename, ".tmp");
4097 FILE *file_tmp = NULL;
4098 char line[MAX_LINE_LEN];
4099 boolean success = FALSE;
4100 LevelDirTree *leveldir = getTreeInfoFromIdentifier(leveldir_first,
4102 // update values in level directory tree
4104 if (level_name != NULL)
4105 setString(&leveldir->name, level_name);
4107 if (level_author != NULL)
4108 setString(&leveldir->author, level_author);
4110 if (num_levels != -1)
4111 leveldir->levels = num_levels;
4113 // update values that depend on other values
4115 setString(&leveldir->name_sorting, leveldir->name);
4117 leveldir->last_level = leveldir->first_level + leveldir->levels - 1;
4119 // sort order of level sets may have changed
4120 sortTreeInfo(&leveldir_first);
4122 if ((file = fopen(filename, MODE_READ)) &&
4123 (file_tmp = fopen(filename_tmp, MODE_WRITE)))
4125 while (fgets(line, MAX_LINE_LEN, file))
4127 if (strPrefix(line, "name:") && level_name != NULL)
4128 fprintf(file_tmp, "%-32s%s\n", "name:", level_name);
4129 else if (strPrefix(line, "author:") && level_author != NULL)
4130 fprintf(file_tmp, "%-32s%s\n", "author:", level_author);
4131 else if (strPrefix(line, "levels:") && num_levels != -1)
4132 fprintf(file_tmp, "%-32s%d\n", "levels:", num_levels);
4134 fputs(line, file_tmp);
4147 success = (rename(filename_tmp, filename) == 0);
4155 boolean CreateUserLevelSet(char *level_subdir, char *level_name,
4156 char *level_author, int num_levels,
4157 boolean use_artwork_set)
4159 LevelDirTree *level_info;
4164 // create user level sub-directory, if needed
4165 createDirectory(getUserLevelDir(level_subdir), "user level", PERMS_PRIVATE);
4167 filename = getPath2(getUserLevelDir(level_subdir), LEVELINFO_FILENAME);
4169 if (!(file = fopen(filename, MODE_WRITE)))
4171 Error(ERR_WARN, "cannot write level info file '%s'", filename);
4177 level_info = newTreeInfo();
4179 // always start with reliable default values
4180 setTreeInfoToDefaults(level_info, TREE_TYPE_LEVEL_DIR);
4182 setString(&level_info->name, level_name);
4183 setString(&level_info->author, level_author);
4184 level_info->levels = num_levels;
4185 level_info->first_level = 1;
4186 level_info->sort_priority = LEVELCLASS_PRIVATE_START;
4187 level_info->readonly = FALSE;
4189 if (use_artwork_set)
4191 level_info->graphics_set =
4192 getStringCopy(getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_GRAPHICS));
4193 level_info->sounds_set =
4194 getStringCopy(getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_SOUNDS));
4195 level_info->music_set =
4196 getStringCopy(getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_MUSIC));
4199 token_value_position = TOKEN_VALUE_POSITION_SHORT;
4201 fprintFileHeader(file, LEVELINFO_FILENAME);
4204 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
4206 if (i == LEVELINFO_TOKEN_NAME ||
4207 i == LEVELINFO_TOKEN_AUTHOR ||
4208 i == LEVELINFO_TOKEN_LEVELS ||
4209 i == LEVELINFO_TOKEN_FIRST_LEVEL ||
4210 i == LEVELINFO_TOKEN_SORT_PRIORITY ||
4211 i == LEVELINFO_TOKEN_READONLY ||
4212 (use_artwork_set && (i == LEVELINFO_TOKEN_GRAPHICS_SET ||
4213 i == LEVELINFO_TOKEN_SOUNDS_SET ||
4214 i == LEVELINFO_TOKEN_MUSIC_SET)))
4215 fprintf(file, "%s\n", getSetupLine(levelinfo_tokens, "", i));
4217 // just to make things nicer :)
4218 if (i == LEVELINFO_TOKEN_AUTHOR ||
4219 i == LEVELINFO_TOKEN_FIRST_LEVEL ||
4220 (use_artwork_set && i == LEVELINFO_TOKEN_READONLY))
4221 fprintf(file, "\n");
4224 token_value_position = TOKEN_VALUE_POSITION_DEFAULT;
4228 SetFilePermissions(filename, PERMS_PRIVATE);
4230 freeTreeInfo(level_info);
4236 static void SaveUserLevelInfo(void)
4238 CreateUserLevelSet(getLoginName(), getLoginName(), getRealName(), 100, FALSE);
4241 char *getSetupValue(int type, void *value)
4243 static char value_string[MAX_LINE_LEN];
4251 strcpy(value_string, (*(boolean *)value ? "true" : "false"));
4255 strcpy(value_string, (*(boolean *)value ? "on" : "off"));
4259 strcpy(value_string, (*(int *)value == AUTO ? "auto" :
4260 *(int *)value == FALSE ? "off" : "on"));
4264 strcpy(value_string, (*(boolean *)value ? "yes" : "no"));
4267 case TYPE_YES_NO_AUTO:
4268 strcpy(value_string, (*(int *)value == AUTO ? "auto" :
4269 *(int *)value == FALSE ? "no" : "yes"));
4273 strcpy(value_string, (*(boolean *)value ? "AGA" : "ECS"));
4277 strcpy(value_string, getKeyNameFromKey(*(Key *)value));
4281 strcpy(value_string, getX11KeyNameFromKey(*(Key *)value));
4285 sprintf(value_string, "%d", *(int *)value);
4289 if (*(char **)value == NULL)
4292 strcpy(value_string, *(char **)value);
4296 sprintf(value_string, "player_%d", *(int *)value + 1);
4300 value_string[0] = '\0';
4304 if (type & TYPE_GHOSTED)
4305 strcpy(value_string, "n/a");
4307 return value_string;
4310 char *getSetupLine(struct TokenInfo *token_info, char *prefix, int token_nr)
4314 static char token_string[MAX_LINE_LEN];
4315 int token_type = token_info[token_nr].type;
4316 void *setup_value = token_info[token_nr].value;
4317 char *token_text = token_info[token_nr].text;
4318 char *value_string = getSetupValue(token_type, setup_value);
4320 // build complete token string
4321 sprintf(token_string, "%s%s", prefix, token_text);
4323 // build setup entry line
4324 line = getFormattedSetupEntry(token_string, value_string);
4326 if (token_type == TYPE_KEY_X11)
4328 Key key = *(Key *)setup_value;
4329 char *keyname = getKeyNameFromKey(key);
4331 // add comment, if useful
4332 if (!strEqual(keyname, "(undefined)") &&
4333 !strEqual(keyname, "(unknown)"))
4335 // add at least one whitespace
4337 for (i = strlen(line); i < token_comment_position; i++)
4341 strcat(line, keyname);
4348 void LoadLevelSetup_LastSeries(void)
4350 // --------------------------------------------------------------------------
4351 // ~/.<program>/levelsetup.conf
4352 // --------------------------------------------------------------------------
4354 char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
4355 SetupFileHash *level_setup_hash = NULL;
4357 // always start with reliable default values
4358 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
4360 if (!strEqual(DEFAULT_LEVELSET, UNDEFINED_LEVELSET))
4362 leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
4364 if (leveldir_current == NULL)
4365 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
4368 if ((level_setup_hash = loadSetupFileHash(filename)))
4370 char *last_level_series =
4371 getHashEntry(level_setup_hash, TOKEN_STR_LAST_LEVEL_SERIES);
4373 leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
4375 if (leveldir_current == NULL)
4376 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
4378 freeSetupFileHash(level_setup_hash);
4382 Error(ERR_DEBUG, "using default setup values");
4388 static void SaveLevelSetup_LastSeries_Ext(boolean deactivate_last_level_series)
4390 // --------------------------------------------------------------------------
4391 // ~/.<program>/levelsetup.conf
4392 // --------------------------------------------------------------------------
4394 // check if the current level directory structure is available at this point
4395 if (leveldir_current == NULL)
4398 char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
4399 char *level_subdir = leveldir_current->subdir;
4402 InitUserDataDirectory();
4404 if (!(file = fopen(filename, MODE_WRITE)))
4406 Error(ERR_WARN, "cannot write setup file '%s'", filename);
4413 fprintFileHeader(file, LEVELSETUP_FILENAME);
4415 if (deactivate_last_level_series)
4416 fprintf(file, "# %s\n# ", "the following level set may have caused a problem and was deactivated");
4418 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_LEVEL_SERIES,
4423 SetFilePermissions(filename, PERMS_PRIVATE);
4428 void SaveLevelSetup_LastSeries(void)
4430 SaveLevelSetup_LastSeries_Ext(FALSE);
4433 void SaveLevelSetup_LastSeries_Deactivate(void)
4435 SaveLevelSetup_LastSeries_Ext(TRUE);
4438 static void checkSeriesInfo(void)
4440 static char *level_directory = NULL;
4443 DirectoryEntry *dir_entry;
4446 checked_free(level_directory);
4448 // check for more levels besides the 'levels' field of 'levelinfo.conf'
4450 level_directory = getPath2((leveldir_current->in_user_dir ?
4451 getUserLevelDir(NULL) :
4452 options.level_directory),
4453 leveldir_current->fullpath);
4455 if ((dir = openDirectory(level_directory)) == NULL)
4457 Error(ERR_WARN, "cannot read level directory '%s'", level_directory);
4463 while ((dir_entry = readDirectory(dir)) != NULL) // loop all entries
4465 if (strlen(dir_entry->basename) > 4 &&
4466 dir_entry->basename[3] == '.' &&
4467 strEqual(&dir_entry->basename[4], LEVELFILE_EXTENSION))
4469 char levelnum_str[4];
4472 strncpy(levelnum_str, dir_entry->basename, 3);
4473 levelnum_str[3] = '\0';
4475 levelnum_value = atoi(levelnum_str);
4477 if (levelnum_value < leveldir_current->first_level)
4479 Error(ERR_WARN, "additional level %d found", levelnum_value);
4480 leveldir_current->first_level = levelnum_value;
4482 else if (levelnum_value > leveldir_current->last_level)
4484 Error(ERR_WARN, "additional level %d found", levelnum_value);
4485 leveldir_current->last_level = levelnum_value;
4491 closeDirectory(dir);
4494 void LoadLevelSetup_SeriesInfo(void)
4497 SetupFileHash *level_setup_hash = NULL;
4498 char *level_subdir = leveldir_current->subdir;
4501 // always start with reliable default values
4502 level_nr = leveldir_current->first_level;
4504 for (i = 0; i < MAX_LEVELS; i++)
4506 LevelStats_setPlayed(i, 0);
4507 LevelStats_setSolved(i, 0);
4512 // --------------------------------------------------------------------------
4513 // ~/.<program>/levelsetup/<level series>/levelsetup.conf
4514 // --------------------------------------------------------------------------
4516 level_subdir = leveldir_current->subdir;
4518 filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
4520 if ((level_setup_hash = loadSetupFileHash(filename)))
4524 // get last played level in this level set
4526 token_value = getHashEntry(level_setup_hash, TOKEN_STR_LAST_PLAYED_LEVEL);
4530 level_nr = atoi(token_value);
4532 if (level_nr < leveldir_current->first_level)
4533 level_nr = leveldir_current->first_level;
4534 if (level_nr > leveldir_current->last_level)
4535 level_nr = leveldir_current->last_level;
4538 // get handicap level in this level set
4540 token_value = getHashEntry(level_setup_hash, TOKEN_STR_HANDICAP_LEVEL);
4544 int level_nr = atoi(token_value);
4546 if (level_nr < leveldir_current->first_level)
4547 level_nr = leveldir_current->first_level;
4548 if (level_nr > leveldir_current->last_level + 1)
4549 level_nr = leveldir_current->last_level;
4551 if (leveldir_current->user_defined || !leveldir_current->handicap)
4552 level_nr = leveldir_current->last_level;
4554 leveldir_current->handicap_level = level_nr;
4557 // get number of played and solved levels in this level set
4559 BEGIN_HASH_ITERATION(level_setup_hash, itr)
4561 char *token = HASH_ITERATION_TOKEN(itr);
4562 char *value = HASH_ITERATION_VALUE(itr);
4564 if (strlen(token) == 3 &&
4565 token[0] >= '0' && token[0] <= '9' &&
4566 token[1] >= '0' && token[1] <= '9' &&
4567 token[2] >= '0' && token[2] <= '9')
4569 int level_nr = atoi(token);
4572 LevelStats_setPlayed(level_nr, atoi(value)); // read 1st column
4574 value = strchr(value, ' ');
4577 LevelStats_setSolved(level_nr, atoi(value)); // read 2nd column
4580 END_HASH_ITERATION(hash, itr)
4582 freeSetupFileHash(level_setup_hash);
4586 Error(ERR_DEBUG, "using default setup values");
4592 void SaveLevelSetup_SeriesInfo(void)
4595 char *level_subdir = leveldir_current->subdir;
4596 char *level_nr_str = int2str(level_nr, 0);
4597 char *handicap_level_str = int2str(leveldir_current->handicap_level, 0);
4601 // --------------------------------------------------------------------------
4602 // ~/.<program>/levelsetup/<level series>/levelsetup.conf
4603 // --------------------------------------------------------------------------
4605 InitLevelSetupDirectory(level_subdir);
4607 filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
4609 if (!(file = fopen(filename, MODE_WRITE)))
4611 Error(ERR_WARN, "cannot write setup file '%s'", filename);
4616 fprintFileHeader(file, LEVELSETUP_FILENAME);
4618 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_PLAYED_LEVEL,
4620 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_HANDICAP_LEVEL,
4621 handicap_level_str));
4623 for (i = leveldir_current->first_level; i <= leveldir_current->last_level;
4626 if (LevelStats_getPlayed(i) > 0 ||
4627 LevelStats_getSolved(i) > 0)
4632 sprintf(token, "%03d", i);
4633 sprintf(value, "%d %d", LevelStats_getPlayed(i), LevelStats_getSolved(i));
4635 fprintf(file, "%s\n", getFormattedSetupEntry(token, value));
4641 SetFilePermissions(filename, PERMS_PRIVATE);
4646 int LevelStats_getPlayed(int nr)
4648 return (nr >= 0 && nr < MAX_LEVELS ? level_stats[nr].played : 0);
4651 int LevelStats_getSolved(int nr)
4653 return (nr >= 0 && nr < MAX_LEVELS ? level_stats[nr].solved : 0);
4656 void LevelStats_setPlayed(int nr, int value)
4658 if (nr >= 0 && nr < MAX_LEVELS)
4659 level_stats[nr].played = value;
4662 void LevelStats_setSolved(int nr, int value)
4664 if (nr >= 0 && nr < MAX_LEVELS)
4665 level_stats[nr].solved = value;
4668 void LevelStats_incPlayed(int nr)
4670 if (nr >= 0 && nr < MAX_LEVELS)
4671 level_stats[nr].played++;
4674 void LevelStats_incSolved(int nr)
4676 if (nr >= 0 && nr < MAX_LEVELS)
4677 level_stats[nr].solved++;