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 SaveUserLevelInfo();
1100 void InitNetworkLevelDirectory(char *level_subdir)
1102 if (!directoryExists(getNetworkLevelDir(level_subdir)))
1104 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
1105 createDirectory(getNetworkDir(), "network data", PERMS_PRIVATE);
1106 createDirectory(getNetworkLevelDir(NULL), "main network level", PERMS_PRIVATE);
1107 createDirectory(getNetworkLevelDir(level_subdir), "network level", PERMS_PRIVATE);
1111 void InitLevelSetupDirectory(char *level_subdir)
1113 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
1114 createDirectory(getLevelSetupDir(NULL), "main level setup", PERMS_PRIVATE);
1115 createDirectory(getLevelSetupDir(level_subdir), "level setup", PERMS_PRIVATE);
1118 static void InitCacheDirectory(void)
1120 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
1121 createDirectory(getCacheDir(), "cache data", PERMS_PRIVATE);
1125 // ----------------------------------------------------------------------------
1126 // some functions to handle lists of level and artwork directories
1127 // ----------------------------------------------------------------------------
1129 TreeInfo *newTreeInfo(void)
1131 return checked_calloc(sizeof(TreeInfo));
1134 TreeInfo *newTreeInfo_setDefaults(int type)
1136 TreeInfo *ti = newTreeInfo();
1138 setTreeInfoToDefaults(ti, type);
1143 void pushTreeInfo(TreeInfo **node_first, TreeInfo *node_new)
1145 node_new->next = *node_first;
1146 *node_first = node_new;
1149 int numTreeInfo(TreeInfo *node)
1162 boolean validLevelSeries(TreeInfo *node)
1164 return (node != NULL && !node->node_group && !node->parent_link);
1167 TreeInfo *getFirstValidTreeInfoEntry(TreeInfo *node)
1172 if (node->node_group) // enter level group (step down into tree)
1173 return getFirstValidTreeInfoEntry(node->node_group);
1174 else if (node->parent_link) // skip start entry of level group
1176 if (node->next) // get first real level series entry
1177 return getFirstValidTreeInfoEntry(node->next);
1178 else // leave empty level group and go on
1179 return getFirstValidTreeInfoEntry(node->node_parent->next);
1181 else // this seems to be a regular level series
1185 TreeInfo *getTreeInfoFirstGroupEntry(TreeInfo *node)
1190 if (node->node_parent == NULL) // top level group
1191 return *node->node_top;
1192 else // sub level group
1193 return node->node_parent->node_group;
1196 int numTreeInfoInGroup(TreeInfo *node)
1198 return numTreeInfo(getTreeInfoFirstGroupEntry(node));
1201 int posTreeInfo(TreeInfo *node)
1203 TreeInfo *node_cmp = getTreeInfoFirstGroupEntry(node);
1208 if (node_cmp == node)
1212 node_cmp = node_cmp->next;
1218 TreeInfo *getTreeInfoFromPos(TreeInfo *node, int pos)
1220 TreeInfo *node_default = node;
1232 return node_default;
1235 TreeInfo *getTreeInfoFromIdentifier(TreeInfo *node, char *identifier)
1237 if (identifier == NULL)
1242 if (node->node_group)
1244 TreeInfo *node_group;
1246 node_group = getTreeInfoFromIdentifier(node->node_group, identifier);
1251 else if (!node->parent_link)
1253 if (strEqual(identifier, node->identifier))
1263 static TreeInfo *cloneTreeNode(TreeInfo **node_top, TreeInfo *node_parent,
1264 TreeInfo *node, boolean skip_sets_without_levels)
1271 if (!node->parent_link && !node->level_group &&
1272 skip_sets_without_levels && node->levels == 0)
1273 return cloneTreeNode(node_top, node_parent, node->next,
1274 skip_sets_without_levels);
1276 node_new = getTreeInfoCopy(node); // copy complete node
1278 node_new->node_top = node_top; // correct top node link
1279 node_new->node_parent = node_parent; // correct parent node link
1281 if (node->level_group)
1282 node_new->node_group = cloneTreeNode(node_top, node_new, node->node_group,
1283 skip_sets_without_levels);
1285 node_new->next = cloneTreeNode(node_top, node_parent, node->next,
1286 skip_sets_without_levels);
1291 static void cloneTree(TreeInfo **ti_new, TreeInfo *ti, boolean skip_empty_sets)
1293 TreeInfo *ti_cloned = cloneTreeNode(ti_new, NULL, ti, skip_empty_sets);
1295 *ti_new = ti_cloned;
1298 static boolean adjustTreeGraphicsForEMC(TreeInfo *node)
1300 boolean settings_changed = FALSE;
1304 if (node->graphics_set_ecs && !setup.prefer_aga_graphics &&
1305 !strEqual(node->graphics_set, node->graphics_set_ecs))
1307 setString(&node->graphics_set, node->graphics_set_ecs);
1308 settings_changed = TRUE;
1310 else if (node->graphics_set_aga && setup.prefer_aga_graphics &&
1311 !strEqual(node->graphics_set, node->graphics_set_aga))
1313 setString(&node->graphics_set, node->graphics_set_aga);
1314 settings_changed = TRUE;
1317 if (node->node_group != NULL)
1318 settings_changed |= adjustTreeGraphicsForEMC(node->node_group);
1323 return settings_changed;
1326 void dumpTreeInfo(TreeInfo *node, int depth)
1330 printf("Dumping TreeInfo:\n");
1334 for (i = 0; i < (depth + 1) * 3; i++)
1337 printf("'%s' / '%s'\n", node->identifier, node->name);
1340 // use for dumping artwork info tree
1341 printf("subdir == '%s' ['%s', '%s'] [%d])\n",
1342 node->subdir, node->fullpath, node->basepath, node->in_user_dir);
1345 if (node->node_group != NULL)
1346 dumpTreeInfo(node->node_group, depth + 1);
1352 void sortTreeInfoBySortFunction(TreeInfo **node_first,
1353 int (*compare_function)(const void *,
1356 int num_nodes = numTreeInfo(*node_first);
1357 TreeInfo **sort_array;
1358 TreeInfo *node = *node_first;
1364 // allocate array for sorting structure pointers
1365 sort_array = checked_calloc(num_nodes * sizeof(TreeInfo *));
1367 // writing structure pointers to sorting array
1368 while (i < num_nodes && node) // double boundary check...
1370 sort_array[i] = node;
1376 // sorting the structure pointers in the sorting array
1377 qsort(sort_array, num_nodes, sizeof(TreeInfo *),
1380 // update the linkage of list elements with the sorted node array
1381 for (i = 0; i < num_nodes - 1; i++)
1382 sort_array[i]->next = sort_array[i + 1];
1383 sort_array[num_nodes - 1]->next = NULL;
1385 // update the linkage of the main list anchor pointer
1386 *node_first = sort_array[0];
1390 // now recursively sort the level group structures
1394 if (node->node_group != NULL)
1395 sortTreeInfoBySortFunction(&node->node_group, compare_function);
1401 void sortTreeInfo(TreeInfo **node_first)
1403 sortTreeInfoBySortFunction(node_first, compareTreeInfoEntries);
1407 // ============================================================================
1408 // some stuff from "files.c"
1409 // ============================================================================
1411 #if defined(PLATFORM_WIN32)
1413 #define S_IRGRP S_IRUSR
1416 #define S_IROTH S_IRUSR
1419 #define S_IWGRP S_IWUSR
1422 #define S_IWOTH S_IWUSR
1425 #define S_IXGRP S_IXUSR
1428 #define S_IXOTH S_IXUSR
1431 #define S_IRWXG (S_IRGRP | S_IWGRP | S_IXGRP)
1436 #endif // PLATFORM_WIN32
1438 // file permissions for newly written files
1439 #define MODE_R_ALL (S_IRUSR | S_IRGRP | S_IROTH)
1440 #define MODE_W_ALL (S_IWUSR | S_IWGRP | S_IWOTH)
1441 #define MODE_X_ALL (S_IXUSR | S_IXGRP | S_IXOTH)
1443 #define MODE_W_PRIVATE (S_IWUSR)
1444 #define MODE_W_PUBLIC_FILE (S_IWUSR | S_IWGRP)
1445 #define MODE_W_PUBLIC_DIR (S_IWUSR | S_IWGRP | S_ISGID)
1447 #define DIR_PERMS_PRIVATE (MODE_R_ALL | MODE_X_ALL | MODE_W_PRIVATE)
1448 #define DIR_PERMS_PUBLIC (MODE_R_ALL | MODE_X_ALL | MODE_W_PUBLIC_DIR)
1449 #define DIR_PERMS_PUBLIC_ALL (MODE_R_ALL | MODE_X_ALL | MODE_W_ALL)
1451 #define FILE_PERMS_PRIVATE (MODE_R_ALL | MODE_W_PRIVATE)
1452 #define FILE_PERMS_PUBLIC (MODE_R_ALL | MODE_W_PUBLIC_FILE)
1453 #define FILE_PERMS_PUBLIC_ALL (MODE_R_ALL | MODE_W_ALL)
1456 char *getHomeDir(void)
1458 static char *dir = NULL;
1460 #if defined(PLATFORM_WIN32)
1463 dir = checked_malloc(MAX_PATH + 1);
1465 if (!SUCCEEDED(SHGetFolderPath(NULL, CSIDL_PERSONAL, NULL, 0, dir)))
1468 #elif defined(PLATFORM_UNIX)
1471 if ((dir = getenv("HOME")) == NULL)
1475 if ((pwd = getpwuid(getuid())) != NULL)
1476 dir = getStringCopy(pwd->pw_dir);
1488 char *getCommonDataDir(void)
1490 static char *common_data_dir = NULL;
1492 #if defined(PLATFORM_WIN32)
1493 if (common_data_dir == NULL)
1495 char *dir = checked_malloc(MAX_PATH + 1);
1497 if (SUCCEEDED(SHGetFolderPath(NULL, CSIDL_COMMON_DOCUMENTS, NULL, 0, dir))
1498 && !strEqual(dir, "")) // empty for Windows 95/98
1499 common_data_dir = getPath2(dir, program.userdata_subdir);
1501 common_data_dir = options.rw_base_directory;
1504 if (common_data_dir == NULL)
1505 common_data_dir = options.rw_base_directory;
1508 return common_data_dir;
1511 char *getPersonalDataDir(void)
1513 static char *personal_data_dir = NULL;
1515 #if defined(PLATFORM_MACOSX)
1516 if (personal_data_dir == NULL)
1517 personal_data_dir = getPath2(getHomeDir(), "Documents");
1519 if (personal_data_dir == NULL)
1520 personal_data_dir = getHomeDir();
1523 return personal_data_dir;
1526 char *getUserGameDataDir(void)
1528 static char *user_game_data_dir = NULL;
1530 #if defined(PLATFORM_ANDROID)
1531 if (user_game_data_dir == NULL)
1532 user_game_data_dir = (char *)(SDL_AndroidGetExternalStorageState() &
1533 SDL_ANDROID_EXTERNAL_STORAGE_WRITE ?
1534 SDL_AndroidGetExternalStoragePath() :
1535 SDL_AndroidGetInternalStoragePath());
1537 if (user_game_data_dir == NULL)
1538 user_game_data_dir = getPath2(getPersonalDataDir(),
1539 program.userdata_subdir);
1542 return user_game_data_dir;
1545 char *getSetupDir(void)
1547 return getUserGameDataDir();
1550 static mode_t posix_umask(mode_t mask)
1552 #if defined(PLATFORM_UNIX)
1559 static int posix_mkdir(const char *pathname, mode_t mode)
1561 #if defined(PLATFORM_WIN32)
1562 return mkdir(pathname);
1564 return mkdir(pathname, mode);
1568 static boolean posix_process_running_setgid(void)
1570 #if defined(PLATFORM_UNIX)
1571 return (getgid() != getegid());
1577 void createDirectory(char *dir, char *text, int permission_class)
1579 if (directoryExists(dir))
1582 // leave "other" permissions in umask untouched, but ensure group parts
1583 // of USERDATA_DIR_MODE are not masked
1584 mode_t dir_mode = (permission_class == PERMS_PRIVATE ?
1585 DIR_PERMS_PRIVATE : DIR_PERMS_PUBLIC);
1586 mode_t last_umask = posix_umask(0);
1587 mode_t group_umask = ~(dir_mode & S_IRWXG);
1588 int running_setgid = posix_process_running_setgid();
1590 if (permission_class == PERMS_PUBLIC)
1592 // if we're setgid, protect files against "other"
1593 // else keep umask(0) to make the dir world-writable
1596 posix_umask(last_umask & group_umask);
1598 dir_mode = DIR_PERMS_PUBLIC_ALL;
1601 if (posix_mkdir(dir, dir_mode) != 0)
1602 Error(ERR_WARN, "cannot create %s directory '%s': %s",
1603 text, dir, strerror(errno));
1605 if (permission_class == PERMS_PUBLIC && !running_setgid)
1606 chmod(dir, dir_mode);
1608 posix_umask(last_umask); // restore previous umask
1611 void InitUserDataDirectory(void)
1613 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
1616 void SetFilePermissions(char *filename, int permission_class)
1618 int running_setgid = posix_process_running_setgid();
1619 int perms = (permission_class == PERMS_PRIVATE ?
1620 FILE_PERMS_PRIVATE : FILE_PERMS_PUBLIC);
1622 if (permission_class == PERMS_PUBLIC && !running_setgid)
1623 perms = FILE_PERMS_PUBLIC_ALL;
1625 chmod(filename, perms);
1628 char *getCookie(char *file_type)
1630 static char cookie[MAX_COOKIE_LEN + 1];
1632 if (strlen(program.cookie_prefix) + 1 +
1633 strlen(file_type) + strlen("_FILE_VERSION_x.x") > MAX_COOKIE_LEN)
1634 return "[COOKIE ERROR]"; // should never happen
1636 sprintf(cookie, "%s_%s_FILE_VERSION_%d.%d",
1637 program.cookie_prefix, file_type,
1638 program.version_super, program.version_major);
1643 void fprintFileHeader(FILE *file, char *basename)
1645 char *prefix = "# ";
1648 fprintf_line_with_prefix(file, prefix, sep1, 77);
1649 fprintf(file, "%s%s\n", prefix, basename);
1650 fprintf_line_with_prefix(file, prefix, sep1, 77);
1651 fprintf(file, "\n");
1654 int getFileVersionFromCookieString(const char *cookie)
1656 const char *ptr_cookie1, *ptr_cookie2;
1657 const char *pattern1 = "_FILE_VERSION_";
1658 const char *pattern2 = "?.?";
1659 const int len_cookie = strlen(cookie);
1660 const int len_pattern1 = strlen(pattern1);
1661 const int len_pattern2 = strlen(pattern2);
1662 const int len_pattern = len_pattern1 + len_pattern2;
1663 int version_super, version_major;
1665 if (len_cookie <= len_pattern)
1668 ptr_cookie1 = &cookie[len_cookie - len_pattern];
1669 ptr_cookie2 = &cookie[len_cookie - len_pattern2];
1671 if (strncmp(ptr_cookie1, pattern1, len_pattern1) != 0)
1674 if (ptr_cookie2[0] < '0' || ptr_cookie2[0] > '9' ||
1675 ptr_cookie2[1] != '.' ||
1676 ptr_cookie2[2] < '0' || ptr_cookie2[2] > '9')
1679 version_super = ptr_cookie2[0] - '0';
1680 version_major = ptr_cookie2[2] - '0';
1682 return VERSION_IDENT(version_super, version_major, 0, 0);
1685 boolean checkCookieString(const char *cookie, const char *template)
1687 const char *pattern = "_FILE_VERSION_?.?";
1688 const int len_cookie = strlen(cookie);
1689 const int len_template = strlen(template);
1690 const int len_pattern = strlen(pattern);
1692 if (len_cookie != len_template)
1695 if (strncmp(cookie, template, len_cookie - len_pattern) != 0)
1702 // ----------------------------------------------------------------------------
1703 // setup file list and hash handling functions
1704 // ----------------------------------------------------------------------------
1706 char *getFormattedSetupEntry(char *token, char *value)
1709 static char entry[MAX_LINE_LEN];
1711 // if value is an empty string, just return token without value
1715 // start with the token and some spaces to format output line
1716 sprintf(entry, "%s:", token);
1717 for (i = strlen(entry); i < token_value_position; i++)
1720 // continue with the token's value
1721 strcat(entry, value);
1726 SetupFileList *newSetupFileList(char *token, char *value)
1728 SetupFileList *new = checked_malloc(sizeof(SetupFileList));
1730 new->token = getStringCopy(token);
1731 new->value = getStringCopy(value);
1738 void freeSetupFileList(SetupFileList *list)
1743 checked_free(list->token);
1744 checked_free(list->value);
1747 freeSetupFileList(list->next);
1752 char *getListEntry(SetupFileList *list, char *token)
1757 if (strEqual(list->token, token))
1760 return getListEntry(list->next, token);
1763 SetupFileList *setListEntry(SetupFileList *list, char *token, char *value)
1768 if (strEqual(list->token, token))
1770 checked_free(list->value);
1772 list->value = getStringCopy(value);
1776 else if (list->next == NULL)
1777 return (list->next = newSetupFileList(token, value));
1779 return setListEntry(list->next, token, value);
1782 SetupFileList *addListEntry(SetupFileList *list, char *token, char *value)
1787 if (list->next == NULL)
1788 return (list->next = newSetupFileList(token, value));
1790 return addListEntry(list->next, token, value);
1793 #if ENABLE_UNUSED_CODE
1795 static void printSetupFileList(SetupFileList *list)
1800 printf("token: '%s'\n", list->token);
1801 printf("value: '%s'\n", list->value);
1803 printSetupFileList(list->next);
1809 DEFINE_HASHTABLE_INSERT(insert_hash_entry, char, char);
1810 DEFINE_HASHTABLE_SEARCH(search_hash_entry, char, char);
1811 DEFINE_HASHTABLE_CHANGE(change_hash_entry, char, char);
1812 DEFINE_HASHTABLE_REMOVE(remove_hash_entry, char, char);
1814 #define insert_hash_entry hashtable_insert
1815 #define search_hash_entry hashtable_search
1816 #define change_hash_entry hashtable_change
1817 #define remove_hash_entry hashtable_remove
1820 unsigned int get_hash_from_key(void *key)
1825 This algorithm (k=33) was first reported by Dan Bernstein many years ago in
1826 'comp.lang.c'. Another version of this algorithm (now favored by Bernstein)
1827 uses XOR: hash(i) = hash(i - 1) * 33 ^ str[i]; the magic of number 33 (why
1828 it works better than many other constants, prime or not) has never been
1829 adequately explained.
1831 If you just want to have a good hash function, and cannot wait, djb2
1832 is one of the best string hash functions i know. It has excellent
1833 distribution and speed on many different sets of keys and table sizes.
1834 You are not likely to do better with one of the "well known" functions
1835 such as PJW, K&R, etc.
1837 Ozan (oz) Yigit [http://www.cs.yorku.ca/~oz/hash.html]
1840 char *str = (char *)key;
1841 unsigned int hash = 5381;
1844 while ((c = *str++))
1845 hash = ((hash << 5) + hash) + c; // hash * 33 + c
1850 static int keys_are_equal(void *key1, void *key2)
1852 return (strEqual((char *)key1, (char *)key2));
1855 SetupFileHash *newSetupFileHash(void)
1857 SetupFileHash *new_hash =
1858 create_hashtable(16, 0.75, get_hash_from_key, keys_are_equal);
1860 if (new_hash == NULL)
1861 Error(ERR_EXIT, "create_hashtable() failed -- out of memory");
1866 void freeSetupFileHash(SetupFileHash *hash)
1871 hashtable_destroy(hash, 1); // 1 == also free values stored in hash
1874 char *getHashEntry(SetupFileHash *hash, char *token)
1879 return search_hash_entry(hash, token);
1882 void setHashEntry(SetupFileHash *hash, char *token, char *value)
1889 value_copy = getStringCopy(value);
1891 // change value; if it does not exist, insert it as new
1892 if (!change_hash_entry(hash, token, value_copy))
1893 if (!insert_hash_entry(hash, getStringCopy(token), value_copy))
1894 Error(ERR_EXIT, "cannot insert into hash -- aborting");
1897 char *removeHashEntry(SetupFileHash *hash, char *token)
1902 return remove_hash_entry(hash, token);
1905 #if ENABLE_UNUSED_CODE
1907 static void printSetupFileHash(SetupFileHash *hash)
1909 BEGIN_HASH_ITERATION(hash, itr)
1911 printf("token: '%s'\n", HASH_ITERATION_TOKEN(itr));
1912 printf("value: '%s'\n", HASH_ITERATION_VALUE(itr));
1914 END_HASH_ITERATION(hash, itr)
1919 #define ALLOW_TOKEN_VALUE_SEPARATOR_BEING_WHITESPACE 1
1920 #define CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING 0
1921 #define CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH 0
1923 static boolean token_value_separator_found = FALSE;
1924 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
1925 static boolean token_value_separator_warning = FALSE;
1927 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
1928 static boolean token_already_exists_warning = FALSE;
1931 static boolean getTokenValueFromSetupLineExt(char *line,
1932 char **token_ptr, char **value_ptr,
1933 char *filename, char *line_raw,
1935 boolean separator_required)
1937 static char line_copy[MAX_LINE_LEN + 1], line_raw_copy[MAX_LINE_LEN + 1];
1938 char *token, *value, *line_ptr;
1940 // when externally invoked via ReadTokenValueFromLine(), copy line buffers
1941 if (line_raw == NULL)
1943 strncpy(line_copy, line, MAX_LINE_LEN);
1944 line_copy[MAX_LINE_LEN] = '\0';
1947 strcpy(line_raw_copy, line_copy);
1948 line_raw = line_raw_copy;
1951 // cut trailing comment from input line
1952 for (line_ptr = line; *line_ptr; line_ptr++)
1954 if (*line_ptr == '#')
1961 // cut trailing whitespaces from input line
1962 for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
1963 if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
1966 // ignore empty lines
1970 // cut leading whitespaces from token
1971 for (token = line; *token; token++)
1972 if (*token != ' ' && *token != '\t')
1975 // start with empty value as reliable default
1978 token_value_separator_found = FALSE;
1980 // find end of token to determine start of value
1981 for (line_ptr = token; *line_ptr; line_ptr++)
1983 // first look for an explicit token/value separator, like ':' or '='
1984 if (*line_ptr == ':' || *line_ptr == '=')
1986 *line_ptr = '\0'; // terminate token string
1987 value = line_ptr + 1; // set beginning of value
1989 token_value_separator_found = TRUE;
1995 #if ALLOW_TOKEN_VALUE_SEPARATOR_BEING_WHITESPACE
1996 // fallback: if no token/value separator found, also allow whitespaces
1997 if (!token_value_separator_found && !separator_required)
1999 for (line_ptr = token; *line_ptr; line_ptr++)
2001 if (*line_ptr == ' ' || *line_ptr == '\t')
2003 *line_ptr = '\0'; // terminate token string
2004 value = line_ptr + 1; // set beginning of value
2006 token_value_separator_found = TRUE;
2012 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2013 if (token_value_separator_found)
2015 if (!token_value_separator_warning)
2017 Error(ERR_INFO_LINE, "-");
2019 if (filename != NULL)
2021 Error(ERR_WARN, "missing token/value separator(s) in config file:");
2022 Error(ERR_INFO, "- config file: '%s'", filename);
2026 Error(ERR_WARN, "missing token/value separator(s):");
2029 token_value_separator_warning = TRUE;
2032 if (filename != NULL)
2033 Error(ERR_INFO, "- line %d: '%s'", line_nr, line_raw);
2035 Error(ERR_INFO, "- line: '%s'", line_raw);
2041 // cut trailing whitespaces from token
2042 for (line_ptr = &token[strlen(token)]; line_ptr >= token; line_ptr--)
2043 if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
2046 // cut leading whitespaces from value
2047 for (; *value; value++)
2048 if (*value != ' ' && *value != '\t')
2057 boolean getTokenValueFromSetupLine(char *line, char **token, char **value)
2059 // while the internal (old) interface does not require a token/value
2060 // separator (for downwards compatibility with existing files which
2061 // don't use them), it is mandatory for the external (new) interface
2063 return getTokenValueFromSetupLineExt(line, token, value, NULL, NULL, 0, TRUE);
2066 static boolean loadSetupFileData(void *setup_file_data, char *filename,
2067 boolean top_recursion_level, boolean is_hash)
2069 static SetupFileHash *include_filename_hash = NULL;
2070 char line[MAX_LINE_LEN], line_raw[MAX_LINE_LEN], previous_line[MAX_LINE_LEN];
2071 char *token, *value, *line_ptr;
2072 void *insert_ptr = NULL;
2073 boolean read_continued_line = FALSE;
2075 int line_nr = 0, token_count = 0, include_count = 0;
2077 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2078 token_value_separator_warning = FALSE;
2081 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2082 token_already_exists_warning = FALSE;
2085 if (!(file = openFile(filename, MODE_READ)))
2087 #if DEBUG_NO_CONFIG_FILE
2088 Error(ERR_DEBUG, "cannot open configuration file '%s'", filename);
2094 // use "insert pointer" to store list end for constant insertion complexity
2096 insert_ptr = setup_file_data;
2098 // on top invocation, create hash to mark included files (to prevent loops)
2099 if (top_recursion_level)
2100 include_filename_hash = newSetupFileHash();
2102 // mark this file as already included (to prevent including it again)
2103 setHashEntry(include_filename_hash, getBaseNamePtr(filename), "true");
2105 while (!checkEndOfFile(file))
2107 // read next line of input file
2108 if (!getStringFromFile(file, line, MAX_LINE_LEN))
2111 // check if line was completely read and is terminated by line break
2112 if (strlen(line) > 0 && line[strlen(line) - 1] == '\n')
2115 // cut trailing line break (this can be newline and/or carriage return)
2116 for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
2117 if ((*line_ptr == '\n' || *line_ptr == '\r') && *(line_ptr + 1) == '\0')
2120 // copy raw input line for later use (mainly debugging output)
2121 strcpy(line_raw, line);
2123 if (read_continued_line)
2125 // append new line to existing line, if there is enough space
2126 if (strlen(previous_line) + strlen(line_ptr) < MAX_LINE_LEN)
2127 strcat(previous_line, line_ptr);
2129 strcpy(line, previous_line); // copy storage buffer to line
2131 read_continued_line = FALSE;
2134 // if the last character is '\', continue at next line
2135 if (strlen(line) > 0 && line[strlen(line) - 1] == '\\')
2137 line[strlen(line) - 1] = '\0'; // cut off trailing backslash
2138 strcpy(previous_line, line); // copy line to storage buffer
2140 read_continued_line = TRUE;
2145 if (!getTokenValueFromSetupLineExt(line, &token, &value, filename,
2146 line_raw, line_nr, FALSE))
2151 if (strEqual(token, "include"))
2153 if (getHashEntry(include_filename_hash, value) == NULL)
2155 char *basepath = getBasePath(filename);
2156 char *basename = getBaseName(value);
2157 char *filename_include = getPath2(basepath, basename);
2159 loadSetupFileData(setup_file_data, filename_include, FALSE, is_hash);
2163 free(filename_include);
2169 Error(ERR_WARN, "ignoring already processed file '%s'", value);
2176 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2178 getHashEntry((SetupFileHash *)setup_file_data, token);
2180 if (old_value != NULL)
2182 if (!token_already_exists_warning)
2184 Error(ERR_INFO_LINE, "-");
2185 Error(ERR_WARN, "duplicate token(s) found in config file:");
2186 Error(ERR_INFO, "- config file: '%s'", filename);
2188 token_already_exists_warning = TRUE;
2191 Error(ERR_INFO, "- token: '%s' (in line %d)", token, line_nr);
2192 Error(ERR_INFO, " old value: '%s'", old_value);
2193 Error(ERR_INFO, " new value: '%s'", value);
2197 setHashEntry((SetupFileHash *)setup_file_data, token, value);
2201 insert_ptr = addListEntry((SetupFileList *)insert_ptr, token, value);
2211 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2212 if (token_value_separator_warning)
2213 Error(ERR_INFO_LINE, "-");
2216 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2217 if (token_already_exists_warning)
2218 Error(ERR_INFO_LINE, "-");
2221 if (token_count == 0 && include_count == 0)
2222 Error(ERR_WARN, "configuration file '%s' is empty", filename);
2224 if (top_recursion_level)
2225 freeSetupFileHash(include_filename_hash);
2230 static void saveSetupFileHash(SetupFileHash *hash, char *filename)
2234 if (!(file = fopen(filename, MODE_WRITE)))
2236 Error(ERR_WARN, "cannot write configuration file '%s'", filename);
2241 BEGIN_HASH_ITERATION(hash, itr)
2243 fprintf(file, "%s\n", getFormattedSetupEntry(HASH_ITERATION_TOKEN(itr),
2244 HASH_ITERATION_VALUE(itr)));
2246 END_HASH_ITERATION(hash, itr)
2251 SetupFileList *loadSetupFileList(char *filename)
2253 SetupFileList *setup_file_list = newSetupFileList("", "");
2254 SetupFileList *first_valid_list_entry;
2256 if (!loadSetupFileData(setup_file_list, filename, TRUE, FALSE))
2258 freeSetupFileList(setup_file_list);
2263 first_valid_list_entry = setup_file_list->next;
2265 // free empty list header
2266 setup_file_list->next = NULL;
2267 freeSetupFileList(setup_file_list);
2269 return first_valid_list_entry;
2272 SetupFileHash *loadSetupFileHash(char *filename)
2274 SetupFileHash *setup_file_hash = newSetupFileHash();
2276 if (!loadSetupFileData(setup_file_hash, filename, TRUE, TRUE))
2278 freeSetupFileHash(setup_file_hash);
2283 return setup_file_hash;
2287 // ============================================================================
2289 // ============================================================================
2291 #define TOKEN_STR_LAST_LEVEL_SERIES "last_level_series"
2292 #define TOKEN_STR_LAST_PLAYED_LEVEL "last_played_level"
2293 #define TOKEN_STR_HANDICAP_LEVEL "handicap_level"
2295 // level directory info
2296 #define LEVELINFO_TOKEN_IDENTIFIER 0
2297 #define LEVELINFO_TOKEN_NAME 1
2298 #define LEVELINFO_TOKEN_NAME_SORTING 2
2299 #define LEVELINFO_TOKEN_AUTHOR 3
2300 #define LEVELINFO_TOKEN_YEAR 4
2301 #define LEVELINFO_TOKEN_PROGRAM_TITLE 5
2302 #define LEVELINFO_TOKEN_PROGRAM_COPYRIGHT 6
2303 #define LEVELINFO_TOKEN_PROGRAM_COMPANY 7
2304 #define LEVELINFO_TOKEN_IMPORTED_FROM 8
2305 #define LEVELINFO_TOKEN_IMPORTED_BY 9
2306 #define LEVELINFO_TOKEN_TESTED_BY 10
2307 #define LEVELINFO_TOKEN_LEVELS 11
2308 #define LEVELINFO_TOKEN_FIRST_LEVEL 12
2309 #define LEVELINFO_TOKEN_SORT_PRIORITY 13
2310 #define LEVELINFO_TOKEN_LATEST_ENGINE 14
2311 #define LEVELINFO_TOKEN_LEVEL_GROUP 15
2312 #define LEVELINFO_TOKEN_READONLY 16
2313 #define LEVELINFO_TOKEN_GRAPHICS_SET_ECS 17
2314 #define LEVELINFO_TOKEN_GRAPHICS_SET_AGA 18
2315 #define LEVELINFO_TOKEN_GRAPHICS_SET 19
2316 #define LEVELINFO_TOKEN_SOUNDS_SET 20
2317 #define LEVELINFO_TOKEN_MUSIC_SET 21
2318 #define LEVELINFO_TOKEN_FILENAME 22
2319 #define LEVELINFO_TOKEN_FILETYPE 23
2320 #define LEVELINFO_TOKEN_SPECIAL_FLAGS 24
2321 #define LEVELINFO_TOKEN_HANDICAP 25
2322 #define LEVELINFO_TOKEN_SKIP_LEVELS 26
2324 #define NUM_LEVELINFO_TOKENS 27
2326 static LevelDirTree ldi;
2328 static struct TokenInfo levelinfo_tokens[] =
2330 // level directory info
2331 { TYPE_STRING, &ldi.identifier, "identifier" },
2332 { TYPE_STRING, &ldi.name, "name" },
2333 { TYPE_STRING, &ldi.name_sorting, "name_sorting" },
2334 { TYPE_STRING, &ldi.author, "author" },
2335 { TYPE_STRING, &ldi.year, "year" },
2336 { TYPE_STRING, &ldi.program_title, "program_title" },
2337 { TYPE_STRING, &ldi.program_copyright, "program_copyright" },
2338 { TYPE_STRING, &ldi.program_company, "program_company" },
2339 { TYPE_STRING, &ldi.imported_from, "imported_from" },
2340 { TYPE_STRING, &ldi.imported_by, "imported_by" },
2341 { TYPE_STRING, &ldi.tested_by, "tested_by" },
2342 { TYPE_INTEGER, &ldi.levels, "levels" },
2343 { TYPE_INTEGER, &ldi.first_level, "first_level" },
2344 { TYPE_INTEGER, &ldi.sort_priority, "sort_priority" },
2345 { TYPE_BOOLEAN, &ldi.latest_engine, "latest_engine" },
2346 { TYPE_BOOLEAN, &ldi.level_group, "level_group" },
2347 { TYPE_BOOLEAN, &ldi.readonly, "readonly" },
2348 { TYPE_STRING, &ldi.graphics_set_ecs, "graphics_set.ecs" },
2349 { TYPE_STRING, &ldi.graphics_set_aga, "graphics_set.aga" },
2350 { TYPE_STRING, &ldi.graphics_set, "graphics_set" },
2351 { TYPE_STRING, &ldi.sounds_set, "sounds_set" },
2352 { TYPE_STRING, &ldi.music_set, "music_set" },
2353 { TYPE_STRING, &ldi.level_filename, "filename" },
2354 { TYPE_STRING, &ldi.level_filetype, "filetype" },
2355 { TYPE_STRING, &ldi.special_flags, "special_flags" },
2356 { TYPE_BOOLEAN, &ldi.handicap, "handicap" },
2357 { TYPE_BOOLEAN, &ldi.skip_levels, "skip_levels" }
2360 static struct TokenInfo artworkinfo_tokens[] =
2362 // artwork directory info
2363 { TYPE_STRING, &ldi.identifier, "identifier" },
2364 { TYPE_STRING, &ldi.subdir, "subdir" },
2365 { TYPE_STRING, &ldi.name, "name" },
2366 { TYPE_STRING, &ldi.name_sorting, "name_sorting" },
2367 { TYPE_STRING, &ldi.author, "author" },
2368 { TYPE_STRING, &ldi.program_title, "program_title" },
2369 { TYPE_STRING, &ldi.program_copyright, "program_copyright" },
2370 { TYPE_STRING, &ldi.program_company, "program_company" },
2371 { TYPE_INTEGER, &ldi.sort_priority, "sort_priority" },
2372 { TYPE_STRING, &ldi.basepath, "basepath" },
2373 { TYPE_STRING, &ldi.fullpath, "fullpath" },
2374 { TYPE_BOOLEAN, &ldi.in_user_dir, "in_user_dir" },
2375 { TYPE_INTEGER, &ldi.color, "color" },
2376 { TYPE_STRING, &ldi.class_desc, "class_desc" },
2381 static void setTreeInfoToDefaults(TreeInfo *ti, int type)
2385 ti->node_top = (ti->type == TREE_TYPE_LEVEL_DIR ? &leveldir_first :
2386 ti->type == TREE_TYPE_GRAPHICS_DIR ? &artwork.gfx_first :
2387 ti->type == TREE_TYPE_SOUNDS_DIR ? &artwork.snd_first :
2388 ti->type == TREE_TYPE_MUSIC_DIR ? &artwork.mus_first :
2391 ti->node_parent = NULL;
2392 ti->node_group = NULL;
2399 ti->fullpath = NULL;
2400 ti->basepath = NULL;
2401 ti->identifier = NULL;
2402 ti->name = getStringCopy(ANONYMOUS_NAME);
2403 ti->name_sorting = NULL;
2404 ti->author = getStringCopy(ANONYMOUS_NAME);
2407 ti->program_title = NULL;
2408 ti->program_copyright = NULL;
2409 ti->program_company = NULL;
2411 ti->sort_priority = LEVELCLASS_UNDEFINED; // default: least priority
2412 ti->latest_engine = FALSE; // default: get from level
2413 ti->parent_link = FALSE;
2414 ti->in_user_dir = FALSE;
2415 ti->user_defined = FALSE;
2417 ti->class_desc = NULL;
2419 ti->infotext = getStringCopy(TREE_INFOTEXT(ti->type));
2421 if (ti->type == TREE_TYPE_LEVEL_DIR)
2423 ti->imported_from = NULL;
2424 ti->imported_by = NULL;
2425 ti->tested_by = NULL;
2427 ti->graphics_set_ecs = NULL;
2428 ti->graphics_set_aga = NULL;
2429 ti->graphics_set = NULL;
2430 ti->sounds_set = NULL;
2431 ti->music_set = NULL;
2432 ti->graphics_path = getStringCopy(UNDEFINED_FILENAME);
2433 ti->sounds_path = getStringCopy(UNDEFINED_FILENAME);
2434 ti->music_path = getStringCopy(UNDEFINED_FILENAME);
2436 ti->level_filename = NULL;
2437 ti->level_filetype = NULL;
2439 ti->special_flags = NULL;
2442 ti->first_level = 0;
2444 ti->level_group = FALSE;
2445 ti->handicap_level = 0;
2446 ti->readonly = TRUE;
2447 ti->handicap = TRUE;
2448 ti->skip_levels = FALSE;
2452 static void setTreeInfoToDefaultsFromParent(TreeInfo *ti, TreeInfo *parent)
2456 Error(ERR_WARN, "setTreeInfoToDefaultsFromParent(): parent == NULL");
2458 setTreeInfoToDefaults(ti, TREE_TYPE_UNDEFINED);
2463 // copy all values from the parent structure
2465 ti->type = parent->type;
2467 ti->node_top = parent->node_top;
2468 ti->node_parent = parent;
2469 ti->node_group = NULL;
2476 ti->fullpath = NULL;
2477 ti->basepath = NULL;
2478 ti->identifier = NULL;
2479 ti->name = getStringCopy(ANONYMOUS_NAME);
2480 ti->name_sorting = NULL;
2481 ti->author = getStringCopy(parent->author);
2482 ti->year = getStringCopy(parent->year);
2484 ti->program_title = getStringCopy(parent->program_title);
2485 ti->program_copyright = getStringCopy(parent->program_copyright);
2486 ti->program_company = getStringCopy(parent->program_company);
2488 ti->sort_priority = parent->sort_priority;
2489 ti->latest_engine = parent->latest_engine;
2490 ti->parent_link = FALSE;
2491 ti->in_user_dir = parent->in_user_dir;
2492 ti->user_defined = parent->user_defined;
2493 ti->color = parent->color;
2494 ti->class_desc = getStringCopy(parent->class_desc);
2496 ti->infotext = getStringCopy(parent->infotext);
2498 if (ti->type == TREE_TYPE_LEVEL_DIR)
2500 ti->imported_from = getStringCopy(parent->imported_from);
2501 ti->imported_by = getStringCopy(parent->imported_by);
2502 ti->tested_by = getStringCopy(parent->tested_by);
2504 ti->graphics_set_ecs = getStringCopy(parent->graphics_set_ecs);
2505 ti->graphics_set_aga = getStringCopy(parent->graphics_set_aga);
2506 ti->graphics_set = getStringCopy(parent->graphics_set);
2507 ti->sounds_set = getStringCopy(parent->sounds_set);
2508 ti->music_set = getStringCopy(parent->music_set);
2509 ti->graphics_path = getStringCopy(UNDEFINED_FILENAME);
2510 ti->sounds_path = getStringCopy(UNDEFINED_FILENAME);
2511 ti->music_path = getStringCopy(UNDEFINED_FILENAME);
2513 ti->level_filename = getStringCopy(parent->level_filename);
2514 ti->level_filetype = getStringCopy(parent->level_filetype);
2516 ti->special_flags = getStringCopy(parent->special_flags);
2518 ti->levels = parent->levels;
2519 ti->first_level = parent->first_level;
2520 ti->last_level = parent->last_level;
2521 ti->level_group = FALSE;
2522 ti->handicap_level = parent->handicap_level;
2523 ti->readonly = parent->readonly;
2524 ti->handicap = parent->handicap;
2525 ti->skip_levels = parent->skip_levels;
2529 static TreeInfo *getTreeInfoCopy(TreeInfo *ti)
2531 TreeInfo *ti_copy = newTreeInfo();
2533 // copy all values from the original structure
2535 ti_copy->type = ti->type;
2537 ti_copy->node_top = ti->node_top;
2538 ti_copy->node_parent = ti->node_parent;
2539 ti_copy->node_group = ti->node_group;
2540 ti_copy->next = ti->next;
2542 ti_copy->cl_first = ti->cl_first;
2543 ti_copy->cl_cursor = ti->cl_cursor;
2545 ti_copy->subdir = getStringCopy(ti->subdir);
2546 ti_copy->fullpath = getStringCopy(ti->fullpath);
2547 ti_copy->basepath = getStringCopy(ti->basepath);
2548 ti_copy->identifier = getStringCopy(ti->identifier);
2549 ti_copy->name = getStringCopy(ti->name);
2550 ti_copy->name_sorting = getStringCopy(ti->name_sorting);
2551 ti_copy->author = getStringCopy(ti->author);
2552 ti_copy->year = getStringCopy(ti->year);
2554 ti_copy->program_title = getStringCopy(ti->program_title);
2555 ti_copy->program_copyright = getStringCopy(ti->program_copyright);
2556 ti_copy->program_company = getStringCopy(ti->program_company);
2558 ti_copy->imported_from = getStringCopy(ti->imported_from);
2559 ti_copy->imported_by = getStringCopy(ti->imported_by);
2560 ti_copy->tested_by = getStringCopy(ti->tested_by);
2562 ti_copy->graphics_set_ecs = getStringCopy(ti->graphics_set_ecs);
2563 ti_copy->graphics_set_aga = getStringCopy(ti->graphics_set_aga);
2564 ti_copy->graphics_set = getStringCopy(ti->graphics_set);
2565 ti_copy->sounds_set = getStringCopy(ti->sounds_set);
2566 ti_copy->music_set = getStringCopy(ti->music_set);
2567 ti_copy->graphics_path = getStringCopy(ti->graphics_path);
2568 ti_copy->sounds_path = getStringCopy(ti->sounds_path);
2569 ti_copy->music_path = getStringCopy(ti->music_path);
2571 ti_copy->level_filename = getStringCopy(ti->level_filename);
2572 ti_copy->level_filetype = getStringCopy(ti->level_filetype);
2574 ti_copy->special_flags = getStringCopy(ti->special_flags);
2576 ti_copy->levels = ti->levels;
2577 ti_copy->first_level = ti->first_level;
2578 ti_copy->last_level = ti->last_level;
2579 ti_copy->sort_priority = ti->sort_priority;
2581 ti_copy->latest_engine = ti->latest_engine;
2583 ti_copy->level_group = ti->level_group;
2584 ti_copy->parent_link = ti->parent_link;
2585 ti_copy->in_user_dir = ti->in_user_dir;
2586 ti_copy->user_defined = ti->user_defined;
2587 ti_copy->readonly = ti->readonly;
2588 ti_copy->handicap = ti->handicap;
2589 ti_copy->skip_levels = ti->skip_levels;
2591 ti_copy->color = ti->color;
2592 ti_copy->class_desc = getStringCopy(ti->class_desc);
2593 ti_copy->handicap_level = ti->handicap_level;
2595 ti_copy->infotext = getStringCopy(ti->infotext);
2600 void freeTreeInfo(TreeInfo *ti)
2605 checked_free(ti->subdir);
2606 checked_free(ti->fullpath);
2607 checked_free(ti->basepath);
2608 checked_free(ti->identifier);
2610 checked_free(ti->name);
2611 checked_free(ti->name_sorting);
2612 checked_free(ti->author);
2613 checked_free(ti->year);
2615 checked_free(ti->program_title);
2616 checked_free(ti->program_copyright);
2617 checked_free(ti->program_company);
2619 checked_free(ti->class_desc);
2621 checked_free(ti->infotext);
2623 if (ti->type == TREE_TYPE_LEVEL_DIR)
2625 checked_free(ti->imported_from);
2626 checked_free(ti->imported_by);
2627 checked_free(ti->tested_by);
2629 checked_free(ti->graphics_set_ecs);
2630 checked_free(ti->graphics_set_aga);
2631 checked_free(ti->graphics_set);
2632 checked_free(ti->sounds_set);
2633 checked_free(ti->music_set);
2635 checked_free(ti->graphics_path);
2636 checked_free(ti->sounds_path);
2637 checked_free(ti->music_path);
2639 checked_free(ti->level_filename);
2640 checked_free(ti->level_filetype);
2642 checked_free(ti->special_flags);
2645 // recursively free child node
2647 freeTreeInfo(ti->node_group);
2649 // recursively free next node
2651 freeTreeInfo(ti->next);
2656 void setSetupInfo(struct TokenInfo *token_info,
2657 int token_nr, char *token_value)
2659 int token_type = token_info[token_nr].type;
2660 void *setup_value = token_info[token_nr].value;
2662 if (token_value == NULL)
2665 // set setup field to corresponding token value
2670 *(boolean *)setup_value = get_boolean_from_string(token_value);
2674 *(int *)setup_value = get_switch3_from_string(token_value);
2678 *(Key *)setup_value = getKeyFromKeyName(token_value);
2682 *(Key *)setup_value = getKeyFromX11KeyName(token_value);
2686 *(int *)setup_value = get_integer_from_string(token_value);
2690 checked_free(*(char **)setup_value);
2691 *(char **)setup_value = getStringCopy(token_value);
2695 *(int *)setup_value = get_player_nr_from_string(token_value);
2703 static int compareTreeInfoEntries(const void *object1, const void *object2)
2705 const TreeInfo *entry1 = *((TreeInfo **)object1);
2706 const TreeInfo *entry2 = *((TreeInfo **)object2);
2707 int class_sorting1 = 0, class_sorting2 = 0;
2710 if (entry1->type == TREE_TYPE_LEVEL_DIR)
2712 class_sorting1 = LEVELSORTING(entry1);
2713 class_sorting2 = LEVELSORTING(entry2);
2715 else if (entry1->type == TREE_TYPE_GRAPHICS_DIR ||
2716 entry1->type == TREE_TYPE_SOUNDS_DIR ||
2717 entry1->type == TREE_TYPE_MUSIC_DIR)
2719 class_sorting1 = ARTWORKSORTING(entry1);
2720 class_sorting2 = ARTWORKSORTING(entry2);
2723 if (entry1->parent_link || entry2->parent_link)
2724 compare_result = (entry1->parent_link ? -1 : +1);
2725 else if (entry1->sort_priority == entry2->sort_priority)
2727 char *name1 = getStringToLower(entry1->name_sorting);
2728 char *name2 = getStringToLower(entry2->name_sorting);
2730 compare_result = strcmp(name1, name2);
2735 else if (class_sorting1 == class_sorting2)
2736 compare_result = entry1->sort_priority - entry2->sort_priority;
2738 compare_result = class_sorting1 - class_sorting2;
2740 return compare_result;
2743 static TreeInfo *createParentTreeInfoNode(TreeInfo *node_parent)
2747 if (node_parent == NULL)
2750 ti_new = newTreeInfo();
2751 setTreeInfoToDefaults(ti_new, node_parent->type);
2753 ti_new->node_parent = node_parent;
2754 ti_new->parent_link = TRUE;
2756 setString(&ti_new->identifier, node_parent->identifier);
2757 setString(&ti_new->name, ".. (parent directory)");
2758 setString(&ti_new->name_sorting, ti_new->name);
2760 setString(&ti_new->subdir, STRING_PARENT_DIRECTORY);
2761 setString(&ti_new->fullpath, node_parent->fullpath);
2763 ti_new->sort_priority = node_parent->sort_priority;
2764 ti_new->latest_engine = node_parent->latest_engine;
2766 setString(&ti_new->class_desc, getLevelClassDescription(ti_new));
2768 pushTreeInfo(&node_parent->node_group, ti_new);
2773 static TreeInfo *createTopTreeInfoNode(TreeInfo *node_first)
2775 TreeInfo *ti_new, *ti_new2;
2777 if (node_first == NULL)
2780 ti_new = newTreeInfo();
2781 setTreeInfoToDefaults(ti_new, TREE_TYPE_LEVEL_DIR);
2783 ti_new->node_parent = NULL;
2784 ti_new->parent_link = FALSE;
2786 setString(&ti_new->identifier, node_first->identifier);
2787 setString(&ti_new->name, "level sets");
2788 setString(&ti_new->name_sorting, ti_new->name);
2790 setString(&ti_new->subdir, STRING_TOP_DIRECTORY);
2791 setString(&ti_new->fullpath, ".");
2793 ti_new->sort_priority = node_first->sort_priority;;
2794 ti_new->latest_engine = node_first->latest_engine;
2796 setString(&ti_new->class_desc, "level sets");
2798 ti_new->node_group = node_first;
2799 ti_new->level_group = TRUE;
2801 ti_new2 = createParentTreeInfoNode(ti_new);
2803 setString(&ti_new2->name, ".. (main menu)");
2804 setString(&ti_new2->name_sorting, ti_new2->name);
2810 // ----------------------------------------------------------------------------
2811 // functions for handling level and custom artwork info cache
2812 // ----------------------------------------------------------------------------
2814 static void LoadArtworkInfoCache(void)
2816 InitCacheDirectory();
2818 if (artworkinfo_cache_old == NULL)
2820 char *filename = getPath2(getCacheDir(), ARTWORKINFO_CACHE_FILE);
2822 // try to load artwork info hash from already existing cache file
2823 artworkinfo_cache_old = loadSetupFileHash(filename);
2825 // if no artwork info cache file was found, start with empty hash
2826 if (artworkinfo_cache_old == NULL)
2827 artworkinfo_cache_old = newSetupFileHash();
2832 if (artworkinfo_cache_new == NULL)
2833 artworkinfo_cache_new = newSetupFileHash();
2836 static void SaveArtworkInfoCache(void)
2838 char *filename = getPath2(getCacheDir(), ARTWORKINFO_CACHE_FILE);
2840 InitCacheDirectory();
2842 saveSetupFileHash(artworkinfo_cache_new, filename);
2847 static char *getCacheTokenPrefix(char *prefix1, char *prefix2)
2849 static char *prefix = NULL;
2851 checked_free(prefix);
2853 prefix = getStringCat2WithSeparator(prefix1, prefix2, ".");
2858 // (identical to above function, but separate string buffer needed -- nasty)
2859 static char *getCacheToken(char *prefix, char *suffix)
2861 static char *token = NULL;
2863 checked_free(token);
2865 token = getStringCat2WithSeparator(prefix, suffix, ".");
2870 static char *getFileTimestampString(char *filename)
2872 return getStringCopy(i_to_a(getFileTimestampEpochSeconds(filename)));
2875 static boolean modifiedFileTimestamp(char *filename, char *timestamp_string)
2877 struct stat file_status;
2879 if (timestamp_string == NULL)
2882 if (stat(filename, &file_status) != 0) // cannot stat file
2885 return (file_status.st_mtime != atoi(timestamp_string));
2888 static TreeInfo *getArtworkInfoCacheEntry(LevelDirTree *level_node, int type)
2890 char *identifier = level_node->subdir;
2891 char *type_string = ARTWORK_DIRECTORY(type);
2892 char *token_prefix = getCacheTokenPrefix(type_string, identifier);
2893 char *token_main = getCacheToken(token_prefix, "CACHED");
2894 char *cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
2895 boolean cached = (cache_entry != NULL && strEqual(cache_entry, "true"));
2896 TreeInfo *artwork_info = NULL;
2898 if (!use_artworkinfo_cache)
2905 artwork_info = newTreeInfo();
2906 setTreeInfoToDefaults(artwork_info, type);
2908 // set all structure fields according to the token/value pairs
2909 ldi = *artwork_info;
2910 for (i = 0; artworkinfo_tokens[i].type != -1; i++)
2912 char *token = getCacheToken(token_prefix, artworkinfo_tokens[i].text);
2913 char *value = getHashEntry(artworkinfo_cache_old, token);
2915 // if defined, use value from cache, else keep default value
2917 setSetupInfo(artworkinfo_tokens, i, value);
2920 *artwork_info = ldi;
2922 char *filename_levelinfo = getPath2(getLevelDirFromTreeInfo(level_node),
2923 LEVELINFO_FILENAME);
2924 char *filename_artworkinfo = getPath2(getSetupArtworkDir(artwork_info),
2925 ARTWORKINFO_FILENAME(type));
2927 // check if corresponding "levelinfo.conf" file has changed
2928 token_main = getCacheToken(token_prefix, "TIMESTAMP_LEVELINFO");
2929 cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
2931 if (modifiedFileTimestamp(filename_levelinfo, cache_entry))
2934 // check if corresponding "<artworkinfo>.conf" file has changed
2935 token_main = getCacheToken(token_prefix, "TIMESTAMP_ARTWORKINFO");
2936 cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
2938 if (modifiedFileTimestamp(filename_artworkinfo, cache_entry))
2941 checked_free(filename_levelinfo);
2942 checked_free(filename_artworkinfo);
2945 if (!cached && artwork_info != NULL)
2947 freeTreeInfo(artwork_info);
2952 return artwork_info;
2955 static void setArtworkInfoCacheEntry(TreeInfo *artwork_info,
2956 LevelDirTree *level_node, int type)
2958 char *identifier = level_node->subdir;
2959 char *type_string = ARTWORK_DIRECTORY(type);
2960 char *token_prefix = getCacheTokenPrefix(type_string, identifier);
2961 char *token_main = getCacheToken(token_prefix, "CACHED");
2962 boolean set_cache_timestamps = TRUE;
2965 setHashEntry(artworkinfo_cache_new, token_main, "true");
2967 if (set_cache_timestamps)
2969 char *filename_levelinfo = getPath2(getLevelDirFromTreeInfo(level_node),
2970 LEVELINFO_FILENAME);
2971 char *filename_artworkinfo = getPath2(getSetupArtworkDir(artwork_info),
2972 ARTWORKINFO_FILENAME(type));
2973 char *timestamp_levelinfo = getFileTimestampString(filename_levelinfo);
2974 char *timestamp_artworkinfo = getFileTimestampString(filename_artworkinfo);
2976 token_main = getCacheToken(token_prefix, "TIMESTAMP_LEVELINFO");
2977 setHashEntry(artworkinfo_cache_new, token_main, timestamp_levelinfo);
2979 token_main = getCacheToken(token_prefix, "TIMESTAMP_ARTWORKINFO");
2980 setHashEntry(artworkinfo_cache_new, token_main, timestamp_artworkinfo);
2982 checked_free(filename_levelinfo);
2983 checked_free(filename_artworkinfo);
2984 checked_free(timestamp_levelinfo);
2985 checked_free(timestamp_artworkinfo);
2988 ldi = *artwork_info;
2989 for (i = 0; artworkinfo_tokens[i].type != -1; i++)
2991 char *token = getCacheToken(token_prefix, artworkinfo_tokens[i].text);
2992 char *value = getSetupValue(artworkinfo_tokens[i].type,
2993 artworkinfo_tokens[i].value);
2995 setHashEntry(artworkinfo_cache_new, token, value);
3000 // ----------------------------------------------------------------------------
3001 // functions for loading level info and custom artwork info
3002 // ----------------------------------------------------------------------------
3004 int GetZipFileTreeType(char *zip_filename)
3006 static char *top_dir_path = NULL;
3007 static char *top_dir_conf_filename[NUM_BASE_TREE_TYPES] = { NULL };
3008 static char *conf_basename[NUM_BASE_TREE_TYPES] =
3010 GRAPHICSINFO_FILENAME,
3011 SOUNDSINFO_FILENAME,
3017 checked_free(top_dir_path);
3018 top_dir_path = NULL;
3020 for (j = 0; j < NUM_BASE_TREE_TYPES; j++)
3022 checked_free(top_dir_conf_filename[j]);
3023 top_dir_conf_filename[j] = NULL;
3026 char **zip_entries = zip_list(zip_filename);
3028 // check if zip file successfully opened
3029 if (zip_entries == NULL || zip_entries[0] == NULL)
3030 return TREE_TYPE_UNDEFINED;
3032 // first zip file entry is expected to be top level directory
3033 char *top_dir = zip_entries[0];
3035 // check if valid top level directory found in zip file
3036 if (!strSuffix(top_dir, "/"))
3037 return TREE_TYPE_UNDEFINED;
3039 // get filenames of valid configuration files in top level directory
3040 for (j = 0; j < NUM_BASE_TREE_TYPES; j++)
3041 top_dir_conf_filename[j] = getStringCat2(top_dir, conf_basename[j]);
3043 int tree_type = TREE_TYPE_UNDEFINED;
3046 while (zip_entries[e] != NULL)
3048 // check if every zip file entry is below top level directory
3049 if (!strPrefix(zip_entries[e], top_dir))
3050 return TREE_TYPE_UNDEFINED;
3052 // check if this zip file entry is a valid configuration filename
3053 for (j = 0; j < NUM_BASE_TREE_TYPES; j++)
3055 if (strEqual(zip_entries[e], top_dir_conf_filename[j]))
3057 // only exactly one valid configuration file allowed
3058 if (tree_type != TREE_TYPE_UNDEFINED)
3059 return TREE_TYPE_UNDEFINED;
3071 static boolean CheckZipFileForDirectory(char *zip_filename, char *directory,
3074 static char *top_dir_path = NULL;
3075 static char *top_dir_conf_filename = NULL;
3077 checked_free(top_dir_path);
3078 checked_free(top_dir_conf_filename);
3080 top_dir_path = NULL;
3081 top_dir_conf_filename = NULL;
3083 char *conf_basename = (tree_type == TREE_TYPE_LEVEL_DIR ? LEVELINFO_FILENAME :
3084 ARTWORKINFO_FILENAME(tree_type));
3086 // check if valid configuration filename determined
3087 if (conf_basename == NULL || strEqual(conf_basename, ""))
3090 char **zip_entries = zip_list(zip_filename);
3092 // check if zip file successfully opened
3093 if (zip_entries == NULL || zip_entries[0] == NULL)
3096 // first zip file entry is expected to be top level directory
3097 char *top_dir = zip_entries[0];
3099 // check if valid top level directory found in zip file
3100 if (!strSuffix(top_dir, "/"))
3103 // get path of extracted top level directory
3104 top_dir_path = getPath2(directory, top_dir);
3106 // remove trailing directory separator from top level directory path
3107 // (required to be able to check for file and directory in next step)
3108 top_dir_path[strlen(top_dir_path) - 1] = '\0';
3110 // check if zip file's top level directory already exists in target directory
3111 if (fileExists(top_dir_path)) // (checks for file and directory)
3114 // get filename of configuration file in top level directory
3115 top_dir_conf_filename = getStringCat2(top_dir, conf_basename);
3117 boolean found_top_dir_conf_filename = FALSE;
3120 while (zip_entries[i] != NULL)
3122 // check if every zip file entry is below top level directory
3123 if (!strPrefix(zip_entries[i], top_dir))
3126 // check if this zip file entry is the configuration filename
3127 if (strEqual(zip_entries[i], top_dir_conf_filename))
3128 found_top_dir_conf_filename = TRUE;
3133 // check if valid configuration filename was found in zip file
3134 if (!found_top_dir_conf_filename)
3140 char *ExtractZipFileIntoDirectory(char *zip_filename, char *directory,
3143 boolean zip_file_valid = CheckZipFileForDirectory(zip_filename, directory,
3146 if (!zip_file_valid)
3148 Error(ERR_WARN, "zip file '%s' rejected!", zip_filename);
3153 char **zip_entries = zip_extract(zip_filename, directory);
3155 if (zip_entries == NULL)
3157 Error(ERR_WARN, "zip file '%s' could not be extracted!", zip_filename);
3162 Error(ERR_INFO, "zip file '%s' successfully extracted!", zip_filename);
3164 // first zip file entry contains top level directory
3165 char *top_dir = zip_entries[0];
3167 // remove trailing directory separator from top level directory
3168 top_dir[strlen(top_dir) - 1] = '\0';
3173 static void ProcessZipFilesInDirectory(char *directory, int tree_type)
3176 DirectoryEntry *dir_entry;
3178 if ((dir = openDirectory(directory)) == NULL)
3180 // display error if directory is main "options.graphics_directory" etc.
3181 if (tree_type == TREE_TYPE_LEVEL_DIR ||
3182 directory == OPTIONS_ARTWORK_DIRECTORY(tree_type))
3183 Error(ERR_WARN, "cannot read directory '%s'", directory);
3188 while ((dir_entry = readDirectory(dir)) != NULL) // loop all entries
3190 // skip non-zip files (and also directories with zip extension)
3191 if (!strSuffixLower(dir_entry->basename, ".zip") || dir_entry->is_directory)
3194 char *zip_filename = getPath2(directory, dir_entry->basename);
3195 char *zip_filename_extracted = getStringCat2(zip_filename, ".extracted");
3196 char *zip_filename_rejected = getStringCat2(zip_filename, ".rejected");
3198 // check if zip file hasn't already been extracted or rejected
3199 if (!fileExists(zip_filename_extracted) &&
3200 !fileExists(zip_filename_rejected))
3202 char *top_dir = ExtractZipFileIntoDirectory(zip_filename, directory,
3204 char *marker_filename = (top_dir != NULL ? zip_filename_extracted :
3205 zip_filename_rejected);
3208 // create empty file to mark zip file as extracted or rejected
3209 if ((marker_file = fopen(marker_filename, MODE_WRITE)))
3210 fclose(marker_file);
3213 free(zip_filename_extracted);
3214 free(zip_filename_rejected);
3218 closeDirectory(dir);
3221 // forward declaration for recursive call by "LoadLevelInfoFromLevelDir()"
3222 static void LoadLevelInfoFromLevelDir(TreeInfo **, TreeInfo *, char *);
3224 static boolean LoadLevelInfoFromLevelConf(TreeInfo **node_first,
3225 TreeInfo *node_parent,
3226 char *level_directory,
3227 char *directory_name)
3229 char *directory_path = getPath2(level_directory, directory_name);
3230 char *filename = getPath2(directory_path, LEVELINFO_FILENAME);
3231 SetupFileHash *setup_file_hash;
3232 LevelDirTree *leveldir_new = NULL;
3235 // unless debugging, silently ignore directories without "levelinfo.conf"
3236 if (!options.debug && !fileExists(filename))
3238 free(directory_path);
3244 setup_file_hash = loadSetupFileHash(filename);
3246 if (setup_file_hash == NULL)
3248 #if DEBUG_NO_CONFIG_FILE
3249 Error(ERR_WARN, "ignoring level directory '%s'", directory_path);
3252 free(directory_path);
3258 leveldir_new = newTreeInfo();
3261 setTreeInfoToDefaultsFromParent(leveldir_new, node_parent);
3263 setTreeInfoToDefaults(leveldir_new, TREE_TYPE_LEVEL_DIR);
3265 leveldir_new->subdir = getStringCopy(directory_name);
3267 // set all structure fields according to the token/value pairs
3268 ldi = *leveldir_new;
3269 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
3270 setSetupInfo(levelinfo_tokens, i,
3271 getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
3272 *leveldir_new = ldi;
3274 if (strEqual(leveldir_new->name, ANONYMOUS_NAME))
3275 setString(&leveldir_new->name, leveldir_new->subdir);
3277 if (leveldir_new->identifier == NULL)
3278 leveldir_new->identifier = getStringCopy(leveldir_new->subdir);
3280 if (leveldir_new->name_sorting == NULL)
3281 leveldir_new->name_sorting = getStringCopy(leveldir_new->name);
3283 if (node_parent == NULL) // top level group
3285 leveldir_new->basepath = getStringCopy(level_directory);
3286 leveldir_new->fullpath = getStringCopy(leveldir_new->subdir);
3288 else // sub level group
3290 leveldir_new->basepath = getStringCopy(node_parent->basepath);
3291 leveldir_new->fullpath = getPath2(node_parent->fullpath, directory_name);
3294 leveldir_new->last_level =
3295 leveldir_new->first_level + leveldir_new->levels - 1;
3297 leveldir_new->in_user_dir =
3298 (!strEqual(leveldir_new->basepath, options.level_directory));
3300 // adjust some settings if user's private level directory was detected
3301 if (leveldir_new->sort_priority == LEVELCLASS_UNDEFINED &&
3302 leveldir_new->in_user_dir &&
3303 (strEqual(leveldir_new->subdir, getLoginName()) ||
3304 strEqual(leveldir_new->name, getLoginName()) ||
3305 strEqual(leveldir_new->author, getRealName())))
3307 leveldir_new->sort_priority = LEVELCLASS_PRIVATE_START;
3308 leveldir_new->readonly = FALSE;
3311 leveldir_new->user_defined =
3312 (leveldir_new->in_user_dir && IS_LEVELCLASS_PRIVATE(leveldir_new));
3314 leveldir_new->color = LEVELCOLOR(leveldir_new);
3316 setString(&leveldir_new->class_desc, getLevelClassDescription(leveldir_new));
3318 leveldir_new->handicap_level = // set handicap to default value
3319 (leveldir_new->user_defined || !leveldir_new->handicap ?
3320 leveldir_new->last_level : leveldir_new->first_level);
3322 DrawInitText(leveldir_new->name, 150, FC_YELLOW);
3324 pushTreeInfo(node_first, leveldir_new);
3326 freeSetupFileHash(setup_file_hash);
3328 if (leveldir_new->level_group)
3330 // create node to link back to current level directory
3331 createParentTreeInfoNode(leveldir_new);
3333 // recursively step into sub-directory and look for more level series
3334 LoadLevelInfoFromLevelDir(&leveldir_new->node_group,
3335 leveldir_new, directory_path);
3338 free(directory_path);
3344 static void LoadLevelInfoFromLevelDir(TreeInfo **node_first,
3345 TreeInfo *node_parent,
3346 char *level_directory)
3348 // ---------- 1st stage: process any level set zip files ----------
3350 ProcessZipFilesInDirectory(level_directory, TREE_TYPE_LEVEL_DIR);
3352 // ---------- 2nd stage: check for level set directories ----------
3355 DirectoryEntry *dir_entry;
3356 boolean valid_entry_found = FALSE;
3358 if ((dir = openDirectory(level_directory)) == NULL)
3360 Error(ERR_WARN, "cannot read level directory '%s'", level_directory);
3365 while ((dir_entry = readDirectory(dir)) != NULL) // loop all entries
3367 char *directory_name = dir_entry->basename;
3368 char *directory_path = getPath2(level_directory, directory_name);
3370 // skip entries for current and parent directory
3371 if (strEqual(directory_name, ".") ||
3372 strEqual(directory_name, ".."))
3374 free(directory_path);
3379 // find out if directory entry is itself a directory
3380 if (!dir_entry->is_directory) // not a directory
3382 free(directory_path);
3387 free(directory_path);
3389 if (strEqual(directory_name, GRAPHICS_DIRECTORY) ||
3390 strEqual(directory_name, SOUNDS_DIRECTORY) ||
3391 strEqual(directory_name, MUSIC_DIRECTORY))
3394 valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
3399 closeDirectory(dir);
3401 // special case: top level directory may directly contain "levelinfo.conf"
3402 if (node_parent == NULL && !valid_entry_found)
3404 // check if this directory directly contains a file "levelinfo.conf"
3405 valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
3406 level_directory, ".");
3409 if (!valid_entry_found)
3410 Error(ERR_WARN, "cannot find any valid level series in directory '%s'",
3414 boolean AdjustGraphicsForEMC(void)
3416 boolean settings_changed = FALSE;
3418 settings_changed |= adjustTreeGraphicsForEMC(leveldir_first_all);
3419 settings_changed |= adjustTreeGraphicsForEMC(leveldir_first);
3421 return settings_changed;
3424 void LoadLevelInfo(void)
3426 InitUserLevelDirectory(getLoginName());
3428 DrawInitText("Loading level series", 120, FC_GREEN);
3430 LoadLevelInfoFromLevelDir(&leveldir_first, NULL, options.level_directory);
3431 LoadLevelInfoFromLevelDir(&leveldir_first, NULL, getUserLevelDir(NULL));
3433 leveldir_first = createTopTreeInfoNode(leveldir_first);
3435 /* after loading all level set information, clone the level directory tree
3436 and remove all level sets without levels (these may still contain artwork
3437 to be offered in the setup menu as "custom artwork", and are therefore
3438 checked for existing artwork in the function "LoadLevelArtworkInfo()") */
3439 leveldir_first_all = leveldir_first;
3440 cloneTree(&leveldir_first, leveldir_first_all, TRUE);
3442 AdjustGraphicsForEMC();
3444 // before sorting, the first entries will be from the user directory
3445 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
3447 if (leveldir_first == NULL)
3448 Error(ERR_EXIT, "cannot find any valid level series in any directory");
3450 sortTreeInfo(&leveldir_first);
3452 #if ENABLE_UNUSED_CODE
3453 dumpTreeInfo(leveldir_first, 0);
3457 static boolean LoadArtworkInfoFromArtworkConf(TreeInfo **node_first,
3458 TreeInfo *node_parent,
3459 char *base_directory,
3460 char *directory_name, int type)
3462 char *directory_path = getPath2(base_directory, directory_name);
3463 char *filename = getPath2(directory_path, ARTWORKINFO_FILENAME(type));
3464 SetupFileHash *setup_file_hash = NULL;
3465 TreeInfo *artwork_new = NULL;
3468 if (fileExists(filename))
3469 setup_file_hash = loadSetupFileHash(filename);
3471 if (setup_file_hash == NULL) // no config file -- look for artwork files
3474 DirectoryEntry *dir_entry;
3475 boolean valid_file_found = FALSE;
3477 if ((dir = openDirectory(directory_path)) != NULL)
3479 while ((dir_entry = readDirectory(dir)) != NULL)
3481 if (FileIsArtworkType(dir_entry->filename, type))
3483 valid_file_found = TRUE;
3489 closeDirectory(dir);
3492 if (!valid_file_found)
3494 #if DEBUG_NO_CONFIG_FILE
3495 if (!strEqual(directory_name, "."))
3496 Error(ERR_WARN, "ignoring artwork directory '%s'", directory_path);
3499 free(directory_path);
3506 artwork_new = newTreeInfo();
3509 setTreeInfoToDefaultsFromParent(artwork_new, node_parent);
3511 setTreeInfoToDefaults(artwork_new, type);
3513 artwork_new->subdir = getStringCopy(directory_name);
3515 if (setup_file_hash) // (before defining ".color" and ".class_desc")
3517 // set all structure fields according to the token/value pairs
3519 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
3520 setSetupInfo(levelinfo_tokens, i,
3521 getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
3524 if (strEqual(artwork_new->name, ANONYMOUS_NAME))
3525 setString(&artwork_new->name, artwork_new->subdir);
3527 if (artwork_new->identifier == NULL)
3528 artwork_new->identifier = getStringCopy(artwork_new->subdir);
3530 if (artwork_new->name_sorting == NULL)
3531 artwork_new->name_sorting = getStringCopy(artwork_new->name);
3534 if (node_parent == NULL) // top level group
3536 artwork_new->basepath = getStringCopy(base_directory);
3537 artwork_new->fullpath = getStringCopy(artwork_new->subdir);
3539 else // sub level group
3541 artwork_new->basepath = getStringCopy(node_parent->basepath);
3542 artwork_new->fullpath = getPath2(node_parent->fullpath, directory_name);
3545 artwork_new->in_user_dir =
3546 (!strEqual(artwork_new->basepath, OPTIONS_ARTWORK_DIRECTORY(type)));
3548 // (may use ".sort_priority" from "setup_file_hash" above)
3549 artwork_new->color = ARTWORKCOLOR(artwork_new);
3551 setString(&artwork_new->class_desc, getLevelClassDescription(artwork_new));
3553 if (setup_file_hash == NULL) // (after determining ".user_defined")
3555 if (strEqual(artwork_new->subdir, "."))
3557 if (artwork_new->user_defined)
3559 setString(&artwork_new->identifier, "private");
3560 artwork_new->sort_priority = ARTWORKCLASS_PRIVATE;
3564 setString(&artwork_new->identifier, "classic");
3565 artwork_new->sort_priority = ARTWORKCLASS_CLASSICS;
3568 // set to new values after changing ".sort_priority"
3569 artwork_new->color = ARTWORKCOLOR(artwork_new);
3571 setString(&artwork_new->class_desc,
3572 getLevelClassDescription(artwork_new));
3576 setString(&artwork_new->identifier, artwork_new->subdir);
3579 setString(&artwork_new->name, artwork_new->identifier);
3580 setString(&artwork_new->name_sorting, artwork_new->name);
3583 pushTreeInfo(node_first, artwork_new);
3585 freeSetupFileHash(setup_file_hash);
3587 free(directory_path);
3593 static void LoadArtworkInfoFromArtworkDir(TreeInfo **node_first,
3594 TreeInfo *node_parent,
3595 char *base_directory, int type)
3597 // ---------- 1st stage: process any artwork set zip files ----------
3599 ProcessZipFilesInDirectory(base_directory, type);
3601 // ---------- 2nd stage: check for artwork set directories ----------
3604 DirectoryEntry *dir_entry;
3605 boolean valid_entry_found = FALSE;
3607 if ((dir = openDirectory(base_directory)) == NULL)
3609 // display error if directory is main "options.graphics_directory" etc.
3610 if (base_directory == OPTIONS_ARTWORK_DIRECTORY(type))
3611 Error(ERR_WARN, "cannot read directory '%s'", base_directory);
3616 while ((dir_entry = readDirectory(dir)) != NULL) // loop all entries
3618 char *directory_name = dir_entry->basename;
3619 char *directory_path = getPath2(base_directory, directory_name);
3621 // skip directory entries for current and parent directory
3622 if (strEqual(directory_name, ".") ||
3623 strEqual(directory_name, ".."))
3625 free(directory_path);
3630 // skip directory entries which are not a directory
3631 if (!dir_entry->is_directory) // not a directory
3633 free(directory_path);
3638 free(directory_path);
3640 // check if this directory contains artwork with or without config file
3641 valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
3643 directory_name, type);
3646 closeDirectory(dir);
3648 // check if this directory directly contains artwork itself
3649 valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
3650 base_directory, ".",
3652 if (!valid_entry_found)
3653 Error(ERR_WARN, "cannot find any valid artwork in directory '%s'",
3657 static TreeInfo *getDummyArtworkInfo(int type)
3659 // this is only needed when there is completely no artwork available
3660 TreeInfo *artwork_new = newTreeInfo();
3662 setTreeInfoToDefaults(artwork_new, type);
3664 setString(&artwork_new->subdir, UNDEFINED_FILENAME);
3665 setString(&artwork_new->fullpath, UNDEFINED_FILENAME);
3666 setString(&artwork_new->basepath, UNDEFINED_FILENAME);
3668 setString(&artwork_new->identifier, UNDEFINED_FILENAME);
3669 setString(&artwork_new->name, UNDEFINED_FILENAME);
3670 setString(&artwork_new->name_sorting, UNDEFINED_FILENAME);
3675 void LoadArtworkInfo(void)
3677 LoadArtworkInfoCache();
3679 DrawInitText("Looking for custom artwork", 120, FC_GREEN);
3681 LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
3682 options.graphics_directory,
3683 TREE_TYPE_GRAPHICS_DIR);
3684 LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
3685 getUserGraphicsDir(),
3686 TREE_TYPE_GRAPHICS_DIR);
3688 LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
3689 options.sounds_directory,
3690 TREE_TYPE_SOUNDS_DIR);
3691 LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
3693 TREE_TYPE_SOUNDS_DIR);
3695 LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
3696 options.music_directory,
3697 TREE_TYPE_MUSIC_DIR);
3698 LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
3700 TREE_TYPE_MUSIC_DIR);
3702 if (artwork.gfx_first == NULL)
3703 artwork.gfx_first = getDummyArtworkInfo(TREE_TYPE_GRAPHICS_DIR);
3704 if (artwork.snd_first == NULL)
3705 artwork.snd_first = getDummyArtworkInfo(TREE_TYPE_SOUNDS_DIR);
3706 if (artwork.mus_first == NULL)
3707 artwork.mus_first = getDummyArtworkInfo(TREE_TYPE_MUSIC_DIR);
3709 // before sorting, the first entries will be from the user directory
3710 artwork.gfx_current =
3711 getTreeInfoFromIdentifier(artwork.gfx_first, setup.graphics_set);
3712 if (artwork.gfx_current == NULL)
3713 artwork.gfx_current =
3714 getTreeInfoFromIdentifier(artwork.gfx_first, GFX_DEFAULT_SUBDIR);
3715 if (artwork.gfx_current == NULL)
3716 artwork.gfx_current = getFirstValidTreeInfoEntry(artwork.gfx_first);
3718 artwork.snd_current =
3719 getTreeInfoFromIdentifier(artwork.snd_first, setup.sounds_set);
3720 if (artwork.snd_current == NULL)
3721 artwork.snd_current =
3722 getTreeInfoFromIdentifier(artwork.snd_first, SND_DEFAULT_SUBDIR);
3723 if (artwork.snd_current == NULL)
3724 artwork.snd_current = getFirstValidTreeInfoEntry(artwork.snd_first);
3726 artwork.mus_current =
3727 getTreeInfoFromIdentifier(artwork.mus_first, setup.music_set);
3728 if (artwork.mus_current == NULL)
3729 artwork.mus_current =
3730 getTreeInfoFromIdentifier(artwork.mus_first, MUS_DEFAULT_SUBDIR);
3731 if (artwork.mus_current == NULL)
3732 artwork.mus_current = getFirstValidTreeInfoEntry(artwork.mus_first);
3734 artwork.gfx_current_identifier = artwork.gfx_current->identifier;
3735 artwork.snd_current_identifier = artwork.snd_current->identifier;
3736 artwork.mus_current_identifier = artwork.mus_current->identifier;
3738 #if ENABLE_UNUSED_CODE
3739 printf("graphics set == %s\n\n", artwork.gfx_current_identifier);
3740 printf("sounds set == %s\n\n", artwork.snd_current_identifier);
3741 printf("music set == %s\n\n", artwork.mus_current_identifier);
3744 sortTreeInfo(&artwork.gfx_first);
3745 sortTreeInfo(&artwork.snd_first);
3746 sortTreeInfo(&artwork.mus_first);
3748 #if ENABLE_UNUSED_CODE
3749 dumpTreeInfo(artwork.gfx_first, 0);
3750 dumpTreeInfo(artwork.snd_first, 0);
3751 dumpTreeInfo(artwork.mus_first, 0);
3755 static void LoadArtworkInfoFromLevelInfo(ArtworkDirTree **artwork_node,
3756 LevelDirTree *level_node)
3758 int type = (*artwork_node)->type;
3760 // recursively check all level directories for artwork sub-directories
3764 // check all tree entries for artwork, but skip parent link entries
3765 if (!level_node->parent_link)
3767 TreeInfo *artwork_new = getArtworkInfoCacheEntry(level_node, type);
3768 boolean cached = (artwork_new != NULL);
3772 pushTreeInfo(artwork_node, artwork_new);
3776 TreeInfo *topnode_last = *artwork_node;
3777 char *path = getPath2(getLevelDirFromTreeInfo(level_node),
3778 ARTWORK_DIRECTORY(type));
3780 LoadArtworkInfoFromArtworkDir(artwork_node, NULL, path, type);
3782 if (topnode_last != *artwork_node) // check for newly added node
3784 artwork_new = *artwork_node;
3786 setString(&artwork_new->identifier, level_node->subdir);
3787 setString(&artwork_new->name, level_node->name);
3788 setString(&artwork_new->name_sorting, level_node->name_sorting);
3790 artwork_new->sort_priority = level_node->sort_priority;
3791 artwork_new->color = LEVELCOLOR(artwork_new);
3797 // insert artwork info (from old cache or filesystem) into new cache
3798 if (artwork_new != NULL)
3799 setArtworkInfoCacheEntry(artwork_new, level_node, type);
3802 DrawInitText(level_node->name, 150, FC_YELLOW);
3804 if (level_node->node_group != NULL)
3805 LoadArtworkInfoFromLevelInfo(artwork_node, level_node->node_group);
3807 level_node = level_node->next;
3811 void LoadLevelArtworkInfo(void)
3813 print_timestamp_init("LoadLevelArtworkInfo");
3815 DrawInitText("Looking for custom level artwork", 120, FC_GREEN);
3817 print_timestamp_time("DrawTimeText");
3819 LoadArtworkInfoFromLevelInfo(&artwork.gfx_first, leveldir_first_all);
3820 print_timestamp_time("LoadArtworkInfoFromLevelInfo (gfx)");
3821 LoadArtworkInfoFromLevelInfo(&artwork.snd_first, leveldir_first_all);
3822 print_timestamp_time("LoadArtworkInfoFromLevelInfo (snd)");
3823 LoadArtworkInfoFromLevelInfo(&artwork.mus_first, leveldir_first_all);
3824 print_timestamp_time("LoadArtworkInfoFromLevelInfo (mus)");
3826 SaveArtworkInfoCache();
3828 print_timestamp_time("SaveArtworkInfoCache");
3830 // needed for reloading level artwork not known at ealier stage
3832 if (!strEqual(artwork.gfx_current_identifier, setup.graphics_set))
3834 artwork.gfx_current =
3835 getTreeInfoFromIdentifier(artwork.gfx_first, setup.graphics_set);
3836 if (artwork.gfx_current == NULL)
3837 artwork.gfx_current =
3838 getTreeInfoFromIdentifier(artwork.gfx_first, GFX_DEFAULT_SUBDIR);
3839 if (artwork.gfx_current == NULL)
3840 artwork.gfx_current = getFirstValidTreeInfoEntry(artwork.gfx_first);
3843 if (!strEqual(artwork.snd_current_identifier, setup.sounds_set))
3845 artwork.snd_current =
3846 getTreeInfoFromIdentifier(artwork.snd_first, setup.sounds_set);
3847 if (artwork.snd_current == NULL)
3848 artwork.snd_current =
3849 getTreeInfoFromIdentifier(artwork.snd_first, SND_DEFAULT_SUBDIR);
3850 if (artwork.snd_current == NULL)
3851 artwork.snd_current = getFirstValidTreeInfoEntry(artwork.snd_first);
3854 if (!strEqual(artwork.mus_current_identifier, setup.music_set))
3856 artwork.mus_current =
3857 getTreeInfoFromIdentifier(artwork.mus_first, setup.music_set);
3858 if (artwork.mus_current == NULL)
3859 artwork.mus_current =
3860 getTreeInfoFromIdentifier(artwork.mus_first, MUS_DEFAULT_SUBDIR);
3861 if (artwork.mus_current == NULL)
3862 artwork.mus_current = getFirstValidTreeInfoEntry(artwork.mus_first);
3865 print_timestamp_time("getTreeInfoFromIdentifier");
3867 sortTreeInfo(&artwork.gfx_first);
3868 sortTreeInfo(&artwork.snd_first);
3869 sortTreeInfo(&artwork.mus_first);
3871 print_timestamp_time("sortTreeInfo");
3873 #if ENABLE_UNUSED_CODE
3874 dumpTreeInfo(artwork.gfx_first, 0);
3875 dumpTreeInfo(artwork.snd_first, 0);
3876 dumpTreeInfo(artwork.mus_first, 0);
3879 print_timestamp_done("LoadLevelArtworkInfo");
3882 static boolean AddTreeSetToTreeInfoExt(TreeInfo *tree_node_old, char *tree_dir,
3883 char *tree_subdir_new, int type)
3885 if (tree_node_old == NULL)
3887 if (type == TREE_TYPE_LEVEL_DIR)
3889 // get level info tree node of personal user level set
3890 tree_node_old = getTreeInfoFromIdentifier(leveldir_first, getLoginName());
3894 // get artwork info tree node of first artwork set
3895 tree_node_old = ARTWORK_FIRST_NODE(artwork, type);
3899 if (tree_dir == NULL)
3900 tree_dir = TREE_USERDIR(type);
3902 if (tree_node_old == NULL ||
3904 tree_subdir_new == NULL) // should not happen
3907 int draw_deactivation_mask = GetDrawDeactivationMask();
3909 // override draw deactivation mask (temporarily disable drawing)
3910 SetDrawDeactivationMask(REDRAW_ALL);
3912 if (type == TREE_TYPE_LEVEL_DIR)
3914 // load new level set config and add it next to first user level set
3915 LoadLevelInfoFromLevelConf(&tree_node_old->next,
3916 tree_node_old->node_parent,
3917 tree_dir, tree_subdir_new);
3921 // load new artwork set config and add it next to first artwork set
3922 LoadArtworkInfoFromArtworkConf(&tree_node_old->next,
3923 tree_node_old->node_parent,
3924 tree_dir, tree_subdir_new, type);
3927 // set draw deactivation mask to previous value
3928 SetDrawDeactivationMask(draw_deactivation_mask);
3930 // get first node of level or artwork info tree
3931 TreeInfo **tree_node_first = TREE_FIRST_NODE_PTR(type);
3933 // get tree info node of newly added level or artwork set
3934 TreeInfo *tree_node_new = getTreeInfoFromIdentifier(*tree_node_first,
3937 if (tree_node_new == NULL) // should not happen
3940 // correct top link and parent node link of newly created tree node
3941 tree_node_new->node_top = tree_node_old->node_top;
3942 tree_node_new->node_parent = tree_node_old->node_parent;
3944 // sort tree info to adjust position of newly added tree set
3945 sortTreeInfo(tree_node_first);
3950 void AddTreeSetToTreeInfo(TreeInfo *tree_node, char *tree_dir,
3951 char *tree_subdir_new, int type)
3953 if (!AddTreeSetToTreeInfoExt(tree_node, tree_dir, tree_subdir_new, type))
3954 Error(ERR_EXIT, "internal tree info structure corrupted -- aborting");
3957 void AddUserLevelSetToLevelInfo(char *level_subdir_new)
3959 AddTreeSetToTreeInfo(NULL, NULL, level_subdir_new, TREE_TYPE_LEVEL_DIR);
3962 char *getArtworkIdentifierForUserLevelSet(int type)
3964 char *classic_artwork_set = getClassicArtworkSet(type);
3966 // check for custom artwork configured in "levelinfo.conf"
3967 char *leveldir_artwork_set =
3968 *LEVELDIR_ARTWORK_SET_PTR(leveldir_current, type);
3969 boolean has_leveldir_artwork_set =
3970 (leveldir_artwork_set != NULL && !strEqual(leveldir_artwork_set,
3971 classic_artwork_set));
3973 // check for custom artwork in sub-directory "graphics" etc.
3974 TreeInfo *artwork_first_node = ARTWORK_FIRST_NODE(artwork, type);
3975 char *leveldir_identifier = leveldir_current->identifier;
3976 boolean has_artwork_subdir =
3977 (getTreeInfoFromIdentifier(artwork_first_node,
3978 leveldir_identifier) != NULL);
3980 return (has_leveldir_artwork_set ? leveldir_artwork_set :
3981 has_artwork_subdir ? leveldir_identifier :
3982 classic_artwork_set);
3985 TreeInfo *getArtworkTreeInfoForUserLevelSet(int type)
3987 char *artwork_set = getArtworkIdentifierForUserLevelSet(type);
3988 TreeInfo *artwork_first_node = ARTWORK_FIRST_NODE(artwork, type);
3989 TreeInfo *ti = getTreeInfoFromIdentifier(artwork_first_node, artwork_set);
3993 ti = getTreeInfoFromIdentifier(artwork_first_node,
3994 ARTWORK_DEFAULT_SUBDIR(type));
3996 Error(ERR_EXIT, "cannot find default graphics -- should not happen");
4002 boolean checkIfCustomArtworkExistsForCurrentLevelSet(void)
4004 char *graphics_set =
4005 getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_GRAPHICS);
4007 getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_SOUNDS);
4009 getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_MUSIC);
4011 return (!strEqual(graphics_set, GFX_CLASSIC_SUBDIR) ||
4012 !strEqual(sounds_set, SND_CLASSIC_SUBDIR) ||
4013 !strEqual(music_set, MUS_CLASSIC_SUBDIR));
4016 boolean UpdateUserLevelSet(char *level_subdir, char *level_name,
4017 char *level_author, int num_levels)
4019 char *filename = getPath2(getUserLevelDir(level_subdir), LEVELINFO_FILENAME);
4020 char *filename_tmp = getStringCat2(filename, ".tmp");
4022 FILE *file_tmp = NULL;
4023 char line[MAX_LINE_LEN];
4024 boolean success = FALSE;
4025 LevelDirTree *leveldir = getTreeInfoFromIdentifier(leveldir_first,
4027 // update values in level directory tree
4029 if (level_name != NULL)
4030 setString(&leveldir->name, level_name);
4032 if (level_author != NULL)
4033 setString(&leveldir->author, level_author);
4035 if (num_levels != -1)
4036 leveldir->levels = num_levels;
4038 // update values that depend on other values
4040 setString(&leveldir->name_sorting, leveldir->name);
4042 leveldir->last_level = leveldir->first_level + leveldir->levels - 1;
4044 // sort order of level sets may have changed
4045 sortTreeInfo(&leveldir_first);
4047 if ((file = fopen(filename, MODE_READ)) &&
4048 (file_tmp = fopen(filename_tmp, MODE_WRITE)))
4050 while (fgets(line, MAX_LINE_LEN, file))
4052 if (strPrefix(line, "name:") && level_name != NULL)
4053 fprintf(file_tmp, "%-32s%s\n", "name:", level_name);
4054 else if (strPrefix(line, "author:") && level_author != NULL)
4055 fprintf(file_tmp, "%-32s%s\n", "author:", level_author);
4056 else if (strPrefix(line, "levels:") && num_levels != -1)
4057 fprintf(file_tmp, "%-32s%d\n", "levels:", num_levels);
4059 fputs(line, file_tmp);
4072 success = (rename(filename_tmp, filename) == 0);
4080 boolean CreateUserLevelSet(char *level_subdir, char *level_name,
4081 char *level_author, int num_levels,
4082 boolean use_artwork_set)
4084 LevelDirTree *level_info;
4089 // create user level sub-directory, if needed
4090 createDirectory(getUserLevelDir(level_subdir), "user level", PERMS_PRIVATE);
4092 filename = getPath2(getUserLevelDir(level_subdir), LEVELINFO_FILENAME);
4094 if (!(file = fopen(filename, MODE_WRITE)))
4096 Error(ERR_WARN, "cannot write level info file '%s'", filename);
4102 level_info = newTreeInfo();
4104 // always start with reliable default values
4105 setTreeInfoToDefaults(level_info, TREE_TYPE_LEVEL_DIR);
4107 setString(&level_info->name, level_name);
4108 setString(&level_info->author, level_author);
4109 level_info->levels = num_levels;
4110 level_info->first_level = 1;
4111 level_info->sort_priority = LEVELCLASS_PRIVATE_START;
4112 level_info->readonly = FALSE;
4114 if (use_artwork_set)
4116 level_info->graphics_set =
4117 getStringCopy(getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_GRAPHICS));
4118 level_info->sounds_set =
4119 getStringCopy(getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_SOUNDS));
4120 level_info->music_set =
4121 getStringCopy(getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_MUSIC));
4124 token_value_position = TOKEN_VALUE_POSITION_SHORT;
4126 fprintFileHeader(file, LEVELINFO_FILENAME);
4129 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
4131 if (i == LEVELINFO_TOKEN_NAME ||
4132 i == LEVELINFO_TOKEN_AUTHOR ||
4133 i == LEVELINFO_TOKEN_LEVELS ||
4134 i == LEVELINFO_TOKEN_FIRST_LEVEL ||
4135 i == LEVELINFO_TOKEN_SORT_PRIORITY ||
4136 i == LEVELINFO_TOKEN_READONLY ||
4137 (use_artwork_set && (i == LEVELINFO_TOKEN_GRAPHICS_SET ||
4138 i == LEVELINFO_TOKEN_SOUNDS_SET ||
4139 i == LEVELINFO_TOKEN_MUSIC_SET)))
4140 fprintf(file, "%s\n", getSetupLine(levelinfo_tokens, "", i));
4142 // just to make things nicer :)
4143 if (i == LEVELINFO_TOKEN_AUTHOR ||
4144 i == LEVELINFO_TOKEN_FIRST_LEVEL ||
4145 (use_artwork_set && i == LEVELINFO_TOKEN_READONLY))
4146 fprintf(file, "\n");
4149 token_value_position = TOKEN_VALUE_POSITION_DEFAULT;
4153 SetFilePermissions(filename, PERMS_PRIVATE);
4155 freeTreeInfo(level_info);
4161 static void SaveUserLevelInfo(void)
4163 CreateUserLevelSet(getLoginName(), getLoginName(), getRealName(), 100, FALSE);
4166 char *getSetupValue(int type, void *value)
4168 static char value_string[MAX_LINE_LEN];
4176 strcpy(value_string, (*(boolean *)value ? "true" : "false"));
4180 strcpy(value_string, (*(boolean *)value ? "on" : "off"));
4184 strcpy(value_string, (*(int *)value == AUTO ? "auto" :
4185 *(int *)value == FALSE ? "off" : "on"));
4189 strcpy(value_string, (*(boolean *)value ? "yes" : "no"));
4192 case TYPE_YES_NO_AUTO:
4193 strcpy(value_string, (*(int *)value == AUTO ? "auto" :
4194 *(int *)value == FALSE ? "no" : "yes"));
4198 strcpy(value_string, (*(boolean *)value ? "AGA" : "ECS"));
4202 strcpy(value_string, getKeyNameFromKey(*(Key *)value));
4206 strcpy(value_string, getX11KeyNameFromKey(*(Key *)value));
4210 sprintf(value_string, "%d", *(int *)value);
4214 if (*(char **)value == NULL)
4217 strcpy(value_string, *(char **)value);
4221 sprintf(value_string, "player_%d", *(int *)value + 1);
4225 value_string[0] = '\0';
4229 if (type & TYPE_GHOSTED)
4230 strcpy(value_string, "n/a");
4232 return value_string;
4235 char *getSetupLine(struct TokenInfo *token_info, char *prefix, int token_nr)
4239 static char token_string[MAX_LINE_LEN];
4240 int token_type = token_info[token_nr].type;
4241 void *setup_value = token_info[token_nr].value;
4242 char *token_text = token_info[token_nr].text;
4243 char *value_string = getSetupValue(token_type, setup_value);
4245 // build complete token string
4246 sprintf(token_string, "%s%s", prefix, token_text);
4248 // build setup entry line
4249 line = getFormattedSetupEntry(token_string, value_string);
4251 if (token_type == TYPE_KEY_X11)
4253 Key key = *(Key *)setup_value;
4254 char *keyname = getKeyNameFromKey(key);
4256 // add comment, if useful
4257 if (!strEqual(keyname, "(undefined)") &&
4258 !strEqual(keyname, "(unknown)"))
4260 // add at least one whitespace
4262 for (i = strlen(line); i < token_comment_position; i++)
4266 strcat(line, keyname);
4273 void LoadLevelSetup_LastSeries(void)
4275 // --------------------------------------------------------------------------
4276 // ~/.<program>/levelsetup.conf
4277 // --------------------------------------------------------------------------
4279 char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
4280 SetupFileHash *level_setup_hash = NULL;
4282 // always start with reliable default values
4283 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
4285 if (!strEqual(DEFAULT_LEVELSET, UNDEFINED_LEVELSET))
4287 leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
4289 if (leveldir_current == NULL)
4290 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
4293 if ((level_setup_hash = loadSetupFileHash(filename)))
4295 char *last_level_series =
4296 getHashEntry(level_setup_hash, TOKEN_STR_LAST_LEVEL_SERIES);
4298 leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
4300 if (leveldir_current == NULL)
4301 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
4303 freeSetupFileHash(level_setup_hash);
4307 Error(ERR_DEBUG, "using default setup values");
4313 static void SaveLevelSetup_LastSeries_Ext(boolean deactivate_last_level_series)
4315 // --------------------------------------------------------------------------
4316 // ~/.<program>/levelsetup.conf
4317 // --------------------------------------------------------------------------
4319 // check if the current level directory structure is available at this point
4320 if (leveldir_current == NULL)
4323 char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
4324 char *level_subdir = leveldir_current->subdir;
4327 InitUserDataDirectory();
4329 if (!(file = fopen(filename, MODE_WRITE)))
4331 Error(ERR_WARN, "cannot write setup file '%s'", filename);
4338 fprintFileHeader(file, LEVELSETUP_FILENAME);
4340 if (deactivate_last_level_series)
4341 fprintf(file, "# %s\n# ", "the following level set may have caused a problem and was deactivated");
4343 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_LEVEL_SERIES,
4348 SetFilePermissions(filename, PERMS_PRIVATE);
4353 void SaveLevelSetup_LastSeries(void)
4355 SaveLevelSetup_LastSeries_Ext(FALSE);
4358 void SaveLevelSetup_LastSeries_Deactivate(void)
4360 SaveLevelSetup_LastSeries_Ext(TRUE);
4363 static void checkSeriesInfo(void)
4365 static char *level_directory = NULL;
4368 DirectoryEntry *dir_entry;
4371 checked_free(level_directory);
4373 // check for more levels besides the 'levels' field of 'levelinfo.conf'
4375 level_directory = getPath2((leveldir_current->in_user_dir ?
4376 getUserLevelDir(NULL) :
4377 options.level_directory),
4378 leveldir_current->fullpath);
4380 if ((dir = openDirectory(level_directory)) == NULL)
4382 Error(ERR_WARN, "cannot read level directory '%s'", level_directory);
4388 while ((dir_entry = readDirectory(dir)) != NULL) // loop all entries
4390 if (strlen(dir_entry->basename) > 4 &&
4391 dir_entry->basename[3] == '.' &&
4392 strEqual(&dir_entry->basename[4], LEVELFILE_EXTENSION))
4394 char levelnum_str[4];
4397 strncpy(levelnum_str, dir_entry->basename, 3);
4398 levelnum_str[3] = '\0';
4400 levelnum_value = atoi(levelnum_str);
4402 if (levelnum_value < leveldir_current->first_level)
4404 Error(ERR_WARN, "additional level %d found", levelnum_value);
4405 leveldir_current->first_level = levelnum_value;
4407 else if (levelnum_value > leveldir_current->last_level)
4409 Error(ERR_WARN, "additional level %d found", levelnum_value);
4410 leveldir_current->last_level = levelnum_value;
4416 closeDirectory(dir);
4419 void LoadLevelSetup_SeriesInfo(void)
4422 SetupFileHash *level_setup_hash = NULL;
4423 char *level_subdir = leveldir_current->subdir;
4426 // always start with reliable default values
4427 level_nr = leveldir_current->first_level;
4429 for (i = 0; i < MAX_LEVELS; i++)
4431 LevelStats_setPlayed(i, 0);
4432 LevelStats_setSolved(i, 0);
4437 // --------------------------------------------------------------------------
4438 // ~/.<program>/levelsetup/<level series>/levelsetup.conf
4439 // --------------------------------------------------------------------------
4441 level_subdir = leveldir_current->subdir;
4443 filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
4445 if ((level_setup_hash = loadSetupFileHash(filename)))
4449 // get last played level in this level set
4451 token_value = getHashEntry(level_setup_hash, TOKEN_STR_LAST_PLAYED_LEVEL);
4455 level_nr = atoi(token_value);
4457 if (level_nr < leveldir_current->first_level)
4458 level_nr = leveldir_current->first_level;
4459 if (level_nr > leveldir_current->last_level)
4460 level_nr = leveldir_current->last_level;
4463 // get handicap level in this level set
4465 token_value = getHashEntry(level_setup_hash, TOKEN_STR_HANDICAP_LEVEL);
4469 int 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 + 1)
4474 level_nr = leveldir_current->last_level;
4476 if (leveldir_current->user_defined || !leveldir_current->handicap)
4477 level_nr = leveldir_current->last_level;
4479 leveldir_current->handicap_level = level_nr;
4482 // get number of played and solved levels in this level set
4484 BEGIN_HASH_ITERATION(level_setup_hash, itr)
4486 char *token = HASH_ITERATION_TOKEN(itr);
4487 char *value = HASH_ITERATION_VALUE(itr);
4489 if (strlen(token) == 3 &&
4490 token[0] >= '0' && token[0] <= '9' &&
4491 token[1] >= '0' && token[1] <= '9' &&
4492 token[2] >= '0' && token[2] <= '9')
4494 int level_nr = atoi(token);
4497 LevelStats_setPlayed(level_nr, atoi(value)); // read 1st column
4499 value = strchr(value, ' ');
4502 LevelStats_setSolved(level_nr, atoi(value)); // read 2nd column
4505 END_HASH_ITERATION(hash, itr)
4507 freeSetupFileHash(level_setup_hash);
4511 Error(ERR_DEBUG, "using default setup values");
4517 void SaveLevelSetup_SeriesInfo(void)
4520 char *level_subdir = leveldir_current->subdir;
4521 char *level_nr_str = int2str(level_nr, 0);
4522 char *handicap_level_str = int2str(leveldir_current->handicap_level, 0);
4526 // --------------------------------------------------------------------------
4527 // ~/.<program>/levelsetup/<level series>/levelsetup.conf
4528 // --------------------------------------------------------------------------
4530 InitLevelSetupDirectory(level_subdir);
4532 filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
4534 if (!(file = fopen(filename, MODE_WRITE)))
4536 Error(ERR_WARN, "cannot write setup file '%s'", filename);
4541 fprintFileHeader(file, LEVELSETUP_FILENAME);
4543 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_PLAYED_LEVEL,
4545 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_HANDICAP_LEVEL,
4546 handicap_level_str));
4548 for (i = leveldir_current->first_level; i <= leveldir_current->last_level;
4551 if (LevelStats_getPlayed(i) > 0 ||
4552 LevelStats_getSolved(i) > 0)
4557 sprintf(token, "%03d", i);
4558 sprintf(value, "%d %d", LevelStats_getPlayed(i), LevelStats_getSolved(i));
4560 fprintf(file, "%s\n", getFormattedSetupEntry(token, value));
4566 SetFilePermissions(filename, PERMS_PRIVATE);
4571 int LevelStats_getPlayed(int nr)
4573 return (nr >= 0 && nr < MAX_LEVELS ? level_stats[nr].played : 0);
4576 int LevelStats_getSolved(int nr)
4578 return (nr >= 0 && nr < MAX_LEVELS ? level_stats[nr].solved : 0);
4581 void LevelStats_setPlayed(int nr, int value)
4583 if (nr >= 0 && nr < MAX_LEVELS)
4584 level_stats[nr].played = value;
4587 void LevelStats_setSolved(int nr, int value)
4589 if (nr >= 0 && nr < MAX_LEVELS)
4590 level_stats[nr].solved = value;
4593 void LevelStats_incPlayed(int nr)
4595 if (nr >= 0 && nr < MAX_LEVELS)
4596 level_stats[nr].played++;
4599 void LevelStats_incSolved(int nr)
4601 if (nr >= 0 && nr < MAX_LEVELS)
4602 level_stats[nr].solved++;