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 if (node->graphics_set_ecs && !setup.prefer_aga_graphics &&
1306 !strEqual(node->graphics_set, node->graphics_set_ecs))
1308 setString(&node->graphics_set, node->graphics_set_ecs);
1309 settings_changed = TRUE;
1311 else if (node->graphics_set_aga && setup.prefer_aga_graphics &&
1312 !strEqual(node->graphics_set, node->graphics_set_aga))
1314 setString(&node->graphics_set, node->graphics_set_aga);
1315 settings_changed = TRUE;
1318 if (node->node_group != NULL)
1319 settings_changed |= adjustTreeGraphicsForEMC(node->node_group);
1324 return settings_changed;
1327 void dumpTreeInfo(TreeInfo *node, int depth)
1331 printf("Dumping TreeInfo:\n");
1335 for (i = 0; i < (depth + 1) * 3; i++)
1338 printf("'%s' / '%s'\n", node->identifier, node->name);
1341 // use for dumping artwork info tree
1342 printf("subdir == '%s' ['%s', '%s'] [%d])\n",
1343 node->subdir, node->fullpath, node->basepath, node->in_user_dir);
1346 if (node->node_group != NULL)
1347 dumpTreeInfo(node->node_group, depth + 1);
1353 void sortTreeInfoBySortFunction(TreeInfo **node_first,
1354 int (*compare_function)(const void *,
1357 int num_nodes = numTreeInfo(*node_first);
1358 TreeInfo **sort_array;
1359 TreeInfo *node = *node_first;
1365 // allocate array for sorting structure pointers
1366 sort_array = checked_calloc(num_nodes * sizeof(TreeInfo *));
1368 // writing structure pointers to sorting array
1369 while (i < num_nodes && node) // double boundary check...
1371 sort_array[i] = node;
1377 // sorting the structure pointers in the sorting array
1378 qsort(sort_array, num_nodes, sizeof(TreeInfo *),
1381 // update the linkage of list elements with the sorted node array
1382 for (i = 0; i < num_nodes - 1; i++)
1383 sort_array[i]->next = sort_array[i + 1];
1384 sort_array[num_nodes - 1]->next = NULL;
1386 // update the linkage of the main list anchor pointer
1387 *node_first = sort_array[0];
1391 // now recursively sort the level group structures
1395 if (node->node_group != NULL)
1396 sortTreeInfoBySortFunction(&node->node_group, compare_function);
1402 void sortTreeInfo(TreeInfo **node_first)
1404 sortTreeInfoBySortFunction(node_first, compareTreeInfoEntries);
1408 // ============================================================================
1409 // some stuff from "files.c"
1410 // ============================================================================
1412 #if defined(PLATFORM_WIN32)
1414 #define S_IRGRP S_IRUSR
1417 #define S_IROTH S_IRUSR
1420 #define S_IWGRP S_IWUSR
1423 #define S_IWOTH S_IWUSR
1426 #define S_IXGRP S_IXUSR
1429 #define S_IXOTH S_IXUSR
1432 #define S_IRWXG (S_IRGRP | S_IWGRP | S_IXGRP)
1437 #endif // PLATFORM_WIN32
1439 // file permissions for newly written files
1440 #define MODE_R_ALL (S_IRUSR | S_IRGRP | S_IROTH)
1441 #define MODE_W_ALL (S_IWUSR | S_IWGRP | S_IWOTH)
1442 #define MODE_X_ALL (S_IXUSR | S_IXGRP | S_IXOTH)
1444 #define MODE_W_PRIVATE (S_IWUSR)
1445 #define MODE_W_PUBLIC_FILE (S_IWUSR | S_IWGRP)
1446 #define MODE_W_PUBLIC_DIR (S_IWUSR | S_IWGRP | S_ISGID)
1448 #define DIR_PERMS_PRIVATE (MODE_R_ALL | MODE_X_ALL | MODE_W_PRIVATE)
1449 #define DIR_PERMS_PUBLIC (MODE_R_ALL | MODE_X_ALL | MODE_W_PUBLIC_DIR)
1450 #define DIR_PERMS_PUBLIC_ALL (MODE_R_ALL | MODE_X_ALL | MODE_W_ALL)
1452 #define FILE_PERMS_PRIVATE (MODE_R_ALL | MODE_W_PRIVATE)
1453 #define FILE_PERMS_PUBLIC (MODE_R_ALL | MODE_W_PUBLIC_FILE)
1454 #define FILE_PERMS_PUBLIC_ALL (MODE_R_ALL | MODE_W_ALL)
1457 char *getHomeDir(void)
1459 static char *dir = NULL;
1461 #if defined(PLATFORM_WIN32)
1464 dir = checked_malloc(MAX_PATH + 1);
1466 if (!SUCCEEDED(SHGetFolderPath(NULL, CSIDL_PERSONAL, NULL, 0, dir)))
1469 #elif defined(PLATFORM_UNIX)
1472 if ((dir = getenv("HOME")) == NULL)
1476 if ((pwd = getpwuid(getuid())) != NULL)
1477 dir = getStringCopy(pwd->pw_dir);
1489 char *getCommonDataDir(void)
1491 static char *common_data_dir = NULL;
1493 #if defined(PLATFORM_WIN32)
1494 if (common_data_dir == NULL)
1496 char *dir = checked_malloc(MAX_PATH + 1);
1498 if (SUCCEEDED(SHGetFolderPath(NULL, CSIDL_COMMON_DOCUMENTS, NULL, 0, dir))
1499 && !strEqual(dir, "")) // empty for Windows 95/98
1500 common_data_dir = getPath2(dir, program.userdata_subdir);
1502 common_data_dir = options.rw_base_directory;
1505 if (common_data_dir == NULL)
1506 common_data_dir = options.rw_base_directory;
1509 return common_data_dir;
1512 char *getPersonalDataDir(void)
1514 static char *personal_data_dir = NULL;
1516 #if defined(PLATFORM_MACOSX)
1517 if (personal_data_dir == NULL)
1518 personal_data_dir = getPath2(getHomeDir(), "Documents");
1520 if (personal_data_dir == NULL)
1521 personal_data_dir = getHomeDir();
1524 return personal_data_dir;
1527 char *getUserGameDataDir(void)
1529 static char *user_game_data_dir = NULL;
1531 #if defined(PLATFORM_ANDROID)
1532 if (user_game_data_dir == NULL)
1533 user_game_data_dir = (char *)(SDL_AndroidGetExternalStorageState() &
1534 SDL_ANDROID_EXTERNAL_STORAGE_WRITE ?
1535 SDL_AndroidGetExternalStoragePath() :
1536 SDL_AndroidGetInternalStoragePath());
1538 if (user_game_data_dir == NULL)
1539 user_game_data_dir = getPath2(getPersonalDataDir(),
1540 program.userdata_subdir);
1543 return user_game_data_dir;
1546 char *getSetupDir(void)
1548 return getUserGameDataDir();
1551 static mode_t posix_umask(mode_t mask)
1553 #if defined(PLATFORM_UNIX)
1560 static int posix_mkdir(const char *pathname, mode_t mode)
1562 #if defined(PLATFORM_WIN32)
1563 return mkdir(pathname);
1565 return mkdir(pathname, mode);
1569 static boolean posix_process_running_setgid(void)
1571 #if defined(PLATFORM_UNIX)
1572 return (getgid() != getegid());
1578 void createDirectory(char *dir, char *text, int permission_class)
1580 if (directoryExists(dir))
1583 // leave "other" permissions in umask untouched, but ensure group parts
1584 // of USERDATA_DIR_MODE are not masked
1585 mode_t dir_mode = (permission_class == PERMS_PRIVATE ?
1586 DIR_PERMS_PRIVATE : DIR_PERMS_PUBLIC);
1587 mode_t last_umask = posix_umask(0);
1588 mode_t group_umask = ~(dir_mode & S_IRWXG);
1589 int running_setgid = posix_process_running_setgid();
1591 if (permission_class == PERMS_PUBLIC)
1593 // if we're setgid, protect files against "other"
1594 // else keep umask(0) to make the dir world-writable
1597 posix_umask(last_umask & group_umask);
1599 dir_mode = DIR_PERMS_PUBLIC_ALL;
1602 if (posix_mkdir(dir, dir_mode) != 0)
1603 Error(ERR_WARN, "cannot create %s directory '%s': %s",
1604 text, dir, strerror(errno));
1606 if (permission_class == PERMS_PUBLIC && !running_setgid)
1607 chmod(dir, dir_mode);
1609 posix_umask(last_umask); // restore previous umask
1612 void InitUserDataDirectory(void)
1614 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
1617 void SetFilePermissions(char *filename, int permission_class)
1619 int running_setgid = posix_process_running_setgid();
1620 int perms = (permission_class == PERMS_PRIVATE ?
1621 FILE_PERMS_PRIVATE : FILE_PERMS_PUBLIC);
1623 if (permission_class == PERMS_PUBLIC && !running_setgid)
1624 perms = FILE_PERMS_PUBLIC_ALL;
1626 chmod(filename, perms);
1629 char *getCookie(char *file_type)
1631 static char cookie[MAX_COOKIE_LEN + 1];
1633 if (strlen(program.cookie_prefix) + 1 +
1634 strlen(file_type) + strlen("_FILE_VERSION_x.x") > MAX_COOKIE_LEN)
1635 return "[COOKIE ERROR]"; // should never happen
1637 sprintf(cookie, "%s_%s_FILE_VERSION_%d.%d",
1638 program.cookie_prefix, file_type,
1639 program.version_super, program.version_major);
1644 void fprintFileHeader(FILE *file, char *basename)
1646 char *prefix = "# ";
1649 fprintf_line_with_prefix(file, prefix, sep1, 77);
1650 fprintf(file, "%s%s\n", prefix, basename);
1651 fprintf_line_with_prefix(file, prefix, sep1, 77);
1652 fprintf(file, "\n");
1655 int getFileVersionFromCookieString(const char *cookie)
1657 const char *ptr_cookie1, *ptr_cookie2;
1658 const char *pattern1 = "_FILE_VERSION_";
1659 const char *pattern2 = "?.?";
1660 const int len_cookie = strlen(cookie);
1661 const int len_pattern1 = strlen(pattern1);
1662 const int len_pattern2 = strlen(pattern2);
1663 const int len_pattern = len_pattern1 + len_pattern2;
1664 int version_super, version_major;
1666 if (len_cookie <= len_pattern)
1669 ptr_cookie1 = &cookie[len_cookie - len_pattern];
1670 ptr_cookie2 = &cookie[len_cookie - len_pattern2];
1672 if (strncmp(ptr_cookie1, pattern1, len_pattern1) != 0)
1675 if (ptr_cookie2[0] < '0' || ptr_cookie2[0] > '9' ||
1676 ptr_cookie2[1] != '.' ||
1677 ptr_cookie2[2] < '0' || ptr_cookie2[2] > '9')
1680 version_super = ptr_cookie2[0] - '0';
1681 version_major = ptr_cookie2[2] - '0';
1683 return VERSION_IDENT(version_super, version_major, 0, 0);
1686 boolean checkCookieString(const char *cookie, const char *template)
1688 const char *pattern = "_FILE_VERSION_?.?";
1689 const int len_cookie = strlen(cookie);
1690 const int len_template = strlen(template);
1691 const int len_pattern = strlen(pattern);
1693 if (len_cookie != len_template)
1696 if (strncmp(cookie, template, len_cookie - len_pattern) != 0)
1703 // ----------------------------------------------------------------------------
1704 // setup file list and hash handling functions
1705 // ----------------------------------------------------------------------------
1707 char *getFormattedSetupEntry(char *token, char *value)
1710 static char entry[MAX_LINE_LEN];
1712 // if value is an empty string, just return token without value
1716 // start with the token and some spaces to format output line
1717 sprintf(entry, "%s:", token);
1718 for (i = strlen(entry); i < token_value_position; i++)
1721 // continue with the token's value
1722 strcat(entry, value);
1727 SetupFileList *newSetupFileList(char *token, char *value)
1729 SetupFileList *new = checked_malloc(sizeof(SetupFileList));
1731 new->token = getStringCopy(token);
1732 new->value = getStringCopy(value);
1739 void freeSetupFileList(SetupFileList *list)
1744 checked_free(list->token);
1745 checked_free(list->value);
1748 freeSetupFileList(list->next);
1753 char *getListEntry(SetupFileList *list, char *token)
1758 if (strEqual(list->token, token))
1761 return getListEntry(list->next, token);
1764 SetupFileList *setListEntry(SetupFileList *list, char *token, char *value)
1769 if (strEqual(list->token, token))
1771 checked_free(list->value);
1773 list->value = getStringCopy(value);
1777 else if (list->next == NULL)
1778 return (list->next = newSetupFileList(token, value));
1780 return setListEntry(list->next, token, value);
1783 SetupFileList *addListEntry(SetupFileList *list, char *token, char *value)
1788 if (list->next == NULL)
1789 return (list->next = newSetupFileList(token, value));
1791 return addListEntry(list->next, token, value);
1794 #if ENABLE_UNUSED_CODE
1796 static void printSetupFileList(SetupFileList *list)
1801 printf("token: '%s'\n", list->token);
1802 printf("value: '%s'\n", list->value);
1804 printSetupFileList(list->next);
1810 DEFINE_HASHTABLE_INSERT(insert_hash_entry, char, char);
1811 DEFINE_HASHTABLE_SEARCH(search_hash_entry, char, char);
1812 DEFINE_HASHTABLE_CHANGE(change_hash_entry, char, char);
1813 DEFINE_HASHTABLE_REMOVE(remove_hash_entry, char, char);
1815 #define insert_hash_entry hashtable_insert
1816 #define search_hash_entry hashtable_search
1817 #define change_hash_entry hashtable_change
1818 #define remove_hash_entry hashtable_remove
1821 unsigned int get_hash_from_key(void *key)
1826 This algorithm (k=33) was first reported by Dan Bernstein many years ago in
1827 'comp.lang.c'. Another version of this algorithm (now favored by Bernstein)
1828 uses XOR: hash(i) = hash(i - 1) * 33 ^ str[i]; the magic of number 33 (why
1829 it works better than many other constants, prime or not) has never been
1830 adequately explained.
1832 If you just want to have a good hash function, and cannot wait, djb2
1833 is one of the best string hash functions i know. It has excellent
1834 distribution and speed on many different sets of keys and table sizes.
1835 You are not likely to do better with one of the "well known" functions
1836 such as PJW, K&R, etc.
1838 Ozan (oz) Yigit [http://www.cs.yorku.ca/~oz/hash.html]
1841 char *str = (char *)key;
1842 unsigned int hash = 5381;
1845 while ((c = *str++))
1846 hash = ((hash << 5) + hash) + c; // hash * 33 + c
1851 static int keys_are_equal(void *key1, void *key2)
1853 return (strEqual((char *)key1, (char *)key2));
1856 SetupFileHash *newSetupFileHash(void)
1858 SetupFileHash *new_hash =
1859 create_hashtable(16, 0.75, get_hash_from_key, keys_are_equal);
1861 if (new_hash == NULL)
1862 Error(ERR_EXIT, "create_hashtable() failed -- out of memory");
1867 void freeSetupFileHash(SetupFileHash *hash)
1872 hashtable_destroy(hash, 1); // 1 == also free values stored in hash
1875 char *getHashEntry(SetupFileHash *hash, char *token)
1880 return search_hash_entry(hash, token);
1883 void setHashEntry(SetupFileHash *hash, char *token, char *value)
1890 value_copy = getStringCopy(value);
1892 // change value; if it does not exist, insert it as new
1893 if (!change_hash_entry(hash, token, value_copy))
1894 if (!insert_hash_entry(hash, getStringCopy(token), value_copy))
1895 Error(ERR_EXIT, "cannot insert into hash -- aborting");
1898 char *removeHashEntry(SetupFileHash *hash, char *token)
1903 return remove_hash_entry(hash, token);
1906 #if ENABLE_UNUSED_CODE
1908 static void printSetupFileHash(SetupFileHash *hash)
1910 BEGIN_HASH_ITERATION(hash, itr)
1912 printf("token: '%s'\n", HASH_ITERATION_TOKEN(itr));
1913 printf("value: '%s'\n", HASH_ITERATION_VALUE(itr));
1915 END_HASH_ITERATION(hash, itr)
1920 #define ALLOW_TOKEN_VALUE_SEPARATOR_BEING_WHITESPACE 1
1921 #define CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING 0
1922 #define CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH 0
1924 static boolean token_value_separator_found = FALSE;
1925 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
1926 static boolean token_value_separator_warning = FALSE;
1928 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
1929 static boolean token_already_exists_warning = FALSE;
1932 static boolean getTokenValueFromSetupLineExt(char *line,
1933 char **token_ptr, char **value_ptr,
1934 char *filename, char *line_raw,
1936 boolean separator_required)
1938 static char line_copy[MAX_LINE_LEN + 1], line_raw_copy[MAX_LINE_LEN + 1];
1939 char *token, *value, *line_ptr;
1941 // when externally invoked via ReadTokenValueFromLine(), copy line buffers
1942 if (line_raw == NULL)
1944 strncpy(line_copy, line, MAX_LINE_LEN);
1945 line_copy[MAX_LINE_LEN] = '\0';
1948 strcpy(line_raw_copy, line_copy);
1949 line_raw = line_raw_copy;
1952 // cut trailing comment from input line
1953 for (line_ptr = line; *line_ptr; line_ptr++)
1955 if (*line_ptr == '#')
1962 // cut trailing whitespaces from input line
1963 for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
1964 if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
1967 // ignore empty lines
1971 // cut leading whitespaces from token
1972 for (token = line; *token; token++)
1973 if (*token != ' ' && *token != '\t')
1976 // start with empty value as reliable default
1979 token_value_separator_found = FALSE;
1981 // find end of token to determine start of value
1982 for (line_ptr = token; *line_ptr; line_ptr++)
1984 // first look for an explicit token/value separator, like ':' or '='
1985 if (*line_ptr == ':' || *line_ptr == '=')
1987 *line_ptr = '\0'; // terminate token string
1988 value = line_ptr + 1; // set beginning of value
1990 token_value_separator_found = TRUE;
1996 #if ALLOW_TOKEN_VALUE_SEPARATOR_BEING_WHITESPACE
1997 // fallback: if no token/value separator found, also allow whitespaces
1998 if (!token_value_separator_found && !separator_required)
2000 for (line_ptr = token; *line_ptr; line_ptr++)
2002 if (*line_ptr == ' ' || *line_ptr == '\t')
2004 *line_ptr = '\0'; // terminate token string
2005 value = line_ptr + 1; // set beginning of value
2007 token_value_separator_found = TRUE;
2013 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2014 if (token_value_separator_found)
2016 if (!token_value_separator_warning)
2018 Error(ERR_INFO_LINE, "-");
2020 if (filename != NULL)
2022 Error(ERR_WARN, "missing token/value separator(s) in config file:");
2023 Error(ERR_INFO, "- config file: '%s'", filename);
2027 Error(ERR_WARN, "missing token/value separator(s):");
2030 token_value_separator_warning = TRUE;
2033 if (filename != NULL)
2034 Error(ERR_INFO, "- line %d: '%s'", line_nr, line_raw);
2036 Error(ERR_INFO, "- line: '%s'", line_raw);
2042 // cut trailing whitespaces from token
2043 for (line_ptr = &token[strlen(token)]; line_ptr >= token; line_ptr--)
2044 if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
2047 // cut leading whitespaces from value
2048 for (; *value; value++)
2049 if (*value != ' ' && *value != '\t')
2058 boolean getTokenValueFromSetupLine(char *line, char **token, char **value)
2060 // while the internal (old) interface does not require a token/value
2061 // separator (for downwards compatibility with existing files which
2062 // don't use them), it is mandatory for the external (new) interface
2064 return getTokenValueFromSetupLineExt(line, token, value, NULL, NULL, 0, TRUE);
2067 static boolean loadSetupFileData(void *setup_file_data, char *filename,
2068 boolean top_recursion_level, boolean is_hash)
2070 static SetupFileHash *include_filename_hash = NULL;
2071 char line[MAX_LINE_LEN], line_raw[MAX_LINE_LEN], previous_line[MAX_LINE_LEN];
2072 char *token, *value, *line_ptr;
2073 void *insert_ptr = NULL;
2074 boolean read_continued_line = FALSE;
2076 int line_nr = 0, token_count = 0, include_count = 0;
2078 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2079 token_value_separator_warning = FALSE;
2082 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2083 token_already_exists_warning = FALSE;
2086 if (!(file = openFile(filename, MODE_READ)))
2088 #if DEBUG_NO_CONFIG_FILE
2089 Error(ERR_DEBUG, "cannot open configuration file '%s'", filename);
2095 // use "insert pointer" to store list end for constant insertion complexity
2097 insert_ptr = setup_file_data;
2099 // on top invocation, create hash to mark included files (to prevent loops)
2100 if (top_recursion_level)
2101 include_filename_hash = newSetupFileHash();
2103 // mark this file as already included (to prevent including it again)
2104 setHashEntry(include_filename_hash, getBaseNamePtr(filename), "true");
2106 while (!checkEndOfFile(file))
2108 // read next line of input file
2109 if (!getStringFromFile(file, line, MAX_LINE_LEN))
2112 // check if line was completely read and is terminated by line break
2113 if (strlen(line) > 0 && line[strlen(line) - 1] == '\n')
2116 // cut trailing line break (this can be newline and/or carriage return)
2117 for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
2118 if ((*line_ptr == '\n' || *line_ptr == '\r') && *(line_ptr + 1) == '\0')
2121 // copy raw input line for later use (mainly debugging output)
2122 strcpy(line_raw, line);
2124 if (read_continued_line)
2126 // append new line to existing line, if there is enough space
2127 if (strlen(previous_line) + strlen(line_ptr) < MAX_LINE_LEN)
2128 strcat(previous_line, line_ptr);
2130 strcpy(line, previous_line); // copy storage buffer to line
2132 read_continued_line = FALSE;
2135 // if the last character is '\', continue at next line
2136 if (strlen(line) > 0 && line[strlen(line) - 1] == '\\')
2138 line[strlen(line) - 1] = '\0'; // cut off trailing backslash
2139 strcpy(previous_line, line); // copy line to storage buffer
2141 read_continued_line = TRUE;
2146 if (!getTokenValueFromSetupLineExt(line, &token, &value, filename,
2147 line_raw, line_nr, FALSE))
2152 if (strEqual(token, "include"))
2154 if (getHashEntry(include_filename_hash, value) == NULL)
2156 char *basepath = getBasePath(filename);
2157 char *basename = getBaseName(value);
2158 char *filename_include = getPath2(basepath, basename);
2160 loadSetupFileData(setup_file_data, filename_include, FALSE, is_hash);
2164 free(filename_include);
2170 Error(ERR_WARN, "ignoring already processed file '%s'", value);
2177 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2179 getHashEntry((SetupFileHash *)setup_file_data, token);
2181 if (old_value != NULL)
2183 if (!token_already_exists_warning)
2185 Error(ERR_INFO_LINE, "-");
2186 Error(ERR_WARN, "duplicate token(s) found in config file:");
2187 Error(ERR_INFO, "- config file: '%s'", filename);
2189 token_already_exists_warning = TRUE;
2192 Error(ERR_INFO, "- token: '%s' (in line %d)", token, line_nr);
2193 Error(ERR_INFO, " old value: '%s'", old_value);
2194 Error(ERR_INFO, " new value: '%s'", value);
2198 setHashEntry((SetupFileHash *)setup_file_data, token, value);
2202 insert_ptr = addListEntry((SetupFileList *)insert_ptr, token, value);
2212 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2213 if (token_value_separator_warning)
2214 Error(ERR_INFO_LINE, "-");
2217 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2218 if (token_already_exists_warning)
2219 Error(ERR_INFO_LINE, "-");
2222 if (token_count == 0 && include_count == 0)
2223 Error(ERR_WARN, "configuration file '%s' is empty", filename);
2225 if (top_recursion_level)
2226 freeSetupFileHash(include_filename_hash);
2231 static void saveSetupFileHash(SetupFileHash *hash, char *filename)
2235 if (!(file = fopen(filename, MODE_WRITE)))
2237 Error(ERR_WARN, "cannot write configuration file '%s'", filename);
2242 BEGIN_HASH_ITERATION(hash, itr)
2244 fprintf(file, "%s\n", getFormattedSetupEntry(HASH_ITERATION_TOKEN(itr),
2245 HASH_ITERATION_VALUE(itr)));
2247 END_HASH_ITERATION(hash, itr)
2252 SetupFileList *loadSetupFileList(char *filename)
2254 SetupFileList *setup_file_list = newSetupFileList("", "");
2255 SetupFileList *first_valid_list_entry;
2257 if (!loadSetupFileData(setup_file_list, filename, TRUE, FALSE))
2259 freeSetupFileList(setup_file_list);
2264 first_valid_list_entry = setup_file_list->next;
2266 // free empty list header
2267 setup_file_list->next = NULL;
2268 freeSetupFileList(setup_file_list);
2270 return first_valid_list_entry;
2273 SetupFileHash *loadSetupFileHash(char *filename)
2275 SetupFileHash *setup_file_hash = newSetupFileHash();
2277 if (!loadSetupFileData(setup_file_hash, filename, TRUE, TRUE))
2279 freeSetupFileHash(setup_file_hash);
2284 return setup_file_hash;
2288 // ============================================================================
2290 // ============================================================================
2292 #define TOKEN_STR_LAST_LEVEL_SERIES "last_level_series"
2293 #define TOKEN_STR_LAST_PLAYED_LEVEL "last_played_level"
2294 #define TOKEN_STR_HANDICAP_LEVEL "handicap_level"
2296 // level directory info
2297 #define LEVELINFO_TOKEN_IDENTIFIER 0
2298 #define LEVELINFO_TOKEN_NAME 1
2299 #define LEVELINFO_TOKEN_NAME_SORTING 2
2300 #define LEVELINFO_TOKEN_AUTHOR 3
2301 #define LEVELINFO_TOKEN_YEAR 4
2302 #define LEVELINFO_TOKEN_PROGRAM_TITLE 5
2303 #define LEVELINFO_TOKEN_PROGRAM_COPYRIGHT 6
2304 #define LEVELINFO_TOKEN_PROGRAM_COMPANY 7
2305 #define LEVELINFO_TOKEN_IMPORTED_FROM 8
2306 #define LEVELINFO_TOKEN_IMPORTED_BY 9
2307 #define LEVELINFO_TOKEN_TESTED_BY 10
2308 #define LEVELINFO_TOKEN_LEVELS 11
2309 #define LEVELINFO_TOKEN_FIRST_LEVEL 12
2310 #define LEVELINFO_TOKEN_SORT_PRIORITY 13
2311 #define LEVELINFO_TOKEN_LATEST_ENGINE 14
2312 #define LEVELINFO_TOKEN_LEVEL_GROUP 15
2313 #define LEVELINFO_TOKEN_READONLY 16
2314 #define LEVELINFO_TOKEN_GRAPHICS_SET_ECS 17
2315 #define LEVELINFO_TOKEN_GRAPHICS_SET_AGA 18
2316 #define LEVELINFO_TOKEN_GRAPHICS_SET 19
2317 #define LEVELINFO_TOKEN_SOUNDS_SET 20
2318 #define LEVELINFO_TOKEN_MUSIC_SET 21
2319 #define LEVELINFO_TOKEN_FILENAME 22
2320 #define LEVELINFO_TOKEN_FILETYPE 23
2321 #define LEVELINFO_TOKEN_SPECIAL_FLAGS 24
2322 #define LEVELINFO_TOKEN_HANDICAP 25
2323 #define LEVELINFO_TOKEN_SKIP_LEVELS 26
2324 #define LEVELINFO_TOKEN_USE_EMC_TILES 27
2326 #define NUM_LEVELINFO_TOKENS 28
2328 static LevelDirTree ldi;
2330 static struct TokenInfo levelinfo_tokens[] =
2332 // level directory info
2333 { TYPE_STRING, &ldi.identifier, "identifier" },
2334 { TYPE_STRING, &ldi.name, "name" },
2335 { TYPE_STRING, &ldi.name_sorting, "name_sorting" },
2336 { TYPE_STRING, &ldi.author, "author" },
2337 { TYPE_STRING, &ldi.year, "year" },
2338 { TYPE_STRING, &ldi.program_title, "program_title" },
2339 { TYPE_STRING, &ldi.program_copyright, "program_copyright" },
2340 { TYPE_STRING, &ldi.program_company, "program_company" },
2341 { TYPE_STRING, &ldi.imported_from, "imported_from" },
2342 { TYPE_STRING, &ldi.imported_by, "imported_by" },
2343 { TYPE_STRING, &ldi.tested_by, "tested_by" },
2344 { TYPE_INTEGER, &ldi.levels, "levels" },
2345 { TYPE_INTEGER, &ldi.first_level, "first_level" },
2346 { TYPE_INTEGER, &ldi.sort_priority, "sort_priority" },
2347 { TYPE_BOOLEAN, &ldi.latest_engine, "latest_engine" },
2348 { TYPE_BOOLEAN, &ldi.level_group, "level_group" },
2349 { TYPE_BOOLEAN, &ldi.readonly, "readonly" },
2350 { TYPE_STRING, &ldi.graphics_set_ecs, "graphics_set.ecs" },
2351 { TYPE_STRING, &ldi.graphics_set_aga, "graphics_set.aga" },
2352 { TYPE_STRING, &ldi.graphics_set, "graphics_set" },
2353 { TYPE_STRING, &ldi.sounds_set, "sounds_set" },
2354 { TYPE_STRING, &ldi.music_set, "music_set" },
2355 { TYPE_STRING, &ldi.level_filename, "filename" },
2356 { TYPE_STRING, &ldi.level_filetype, "filetype" },
2357 { TYPE_STRING, &ldi.special_flags, "special_flags" },
2358 { TYPE_BOOLEAN, &ldi.handicap, "handicap" },
2359 { TYPE_BOOLEAN, &ldi.skip_levels, "skip_levels" },
2360 { TYPE_BOOLEAN, &ldi.use_emc_tiles, "use_emc_tiles" }
2363 static struct TokenInfo artworkinfo_tokens[] =
2365 // artwork directory info
2366 { TYPE_STRING, &ldi.identifier, "identifier" },
2367 { TYPE_STRING, &ldi.subdir, "subdir" },
2368 { TYPE_STRING, &ldi.name, "name" },
2369 { TYPE_STRING, &ldi.name_sorting, "name_sorting" },
2370 { TYPE_STRING, &ldi.author, "author" },
2371 { TYPE_STRING, &ldi.program_title, "program_title" },
2372 { TYPE_STRING, &ldi.program_copyright, "program_copyright" },
2373 { TYPE_STRING, &ldi.program_company, "program_company" },
2374 { TYPE_INTEGER, &ldi.sort_priority, "sort_priority" },
2375 { TYPE_STRING, &ldi.basepath, "basepath" },
2376 { TYPE_STRING, &ldi.fullpath, "fullpath" },
2377 { TYPE_BOOLEAN, &ldi.in_user_dir, "in_user_dir" },
2378 { TYPE_INTEGER, &ldi.color, "color" },
2379 { TYPE_STRING, &ldi.class_desc, "class_desc" },
2384 static void setTreeInfoToDefaults(TreeInfo *ti, int type)
2388 ti->node_top = (ti->type == TREE_TYPE_LEVEL_DIR ? &leveldir_first :
2389 ti->type == TREE_TYPE_GRAPHICS_DIR ? &artwork.gfx_first :
2390 ti->type == TREE_TYPE_SOUNDS_DIR ? &artwork.snd_first :
2391 ti->type == TREE_TYPE_MUSIC_DIR ? &artwork.mus_first :
2394 ti->node_parent = NULL;
2395 ti->node_group = NULL;
2402 ti->fullpath = NULL;
2403 ti->basepath = NULL;
2404 ti->identifier = NULL;
2405 ti->name = getStringCopy(ANONYMOUS_NAME);
2406 ti->name_sorting = NULL;
2407 ti->author = getStringCopy(ANONYMOUS_NAME);
2410 ti->program_title = NULL;
2411 ti->program_copyright = NULL;
2412 ti->program_company = NULL;
2414 ti->sort_priority = LEVELCLASS_UNDEFINED; // default: least priority
2415 ti->latest_engine = FALSE; // default: get from level
2416 ti->parent_link = FALSE;
2417 ti->in_user_dir = FALSE;
2418 ti->user_defined = FALSE;
2420 ti->class_desc = NULL;
2422 ti->infotext = getStringCopy(TREE_INFOTEXT(ti->type));
2424 if (ti->type == TREE_TYPE_LEVEL_DIR)
2426 ti->imported_from = NULL;
2427 ti->imported_by = NULL;
2428 ti->tested_by = NULL;
2430 ti->graphics_set_ecs = NULL;
2431 ti->graphics_set_aga = NULL;
2432 ti->graphics_set = NULL;
2433 ti->sounds_set = NULL;
2434 ti->music_set = NULL;
2435 ti->graphics_path = getStringCopy(UNDEFINED_FILENAME);
2436 ti->sounds_path = getStringCopy(UNDEFINED_FILENAME);
2437 ti->music_path = getStringCopy(UNDEFINED_FILENAME);
2439 ti->level_filename = NULL;
2440 ti->level_filetype = NULL;
2442 ti->special_flags = NULL;
2445 ti->first_level = 0;
2447 ti->level_group = FALSE;
2448 ti->handicap_level = 0;
2449 ti->readonly = TRUE;
2450 ti->handicap = TRUE;
2451 ti->skip_levels = FALSE;
2453 ti->use_emc_tiles = FALSE;
2457 static void setTreeInfoToDefaultsFromParent(TreeInfo *ti, TreeInfo *parent)
2461 Error(ERR_WARN, "setTreeInfoToDefaultsFromParent(): parent == NULL");
2463 setTreeInfoToDefaults(ti, TREE_TYPE_UNDEFINED);
2468 // copy all values from the parent structure
2470 ti->type = parent->type;
2472 ti->node_top = parent->node_top;
2473 ti->node_parent = parent;
2474 ti->node_group = NULL;
2481 ti->fullpath = NULL;
2482 ti->basepath = NULL;
2483 ti->identifier = NULL;
2484 ti->name = getStringCopy(ANONYMOUS_NAME);
2485 ti->name_sorting = NULL;
2486 ti->author = getStringCopy(parent->author);
2487 ti->year = getStringCopy(parent->year);
2489 ti->program_title = getStringCopy(parent->program_title);
2490 ti->program_copyright = getStringCopy(parent->program_copyright);
2491 ti->program_company = getStringCopy(parent->program_company);
2493 ti->sort_priority = parent->sort_priority;
2494 ti->latest_engine = parent->latest_engine;
2495 ti->parent_link = FALSE;
2496 ti->in_user_dir = parent->in_user_dir;
2497 ti->user_defined = parent->user_defined;
2498 ti->color = parent->color;
2499 ti->class_desc = getStringCopy(parent->class_desc);
2501 ti->infotext = getStringCopy(parent->infotext);
2503 if (ti->type == TREE_TYPE_LEVEL_DIR)
2505 ti->imported_from = getStringCopy(parent->imported_from);
2506 ti->imported_by = getStringCopy(parent->imported_by);
2507 ti->tested_by = getStringCopy(parent->tested_by);
2509 ti->graphics_set_ecs = getStringCopy(parent->graphics_set_ecs);
2510 ti->graphics_set_aga = getStringCopy(parent->graphics_set_aga);
2511 ti->graphics_set = getStringCopy(parent->graphics_set);
2512 ti->sounds_set = getStringCopy(parent->sounds_set);
2513 ti->music_set = getStringCopy(parent->music_set);
2514 ti->graphics_path = getStringCopy(UNDEFINED_FILENAME);
2515 ti->sounds_path = getStringCopy(UNDEFINED_FILENAME);
2516 ti->music_path = getStringCopy(UNDEFINED_FILENAME);
2518 ti->level_filename = getStringCopy(parent->level_filename);
2519 ti->level_filetype = getStringCopy(parent->level_filetype);
2521 ti->special_flags = getStringCopy(parent->special_flags);
2523 ti->levels = parent->levels;
2524 ti->first_level = parent->first_level;
2525 ti->last_level = parent->last_level;
2526 ti->level_group = FALSE;
2527 ti->handicap_level = parent->handicap_level;
2528 ti->readonly = parent->readonly;
2529 ti->handicap = parent->handicap;
2530 ti->skip_levels = parent->skip_levels;
2532 ti->use_emc_tiles = parent->use_emc_tiles;
2536 static TreeInfo *getTreeInfoCopy(TreeInfo *ti)
2538 TreeInfo *ti_copy = newTreeInfo();
2540 // copy all values from the original structure
2542 ti_copy->type = ti->type;
2544 ti_copy->node_top = ti->node_top;
2545 ti_copy->node_parent = ti->node_parent;
2546 ti_copy->node_group = ti->node_group;
2547 ti_copy->next = ti->next;
2549 ti_copy->cl_first = ti->cl_first;
2550 ti_copy->cl_cursor = ti->cl_cursor;
2552 ti_copy->subdir = getStringCopy(ti->subdir);
2553 ti_copy->fullpath = getStringCopy(ti->fullpath);
2554 ti_copy->basepath = getStringCopy(ti->basepath);
2555 ti_copy->identifier = getStringCopy(ti->identifier);
2556 ti_copy->name = getStringCopy(ti->name);
2557 ti_copy->name_sorting = getStringCopy(ti->name_sorting);
2558 ti_copy->author = getStringCopy(ti->author);
2559 ti_copy->year = getStringCopy(ti->year);
2561 ti_copy->program_title = getStringCopy(ti->program_title);
2562 ti_copy->program_copyright = getStringCopy(ti->program_copyright);
2563 ti_copy->program_company = getStringCopy(ti->program_company);
2565 ti_copy->imported_from = getStringCopy(ti->imported_from);
2566 ti_copy->imported_by = getStringCopy(ti->imported_by);
2567 ti_copy->tested_by = getStringCopy(ti->tested_by);
2569 ti_copy->graphics_set_ecs = getStringCopy(ti->graphics_set_ecs);
2570 ti_copy->graphics_set_aga = getStringCopy(ti->graphics_set_aga);
2571 ti_copy->graphics_set = getStringCopy(ti->graphics_set);
2572 ti_copy->sounds_set = getStringCopy(ti->sounds_set);
2573 ti_copy->music_set = getStringCopy(ti->music_set);
2574 ti_copy->graphics_path = getStringCopy(ti->graphics_path);
2575 ti_copy->sounds_path = getStringCopy(ti->sounds_path);
2576 ti_copy->music_path = getStringCopy(ti->music_path);
2578 ti_copy->level_filename = getStringCopy(ti->level_filename);
2579 ti_copy->level_filetype = getStringCopy(ti->level_filetype);
2581 ti_copy->special_flags = getStringCopy(ti->special_flags);
2583 ti_copy->levels = ti->levels;
2584 ti_copy->first_level = ti->first_level;
2585 ti_copy->last_level = ti->last_level;
2586 ti_copy->sort_priority = ti->sort_priority;
2588 ti_copy->latest_engine = ti->latest_engine;
2590 ti_copy->level_group = ti->level_group;
2591 ti_copy->parent_link = ti->parent_link;
2592 ti_copy->in_user_dir = ti->in_user_dir;
2593 ti_copy->user_defined = ti->user_defined;
2594 ti_copy->readonly = ti->readonly;
2595 ti_copy->handicap = ti->handicap;
2596 ti_copy->skip_levels = ti->skip_levels;
2598 ti_copy->use_emc_tiles = ti->use_emc_tiles;
2600 ti_copy->color = ti->color;
2601 ti_copy->class_desc = getStringCopy(ti->class_desc);
2602 ti_copy->handicap_level = ti->handicap_level;
2604 ti_copy->infotext = getStringCopy(ti->infotext);
2609 void freeTreeInfo(TreeInfo *ti)
2614 checked_free(ti->subdir);
2615 checked_free(ti->fullpath);
2616 checked_free(ti->basepath);
2617 checked_free(ti->identifier);
2619 checked_free(ti->name);
2620 checked_free(ti->name_sorting);
2621 checked_free(ti->author);
2622 checked_free(ti->year);
2624 checked_free(ti->program_title);
2625 checked_free(ti->program_copyright);
2626 checked_free(ti->program_company);
2628 checked_free(ti->class_desc);
2630 checked_free(ti->infotext);
2632 if (ti->type == TREE_TYPE_LEVEL_DIR)
2634 checked_free(ti->imported_from);
2635 checked_free(ti->imported_by);
2636 checked_free(ti->tested_by);
2638 checked_free(ti->graphics_set_ecs);
2639 checked_free(ti->graphics_set_aga);
2640 checked_free(ti->graphics_set);
2641 checked_free(ti->sounds_set);
2642 checked_free(ti->music_set);
2644 checked_free(ti->graphics_path);
2645 checked_free(ti->sounds_path);
2646 checked_free(ti->music_path);
2648 checked_free(ti->level_filename);
2649 checked_free(ti->level_filetype);
2651 checked_free(ti->special_flags);
2654 // recursively free child node
2656 freeTreeInfo(ti->node_group);
2658 // recursively free next node
2660 freeTreeInfo(ti->next);
2665 void setSetupInfo(struct TokenInfo *token_info,
2666 int token_nr, char *token_value)
2668 int token_type = token_info[token_nr].type;
2669 void *setup_value = token_info[token_nr].value;
2671 if (token_value == NULL)
2674 // set setup field to corresponding token value
2679 *(boolean *)setup_value = get_boolean_from_string(token_value);
2683 *(int *)setup_value = get_switch3_from_string(token_value);
2687 *(Key *)setup_value = getKeyFromKeyName(token_value);
2691 *(Key *)setup_value = getKeyFromX11KeyName(token_value);
2695 *(int *)setup_value = get_integer_from_string(token_value);
2699 checked_free(*(char **)setup_value);
2700 *(char **)setup_value = getStringCopy(token_value);
2704 *(int *)setup_value = get_player_nr_from_string(token_value);
2712 static int compareTreeInfoEntries(const void *object1, const void *object2)
2714 const TreeInfo *entry1 = *((TreeInfo **)object1);
2715 const TreeInfo *entry2 = *((TreeInfo **)object2);
2716 int class_sorting1 = 0, class_sorting2 = 0;
2719 if (entry1->type == TREE_TYPE_LEVEL_DIR)
2721 class_sorting1 = LEVELSORTING(entry1);
2722 class_sorting2 = LEVELSORTING(entry2);
2724 else if (entry1->type == TREE_TYPE_GRAPHICS_DIR ||
2725 entry1->type == TREE_TYPE_SOUNDS_DIR ||
2726 entry1->type == TREE_TYPE_MUSIC_DIR)
2728 class_sorting1 = ARTWORKSORTING(entry1);
2729 class_sorting2 = ARTWORKSORTING(entry2);
2732 if (entry1->parent_link || entry2->parent_link)
2733 compare_result = (entry1->parent_link ? -1 : +1);
2734 else if (entry1->sort_priority == entry2->sort_priority)
2736 char *name1 = getStringToLower(entry1->name_sorting);
2737 char *name2 = getStringToLower(entry2->name_sorting);
2739 compare_result = strcmp(name1, name2);
2744 else if (class_sorting1 == class_sorting2)
2745 compare_result = entry1->sort_priority - entry2->sort_priority;
2747 compare_result = class_sorting1 - class_sorting2;
2749 return compare_result;
2752 static TreeInfo *createParentTreeInfoNode(TreeInfo *node_parent)
2756 if (node_parent == NULL)
2759 ti_new = newTreeInfo();
2760 setTreeInfoToDefaults(ti_new, node_parent->type);
2762 ti_new->node_parent = node_parent;
2763 ti_new->parent_link = TRUE;
2765 setString(&ti_new->identifier, node_parent->identifier);
2766 setString(&ti_new->name, ".. (parent directory)");
2767 setString(&ti_new->name_sorting, ti_new->name);
2769 setString(&ti_new->subdir, STRING_PARENT_DIRECTORY);
2770 setString(&ti_new->fullpath, node_parent->fullpath);
2772 ti_new->sort_priority = node_parent->sort_priority;
2773 ti_new->latest_engine = node_parent->latest_engine;
2775 setString(&ti_new->class_desc, getLevelClassDescription(ti_new));
2777 pushTreeInfo(&node_parent->node_group, ti_new);
2782 static TreeInfo *createTopTreeInfoNode(TreeInfo *node_first)
2784 TreeInfo *ti_new, *ti_new2;
2786 if (node_first == NULL)
2789 ti_new = newTreeInfo();
2790 setTreeInfoToDefaults(ti_new, TREE_TYPE_LEVEL_DIR);
2792 ti_new->node_parent = NULL;
2793 ti_new->parent_link = FALSE;
2795 setString(&ti_new->identifier, node_first->identifier);
2796 setString(&ti_new->name, "level sets");
2797 setString(&ti_new->name_sorting, ti_new->name);
2799 setString(&ti_new->subdir, STRING_TOP_DIRECTORY);
2800 setString(&ti_new->fullpath, ".");
2802 ti_new->sort_priority = node_first->sort_priority;;
2803 ti_new->latest_engine = node_first->latest_engine;
2805 setString(&ti_new->class_desc, "level sets");
2807 ti_new->node_group = node_first;
2808 ti_new->level_group = TRUE;
2810 ti_new2 = createParentTreeInfoNode(ti_new);
2812 setString(&ti_new2->name, ".. (main menu)");
2813 setString(&ti_new2->name_sorting, ti_new2->name);
2819 // ----------------------------------------------------------------------------
2820 // functions for handling level and custom artwork info cache
2821 // ----------------------------------------------------------------------------
2823 static void LoadArtworkInfoCache(void)
2825 InitCacheDirectory();
2827 if (artworkinfo_cache_old == NULL)
2829 char *filename = getPath2(getCacheDir(), ARTWORKINFO_CACHE_FILE);
2831 // try to load artwork info hash from already existing cache file
2832 artworkinfo_cache_old = loadSetupFileHash(filename);
2834 // if no artwork info cache file was found, start with empty hash
2835 if (artworkinfo_cache_old == NULL)
2836 artworkinfo_cache_old = newSetupFileHash();
2841 if (artworkinfo_cache_new == NULL)
2842 artworkinfo_cache_new = newSetupFileHash();
2845 static void SaveArtworkInfoCache(void)
2847 char *filename = getPath2(getCacheDir(), ARTWORKINFO_CACHE_FILE);
2849 InitCacheDirectory();
2851 saveSetupFileHash(artworkinfo_cache_new, filename);
2856 static char *getCacheTokenPrefix(char *prefix1, char *prefix2)
2858 static char *prefix = NULL;
2860 checked_free(prefix);
2862 prefix = getStringCat2WithSeparator(prefix1, prefix2, ".");
2867 // (identical to above function, but separate string buffer needed -- nasty)
2868 static char *getCacheToken(char *prefix, char *suffix)
2870 static char *token = NULL;
2872 checked_free(token);
2874 token = getStringCat2WithSeparator(prefix, suffix, ".");
2879 static char *getFileTimestampString(char *filename)
2881 return getStringCopy(i_to_a(getFileTimestampEpochSeconds(filename)));
2884 static boolean modifiedFileTimestamp(char *filename, char *timestamp_string)
2886 struct stat file_status;
2888 if (timestamp_string == NULL)
2891 if (stat(filename, &file_status) != 0) // cannot stat file
2894 return (file_status.st_mtime != atoi(timestamp_string));
2897 static TreeInfo *getArtworkInfoCacheEntry(LevelDirTree *level_node, int type)
2899 char *identifier = level_node->subdir;
2900 char *type_string = ARTWORK_DIRECTORY(type);
2901 char *token_prefix = getCacheTokenPrefix(type_string, identifier);
2902 char *token_main = getCacheToken(token_prefix, "CACHED");
2903 char *cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
2904 boolean cached = (cache_entry != NULL && strEqual(cache_entry, "true"));
2905 TreeInfo *artwork_info = NULL;
2907 if (!use_artworkinfo_cache)
2914 artwork_info = newTreeInfo();
2915 setTreeInfoToDefaults(artwork_info, type);
2917 // set all structure fields according to the token/value pairs
2918 ldi = *artwork_info;
2919 for (i = 0; artworkinfo_tokens[i].type != -1; i++)
2921 char *token = getCacheToken(token_prefix, artworkinfo_tokens[i].text);
2922 char *value = getHashEntry(artworkinfo_cache_old, token);
2924 // if defined, use value from cache, else keep default value
2926 setSetupInfo(artworkinfo_tokens, i, value);
2929 *artwork_info = ldi;
2931 char *filename_levelinfo = getPath2(getLevelDirFromTreeInfo(level_node),
2932 LEVELINFO_FILENAME);
2933 char *filename_artworkinfo = getPath2(getSetupArtworkDir(artwork_info),
2934 ARTWORKINFO_FILENAME(type));
2936 // check if corresponding "levelinfo.conf" file has changed
2937 token_main = getCacheToken(token_prefix, "TIMESTAMP_LEVELINFO");
2938 cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
2940 if (modifiedFileTimestamp(filename_levelinfo, cache_entry))
2943 // check if corresponding "<artworkinfo>.conf" file has changed
2944 token_main = getCacheToken(token_prefix, "TIMESTAMP_ARTWORKINFO");
2945 cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
2947 if (modifiedFileTimestamp(filename_artworkinfo, cache_entry))
2950 checked_free(filename_levelinfo);
2951 checked_free(filename_artworkinfo);
2954 if (!cached && artwork_info != NULL)
2956 freeTreeInfo(artwork_info);
2961 return artwork_info;
2964 static void setArtworkInfoCacheEntry(TreeInfo *artwork_info,
2965 LevelDirTree *level_node, int type)
2967 char *identifier = level_node->subdir;
2968 char *type_string = ARTWORK_DIRECTORY(type);
2969 char *token_prefix = getCacheTokenPrefix(type_string, identifier);
2970 char *token_main = getCacheToken(token_prefix, "CACHED");
2971 boolean set_cache_timestamps = TRUE;
2974 setHashEntry(artworkinfo_cache_new, token_main, "true");
2976 if (set_cache_timestamps)
2978 char *filename_levelinfo = getPath2(getLevelDirFromTreeInfo(level_node),
2979 LEVELINFO_FILENAME);
2980 char *filename_artworkinfo = getPath2(getSetupArtworkDir(artwork_info),
2981 ARTWORKINFO_FILENAME(type));
2982 char *timestamp_levelinfo = getFileTimestampString(filename_levelinfo);
2983 char *timestamp_artworkinfo = getFileTimestampString(filename_artworkinfo);
2985 token_main = getCacheToken(token_prefix, "TIMESTAMP_LEVELINFO");
2986 setHashEntry(artworkinfo_cache_new, token_main, timestamp_levelinfo);
2988 token_main = getCacheToken(token_prefix, "TIMESTAMP_ARTWORKINFO");
2989 setHashEntry(artworkinfo_cache_new, token_main, timestamp_artworkinfo);
2991 checked_free(filename_levelinfo);
2992 checked_free(filename_artworkinfo);
2993 checked_free(timestamp_levelinfo);
2994 checked_free(timestamp_artworkinfo);
2997 ldi = *artwork_info;
2998 for (i = 0; artworkinfo_tokens[i].type != -1; i++)
3000 char *token = getCacheToken(token_prefix, artworkinfo_tokens[i].text);
3001 char *value = getSetupValue(artworkinfo_tokens[i].type,
3002 artworkinfo_tokens[i].value);
3004 setHashEntry(artworkinfo_cache_new, token, value);
3009 // ----------------------------------------------------------------------------
3010 // functions for loading level info and custom artwork info
3011 // ----------------------------------------------------------------------------
3013 int GetZipFileTreeType(char *zip_filename)
3015 static char *top_dir_path = NULL;
3016 static char *top_dir_conf_filename[NUM_BASE_TREE_TYPES] = { NULL };
3017 static char *conf_basename[NUM_BASE_TREE_TYPES] =
3019 GRAPHICSINFO_FILENAME,
3020 SOUNDSINFO_FILENAME,
3026 checked_free(top_dir_path);
3027 top_dir_path = NULL;
3029 for (j = 0; j < NUM_BASE_TREE_TYPES; j++)
3031 checked_free(top_dir_conf_filename[j]);
3032 top_dir_conf_filename[j] = NULL;
3035 char **zip_entries = zip_list(zip_filename);
3037 // check if zip file successfully opened
3038 if (zip_entries == NULL || zip_entries[0] == NULL)
3039 return TREE_TYPE_UNDEFINED;
3041 // first zip file entry is expected to be top level directory
3042 char *top_dir = zip_entries[0];
3044 // check if valid top level directory found in zip file
3045 if (!strSuffix(top_dir, "/"))
3046 return TREE_TYPE_UNDEFINED;
3048 // get filenames of valid configuration files in top level directory
3049 for (j = 0; j < NUM_BASE_TREE_TYPES; j++)
3050 top_dir_conf_filename[j] = getStringCat2(top_dir, conf_basename[j]);
3052 int tree_type = TREE_TYPE_UNDEFINED;
3055 while (zip_entries[e] != NULL)
3057 // check if every zip file entry is below top level directory
3058 if (!strPrefix(zip_entries[e], top_dir))
3059 return TREE_TYPE_UNDEFINED;
3061 // check if this zip file entry is a valid configuration filename
3062 for (j = 0; j < NUM_BASE_TREE_TYPES; j++)
3064 if (strEqual(zip_entries[e], top_dir_conf_filename[j]))
3066 // only exactly one valid configuration file allowed
3067 if (tree_type != TREE_TYPE_UNDEFINED)
3068 return TREE_TYPE_UNDEFINED;
3080 static boolean CheckZipFileForDirectory(char *zip_filename, char *directory,
3083 static char *top_dir_path = NULL;
3084 static char *top_dir_conf_filename = NULL;
3086 checked_free(top_dir_path);
3087 checked_free(top_dir_conf_filename);
3089 top_dir_path = NULL;
3090 top_dir_conf_filename = NULL;
3092 char *conf_basename = (tree_type == TREE_TYPE_LEVEL_DIR ? LEVELINFO_FILENAME :
3093 ARTWORKINFO_FILENAME(tree_type));
3095 // check if valid configuration filename determined
3096 if (conf_basename == NULL || strEqual(conf_basename, ""))
3099 char **zip_entries = zip_list(zip_filename);
3101 // check if zip file successfully opened
3102 if (zip_entries == NULL || zip_entries[0] == NULL)
3105 // first zip file entry is expected to be top level directory
3106 char *top_dir = zip_entries[0];
3108 // check if valid top level directory found in zip file
3109 if (!strSuffix(top_dir, "/"))
3112 // get path of extracted top level directory
3113 top_dir_path = getPath2(directory, top_dir);
3115 // remove trailing directory separator from top level directory path
3116 // (required to be able to check for file and directory in next step)
3117 top_dir_path[strlen(top_dir_path) - 1] = '\0';
3119 // check if zip file's top level directory already exists in target directory
3120 if (fileExists(top_dir_path)) // (checks for file and directory)
3123 // get filename of configuration file in top level directory
3124 top_dir_conf_filename = getStringCat2(top_dir, conf_basename);
3126 boolean found_top_dir_conf_filename = FALSE;
3129 while (zip_entries[i] != NULL)
3131 // check if every zip file entry is below top level directory
3132 if (!strPrefix(zip_entries[i], top_dir))
3135 // check if this zip file entry is the configuration filename
3136 if (strEqual(zip_entries[i], top_dir_conf_filename))
3137 found_top_dir_conf_filename = TRUE;
3142 // check if valid configuration filename was found in zip file
3143 if (!found_top_dir_conf_filename)
3149 char *ExtractZipFileIntoDirectory(char *zip_filename, char *directory,
3152 boolean zip_file_valid = CheckZipFileForDirectory(zip_filename, directory,
3155 if (!zip_file_valid)
3157 Error(ERR_WARN, "zip file '%s' rejected!", zip_filename);
3162 char **zip_entries = zip_extract(zip_filename, directory);
3164 if (zip_entries == NULL)
3166 Error(ERR_WARN, "zip file '%s' could not be extracted!", zip_filename);
3171 Error(ERR_INFO, "zip file '%s' successfully extracted!", zip_filename);
3173 // first zip file entry contains top level directory
3174 char *top_dir = zip_entries[0];
3176 // remove trailing directory separator from top level directory
3177 top_dir[strlen(top_dir) - 1] = '\0';
3182 static void ProcessZipFilesInDirectory(char *directory, int tree_type)
3185 DirectoryEntry *dir_entry;
3187 if ((dir = openDirectory(directory)) == NULL)
3189 // display error if directory is main "options.graphics_directory" etc.
3190 if (tree_type == TREE_TYPE_LEVEL_DIR ||
3191 directory == OPTIONS_ARTWORK_DIRECTORY(tree_type))
3192 Error(ERR_WARN, "cannot read directory '%s'", directory);
3197 while ((dir_entry = readDirectory(dir)) != NULL) // loop all entries
3199 // skip non-zip files (and also directories with zip extension)
3200 if (!strSuffixLower(dir_entry->basename, ".zip") || dir_entry->is_directory)
3203 char *zip_filename = getPath2(directory, dir_entry->basename);
3204 char *zip_filename_extracted = getStringCat2(zip_filename, ".extracted");
3205 char *zip_filename_rejected = getStringCat2(zip_filename, ".rejected");
3207 // check if zip file hasn't already been extracted or rejected
3208 if (!fileExists(zip_filename_extracted) &&
3209 !fileExists(zip_filename_rejected))
3211 char *top_dir = ExtractZipFileIntoDirectory(zip_filename, directory,
3213 char *marker_filename = (top_dir != NULL ? zip_filename_extracted :
3214 zip_filename_rejected);
3217 // create empty file to mark zip file as extracted or rejected
3218 if ((marker_file = fopen(marker_filename, MODE_WRITE)))
3219 fclose(marker_file);
3222 free(zip_filename_extracted);
3223 free(zip_filename_rejected);
3227 closeDirectory(dir);
3230 // forward declaration for recursive call by "LoadLevelInfoFromLevelDir()"
3231 static void LoadLevelInfoFromLevelDir(TreeInfo **, TreeInfo *, char *);
3233 static boolean LoadLevelInfoFromLevelConf(TreeInfo **node_first,
3234 TreeInfo *node_parent,
3235 char *level_directory,
3236 char *directory_name)
3238 char *directory_path = getPath2(level_directory, directory_name);
3239 char *filename = getPath2(directory_path, LEVELINFO_FILENAME);
3240 SetupFileHash *setup_file_hash;
3241 LevelDirTree *leveldir_new = NULL;
3244 // unless debugging, silently ignore directories without "levelinfo.conf"
3245 if (!options.debug && !fileExists(filename))
3247 free(directory_path);
3253 setup_file_hash = loadSetupFileHash(filename);
3255 if (setup_file_hash == NULL)
3257 #if DEBUG_NO_CONFIG_FILE
3258 Error(ERR_WARN, "ignoring level directory '%s'", directory_path);
3261 free(directory_path);
3267 leveldir_new = newTreeInfo();
3270 setTreeInfoToDefaultsFromParent(leveldir_new, node_parent);
3272 setTreeInfoToDefaults(leveldir_new, TREE_TYPE_LEVEL_DIR);
3274 leveldir_new->subdir = getStringCopy(directory_name);
3276 // set all structure fields according to the token/value pairs
3277 ldi = *leveldir_new;
3278 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
3279 setSetupInfo(levelinfo_tokens, i,
3280 getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
3281 *leveldir_new = ldi;
3283 if (strEqual(leveldir_new->name, ANONYMOUS_NAME))
3284 setString(&leveldir_new->name, leveldir_new->subdir);
3286 if (leveldir_new->identifier == NULL)
3287 leveldir_new->identifier = getStringCopy(leveldir_new->subdir);
3289 if (leveldir_new->name_sorting == NULL)
3290 leveldir_new->name_sorting = getStringCopy(leveldir_new->name);
3292 if (node_parent == NULL) // top level group
3294 leveldir_new->basepath = getStringCopy(level_directory);
3295 leveldir_new->fullpath = getStringCopy(leveldir_new->subdir);
3297 else // sub level group
3299 leveldir_new->basepath = getStringCopy(node_parent->basepath);
3300 leveldir_new->fullpath = getPath2(node_parent->fullpath, directory_name);
3303 leveldir_new->last_level =
3304 leveldir_new->first_level + leveldir_new->levels - 1;
3306 leveldir_new->in_user_dir =
3307 (!strEqual(leveldir_new->basepath, options.level_directory));
3309 // adjust some settings if user's private level directory was detected
3310 if (leveldir_new->sort_priority == LEVELCLASS_UNDEFINED &&
3311 leveldir_new->in_user_dir &&
3312 (strEqual(leveldir_new->subdir, getLoginName()) ||
3313 strEqual(leveldir_new->name, getLoginName()) ||
3314 strEqual(leveldir_new->author, getRealName())))
3316 leveldir_new->sort_priority = LEVELCLASS_PRIVATE_START;
3317 leveldir_new->readonly = FALSE;
3320 leveldir_new->user_defined =
3321 (leveldir_new->in_user_dir && IS_LEVELCLASS_PRIVATE(leveldir_new));
3323 leveldir_new->color = LEVELCOLOR(leveldir_new);
3325 setString(&leveldir_new->class_desc, getLevelClassDescription(leveldir_new));
3327 leveldir_new->handicap_level = // set handicap to default value
3328 (leveldir_new->user_defined || !leveldir_new->handicap ?
3329 leveldir_new->last_level : leveldir_new->first_level);
3331 DrawInitText(leveldir_new->name, 150, FC_YELLOW);
3333 pushTreeInfo(node_first, leveldir_new);
3335 freeSetupFileHash(setup_file_hash);
3337 if (leveldir_new->level_group)
3339 // create node to link back to current level directory
3340 createParentTreeInfoNode(leveldir_new);
3342 // recursively step into sub-directory and look for more level series
3343 LoadLevelInfoFromLevelDir(&leveldir_new->node_group,
3344 leveldir_new, directory_path);
3347 free(directory_path);
3353 static void LoadLevelInfoFromLevelDir(TreeInfo **node_first,
3354 TreeInfo *node_parent,
3355 char *level_directory)
3357 // ---------- 1st stage: process any level set zip files ----------
3359 ProcessZipFilesInDirectory(level_directory, TREE_TYPE_LEVEL_DIR);
3361 // ---------- 2nd stage: check for level set directories ----------
3364 DirectoryEntry *dir_entry;
3365 boolean valid_entry_found = FALSE;
3367 if ((dir = openDirectory(level_directory)) == NULL)
3369 Error(ERR_WARN, "cannot read level directory '%s'", level_directory);
3374 while ((dir_entry = readDirectory(dir)) != NULL) // loop all entries
3376 char *directory_name = dir_entry->basename;
3377 char *directory_path = getPath2(level_directory, directory_name);
3379 // skip entries for current and parent directory
3380 if (strEqual(directory_name, ".") ||
3381 strEqual(directory_name, ".."))
3383 free(directory_path);
3388 // find out if directory entry is itself a directory
3389 if (!dir_entry->is_directory) // not a directory
3391 free(directory_path);
3396 free(directory_path);
3398 if (strEqual(directory_name, GRAPHICS_DIRECTORY) ||
3399 strEqual(directory_name, SOUNDS_DIRECTORY) ||
3400 strEqual(directory_name, MUSIC_DIRECTORY))
3403 valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
3408 closeDirectory(dir);
3410 // special case: top level directory may directly contain "levelinfo.conf"
3411 if (node_parent == NULL && !valid_entry_found)
3413 // check if this directory directly contains a file "levelinfo.conf"
3414 valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
3415 level_directory, ".");
3418 if (!valid_entry_found)
3419 Error(ERR_WARN, "cannot find any valid level series in directory '%s'",
3423 boolean AdjustGraphicsForEMC(void)
3425 boolean settings_changed = FALSE;
3427 settings_changed |= adjustTreeGraphicsForEMC(leveldir_first_all);
3428 settings_changed |= adjustTreeGraphicsForEMC(leveldir_first);
3430 return settings_changed;
3433 void LoadLevelInfo(void)
3435 InitUserLevelDirectory(getLoginName());
3437 DrawInitText("Loading level series", 120, FC_GREEN);
3439 LoadLevelInfoFromLevelDir(&leveldir_first, NULL, options.level_directory);
3440 LoadLevelInfoFromLevelDir(&leveldir_first, NULL, getUserLevelDir(NULL));
3442 leveldir_first = createTopTreeInfoNode(leveldir_first);
3444 /* after loading all level set information, clone the level directory tree
3445 and remove all level sets without levels (these may still contain artwork
3446 to be offered in the setup menu as "custom artwork", and are therefore
3447 checked for existing artwork in the function "LoadLevelArtworkInfo()") */
3448 leveldir_first_all = leveldir_first;
3449 cloneTree(&leveldir_first, leveldir_first_all, TRUE);
3451 AdjustGraphicsForEMC();
3453 // before sorting, the first entries will be from the user directory
3454 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
3456 if (leveldir_first == NULL)
3457 Error(ERR_EXIT, "cannot find any valid level series in any directory");
3459 sortTreeInfo(&leveldir_first);
3461 #if ENABLE_UNUSED_CODE
3462 dumpTreeInfo(leveldir_first, 0);
3466 static boolean LoadArtworkInfoFromArtworkConf(TreeInfo **node_first,
3467 TreeInfo *node_parent,
3468 char *base_directory,
3469 char *directory_name, int type)
3471 char *directory_path = getPath2(base_directory, directory_name);
3472 char *filename = getPath2(directory_path, ARTWORKINFO_FILENAME(type));
3473 SetupFileHash *setup_file_hash = NULL;
3474 TreeInfo *artwork_new = NULL;
3477 if (fileExists(filename))
3478 setup_file_hash = loadSetupFileHash(filename);
3480 if (setup_file_hash == NULL) // no config file -- look for artwork files
3483 DirectoryEntry *dir_entry;
3484 boolean valid_file_found = FALSE;
3486 if ((dir = openDirectory(directory_path)) != NULL)
3488 while ((dir_entry = readDirectory(dir)) != NULL)
3490 if (FileIsArtworkType(dir_entry->filename, type))
3492 valid_file_found = TRUE;
3498 closeDirectory(dir);
3501 if (!valid_file_found)
3503 #if DEBUG_NO_CONFIG_FILE
3504 if (!strEqual(directory_name, "."))
3505 Error(ERR_WARN, "ignoring artwork directory '%s'", directory_path);
3508 free(directory_path);
3515 artwork_new = newTreeInfo();
3518 setTreeInfoToDefaultsFromParent(artwork_new, node_parent);
3520 setTreeInfoToDefaults(artwork_new, type);
3522 artwork_new->subdir = getStringCopy(directory_name);
3524 if (setup_file_hash) // (before defining ".color" and ".class_desc")
3526 // set all structure fields according to the token/value pairs
3528 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
3529 setSetupInfo(levelinfo_tokens, i,
3530 getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
3533 if (strEqual(artwork_new->name, ANONYMOUS_NAME))
3534 setString(&artwork_new->name, artwork_new->subdir);
3536 if (artwork_new->identifier == NULL)
3537 artwork_new->identifier = getStringCopy(artwork_new->subdir);
3539 if (artwork_new->name_sorting == NULL)
3540 artwork_new->name_sorting = getStringCopy(artwork_new->name);
3543 if (node_parent == NULL) // top level group
3545 artwork_new->basepath = getStringCopy(base_directory);
3546 artwork_new->fullpath = getStringCopy(artwork_new->subdir);
3548 else // sub level group
3550 artwork_new->basepath = getStringCopy(node_parent->basepath);
3551 artwork_new->fullpath = getPath2(node_parent->fullpath, directory_name);
3554 artwork_new->in_user_dir =
3555 (!strEqual(artwork_new->basepath, OPTIONS_ARTWORK_DIRECTORY(type)));
3557 // (may use ".sort_priority" from "setup_file_hash" above)
3558 artwork_new->color = ARTWORKCOLOR(artwork_new);
3560 setString(&artwork_new->class_desc, getLevelClassDescription(artwork_new));
3562 if (setup_file_hash == NULL) // (after determining ".user_defined")
3564 if (strEqual(artwork_new->subdir, "."))
3566 if (artwork_new->user_defined)
3568 setString(&artwork_new->identifier, "private");
3569 artwork_new->sort_priority = ARTWORKCLASS_PRIVATE;
3573 setString(&artwork_new->identifier, "classic");
3574 artwork_new->sort_priority = ARTWORKCLASS_CLASSICS;
3577 // set to new values after changing ".sort_priority"
3578 artwork_new->color = ARTWORKCOLOR(artwork_new);
3580 setString(&artwork_new->class_desc,
3581 getLevelClassDescription(artwork_new));
3585 setString(&artwork_new->identifier, artwork_new->subdir);
3588 setString(&artwork_new->name, artwork_new->identifier);
3589 setString(&artwork_new->name_sorting, artwork_new->name);
3592 pushTreeInfo(node_first, artwork_new);
3594 freeSetupFileHash(setup_file_hash);
3596 free(directory_path);
3602 static void LoadArtworkInfoFromArtworkDir(TreeInfo **node_first,
3603 TreeInfo *node_parent,
3604 char *base_directory, int type)
3606 // ---------- 1st stage: process any artwork set zip files ----------
3608 ProcessZipFilesInDirectory(base_directory, type);
3610 // ---------- 2nd stage: check for artwork set directories ----------
3613 DirectoryEntry *dir_entry;
3614 boolean valid_entry_found = FALSE;
3616 if ((dir = openDirectory(base_directory)) == NULL)
3618 // display error if directory is main "options.graphics_directory" etc.
3619 if (base_directory == OPTIONS_ARTWORK_DIRECTORY(type))
3620 Error(ERR_WARN, "cannot read directory '%s'", base_directory);
3625 while ((dir_entry = readDirectory(dir)) != NULL) // loop all entries
3627 char *directory_name = dir_entry->basename;
3628 char *directory_path = getPath2(base_directory, directory_name);
3630 // skip directory entries for current and parent directory
3631 if (strEqual(directory_name, ".") ||
3632 strEqual(directory_name, ".."))
3634 free(directory_path);
3639 // skip directory entries which are not a directory
3640 if (!dir_entry->is_directory) // not a directory
3642 free(directory_path);
3647 free(directory_path);
3649 // check if this directory contains artwork with or without config file
3650 valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
3652 directory_name, type);
3655 closeDirectory(dir);
3657 // check if this directory directly contains artwork itself
3658 valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
3659 base_directory, ".",
3661 if (!valid_entry_found)
3662 Error(ERR_WARN, "cannot find any valid artwork in directory '%s'",
3666 static TreeInfo *getDummyArtworkInfo(int type)
3668 // this is only needed when there is completely no artwork available
3669 TreeInfo *artwork_new = newTreeInfo();
3671 setTreeInfoToDefaults(artwork_new, type);
3673 setString(&artwork_new->subdir, UNDEFINED_FILENAME);
3674 setString(&artwork_new->fullpath, UNDEFINED_FILENAME);
3675 setString(&artwork_new->basepath, UNDEFINED_FILENAME);
3677 setString(&artwork_new->identifier, UNDEFINED_FILENAME);
3678 setString(&artwork_new->name, UNDEFINED_FILENAME);
3679 setString(&artwork_new->name_sorting, UNDEFINED_FILENAME);
3684 void LoadArtworkInfo(void)
3686 LoadArtworkInfoCache();
3688 DrawInitText("Looking for custom artwork", 120, FC_GREEN);
3690 LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
3691 options.graphics_directory,
3692 TREE_TYPE_GRAPHICS_DIR);
3693 LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
3694 getUserGraphicsDir(),
3695 TREE_TYPE_GRAPHICS_DIR);
3697 LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
3698 options.sounds_directory,
3699 TREE_TYPE_SOUNDS_DIR);
3700 LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
3702 TREE_TYPE_SOUNDS_DIR);
3704 LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
3705 options.music_directory,
3706 TREE_TYPE_MUSIC_DIR);
3707 LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
3709 TREE_TYPE_MUSIC_DIR);
3711 if (artwork.gfx_first == NULL)
3712 artwork.gfx_first = getDummyArtworkInfo(TREE_TYPE_GRAPHICS_DIR);
3713 if (artwork.snd_first == NULL)
3714 artwork.snd_first = getDummyArtworkInfo(TREE_TYPE_SOUNDS_DIR);
3715 if (artwork.mus_first == NULL)
3716 artwork.mus_first = getDummyArtworkInfo(TREE_TYPE_MUSIC_DIR);
3718 // before sorting, the first entries will be from the user directory
3719 artwork.gfx_current =
3720 getTreeInfoFromIdentifier(artwork.gfx_first, setup.graphics_set);
3721 if (artwork.gfx_current == NULL)
3722 artwork.gfx_current =
3723 getTreeInfoFromIdentifier(artwork.gfx_first, GFX_DEFAULT_SUBDIR);
3724 if (artwork.gfx_current == NULL)
3725 artwork.gfx_current = getFirstValidTreeInfoEntry(artwork.gfx_first);
3727 artwork.snd_current =
3728 getTreeInfoFromIdentifier(artwork.snd_first, setup.sounds_set);
3729 if (artwork.snd_current == NULL)
3730 artwork.snd_current =
3731 getTreeInfoFromIdentifier(artwork.snd_first, SND_DEFAULT_SUBDIR);
3732 if (artwork.snd_current == NULL)
3733 artwork.snd_current = getFirstValidTreeInfoEntry(artwork.snd_first);
3735 artwork.mus_current =
3736 getTreeInfoFromIdentifier(artwork.mus_first, setup.music_set);
3737 if (artwork.mus_current == NULL)
3738 artwork.mus_current =
3739 getTreeInfoFromIdentifier(artwork.mus_first, MUS_DEFAULT_SUBDIR);
3740 if (artwork.mus_current == NULL)
3741 artwork.mus_current = getFirstValidTreeInfoEntry(artwork.mus_first);
3743 artwork.gfx_current_identifier = artwork.gfx_current->identifier;
3744 artwork.snd_current_identifier = artwork.snd_current->identifier;
3745 artwork.mus_current_identifier = artwork.mus_current->identifier;
3747 #if ENABLE_UNUSED_CODE
3748 printf("graphics set == %s\n\n", artwork.gfx_current_identifier);
3749 printf("sounds set == %s\n\n", artwork.snd_current_identifier);
3750 printf("music set == %s\n\n", artwork.mus_current_identifier);
3753 sortTreeInfo(&artwork.gfx_first);
3754 sortTreeInfo(&artwork.snd_first);
3755 sortTreeInfo(&artwork.mus_first);
3757 #if ENABLE_UNUSED_CODE
3758 dumpTreeInfo(artwork.gfx_first, 0);
3759 dumpTreeInfo(artwork.snd_first, 0);
3760 dumpTreeInfo(artwork.mus_first, 0);
3764 static void LoadArtworkInfoFromLevelInfo(ArtworkDirTree **artwork_node,
3765 LevelDirTree *level_node)
3767 int type = (*artwork_node)->type;
3769 // recursively check all level directories for artwork sub-directories
3773 // check all tree entries for artwork, but skip parent link entries
3774 if (!level_node->parent_link)
3776 TreeInfo *artwork_new = getArtworkInfoCacheEntry(level_node, type);
3777 boolean cached = (artwork_new != NULL);
3781 pushTreeInfo(artwork_node, artwork_new);
3785 TreeInfo *topnode_last = *artwork_node;
3786 char *path = getPath2(getLevelDirFromTreeInfo(level_node),
3787 ARTWORK_DIRECTORY(type));
3789 LoadArtworkInfoFromArtworkDir(artwork_node, NULL, path, type);
3791 if (topnode_last != *artwork_node) // check for newly added node
3793 artwork_new = *artwork_node;
3795 setString(&artwork_new->identifier, level_node->subdir);
3796 setString(&artwork_new->name, level_node->name);
3797 setString(&artwork_new->name_sorting, level_node->name_sorting);
3799 artwork_new->sort_priority = level_node->sort_priority;
3800 artwork_new->color = LEVELCOLOR(artwork_new);
3806 // insert artwork info (from old cache or filesystem) into new cache
3807 if (artwork_new != NULL)
3808 setArtworkInfoCacheEntry(artwork_new, level_node, type);
3811 DrawInitText(level_node->name, 150, FC_YELLOW);
3813 if (level_node->node_group != NULL)
3814 LoadArtworkInfoFromLevelInfo(artwork_node, level_node->node_group);
3816 level_node = level_node->next;
3820 void LoadLevelArtworkInfo(void)
3822 print_timestamp_init("LoadLevelArtworkInfo");
3824 DrawInitText("Looking for custom level artwork", 120, FC_GREEN);
3826 print_timestamp_time("DrawTimeText");
3828 LoadArtworkInfoFromLevelInfo(&artwork.gfx_first, leveldir_first_all);
3829 print_timestamp_time("LoadArtworkInfoFromLevelInfo (gfx)");
3830 LoadArtworkInfoFromLevelInfo(&artwork.snd_first, leveldir_first_all);
3831 print_timestamp_time("LoadArtworkInfoFromLevelInfo (snd)");
3832 LoadArtworkInfoFromLevelInfo(&artwork.mus_first, leveldir_first_all);
3833 print_timestamp_time("LoadArtworkInfoFromLevelInfo (mus)");
3835 SaveArtworkInfoCache();
3837 print_timestamp_time("SaveArtworkInfoCache");
3839 // needed for reloading level artwork not known at ealier stage
3841 if (!strEqual(artwork.gfx_current_identifier, setup.graphics_set))
3843 artwork.gfx_current =
3844 getTreeInfoFromIdentifier(artwork.gfx_first, setup.graphics_set);
3845 if (artwork.gfx_current == NULL)
3846 artwork.gfx_current =
3847 getTreeInfoFromIdentifier(artwork.gfx_first, GFX_DEFAULT_SUBDIR);
3848 if (artwork.gfx_current == NULL)
3849 artwork.gfx_current = getFirstValidTreeInfoEntry(artwork.gfx_first);
3852 if (!strEqual(artwork.snd_current_identifier, setup.sounds_set))
3854 artwork.snd_current =
3855 getTreeInfoFromIdentifier(artwork.snd_first, setup.sounds_set);
3856 if (artwork.snd_current == NULL)
3857 artwork.snd_current =
3858 getTreeInfoFromIdentifier(artwork.snd_first, SND_DEFAULT_SUBDIR);
3859 if (artwork.snd_current == NULL)
3860 artwork.snd_current = getFirstValidTreeInfoEntry(artwork.snd_first);
3863 if (!strEqual(artwork.mus_current_identifier, setup.music_set))
3865 artwork.mus_current =
3866 getTreeInfoFromIdentifier(artwork.mus_first, setup.music_set);
3867 if (artwork.mus_current == NULL)
3868 artwork.mus_current =
3869 getTreeInfoFromIdentifier(artwork.mus_first, MUS_DEFAULT_SUBDIR);
3870 if (artwork.mus_current == NULL)
3871 artwork.mus_current = getFirstValidTreeInfoEntry(artwork.mus_first);
3874 print_timestamp_time("getTreeInfoFromIdentifier");
3876 sortTreeInfo(&artwork.gfx_first);
3877 sortTreeInfo(&artwork.snd_first);
3878 sortTreeInfo(&artwork.mus_first);
3880 print_timestamp_time("sortTreeInfo");
3882 #if ENABLE_UNUSED_CODE
3883 dumpTreeInfo(artwork.gfx_first, 0);
3884 dumpTreeInfo(artwork.snd_first, 0);
3885 dumpTreeInfo(artwork.mus_first, 0);
3888 print_timestamp_done("LoadLevelArtworkInfo");
3891 static boolean AddTreeSetToTreeInfoExt(TreeInfo *tree_node_old, char *tree_dir,
3892 char *tree_subdir_new, int type)
3894 if (tree_node_old == NULL)
3896 if (type == TREE_TYPE_LEVEL_DIR)
3898 // get level info tree node of personal user level set
3899 tree_node_old = getTreeInfoFromIdentifier(leveldir_first, getLoginName());
3901 // this may happen if "setup.internal.create_user_levelset" is FALSE
3902 // or if file "levelinfo.conf" is missing in personal user level set
3903 if (tree_node_old == NULL)
3904 tree_node_old = leveldir_first->node_group;
3908 // get artwork info tree node of first artwork set
3909 tree_node_old = ARTWORK_FIRST_NODE(artwork, type);
3913 if (tree_dir == NULL)
3914 tree_dir = TREE_USERDIR(type);
3916 if (tree_node_old == NULL ||
3918 tree_subdir_new == NULL) // should not happen
3921 int draw_deactivation_mask = GetDrawDeactivationMask();
3923 // override draw deactivation mask (temporarily disable drawing)
3924 SetDrawDeactivationMask(REDRAW_ALL);
3926 if (type == TREE_TYPE_LEVEL_DIR)
3928 // load new level set config and add it next to first user level set
3929 LoadLevelInfoFromLevelConf(&tree_node_old->next,
3930 tree_node_old->node_parent,
3931 tree_dir, tree_subdir_new);
3935 // load new artwork set config and add it next to first artwork set
3936 LoadArtworkInfoFromArtworkConf(&tree_node_old->next,
3937 tree_node_old->node_parent,
3938 tree_dir, tree_subdir_new, type);
3941 // set draw deactivation mask to previous value
3942 SetDrawDeactivationMask(draw_deactivation_mask);
3944 // get first node of level or artwork info tree
3945 TreeInfo **tree_node_first = TREE_FIRST_NODE_PTR(type);
3947 // get tree info node of newly added level or artwork set
3948 TreeInfo *tree_node_new = getTreeInfoFromIdentifier(*tree_node_first,
3951 if (tree_node_new == NULL) // should not happen
3954 // correct top link and parent node link of newly created tree node
3955 tree_node_new->node_top = tree_node_old->node_top;
3956 tree_node_new->node_parent = tree_node_old->node_parent;
3958 // sort tree info to adjust position of newly added tree set
3959 sortTreeInfo(tree_node_first);
3964 void AddTreeSetToTreeInfo(TreeInfo *tree_node, char *tree_dir,
3965 char *tree_subdir_new, int type)
3967 if (!AddTreeSetToTreeInfoExt(tree_node, tree_dir, tree_subdir_new, type))
3968 Error(ERR_EXIT, "internal tree info structure corrupted -- aborting");
3971 void AddUserLevelSetToLevelInfo(char *level_subdir_new)
3973 AddTreeSetToTreeInfo(NULL, NULL, level_subdir_new, TREE_TYPE_LEVEL_DIR);
3976 char *getArtworkIdentifierForUserLevelSet(int type)
3978 char *classic_artwork_set = getClassicArtworkSet(type);
3980 // check for custom artwork configured in "levelinfo.conf"
3981 char *leveldir_artwork_set =
3982 *LEVELDIR_ARTWORK_SET_PTR(leveldir_current, type);
3983 boolean has_leveldir_artwork_set =
3984 (leveldir_artwork_set != NULL && !strEqual(leveldir_artwork_set,
3985 classic_artwork_set));
3987 // check for custom artwork in sub-directory "graphics" etc.
3988 TreeInfo *artwork_first_node = ARTWORK_FIRST_NODE(artwork, type);
3989 char *leveldir_identifier = leveldir_current->identifier;
3990 boolean has_artwork_subdir =
3991 (getTreeInfoFromIdentifier(artwork_first_node,
3992 leveldir_identifier) != NULL);
3994 return (has_leveldir_artwork_set ? leveldir_artwork_set :
3995 has_artwork_subdir ? leveldir_identifier :
3996 classic_artwork_set);
3999 TreeInfo *getArtworkTreeInfoForUserLevelSet(int type)
4001 char *artwork_set = getArtworkIdentifierForUserLevelSet(type);
4002 TreeInfo *artwork_first_node = ARTWORK_FIRST_NODE(artwork, type);
4003 TreeInfo *ti = getTreeInfoFromIdentifier(artwork_first_node, artwork_set);
4007 ti = getTreeInfoFromIdentifier(artwork_first_node,
4008 ARTWORK_DEFAULT_SUBDIR(type));
4010 Error(ERR_EXIT, "cannot find default graphics -- should not happen");
4016 boolean checkIfCustomArtworkExistsForCurrentLevelSet(void)
4018 char *graphics_set =
4019 getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_GRAPHICS);
4021 getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_SOUNDS);
4023 getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_MUSIC);
4025 return (!strEqual(graphics_set, GFX_CLASSIC_SUBDIR) ||
4026 !strEqual(sounds_set, SND_CLASSIC_SUBDIR) ||
4027 !strEqual(music_set, MUS_CLASSIC_SUBDIR));
4030 boolean UpdateUserLevelSet(char *level_subdir, char *level_name,
4031 char *level_author, int num_levels)
4033 char *filename = getPath2(getUserLevelDir(level_subdir), LEVELINFO_FILENAME);
4034 char *filename_tmp = getStringCat2(filename, ".tmp");
4036 FILE *file_tmp = NULL;
4037 char line[MAX_LINE_LEN];
4038 boolean success = FALSE;
4039 LevelDirTree *leveldir = getTreeInfoFromIdentifier(leveldir_first,
4041 // update values in level directory tree
4043 if (level_name != NULL)
4044 setString(&leveldir->name, level_name);
4046 if (level_author != NULL)
4047 setString(&leveldir->author, level_author);
4049 if (num_levels != -1)
4050 leveldir->levels = num_levels;
4052 // update values that depend on other values
4054 setString(&leveldir->name_sorting, leveldir->name);
4056 leveldir->last_level = leveldir->first_level + leveldir->levels - 1;
4058 // sort order of level sets may have changed
4059 sortTreeInfo(&leveldir_first);
4061 if ((file = fopen(filename, MODE_READ)) &&
4062 (file_tmp = fopen(filename_tmp, MODE_WRITE)))
4064 while (fgets(line, MAX_LINE_LEN, file))
4066 if (strPrefix(line, "name:") && level_name != NULL)
4067 fprintf(file_tmp, "%-32s%s\n", "name:", level_name);
4068 else if (strPrefix(line, "author:") && level_author != NULL)
4069 fprintf(file_tmp, "%-32s%s\n", "author:", level_author);
4070 else if (strPrefix(line, "levels:") && num_levels != -1)
4071 fprintf(file_tmp, "%-32s%d\n", "levels:", num_levels);
4073 fputs(line, file_tmp);
4086 success = (rename(filename_tmp, filename) == 0);
4094 boolean CreateUserLevelSet(char *level_subdir, char *level_name,
4095 char *level_author, int num_levels,
4096 boolean use_artwork_set)
4098 LevelDirTree *level_info;
4103 // create user level sub-directory, if needed
4104 createDirectory(getUserLevelDir(level_subdir), "user level", PERMS_PRIVATE);
4106 filename = getPath2(getUserLevelDir(level_subdir), LEVELINFO_FILENAME);
4108 if (!(file = fopen(filename, MODE_WRITE)))
4110 Error(ERR_WARN, "cannot write level info file '%s'", filename);
4116 level_info = newTreeInfo();
4118 // always start with reliable default values
4119 setTreeInfoToDefaults(level_info, TREE_TYPE_LEVEL_DIR);
4121 setString(&level_info->name, level_name);
4122 setString(&level_info->author, level_author);
4123 level_info->levels = num_levels;
4124 level_info->first_level = 1;
4125 level_info->sort_priority = LEVELCLASS_PRIVATE_START;
4126 level_info->readonly = FALSE;
4128 if (use_artwork_set)
4130 level_info->graphics_set =
4131 getStringCopy(getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_GRAPHICS));
4132 level_info->sounds_set =
4133 getStringCopy(getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_SOUNDS));
4134 level_info->music_set =
4135 getStringCopy(getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_MUSIC));
4138 token_value_position = TOKEN_VALUE_POSITION_SHORT;
4140 fprintFileHeader(file, LEVELINFO_FILENAME);
4143 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
4145 if (i == LEVELINFO_TOKEN_NAME ||
4146 i == LEVELINFO_TOKEN_AUTHOR ||
4147 i == LEVELINFO_TOKEN_LEVELS ||
4148 i == LEVELINFO_TOKEN_FIRST_LEVEL ||
4149 i == LEVELINFO_TOKEN_SORT_PRIORITY ||
4150 i == LEVELINFO_TOKEN_READONLY ||
4151 (use_artwork_set && (i == LEVELINFO_TOKEN_GRAPHICS_SET ||
4152 i == LEVELINFO_TOKEN_SOUNDS_SET ||
4153 i == LEVELINFO_TOKEN_MUSIC_SET)))
4154 fprintf(file, "%s\n", getSetupLine(levelinfo_tokens, "", i));
4156 // just to make things nicer :)
4157 if (i == LEVELINFO_TOKEN_AUTHOR ||
4158 i == LEVELINFO_TOKEN_FIRST_LEVEL ||
4159 (use_artwork_set && i == LEVELINFO_TOKEN_READONLY))
4160 fprintf(file, "\n");
4163 token_value_position = TOKEN_VALUE_POSITION_DEFAULT;
4167 SetFilePermissions(filename, PERMS_PRIVATE);
4169 freeTreeInfo(level_info);
4175 static void SaveUserLevelInfo(void)
4177 CreateUserLevelSet(getLoginName(), getLoginName(), getRealName(), 100, FALSE);
4180 char *getSetupValue(int type, void *value)
4182 static char value_string[MAX_LINE_LEN];
4190 strcpy(value_string, (*(boolean *)value ? "true" : "false"));
4194 strcpy(value_string, (*(boolean *)value ? "on" : "off"));
4198 strcpy(value_string, (*(int *)value == AUTO ? "auto" :
4199 *(int *)value == FALSE ? "off" : "on"));
4203 strcpy(value_string, (*(boolean *)value ? "yes" : "no"));
4206 case TYPE_YES_NO_AUTO:
4207 strcpy(value_string, (*(int *)value == AUTO ? "auto" :
4208 *(int *)value == FALSE ? "no" : "yes"));
4212 strcpy(value_string, (*(boolean *)value ? "AGA" : "ECS"));
4216 strcpy(value_string, getKeyNameFromKey(*(Key *)value));
4220 strcpy(value_string, getX11KeyNameFromKey(*(Key *)value));
4224 sprintf(value_string, "%d", *(int *)value);
4228 if (*(char **)value == NULL)
4231 strcpy(value_string, *(char **)value);
4235 sprintf(value_string, "player_%d", *(int *)value + 1);
4239 value_string[0] = '\0';
4243 if (type & TYPE_GHOSTED)
4244 strcpy(value_string, "n/a");
4246 return value_string;
4249 char *getSetupLine(struct TokenInfo *token_info, char *prefix, int token_nr)
4253 static char token_string[MAX_LINE_LEN];
4254 int token_type = token_info[token_nr].type;
4255 void *setup_value = token_info[token_nr].value;
4256 char *token_text = token_info[token_nr].text;
4257 char *value_string = getSetupValue(token_type, setup_value);
4259 // build complete token string
4260 sprintf(token_string, "%s%s", prefix, token_text);
4262 // build setup entry line
4263 line = getFormattedSetupEntry(token_string, value_string);
4265 if (token_type == TYPE_KEY_X11)
4267 Key key = *(Key *)setup_value;
4268 char *keyname = getKeyNameFromKey(key);
4270 // add comment, if useful
4271 if (!strEqual(keyname, "(undefined)") &&
4272 !strEqual(keyname, "(unknown)"))
4274 // add at least one whitespace
4276 for (i = strlen(line); i < token_comment_position; i++)
4280 strcat(line, keyname);
4287 void LoadLevelSetup_LastSeries(void)
4289 // --------------------------------------------------------------------------
4290 // ~/.<program>/levelsetup.conf
4291 // --------------------------------------------------------------------------
4293 char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
4294 SetupFileHash *level_setup_hash = NULL;
4296 // always start with reliable default values
4297 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
4299 if (!strEqual(DEFAULT_LEVELSET, UNDEFINED_LEVELSET))
4301 leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
4303 if (leveldir_current == NULL)
4304 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
4307 if ((level_setup_hash = loadSetupFileHash(filename)))
4309 char *last_level_series =
4310 getHashEntry(level_setup_hash, TOKEN_STR_LAST_LEVEL_SERIES);
4312 leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
4314 if (leveldir_current == NULL)
4315 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
4317 freeSetupFileHash(level_setup_hash);
4321 Error(ERR_DEBUG, "using default setup values");
4327 static void SaveLevelSetup_LastSeries_Ext(boolean deactivate_last_level_series)
4329 // --------------------------------------------------------------------------
4330 // ~/.<program>/levelsetup.conf
4331 // --------------------------------------------------------------------------
4333 // check if the current level directory structure is available at this point
4334 if (leveldir_current == NULL)
4337 char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
4338 char *level_subdir = leveldir_current->subdir;
4341 InitUserDataDirectory();
4343 if (!(file = fopen(filename, MODE_WRITE)))
4345 Error(ERR_WARN, "cannot write setup file '%s'", filename);
4352 fprintFileHeader(file, LEVELSETUP_FILENAME);
4354 if (deactivate_last_level_series)
4355 fprintf(file, "# %s\n# ", "the following level set may have caused a problem and was deactivated");
4357 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_LEVEL_SERIES,
4362 SetFilePermissions(filename, PERMS_PRIVATE);
4367 void SaveLevelSetup_LastSeries(void)
4369 SaveLevelSetup_LastSeries_Ext(FALSE);
4372 void SaveLevelSetup_LastSeries_Deactivate(void)
4374 SaveLevelSetup_LastSeries_Ext(TRUE);
4377 static void checkSeriesInfo(void)
4379 static char *level_directory = NULL;
4382 DirectoryEntry *dir_entry;
4385 checked_free(level_directory);
4387 // check for more levels besides the 'levels' field of 'levelinfo.conf'
4389 level_directory = getPath2((leveldir_current->in_user_dir ?
4390 getUserLevelDir(NULL) :
4391 options.level_directory),
4392 leveldir_current->fullpath);
4394 if ((dir = openDirectory(level_directory)) == NULL)
4396 Error(ERR_WARN, "cannot read level directory '%s'", level_directory);
4402 while ((dir_entry = readDirectory(dir)) != NULL) // loop all entries
4404 if (strlen(dir_entry->basename) > 4 &&
4405 dir_entry->basename[3] == '.' &&
4406 strEqual(&dir_entry->basename[4], LEVELFILE_EXTENSION))
4408 char levelnum_str[4];
4411 strncpy(levelnum_str, dir_entry->basename, 3);
4412 levelnum_str[3] = '\0';
4414 levelnum_value = atoi(levelnum_str);
4416 if (levelnum_value < leveldir_current->first_level)
4418 Error(ERR_WARN, "additional level %d found", levelnum_value);
4419 leveldir_current->first_level = levelnum_value;
4421 else if (levelnum_value > leveldir_current->last_level)
4423 Error(ERR_WARN, "additional level %d found", levelnum_value);
4424 leveldir_current->last_level = levelnum_value;
4430 closeDirectory(dir);
4433 void LoadLevelSetup_SeriesInfo(void)
4436 SetupFileHash *level_setup_hash = NULL;
4437 char *level_subdir = leveldir_current->subdir;
4440 // always start with reliable default values
4441 level_nr = leveldir_current->first_level;
4443 for (i = 0; i < MAX_LEVELS; i++)
4445 LevelStats_setPlayed(i, 0);
4446 LevelStats_setSolved(i, 0);
4451 // --------------------------------------------------------------------------
4452 // ~/.<program>/levelsetup/<level series>/levelsetup.conf
4453 // --------------------------------------------------------------------------
4455 level_subdir = leveldir_current->subdir;
4457 filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
4459 if ((level_setup_hash = loadSetupFileHash(filename)))
4463 // get last played level in this level set
4465 token_value = getHashEntry(level_setup_hash, TOKEN_STR_LAST_PLAYED_LEVEL);
4469 level_nr = atoi(token_value);
4471 if (level_nr < leveldir_current->first_level)
4472 level_nr = leveldir_current->first_level;
4473 if (level_nr > leveldir_current->last_level)
4474 level_nr = leveldir_current->last_level;
4477 // get handicap level in this level set
4479 token_value = getHashEntry(level_setup_hash, TOKEN_STR_HANDICAP_LEVEL);
4483 int level_nr = atoi(token_value);
4485 if (level_nr < leveldir_current->first_level)
4486 level_nr = leveldir_current->first_level;
4487 if (level_nr > leveldir_current->last_level + 1)
4488 level_nr = leveldir_current->last_level;
4490 if (leveldir_current->user_defined || !leveldir_current->handicap)
4491 level_nr = leveldir_current->last_level;
4493 leveldir_current->handicap_level = level_nr;
4496 // get number of played and solved levels in this level set
4498 BEGIN_HASH_ITERATION(level_setup_hash, itr)
4500 char *token = HASH_ITERATION_TOKEN(itr);
4501 char *value = HASH_ITERATION_VALUE(itr);
4503 if (strlen(token) == 3 &&
4504 token[0] >= '0' && token[0] <= '9' &&
4505 token[1] >= '0' && token[1] <= '9' &&
4506 token[2] >= '0' && token[2] <= '9')
4508 int level_nr = atoi(token);
4511 LevelStats_setPlayed(level_nr, atoi(value)); // read 1st column
4513 value = strchr(value, ' ');
4516 LevelStats_setSolved(level_nr, atoi(value)); // read 2nd column
4519 END_HASH_ITERATION(hash, itr)
4521 freeSetupFileHash(level_setup_hash);
4525 Error(ERR_DEBUG, "using default setup values");
4531 void SaveLevelSetup_SeriesInfo(void)
4534 char *level_subdir = leveldir_current->subdir;
4535 char *level_nr_str = int2str(level_nr, 0);
4536 char *handicap_level_str = int2str(leveldir_current->handicap_level, 0);
4540 // --------------------------------------------------------------------------
4541 // ~/.<program>/levelsetup/<level series>/levelsetup.conf
4542 // --------------------------------------------------------------------------
4544 InitLevelSetupDirectory(level_subdir);
4546 filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
4548 if (!(file = fopen(filename, MODE_WRITE)))
4550 Error(ERR_WARN, "cannot write setup file '%s'", filename);
4555 fprintFileHeader(file, LEVELSETUP_FILENAME);
4557 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_PLAYED_LEVEL,
4559 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_HANDICAP_LEVEL,
4560 handicap_level_str));
4562 for (i = leveldir_current->first_level; i <= leveldir_current->last_level;
4565 if (LevelStats_getPlayed(i) > 0 ||
4566 LevelStats_getSolved(i) > 0)
4571 sprintf(token, "%03d", i);
4572 sprintf(value, "%d %d", LevelStats_getPlayed(i), LevelStats_getSolved(i));
4574 fprintf(file, "%s\n", getFormattedSetupEntry(token, value));
4580 SetFilePermissions(filename, PERMS_PRIVATE);
4585 int LevelStats_getPlayed(int nr)
4587 return (nr >= 0 && nr < MAX_LEVELS ? level_stats[nr].played : 0);
4590 int LevelStats_getSolved(int nr)
4592 return (nr >= 0 && nr < MAX_LEVELS ? level_stats[nr].solved : 0);
4595 void LevelStats_setPlayed(int nr, int value)
4597 if (nr >= 0 && nr < MAX_LEVELS)
4598 level_stats[nr].played = value;
4601 void LevelStats_setSolved(int nr, int value)
4603 if (nr >= 0 && nr < MAX_LEVELS)
4604 level_stats[nr].solved = value;
4607 void LevelStats_incPlayed(int nr)
4609 if (nr >= 0 && nr < MAX_LEVELS)
4610 level_stats[nr].played++;
4613 void LevelStats_incSolved(int nr)
4615 if (nr >= 0 && nr < MAX_LEVELS)
4616 level_stats[nr].solved++;