1 // ============================================================================
2 // Artsoft Retro-Game Library
3 // ----------------------------------------------------------------------------
4 // (c) 1995-2014 by Artsoft Entertainment
7 // http://www.artsoft.org/
8 // ----------------------------------------------------------------------------
10 // ============================================================================
12 #include <sys/types.h>
21 #if !defined(PLATFORM_WIN32)
23 #include <sys/param.h>
31 #include "zip/miniunz.h"
34 #define ENABLE_UNUSED_CODE FALSE // for currently unused functions
35 #define DEBUG_NO_CONFIG_FILE FALSE // for extra-verbose debug output
37 #define NUM_LEVELCLASS_DESC 8
39 static char *levelclass_desc[NUM_LEVELCLASS_DESC] =
52 #define LEVELCOLOR(n) (IS_LEVELCLASS_TUTORIAL(n) ? FC_BLUE : \
53 IS_LEVELCLASS_CLASSICS(n) ? FC_RED : \
54 IS_LEVELCLASS_BD(n) ? FC_YELLOW : \
55 IS_LEVELCLASS_EM(n) ? FC_YELLOW : \
56 IS_LEVELCLASS_SP(n) ? FC_YELLOW : \
57 IS_LEVELCLASS_DX(n) ? FC_YELLOW : \
58 IS_LEVELCLASS_SB(n) ? FC_YELLOW : \
59 IS_LEVELCLASS_CONTRIB(n) ? FC_GREEN : \
60 IS_LEVELCLASS_PRIVATE(n) ? FC_RED : \
63 #define LEVELSORTING(n) (IS_LEVELCLASS_TUTORIAL(n) ? 0 : \
64 IS_LEVELCLASS_CLASSICS(n) ? 1 : \
65 IS_LEVELCLASS_BD(n) ? 2 : \
66 IS_LEVELCLASS_EM(n) ? 3 : \
67 IS_LEVELCLASS_SP(n) ? 4 : \
68 IS_LEVELCLASS_DX(n) ? 5 : \
69 IS_LEVELCLASS_SB(n) ? 6 : \
70 IS_LEVELCLASS_CONTRIB(n) ? 7 : \
71 IS_LEVELCLASS_PRIVATE(n) ? 8 : \
74 #define ARTWORKCOLOR(n) (IS_ARTWORKCLASS_CLASSICS(n) ? FC_RED : \
75 IS_ARTWORKCLASS_CONTRIB(n) ? FC_GREEN : \
76 IS_ARTWORKCLASS_PRIVATE(n) ? FC_RED : \
77 IS_ARTWORKCLASS_LEVEL(n) ? FC_YELLOW : \
80 #define ARTWORKSORTING(n) (IS_ARTWORKCLASS_CLASSICS(n) ? 0 : \
81 IS_ARTWORKCLASS_LEVEL(n) ? 1 : \
82 IS_ARTWORKCLASS_CONTRIB(n) ? 2 : \
83 IS_ARTWORKCLASS_PRIVATE(n) ? 3 : \
86 #define TOKEN_VALUE_POSITION_SHORT 32
87 #define TOKEN_VALUE_POSITION_DEFAULT 40
88 #define TOKEN_COMMENT_POSITION_DEFAULT 60
90 #define MAX_COOKIE_LEN 256
93 static void setTreeInfoToDefaults(TreeInfo *, int);
94 static TreeInfo *getTreeInfoCopy(TreeInfo *ti);
95 static int compareTreeInfoEntries(const void *, const void *);
97 static int token_value_position = TOKEN_VALUE_POSITION_DEFAULT;
98 static int token_comment_position = TOKEN_COMMENT_POSITION_DEFAULT;
100 static SetupFileHash *artworkinfo_cache_old = NULL;
101 static SetupFileHash *artworkinfo_cache_new = NULL;
102 static boolean use_artworkinfo_cache = TRUE;
105 // ----------------------------------------------------------------------------
107 // ----------------------------------------------------------------------------
109 static char *getLevelClassDescription(TreeInfo *ti)
111 int position = ti->sort_priority / 100;
113 if (position >= 0 && position < NUM_LEVELCLASS_DESC)
114 return levelclass_desc[position];
116 return "Unknown Level Class";
119 static char *getScoreDir(char *level_subdir)
121 static char *score_dir = NULL;
122 static char *score_level_dir = NULL;
123 char *score_subdir = SCORES_DIRECTORY;
125 if (score_dir == NULL)
127 if (program.global_scores)
128 score_dir = getPath2(getCommonDataDir(), score_subdir);
130 score_dir = getPath2(getUserGameDataDir(), score_subdir);
133 if (level_subdir != NULL)
135 checked_free(score_level_dir);
137 score_level_dir = getPath2(score_dir, level_subdir);
139 return score_level_dir;
145 static char *getLevelSetupDir(char *level_subdir)
147 static char *levelsetup_dir = NULL;
148 char *data_dir = getUserGameDataDir();
149 char *levelsetup_subdir = LEVELSETUP_DIRECTORY;
151 checked_free(levelsetup_dir);
153 if (level_subdir != NULL)
154 levelsetup_dir = getPath3(data_dir, levelsetup_subdir, level_subdir);
156 levelsetup_dir = getPath2(data_dir, levelsetup_subdir);
158 return levelsetup_dir;
161 static char *getCacheDir(void)
163 static char *cache_dir = NULL;
165 if (cache_dir == NULL)
166 cache_dir = getPath2(getUserGameDataDir(), CACHE_DIRECTORY);
171 static char *getNetworkDir(void)
173 static char *network_dir = NULL;
175 if (network_dir == NULL)
176 network_dir = getPath2(getUserGameDataDir(), NETWORK_DIRECTORY);
181 char *getLevelDirFromTreeInfo(TreeInfo *node)
183 static char *level_dir = NULL;
186 return options.level_directory;
188 checked_free(level_dir);
190 level_dir = getPath2((node->in_user_dir ? getUserLevelDir(NULL) :
191 options.level_directory), node->fullpath);
196 char *getUserLevelDir(char *level_subdir)
198 static char *userlevel_dir = NULL;
199 char *data_dir = getUserGameDataDir();
200 char *userlevel_subdir = LEVELS_DIRECTORY;
202 checked_free(userlevel_dir);
204 if (level_subdir != NULL)
205 userlevel_dir = getPath3(data_dir, userlevel_subdir, level_subdir);
207 userlevel_dir = getPath2(data_dir, userlevel_subdir);
209 return userlevel_dir;
212 char *getNetworkLevelDir(char *level_subdir)
214 static char *network_level_dir = NULL;
215 char *data_dir = getNetworkDir();
216 char *networklevel_subdir = LEVELS_DIRECTORY;
218 checked_free(network_level_dir);
220 if (level_subdir != NULL)
221 network_level_dir = getPath3(data_dir, networklevel_subdir, level_subdir);
223 network_level_dir = getPath2(data_dir, networklevel_subdir);
225 return network_level_dir;
228 char *getCurrentLevelDir(void)
230 return getLevelDirFromTreeInfo(leveldir_current);
233 char *getNewUserLevelSubdir(void)
235 static char *new_level_subdir = NULL;
236 char *subdir_prefix = getLoginName();
237 char subdir_suffix[10];
238 int max_suffix_number = 1000;
241 while (++i < max_suffix_number)
243 sprintf(subdir_suffix, "_%d", i);
245 checked_free(new_level_subdir);
246 new_level_subdir = getStringCat2(subdir_prefix, subdir_suffix);
248 if (!directoryExists(getUserLevelDir(new_level_subdir)))
252 return new_level_subdir;
255 static char *getTapeDir(char *level_subdir)
257 static char *tape_dir = NULL;
258 char *data_dir = getUserGameDataDir();
259 char *tape_subdir = TAPES_DIRECTORY;
261 checked_free(tape_dir);
263 if (level_subdir != NULL)
264 tape_dir = getPath3(data_dir, tape_subdir, level_subdir);
266 tape_dir = getPath2(data_dir, tape_subdir);
271 static char *getSolutionTapeDir(void)
273 static char *tape_dir = NULL;
274 char *data_dir = getCurrentLevelDir();
275 char *tape_subdir = TAPES_DIRECTORY;
277 checked_free(tape_dir);
279 tape_dir = getPath2(data_dir, tape_subdir);
284 static char *getDefaultGraphicsDir(char *graphics_subdir)
286 static char *graphics_dir = NULL;
288 if (graphics_subdir == NULL)
289 return options.graphics_directory;
291 checked_free(graphics_dir);
293 graphics_dir = getPath2(options.graphics_directory, graphics_subdir);
298 static char *getDefaultSoundsDir(char *sounds_subdir)
300 static char *sounds_dir = NULL;
302 if (sounds_subdir == NULL)
303 return options.sounds_directory;
305 checked_free(sounds_dir);
307 sounds_dir = getPath2(options.sounds_directory, sounds_subdir);
312 static char *getDefaultMusicDir(char *music_subdir)
314 static char *music_dir = NULL;
316 if (music_subdir == NULL)
317 return options.music_directory;
319 checked_free(music_dir);
321 music_dir = getPath2(options.music_directory, music_subdir);
326 static char *getClassicArtworkSet(int type)
328 return (type == TREE_TYPE_GRAPHICS_DIR ? GFX_CLASSIC_SUBDIR :
329 type == TREE_TYPE_SOUNDS_DIR ? SND_CLASSIC_SUBDIR :
330 type == TREE_TYPE_MUSIC_DIR ? MUS_CLASSIC_SUBDIR : "");
333 static char *getClassicArtworkDir(int type)
335 return (type == TREE_TYPE_GRAPHICS_DIR ?
336 getDefaultGraphicsDir(GFX_CLASSIC_SUBDIR) :
337 type == TREE_TYPE_SOUNDS_DIR ?
338 getDefaultSoundsDir(SND_CLASSIC_SUBDIR) :
339 type == TREE_TYPE_MUSIC_DIR ?
340 getDefaultMusicDir(MUS_CLASSIC_SUBDIR) : "");
343 char *getUserGraphicsDir(void)
345 static char *usergraphics_dir = NULL;
347 if (usergraphics_dir == NULL)
348 usergraphics_dir = getPath2(getUserGameDataDir(), GRAPHICS_DIRECTORY);
350 return usergraphics_dir;
353 char *getUserSoundsDir(void)
355 static char *usersounds_dir = NULL;
357 if (usersounds_dir == NULL)
358 usersounds_dir = getPath2(getUserGameDataDir(), SOUNDS_DIRECTORY);
360 return usersounds_dir;
363 char *getUserMusicDir(void)
365 static char *usermusic_dir = NULL;
367 if (usermusic_dir == NULL)
368 usermusic_dir = getPath2(getUserGameDataDir(), MUSIC_DIRECTORY);
370 return usermusic_dir;
373 static char *getSetupArtworkDir(TreeInfo *ti)
375 static char *artwork_dir = NULL;
380 checked_free(artwork_dir);
382 artwork_dir = getPath2(ti->basepath, ti->fullpath);
387 char *setLevelArtworkDir(TreeInfo *ti)
389 char **artwork_path_ptr, **artwork_set_ptr;
390 TreeInfo *level_artwork;
392 if (ti == NULL || leveldir_current == NULL)
395 artwork_path_ptr = LEVELDIR_ARTWORK_PATH_PTR(leveldir_current, ti->type);
396 artwork_set_ptr = LEVELDIR_ARTWORK_SET_PTR( leveldir_current, ti->type);
398 checked_free(*artwork_path_ptr);
400 if ((level_artwork = getTreeInfoFromIdentifier(ti, *artwork_set_ptr)))
402 *artwork_path_ptr = getStringCopy(getSetupArtworkDir(level_artwork));
407 No (or non-existing) artwork configured in "levelinfo.conf". This would
408 normally result in using the artwork configured in the setup menu. But
409 if an artwork subdirectory exists (which might contain custom artwork
410 or an artwork configuration file), this level artwork must be treated
411 as relative to the default "classic" artwork, not to the artwork that
412 is currently configured in the setup menu.
414 Update: For "special" versions of R'n'D (like "R'n'D jue"), do not use
415 the "default" artwork (which would be "jue0" for "R'n'D jue"), but use
416 the real "classic" artwork from the original R'n'D (like "gfx_classic").
419 char *dir = getPath2(getCurrentLevelDir(), ARTWORK_DIRECTORY(ti->type));
421 checked_free(*artwork_set_ptr);
423 if (directoryExists(dir))
425 *artwork_path_ptr = getStringCopy(getClassicArtworkDir(ti->type));
426 *artwork_set_ptr = getStringCopy(getClassicArtworkSet(ti->type));
430 *artwork_path_ptr = getStringCopy(UNDEFINED_FILENAME);
431 *artwork_set_ptr = NULL;
437 return *artwork_set_ptr;
440 static char *getLevelArtworkSet(int type)
442 if (leveldir_current == NULL)
445 return LEVELDIR_ARTWORK_SET(leveldir_current, type);
448 static char *getLevelArtworkDir(int type)
450 if (leveldir_current == NULL)
451 return UNDEFINED_FILENAME;
453 return LEVELDIR_ARTWORK_PATH(leveldir_current, type);
456 char *getProgramMainDataPath(char *command_filename, char *base_path)
458 // check if the program's main data base directory is configured
459 if (!strEqual(base_path, "."))
460 return getStringCopy(base_path);
462 /* if the program is configured to start from current directory (default),
463 determine program package directory from program binary (some versions
464 of KDE/Konqueror and Mac OS X (especially "Mavericks") apparently do not
465 set the current working directory to the program package directory) */
466 char *main_data_path = getBasePath(command_filename);
468 #if defined(PLATFORM_MACOSX)
469 if (strSuffix(main_data_path, MAC_APP_BINARY_SUBDIR))
471 char *main_data_path_old = main_data_path;
473 // cut relative path to Mac OS X application binary directory from path
474 main_data_path[strlen(main_data_path) -
475 strlen(MAC_APP_BINARY_SUBDIR)] = '\0';
477 // cut trailing path separator from path (but not if path is root directory)
478 if (strSuffix(main_data_path, "/") && !strEqual(main_data_path, "/"))
479 main_data_path[strlen(main_data_path) - 1] = '\0';
481 // replace empty path with current directory
482 if (strEqual(main_data_path, ""))
483 main_data_path = ".";
485 // add relative path to Mac OS X application resources directory to path
486 main_data_path = getPath2(main_data_path, MAC_APP_FILES_SUBDIR);
488 free(main_data_path_old);
492 return main_data_path;
495 char *getProgramConfigFilename(char *command_filename)
497 static char *config_filename_1 = NULL;
498 static char *config_filename_2 = NULL;
499 static char *config_filename_3 = NULL;
500 static boolean initialized = FALSE;
504 char *command_filename_1 = getStringCopy(command_filename);
506 // strip trailing executable suffix from command filename
507 if (strSuffix(command_filename_1, ".exe"))
508 command_filename_1[strlen(command_filename_1) - 4] = '\0';
510 char *ro_base_path = getProgramMainDataPath(command_filename, RO_BASE_PATH);
511 char *conf_directory = getPath2(ro_base_path, CONF_DIRECTORY);
513 char *command_basepath = getBasePath(command_filename);
514 char *command_basename = getBaseNameNoSuffix(command_filename);
515 char *command_filename_2 = getPath2(command_basepath, command_basename);
517 config_filename_1 = getStringCat2(command_filename_1, ".conf");
518 config_filename_2 = getStringCat2(command_filename_2, ".conf");
519 config_filename_3 = getPath2(conf_directory, SETUP_FILENAME);
521 checked_free(ro_base_path);
522 checked_free(conf_directory);
524 checked_free(command_basepath);
525 checked_free(command_basename);
527 checked_free(command_filename_1);
528 checked_free(command_filename_2);
533 // 1st try: look for config file that exactly matches the binary filename
534 if (fileExists(config_filename_1))
535 return config_filename_1;
537 // 2nd try: look for config file that matches binary filename without suffix
538 if (fileExists(config_filename_2))
539 return config_filename_2;
541 // 3rd try: return setup config filename in global program config directory
542 return config_filename_3;
545 char *getTapeFilename(int nr)
547 static char *filename = NULL;
548 char basename[MAX_FILENAME_LEN];
550 checked_free(filename);
552 sprintf(basename, "%03d.%s", nr, TAPEFILE_EXTENSION);
553 filename = getPath2(getTapeDir(leveldir_current->subdir), basename);
558 char *getSolutionTapeFilename(int nr)
560 static char *filename = NULL;
561 char basename[MAX_FILENAME_LEN];
563 checked_free(filename);
565 sprintf(basename, "%03d.%s", nr, TAPEFILE_EXTENSION);
566 filename = getPath2(getSolutionTapeDir(), basename);
568 if (!fileExists(filename))
570 static char *filename_sln = NULL;
572 checked_free(filename_sln);
574 sprintf(basename, "%03d.sln", nr);
575 filename_sln = getPath2(getSolutionTapeDir(), basename);
577 if (fileExists(filename_sln))
584 char *getScoreFilename(int nr)
586 static char *filename = NULL;
587 char basename[MAX_FILENAME_LEN];
589 checked_free(filename);
591 sprintf(basename, "%03d.%s", nr, SCOREFILE_EXTENSION);
593 // used instead of "leveldir_current->subdir" (for network games)
594 filename = getPath2(getScoreDir(levelset.identifier), basename);
599 char *getSetupFilename(void)
601 static char *filename = NULL;
603 checked_free(filename);
605 filename = getPath2(getSetupDir(), SETUP_FILENAME);
610 char *getDefaultSetupFilename(void)
612 return program.config_filename;
615 char *getEditorSetupFilename(void)
617 static char *filename = NULL;
619 checked_free(filename);
620 filename = getPath2(getCurrentLevelDir(), EDITORSETUP_FILENAME);
622 if (fileExists(filename))
625 checked_free(filename);
626 filename = getPath2(getSetupDir(), EDITORSETUP_FILENAME);
631 char *getHelpAnimFilename(void)
633 static char *filename = NULL;
635 checked_free(filename);
637 filename = getPath2(getCurrentLevelDir(), HELPANIM_FILENAME);
642 char *getHelpTextFilename(void)
644 static char *filename = NULL;
646 checked_free(filename);
648 filename = getPath2(getCurrentLevelDir(), HELPTEXT_FILENAME);
653 char *getLevelSetInfoFilename(void)
655 static char *filename = NULL;
670 for (i = 0; basenames[i] != NULL; i++)
672 checked_free(filename);
673 filename = getPath2(getCurrentLevelDir(), basenames[i]);
675 if (fileExists(filename))
682 static char *getLevelSetTitleMessageBasename(int nr, boolean initial)
684 static char basename[32];
686 sprintf(basename, "%s_%d.txt",
687 (initial ? "titlemessage_initial" : "titlemessage"), nr + 1);
692 char *getLevelSetTitleMessageFilename(int nr, boolean initial)
694 static char *filename = NULL;
696 boolean skip_setup_artwork = FALSE;
698 checked_free(filename);
700 basename = getLevelSetTitleMessageBasename(nr, initial);
702 if (!gfx.override_level_graphics)
704 // 1st try: look for special artwork in current level series directory
705 filename = getPath3(getCurrentLevelDir(), GRAPHICS_DIRECTORY, basename);
706 if (fileExists(filename))
711 // 2nd try: look for message file in current level set directory
712 filename = getPath2(getCurrentLevelDir(), basename);
713 if (fileExists(filename))
718 // check if there is special artwork configured in level series config
719 if (getLevelArtworkSet(ARTWORK_TYPE_GRAPHICS) != NULL)
721 // 3rd try: look for special artwork configured in level series config
722 filename = getPath2(getLevelArtworkDir(ARTWORK_TYPE_GRAPHICS), basename);
723 if (fileExists(filename))
728 // take missing artwork configured in level set config from default
729 skip_setup_artwork = TRUE;
733 if (!skip_setup_artwork)
735 // 4th try: look for special artwork in configured artwork directory
736 filename = getPath2(getSetupArtworkDir(artwork.gfx_current), basename);
737 if (fileExists(filename))
743 // 5th try: look for default artwork in new default artwork directory
744 filename = getPath2(getDefaultGraphicsDir(GFX_DEFAULT_SUBDIR), basename);
745 if (fileExists(filename))
750 // 6th try: look for default artwork in old default artwork directory
751 filename = getPath2(options.graphics_directory, basename);
752 if (fileExists(filename))
755 return NULL; // cannot find specified artwork file anywhere
758 static char *getCorrectedArtworkBasename(char *basename)
763 char *getCustomImageFilename(char *basename)
765 static char *filename = NULL;
766 boolean skip_setup_artwork = FALSE;
768 checked_free(filename);
770 basename = getCorrectedArtworkBasename(basename);
772 if (!gfx.override_level_graphics)
774 // 1st try: look for special artwork in current level series directory
775 filename = getImg3(getCurrentLevelDir(), GRAPHICS_DIRECTORY, basename);
776 if (fileExists(filename))
781 // check if there is special artwork configured in level series config
782 if (getLevelArtworkSet(ARTWORK_TYPE_GRAPHICS) != NULL)
784 // 2nd try: look for special artwork configured in level series config
785 filename = getImg2(getLevelArtworkDir(ARTWORK_TYPE_GRAPHICS), basename);
786 if (fileExists(filename))
791 // take missing artwork configured in level set config from default
792 skip_setup_artwork = TRUE;
796 if (!skip_setup_artwork)
798 // 3rd try: look for special artwork in configured artwork directory
799 filename = getImg2(getSetupArtworkDir(artwork.gfx_current), basename);
800 if (fileExists(filename))
806 // 4th try: look for default artwork in new default artwork directory
807 filename = getImg2(getDefaultGraphicsDir(GFX_DEFAULT_SUBDIR), basename);
808 if (fileExists(filename))
813 // 5th try: look for default artwork in old default artwork directory
814 filename = getImg2(options.graphics_directory, basename);
815 if (fileExists(filename))
818 if (!strEqual(GFX_FALLBACK_FILENAME, UNDEFINED_FILENAME))
823 Error(ERR_WARN, "cannot find artwork file '%s' (using fallback)",
826 // 6th try: look for fallback artwork in old default artwork directory
827 // (needed to prevent errors when trying to access unused artwork files)
828 filename = getImg2(options.graphics_directory, GFX_FALLBACK_FILENAME);
829 if (fileExists(filename))
833 return NULL; // cannot find specified artwork file anywhere
836 char *getCustomSoundFilename(char *basename)
838 static char *filename = NULL;
839 boolean skip_setup_artwork = FALSE;
841 checked_free(filename);
843 basename = getCorrectedArtworkBasename(basename);
845 if (!gfx.override_level_sounds)
847 // 1st try: look for special artwork in current level series directory
848 filename = getPath3(getCurrentLevelDir(), SOUNDS_DIRECTORY, basename);
849 if (fileExists(filename))
854 // check if there is special artwork configured in level series config
855 if (getLevelArtworkSet(ARTWORK_TYPE_SOUNDS) != NULL)
857 // 2nd try: look for special artwork configured in level series config
858 filename = getPath2(getLevelArtworkDir(TREE_TYPE_SOUNDS_DIR), basename);
859 if (fileExists(filename))
864 // take missing artwork configured in level set config from default
865 skip_setup_artwork = TRUE;
869 if (!skip_setup_artwork)
871 // 3rd try: look for special artwork in configured artwork directory
872 filename = getPath2(getSetupArtworkDir(artwork.snd_current), basename);
873 if (fileExists(filename))
879 // 4th try: look for default artwork in new default artwork directory
880 filename = getPath2(getDefaultSoundsDir(SND_DEFAULT_SUBDIR), basename);
881 if (fileExists(filename))
886 // 5th try: look for default artwork in old default artwork directory
887 filename = getPath2(options.sounds_directory, basename);
888 if (fileExists(filename))
891 if (!strEqual(SND_FALLBACK_FILENAME, UNDEFINED_FILENAME))
896 Error(ERR_WARN, "cannot find artwork file '%s' (using fallback)",
899 // 6th try: look for fallback artwork in old default artwork directory
900 // (needed to prevent errors when trying to access unused artwork files)
901 filename = getPath2(options.sounds_directory, SND_FALLBACK_FILENAME);
902 if (fileExists(filename))
906 return NULL; // cannot find specified artwork file anywhere
909 char *getCustomMusicFilename(char *basename)
911 static char *filename = NULL;
912 boolean skip_setup_artwork = FALSE;
914 checked_free(filename);
916 basename = getCorrectedArtworkBasename(basename);
918 if (!gfx.override_level_music)
920 // 1st try: look for special artwork in current level series directory
921 filename = getPath3(getCurrentLevelDir(), MUSIC_DIRECTORY, basename);
922 if (fileExists(filename))
927 // check if there is special artwork configured in level series config
928 if (getLevelArtworkSet(ARTWORK_TYPE_MUSIC) != NULL)
930 // 2nd try: look for special artwork configured in level series config
931 filename = getPath2(getLevelArtworkDir(TREE_TYPE_MUSIC_DIR), basename);
932 if (fileExists(filename))
937 // take missing artwork configured in level set config from default
938 skip_setup_artwork = TRUE;
942 if (!skip_setup_artwork)
944 // 3rd try: look for special artwork in configured artwork directory
945 filename = getPath2(getSetupArtworkDir(artwork.mus_current), basename);
946 if (fileExists(filename))
952 // 4th try: look for default artwork in new default artwork directory
953 filename = getPath2(getDefaultMusicDir(MUS_DEFAULT_SUBDIR), basename);
954 if (fileExists(filename))
959 // 5th try: look for default artwork in old default artwork directory
960 filename = getPath2(options.music_directory, basename);
961 if (fileExists(filename))
964 if (!strEqual(MUS_FALLBACK_FILENAME, UNDEFINED_FILENAME))
969 Error(ERR_WARN, "cannot find artwork file '%s' (using fallback)",
972 // 6th try: look for fallback artwork in old default artwork directory
973 // (needed to prevent errors when trying to access unused artwork files)
974 filename = getPath2(options.music_directory, MUS_FALLBACK_FILENAME);
975 if (fileExists(filename))
979 return NULL; // cannot find specified artwork file anywhere
982 char *getCustomArtworkFilename(char *basename, int type)
984 if (type == ARTWORK_TYPE_GRAPHICS)
985 return getCustomImageFilename(basename);
986 else if (type == ARTWORK_TYPE_SOUNDS)
987 return getCustomSoundFilename(basename);
988 else if (type == ARTWORK_TYPE_MUSIC)
989 return getCustomMusicFilename(basename);
991 return UNDEFINED_FILENAME;
994 char *getCustomArtworkConfigFilename(int type)
996 return getCustomArtworkFilename(ARTWORKINFO_FILENAME(type), type);
999 char *getCustomArtworkLevelConfigFilename(int type)
1001 static char *filename = NULL;
1003 checked_free(filename);
1005 filename = getPath2(getLevelArtworkDir(type), ARTWORKINFO_FILENAME(type));
1010 char *getCustomMusicDirectory(void)
1012 static char *directory = NULL;
1013 boolean skip_setup_artwork = FALSE;
1015 checked_free(directory);
1017 if (!gfx.override_level_music)
1019 // 1st try: look for special artwork in current level series directory
1020 directory = getPath2(getCurrentLevelDir(), MUSIC_DIRECTORY);
1021 if (directoryExists(directory))
1026 // check if there is special artwork configured in level series config
1027 if (getLevelArtworkSet(ARTWORK_TYPE_MUSIC) != NULL)
1029 // 2nd try: look for special artwork configured in level series config
1030 directory = getStringCopy(getLevelArtworkDir(TREE_TYPE_MUSIC_DIR));
1031 if (directoryExists(directory))
1036 // take missing artwork configured in level set config from default
1037 skip_setup_artwork = TRUE;
1041 if (!skip_setup_artwork)
1043 // 3rd try: look for special artwork in configured artwork directory
1044 directory = getStringCopy(getSetupArtworkDir(artwork.mus_current));
1045 if (directoryExists(directory))
1051 // 4th try: look for default artwork in new default artwork directory
1052 directory = getStringCopy(getDefaultMusicDir(MUS_DEFAULT_SUBDIR));
1053 if (directoryExists(directory))
1058 // 5th try: look for default artwork in old default artwork directory
1059 directory = getStringCopy(options.music_directory);
1060 if (directoryExists(directory))
1063 return NULL; // cannot find specified artwork file anywhere
1066 void InitTapeDirectory(char *level_subdir)
1068 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
1069 createDirectory(getTapeDir(NULL), "main tape", PERMS_PRIVATE);
1070 createDirectory(getTapeDir(level_subdir), "level tape", PERMS_PRIVATE);
1073 void InitScoreDirectory(char *level_subdir)
1075 int permissions = (program.global_scores ? PERMS_PUBLIC : PERMS_PRIVATE);
1077 if (program.global_scores)
1078 createDirectory(getCommonDataDir(), "common data", permissions);
1080 createDirectory(getUserGameDataDir(), "user data", permissions);
1082 createDirectory(getScoreDir(NULL), "main score", permissions);
1083 createDirectory(getScoreDir(level_subdir), "level score", permissions);
1086 static void SaveUserLevelInfo(void);
1088 void InitUserLevelDirectory(char *level_subdir)
1090 if (!directoryExists(getUserLevelDir(level_subdir)))
1092 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
1093 createDirectory(getUserLevelDir(NULL), "main user level", PERMS_PRIVATE);
1094 createDirectory(getUserLevelDir(level_subdir), "user level", PERMS_PRIVATE);
1096 if (setup.internal.create_user_levelset)
1097 SaveUserLevelInfo();
1101 void InitNetworkLevelDirectory(char *level_subdir)
1103 if (!directoryExists(getNetworkLevelDir(level_subdir)))
1105 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
1106 createDirectory(getNetworkDir(), "network data", PERMS_PRIVATE);
1107 createDirectory(getNetworkLevelDir(NULL), "main network level", PERMS_PRIVATE);
1108 createDirectory(getNetworkLevelDir(level_subdir), "network level", PERMS_PRIVATE);
1112 void InitLevelSetupDirectory(char *level_subdir)
1114 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
1115 createDirectory(getLevelSetupDir(NULL), "main level setup", PERMS_PRIVATE);
1116 createDirectory(getLevelSetupDir(level_subdir), "level setup", PERMS_PRIVATE);
1119 static void InitCacheDirectory(void)
1121 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
1122 createDirectory(getCacheDir(), "cache data", PERMS_PRIVATE);
1126 // ----------------------------------------------------------------------------
1127 // some functions to handle lists of level and artwork directories
1128 // ----------------------------------------------------------------------------
1130 TreeInfo *newTreeInfo(void)
1132 return checked_calloc(sizeof(TreeInfo));
1135 TreeInfo *newTreeInfo_setDefaults(int type)
1137 TreeInfo *ti = newTreeInfo();
1139 setTreeInfoToDefaults(ti, type);
1144 void pushTreeInfo(TreeInfo **node_first, TreeInfo *node_new)
1146 node_new->next = *node_first;
1147 *node_first = node_new;
1150 int numTreeInfo(TreeInfo *node)
1163 boolean validLevelSeries(TreeInfo *node)
1165 return (node != NULL && !node->node_group && !node->parent_link);
1168 TreeInfo *getFirstValidTreeInfoEntry(TreeInfo *node)
1173 if (node->node_group) // enter level group (step down into tree)
1174 return getFirstValidTreeInfoEntry(node->node_group);
1175 else if (node->parent_link) // skip start entry of level group
1177 if (node->next) // get first real level series entry
1178 return getFirstValidTreeInfoEntry(node->next);
1179 else // leave empty level group and go on
1180 return getFirstValidTreeInfoEntry(node->node_parent->next);
1182 else // this seems to be a regular level series
1186 TreeInfo *getTreeInfoFirstGroupEntry(TreeInfo *node)
1191 if (node->node_parent == NULL) // top level group
1192 return *node->node_top;
1193 else // sub level group
1194 return node->node_parent->node_group;
1197 int numTreeInfoInGroup(TreeInfo *node)
1199 return numTreeInfo(getTreeInfoFirstGroupEntry(node));
1202 int posTreeInfo(TreeInfo *node)
1204 TreeInfo *node_cmp = getTreeInfoFirstGroupEntry(node);
1209 if (node_cmp == node)
1213 node_cmp = node_cmp->next;
1219 TreeInfo *getTreeInfoFromPos(TreeInfo *node, int pos)
1221 TreeInfo *node_default = node;
1233 return node_default;
1236 TreeInfo *getTreeInfoFromIdentifier(TreeInfo *node, char *identifier)
1238 if (identifier == NULL)
1243 if (node->node_group)
1245 TreeInfo *node_group;
1247 node_group = getTreeInfoFromIdentifier(node->node_group, identifier);
1252 else if (!node->parent_link)
1254 if (strEqual(identifier, node->identifier))
1264 static TreeInfo *cloneTreeNode(TreeInfo **node_top, TreeInfo *node_parent,
1265 TreeInfo *node, boolean skip_sets_without_levels)
1272 if (!node->parent_link && !node->level_group &&
1273 skip_sets_without_levels && node->levels == 0)
1274 return cloneTreeNode(node_top, node_parent, node->next,
1275 skip_sets_without_levels);
1277 node_new = getTreeInfoCopy(node); // copy complete node
1279 node_new->node_top = node_top; // correct top node link
1280 node_new->node_parent = node_parent; // correct parent node link
1282 if (node->level_group)
1283 node_new->node_group = cloneTreeNode(node_top, node_new, node->node_group,
1284 skip_sets_without_levels);
1286 node_new->next = cloneTreeNode(node_top, node_parent, node->next,
1287 skip_sets_without_levels);
1292 static void cloneTree(TreeInfo **ti_new, TreeInfo *ti, boolean skip_empty_sets)
1294 TreeInfo *ti_cloned = cloneTreeNode(ti_new, NULL, ti, skip_empty_sets);
1296 *ti_new = ti_cloned;
1299 static boolean adjustTreeGraphicsForEMC(TreeInfo *node)
1301 boolean settings_changed = FALSE;
1305 boolean want_ecs = (setup.prefer_aga_graphics == FALSE);
1306 boolean want_aga = (setup.prefer_aga_graphics == TRUE);
1307 boolean has_only_ecs = (!node->graphics_set && !node->graphics_set_aga);
1308 boolean has_only_aga = (!node->graphics_set && !node->graphics_set_ecs);
1309 char *graphics_set = NULL;
1311 if (node->graphics_set_ecs && (want_ecs || has_only_ecs))
1312 graphics_set = node->graphics_set_ecs;
1314 if (node->graphics_set_aga && (want_aga || has_only_aga))
1315 graphics_set = node->graphics_set_aga;
1317 if (graphics_set && !strEqual(node->graphics_set, graphics_set))
1319 setString(&node->graphics_set, graphics_set);
1320 settings_changed = TRUE;
1323 if (node->node_group != NULL)
1324 settings_changed |= adjustTreeGraphicsForEMC(node->node_group);
1329 return settings_changed;
1332 void dumpTreeInfo(TreeInfo *node, int depth)
1336 printf("Dumping TreeInfo:\n");
1340 for (i = 0; i < (depth + 1) * 3; i++)
1343 printf("'%s' / '%s'\n", node->identifier, node->name);
1346 // use for dumping artwork info tree
1347 printf("subdir == '%s' ['%s', '%s'] [%d])\n",
1348 node->subdir, node->fullpath, node->basepath, node->in_user_dir);
1351 if (node->node_group != NULL)
1352 dumpTreeInfo(node->node_group, depth + 1);
1358 void sortTreeInfoBySortFunction(TreeInfo **node_first,
1359 int (*compare_function)(const void *,
1362 int num_nodes = numTreeInfo(*node_first);
1363 TreeInfo **sort_array;
1364 TreeInfo *node = *node_first;
1370 // allocate array for sorting structure pointers
1371 sort_array = checked_calloc(num_nodes * sizeof(TreeInfo *));
1373 // writing structure pointers to sorting array
1374 while (i < num_nodes && node) // double boundary check...
1376 sort_array[i] = node;
1382 // sorting the structure pointers in the sorting array
1383 qsort(sort_array, num_nodes, sizeof(TreeInfo *),
1386 // update the linkage of list elements with the sorted node array
1387 for (i = 0; i < num_nodes - 1; i++)
1388 sort_array[i]->next = sort_array[i + 1];
1389 sort_array[num_nodes - 1]->next = NULL;
1391 // update the linkage of the main list anchor pointer
1392 *node_first = sort_array[0];
1396 // now recursively sort the level group structures
1400 if (node->node_group != NULL)
1401 sortTreeInfoBySortFunction(&node->node_group, compare_function);
1407 void sortTreeInfo(TreeInfo **node_first)
1409 sortTreeInfoBySortFunction(node_first, compareTreeInfoEntries);
1413 // ============================================================================
1414 // some stuff from "files.c"
1415 // ============================================================================
1417 #if defined(PLATFORM_WIN32)
1419 #define S_IRGRP S_IRUSR
1422 #define S_IROTH S_IRUSR
1425 #define S_IWGRP S_IWUSR
1428 #define S_IWOTH S_IWUSR
1431 #define S_IXGRP S_IXUSR
1434 #define S_IXOTH S_IXUSR
1437 #define S_IRWXG (S_IRGRP | S_IWGRP | S_IXGRP)
1442 #endif // PLATFORM_WIN32
1444 // file permissions for newly written files
1445 #define MODE_R_ALL (S_IRUSR | S_IRGRP | S_IROTH)
1446 #define MODE_W_ALL (S_IWUSR | S_IWGRP | S_IWOTH)
1447 #define MODE_X_ALL (S_IXUSR | S_IXGRP | S_IXOTH)
1449 #define MODE_W_PRIVATE (S_IWUSR)
1450 #define MODE_W_PUBLIC_FILE (S_IWUSR | S_IWGRP)
1451 #define MODE_W_PUBLIC_DIR (S_IWUSR | S_IWGRP | S_ISGID)
1453 #define DIR_PERMS_PRIVATE (MODE_R_ALL | MODE_X_ALL | MODE_W_PRIVATE)
1454 #define DIR_PERMS_PUBLIC (MODE_R_ALL | MODE_X_ALL | MODE_W_PUBLIC_DIR)
1455 #define DIR_PERMS_PUBLIC_ALL (MODE_R_ALL | MODE_X_ALL | MODE_W_ALL)
1457 #define FILE_PERMS_PRIVATE (MODE_R_ALL | MODE_W_PRIVATE)
1458 #define FILE_PERMS_PUBLIC (MODE_R_ALL | MODE_W_PUBLIC_FILE)
1459 #define FILE_PERMS_PUBLIC_ALL (MODE_R_ALL | MODE_W_ALL)
1462 char *getHomeDir(void)
1464 static char *dir = NULL;
1466 #if defined(PLATFORM_WIN32)
1469 dir = checked_malloc(MAX_PATH + 1);
1471 if (!SUCCEEDED(SHGetFolderPath(NULL, CSIDL_PERSONAL, NULL, 0, dir)))
1474 #elif defined(PLATFORM_UNIX)
1477 if ((dir = getenv("HOME")) == NULL)
1481 if ((pwd = getpwuid(getuid())) != NULL)
1482 dir = getStringCopy(pwd->pw_dir);
1494 char *getCommonDataDir(void)
1496 static char *common_data_dir = NULL;
1498 #if defined(PLATFORM_WIN32)
1499 if (common_data_dir == NULL)
1501 char *dir = checked_malloc(MAX_PATH + 1);
1503 if (SUCCEEDED(SHGetFolderPath(NULL, CSIDL_COMMON_DOCUMENTS, NULL, 0, dir))
1504 && !strEqual(dir, "")) // empty for Windows 95/98
1505 common_data_dir = getPath2(dir, program.userdata_subdir);
1507 common_data_dir = options.rw_base_directory;
1510 if (common_data_dir == NULL)
1511 common_data_dir = options.rw_base_directory;
1514 return common_data_dir;
1517 char *getPersonalDataDir(void)
1519 static char *personal_data_dir = NULL;
1521 #if defined(PLATFORM_MACOSX)
1522 if (personal_data_dir == NULL)
1523 personal_data_dir = getPath2(getHomeDir(), "Documents");
1525 if (personal_data_dir == NULL)
1526 personal_data_dir = getHomeDir();
1529 return personal_data_dir;
1532 char *getUserGameDataDir(void)
1534 static char *user_game_data_dir = NULL;
1536 #if defined(PLATFORM_ANDROID)
1537 if (user_game_data_dir == NULL)
1538 user_game_data_dir = (char *)(SDL_AndroidGetExternalStorageState() &
1539 SDL_ANDROID_EXTERNAL_STORAGE_WRITE ?
1540 SDL_AndroidGetExternalStoragePath() :
1541 SDL_AndroidGetInternalStoragePath());
1543 if (user_game_data_dir == NULL)
1544 user_game_data_dir = getPath2(getPersonalDataDir(),
1545 program.userdata_subdir);
1548 return user_game_data_dir;
1551 char *getSetupDir(void)
1553 return getUserGameDataDir();
1556 static mode_t posix_umask(mode_t mask)
1558 #if defined(PLATFORM_UNIX)
1565 static int posix_mkdir(const char *pathname, mode_t mode)
1567 #if defined(PLATFORM_WIN32)
1568 return mkdir(pathname);
1570 return mkdir(pathname, mode);
1574 static boolean posix_process_running_setgid(void)
1576 #if defined(PLATFORM_UNIX)
1577 return (getgid() != getegid());
1583 void createDirectory(char *dir, char *text, int permission_class)
1585 if (directoryExists(dir))
1588 // leave "other" permissions in umask untouched, but ensure group parts
1589 // of USERDATA_DIR_MODE are not masked
1590 mode_t dir_mode = (permission_class == PERMS_PRIVATE ?
1591 DIR_PERMS_PRIVATE : DIR_PERMS_PUBLIC);
1592 mode_t last_umask = posix_umask(0);
1593 mode_t group_umask = ~(dir_mode & S_IRWXG);
1594 int running_setgid = posix_process_running_setgid();
1596 if (permission_class == PERMS_PUBLIC)
1598 // if we're setgid, protect files against "other"
1599 // else keep umask(0) to make the dir world-writable
1602 posix_umask(last_umask & group_umask);
1604 dir_mode = DIR_PERMS_PUBLIC_ALL;
1607 if (posix_mkdir(dir, dir_mode) != 0)
1608 Error(ERR_WARN, "cannot create %s directory '%s': %s",
1609 text, dir, strerror(errno));
1611 if (permission_class == PERMS_PUBLIC && !running_setgid)
1612 chmod(dir, dir_mode);
1614 posix_umask(last_umask); // restore previous umask
1617 void InitUserDataDirectory(void)
1619 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
1622 void SetFilePermissions(char *filename, int permission_class)
1624 int running_setgid = posix_process_running_setgid();
1625 int perms = (permission_class == PERMS_PRIVATE ?
1626 FILE_PERMS_PRIVATE : FILE_PERMS_PUBLIC);
1628 if (permission_class == PERMS_PUBLIC && !running_setgid)
1629 perms = FILE_PERMS_PUBLIC_ALL;
1631 chmod(filename, perms);
1634 char *getCookie(char *file_type)
1636 static char cookie[MAX_COOKIE_LEN + 1];
1638 if (strlen(program.cookie_prefix) + 1 +
1639 strlen(file_type) + strlen("_FILE_VERSION_x.x") > MAX_COOKIE_LEN)
1640 return "[COOKIE ERROR]"; // should never happen
1642 sprintf(cookie, "%s_%s_FILE_VERSION_%d.%d",
1643 program.cookie_prefix, file_type,
1644 program.version_super, program.version_major);
1649 void fprintFileHeader(FILE *file, char *basename)
1651 char *prefix = "# ";
1654 fprintf_line_with_prefix(file, prefix, sep1, 77);
1655 fprintf(file, "%s%s\n", prefix, basename);
1656 fprintf_line_with_prefix(file, prefix, sep1, 77);
1657 fprintf(file, "\n");
1660 int getFileVersionFromCookieString(const char *cookie)
1662 const char *ptr_cookie1, *ptr_cookie2;
1663 const char *pattern1 = "_FILE_VERSION_";
1664 const char *pattern2 = "?.?";
1665 const int len_cookie = strlen(cookie);
1666 const int len_pattern1 = strlen(pattern1);
1667 const int len_pattern2 = strlen(pattern2);
1668 const int len_pattern = len_pattern1 + len_pattern2;
1669 int version_super, version_major;
1671 if (len_cookie <= len_pattern)
1674 ptr_cookie1 = &cookie[len_cookie - len_pattern];
1675 ptr_cookie2 = &cookie[len_cookie - len_pattern2];
1677 if (strncmp(ptr_cookie1, pattern1, len_pattern1) != 0)
1680 if (ptr_cookie2[0] < '0' || ptr_cookie2[0] > '9' ||
1681 ptr_cookie2[1] != '.' ||
1682 ptr_cookie2[2] < '0' || ptr_cookie2[2] > '9')
1685 version_super = ptr_cookie2[0] - '0';
1686 version_major = ptr_cookie2[2] - '0';
1688 return VERSION_IDENT(version_super, version_major, 0, 0);
1691 boolean checkCookieString(const char *cookie, const char *template)
1693 const char *pattern = "_FILE_VERSION_?.?";
1694 const int len_cookie = strlen(cookie);
1695 const int len_template = strlen(template);
1696 const int len_pattern = strlen(pattern);
1698 if (len_cookie != len_template)
1701 if (strncmp(cookie, template, len_cookie - len_pattern) != 0)
1708 // ----------------------------------------------------------------------------
1709 // setup file list and hash handling functions
1710 // ----------------------------------------------------------------------------
1712 char *getFormattedSetupEntry(char *token, char *value)
1715 static char entry[MAX_LINE_LEN];
1717 // if value is an empty string, just return token without value
1721 // start with the token and some spaces to format output line
1722 sprintf(entry, "%s:", token);
1723 for (i = strlen(entry); i < token_value_position; i++)
1726 // continue with the token's value
1727 strcat(entry, value);
1732 SetupFileList *newSetupFileList(char *token, char *value)
1734 SetupFileList *new = checked_malloc(sizeof(SetupFileList));
1736 new->token = getStringCopy(token);
1737 new->value = getStringCopy(value);
1744 void freeSetupFileList(SetupFileList *list)
1749 checked_free(list->token);
1750 checked_free(list->value);
1753 freeSetupFileList(list->next);
1758 char *getListEntry(SetupFileList *list, char *token)
1763 if (strEqual(list->token, token))
1766 return getListEntry(list->next, token);
1769 SetupFileList *setListEntry(SetupFileList *list, char *token, char *value)
1774 if (strEqual(list->token, token))
1776 checked_free(list->value);
1778 list->value = getStringCopy(value);
1782 else if (list->next == NULL)
1783 return (list->next = newSetupFileList(token, value));
1785 return setListEntry(list->next, token, value);
1788 SetupFileList *addListEntry(SetupFileList *list, char *token, char *value)
1793 if (list->next == NULL)
1794 return (list->next = newSetupFileList(token, value));
1796 return addListEntry(list->next, token, value);
1799 #if ENABLE_UNUSED_CODE
1801 static void printSetupFileList(SetupFileList *list)
1806 printf("token: '%s'\n", list->token);
1807 printf("value: '%s'\n", list->value);
1809 printSetupFileList(list->next);
1815 DEFINE_HASHTABLE_INSERT(insert_hash_entry, char, char);
1816 DEFINE_HASHTABLE_SEARCH(search_hash_entry, char, char);
1817 DEFINE_HASHTABLE_CHANGE(change_hash_entry, char, char);
1818 DEFINE_HASHTABLE_REMOVE(remove_hash_entry, char, char);
1820 #define insert_hash_entry hashtable_insert
1821 #define search_hash_entry hashtable_search
1822 #define change_hash_entry hashtable_change
1823 #define remove_hash_entry hashtable_remove
1826 unsigned int get_hash_from_key(void *key)
1831 This algorithm (k=33) was first reported by Dan Bernstein many years ago in
1832 'comp.lang.c'. Another version of this algorithm (now favored by Bernstein)
1833 uses XOR: hash(i) = hash(i - 1) * 33 ^ str[i]; the magic of number 33 (why
1834 it works better than many other constants, prime or not) has never been
1835 adequately explained.
1837 If you just want to have a good hash function, and cannot wait, djb2
1838 is one of the best string hash functions i know. It has excellent
1839 distribution and speed on many different sets of keys and table sizes.
1840 You are not likely to do better with one of the "well known" functions
1841 such as PJW, K&R, etc.
1843 Ozan (oz) Yigit [http://www.cs.yorku.ca/~oz/hash.html]
1846 char *str = (char *)key;
1847 unsigned int hash = 5381;
1850 while ((c = *str++))
1851 hash = ((hash << 5) + hash) + c; // hash * 33 + c
1856 static int keys_are_equal(void *key1, void *key2)
1858 return (strEqual((char *)key1, (char *)key2));
1861 SetupFileHash *newSetupFileHash(void)
1863 SetupFileHash *new_hash =
1864 create_hashtable(16, 0.75, get_hash_from_key, keys_are_equal);
1866 if (new_hash == NULL)
1867 Error(ERR_EXIT, "create_hashtable() failed -- out of memory");
1872 void freeSetupFileHash(SetupFileHash *hash)
1877 hashtable_destroy(hash, 1); // 1 == also free values stored in hash
1880 char *getHashEntry(SetupFileHash *hash, char *token)
1885 return search_hash_entry(hash, token);
1888 void setHashEntry(SetupFileHash *hash, char *token, char *value)
1895 value_copy = getStringCopy(value);
1897 // change value; if it does not exist, insert it as new
1898 if (!change_hash_entry(hash, token, value_copy))
1899 if (!insert_hash_entry(hash, getStringCopy(token), value_copy))
1900 Error(ERR_EXIT, "cannot insert into hash -- aborting");
1903 char *removeHashEntry(SetupFileHash *hash, char *token)
1908 return remove_hash_entry(hash, token);
1911 #if ENABLE_UNUSED_CODE
1913 static void printSetupFileHash(SetupFileHash *hash)
1915 BEGIN_HASH_ITERATION(hash, itr)
1917 printf("token: '%s'\n", HASH_ITERATION_TOKEN(itr));
1918 printf("value: '%s'\n", HASH_ITERATION_VALUE(itr));
1920 END_HASH_ITERATION(hash, itr)
1925 #define ALLOW_TOKEN_VALUE_SEPARATOR_BEING_WHITESPACE 1
1926 #define CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING 0
1927 #define CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH 0
1929 static boolean token_value_separator_found = FALSE;
1930 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
1931 static boolean token_value_separator_warning = FALSE;
1933 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
1934 static boolean token_already_exists_warning = FALSE;
1937 static boolean getTokenValueFromSetupLineExt(char *line,
1938 char **token_ptr, char **value_ptr,
1939 char *filename, char *line_raw,
1941 boolean separator_required)
1943 static char line_copy[MAX_LINE_LEN + 1], line_raw_copy[MAX_LINE_LEN + 1];
1944 char *token, *value, *line_ptr;
1946 // when externally invoked via ReadTokenValueFromLine(), copy line buffers
1947 if (line_raw == NULL)
1949 strncpy(line_copy, line, MAX_LINE_LEN);
1950 line_copy[MAX_LINE_LEN] = '\0';
1953 strcpy(line_raw_copy, line_copy);
1954 line_raw = line_raw_copy;
1957 // cut trailing comment from input line
1958 for (line_ptr = line; *line_ptr; line_ptr++)
1960 if (*line_ptr == '#')
1967 // cut trailing whitespaces from input line
1968 for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
1969 if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
1972 // ignore empty lines
1976 // cut leading whitespaces from token
1977 for (token = line; *token; token++)
1978 if (*token != ' ' && *token != '\t')
1981 // start with empty value as reliable default
1984 token_value_separator_found = FALSE;
1986 // find end of token to determine start of value
1987 for (line_ptr = token; *line_ptr; line_ptr++)
1989 // first look for an explicit token/value separator, like ':' or '='
1990 if (*line_ptr == ':' || *line_ptr == '=')
1992 *line_ptr = '\0'; // terminate token string
1993 value = line_ptr + 1; // set beginning of value
1995 token_value_separator_found = TRUE;
2001 #if ALLOW_TOKEN_VALUE_SEPARATOR_BEING_WHITESPACE
2002 // fallback: if no token/value separator found, also allow whitespaces
2003 if (!token_value_separator_found && !separator_required)
2005 for (line_ptr = token; *line_ptr; line_ptr++)
2007 if (*line_ptr == ' ' || *line_ptr == '\t')
2009 *line_ptr = '\0'; // terminate token string
2010 value = line_ptr + 1; // set beginning of value
2012 token_value_separator_found = TRUE;
2018 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2019 if (token_value_separator_found)
2021 if (!token_value_separator_warning)
2023 Error(ERR_INFO_LINE, "-");
2025 if (filename != NULL)
2027 Error(ERR_WARN, "missing token/value separator(s) in config file:");
2028 Error(ERR_INFO, "- config file: '%s'", filename);
2032 Error(ERR_WARN, "missing token/value separator(s):");
2035 token_value_separator_warning = TRUE;
2038 if (filename != NULL)
2039 Error(ERR_INFO, "- line %d: '%s'", line_nr, line_raw);
2041 Error(ERR_INFO, "- line: '%s'", line_raw);
2047 // cut trailing whitespaces from token
2048 for (line_ptr = &token[strlen(token)]; line_ptr >= token; line_ptr--)
2049 if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
2052 // cut leading whitespaces from value
2053 for (; *value; value++)
2054 if (*value != ' ' && *value != '\t')
2063 boolean getTokenValueFromSetupLine(char *line, char **token, char **value)
2065 // while the internal (old) interface does not require a token/value
2066 // separator (for downwards compatibility with existing files which
2067 // don't use them), it is mandatory for the external (new) interface
2069 return getTokenValueFromSetupLineExt(line, token, value, NULL, NULL, 0, TRUE);
2072 static boolean loadSetupFileData(void *setup_file_data, char *filename,
2073 boolean top_recursion_level, boolean is_hash)
2075 static SetupFileHash *include_filename_hash = NULL;
2076 char line[MAX_LINE_LEN], line_raw[MAX_LINE_LEN], previous_line[MAX_LINE_LEN];
2077 char *token, *value, *line_ptr;
2078 void *insert_ptr = NULL;
2079 boolean read_continued_line = FALSE;
2081 int line_nr = 0, token_count = 0, include_count = 0;
2083 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2084 token_value_separator_warning = FALSE;
2087 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2088 token_already_exists_warning = FALSE;
2091 if (!(file = openFile(filename, MODE_READ)))
2093 #if DEBUG_NO_CONFIG_FILE
2094 Error(ERR_DEBUG, "cannot open configuration file '%s'", filename);
2100 // use "insert pointer" to store list end for constant insertion complexity
2102 insert_ptr = setup_file_data;
2104 // on top invocation, create hash to mark included files (to prevent loops)
2105 if (top_recursion_level)
2106 include_filename_hash = newSetupFileHash();
2108 // mark this file as already included (to prevent including it again)
2109 setHashEntry(include_filename_hash, getBaseNamePtr(filename), "true");
2111 while (!checkEndOfFile(file))
2113 // read next line of input file
2114 if (!getStringFromFile(file, line, MAX_LINE_LEN))
2117 // check if line was completely read and is terminated by line break
2118 if (strlen(line) > 0 && line[strlen(line) - 1] == '\n')
2121 // cut trailing line break (this can be newline and/or carriage return)
2122 for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
2123 if ((*line_ptr == '\n' || *line_ptr == '\r') && *(line_ptr + 1) == '\0')
2126 // copy raw input line for later use (mainly debugging output)
2127 strcpy(line_raw, line);
2129 if (read_continued_line)
2131 // append new line to existing line, if there is enough space
2132 if (strlen(previous_line) + strlen(line_ptr) < MAX_LINE_LEN)
2133 strcat(previous_line, line_ptr);
2135 strcpy(line, previous_line); // copy storage buffer to line
2137 read_continued_line = FALSE;
2140 // if the last character is '\', continue at next line
2141 if (strlen(line) > 0 && line[strlen(line) - 1] == '\\')
2143 line[strlen(line) - 1] = '\0'; // cut off trailing backslash
2144 strcpy(previous_line, line); // copy line to storage buffer
2146 read_continued_line = TRUE;
2151 if (!getTokenValueFromSetupLineExt(line, &token, &value, filename,
2152 line_raw, line_nr, FALSE))
2157 if (strEqual(token, "include"))
2159 if (getHashEntry(include_filename_hash, value) == NULL)
2161 char *basepath = getBasePath(filename);
2162 char *basename = getBaseName(value);
2163 char *filename_include = getPath2(basepath, basename);
2165 loadSetupFileData(setup_file_data, filename_include, FALSE, is_hash);
2169 free(filename_include);
2175 Error(ERR_WARN, "ignoring already processed file '%s'", value);
2182 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2184 getHashEntry((SetupFileHash *)setup_file_data, token);
2186 if (old_value != NULL)
2188 if (!token_already_exists_warning)
2190 Error(ERR_INFO_LINE, "-");
2191 Error(ERR_WARN, "duplicate token(s) found in config file:");
2192 Error(ERR_INFO, "- config file: '%s'", filename);
2194 token_already_exists_warning = TRUE;
2197 Error(ERR_INFO, "- token: '%s' (in line %d)", token, line_nr);
2198 Error(ERR_INFO, " old value: '%s'", old_value);
2199 Error(ERR_INFO, " new value: '%s'", value);
2203 setHashEntry((SetupFileHash *)setup_file_data, token, value);
2207 insert_ptr = addListEntry((SetupFileList *)insert_ptr, token, value);
2217 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2218 if (token_value_separator_warning)
2219 Error(ERR_INFO_LINE, "-");
2222 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2223 if (token_already_exists_warning)
2224 Error(ERR_INFO_LINE, "-");
2227 if (token_count == 0 && include_count == 0)
2228 Error(ERR_WARN, "configuration file '%s' is empty", filename);
2230 if (top_recursion_level)
2231 freeSetupFileHash(include_filename_hash);
2236 static void saveSetupFileHash(SetupFileHash *hash, char *filename)
2240 if (!(file = fopen(filename, MODE_WRITE)))
2242 Error(ERR_WARN, "cannot write configuration file '%s'", filename);
2247 BEGIN_HASH_ITERATION(hash, itr)
2249 fprintf(file, "%s\n", getFormattedSetupEntry(HASH_ITERATION_TOKEN(itr),
2250 HASH_ITERATION_VALUE(itr)));
2252 END_HASH_ITERATION(hash, itr)
2257 SetupFileList *loadSetupFileList(char *filename)
2259 SetupFileList *setup_file_list = newSetupFileList("", "");
2260 SetupFileList *first_valid_list_entry;
2262 if (!loadSetupFileData(setup_file_list, filename, TRUE, FALSE))
2264 freeSetupFileList(setup_file_list);
2269 first_valid_list_entry = setup_file_list->next;
2271 // free empty list header
2272 setup_file_list->next = NULL;
2273 freeSetupFileList(setup_file_list);
2275 return first_valid_list_entry;
2278 SetupFileHash *loadSetupFileHash(char *filename)
2280 SetupFileHash *setup_file_hash = newSetupFileHash();
2282 if (!loadSetupFileData(setup_file_hash, filename, TRUE, TRUE))
2284 freeSetupFileHash(setup_file_hash);
2289 return setup_file_hash;
2293 // ============================================================================
2295 // ============================================================================
2297 #define TOKEN_STR_LAST_LEVEL_SERIES "last_level_series"
2298 #define TOKEN_STR_LAST_PLAYED_LEVEL "last_played_level"
2299 #define TOKEN_STR_HANDICAP_LEVEL "handicap_level"
2301 // level directory info
2302 #define LEVELINFO_TOKEN_IDENTIFIER 0
2303 #define LEVELINFO_TOKEN_NAME 1
2304 #define LEVELINFO_TOKEN_NAME_SORTING 2
2305 #define LEVELINFO_TOKEN_AUTHOR 3
2306 #define LEVELINFO_TOKEN_YEAR 4
2307 #define LEVELINFO_TOKEN_PROGRAM_TITLE 5
2308 #define LEVELINFO_TOKEN_PROGRAM_COPYRIGHT 6
2309 #define LEVELINFO_TOKEN_PROGRAM_COMPANY 7
2310 #define LEVELINFO_TOKEN_IMPORTED_FROM 8
2311 #define LEVELINFO_TOKEN_IMPORTED_BY 9
2312 #define LEVELINFO_TOKEN_TESTED_BY 10
2313 #define LEVELINFO_TOKEN_LEVELS 11
2314 #define LEVELINFO_TOKEN_FIRST_LEVEL 12
2315 #define LEVELINFO_TOKEN_SORT_PRIORITY 13
2316 #define LEVELINFO_TOKEN_LATEST_ENGINE 14
2317 #define LEVELINFO_TOKEN_LEVEL_GROUP 15
2318 #define LEVELINFO_TOKEN_READONLY 16
2319 #define LEVELINFO_TOKEN_GRAPHICS_SET_ECS 17
2320 #define LEVELINFO_TOKEN_GRAPHICS_SET_AGA 18
2321 #define LEVELINFO_TOKEN_GRAPHICS_SET 19
2322 #define LEVELINFO_TOKEN_SOUNDS_SET 20
2323 #define LEVELINFO_TOKEN_MUSIC_SET 21
2324 #define LEVELINFO_TOKEN_FILENAME 22
2325 #define LEVELINFO_TOKEN_FILETYPE 23
2326 #define LEVELINFO_TOKEN_SPECIAL_FLAGS 24
2327 #define LEVELINFO_TOKEN_HANDICAP 25
2328 #define LEVELINFO_TOKEN_SKIP_LEVELS 26
2329 #define LEVELINFO_TOKEN_USE_EMC_TILES 27
2331 #define NUM_LEVELINFO_TOKENS 28
2333 static LevelDirTree ldi;
2335 static struct TokenInfo levelinfo_tokens[] =
2337 // level directory info
2338 { TYPE_STRING, &ldi.identifier, "identifier" },
2339 { TYPE_STRING, &ldi.name, "name" },
2340 { TYPE_STRING, &ldi.name_sorting, "name_sorting" },
2341 { TYPE_STRING, &ldi.author, "author" },
2342 { TYPE_STRING, &ldi.year, "year" },
2343 { TYPE_STRING, &ldi.program_title, "program_title" },
2344 { TYPE_STRING, &ldi.program_copyright, "program_copyright" },
2345 { TYPE_STRING, &ldi.program_company, "program_company" },
2346 { TYPE_STRING, &ldi.imported_from, "imported_from" },
2347 { TYPE_STRING, &ldi.imported_by, "imported_by" },
2348 { TYPE_STRING, &ldi.tested_by, "tested_by" },
2349 { TYPE_INTEGER, &ldi.levels, "levels" },
2350 { TYPE_INTEGER, &ldi.first_level, "first_level" },
2351 { TYPE_INTEGER, &ldi.sort_priority, "sort_priority" },
2352 { TYPE_BOOLEAN, &ldi.latest_engine, "latest_engine" },
2353 { TYPE_BOOLEAN, &ldi.level_group, "level_group" },
2354 { TYPE_BOOLEAN, &ldi.readonly, "readonly" },
2355 { TYPE_STRING, &ldi.graphics_set_ecs, "graphics_set.ecs" },
2356 { TYPE_STRING, &ldi.graphics_set_aga, "graphics_set.aga" },
2357 { TYPE_STRING, &ldi.graphics_set, "graphics_set" },
2358 { TYPE_STRING, &ldi.sounds_set, "sounds_set" },
2359 { TYPE_STRING, &ldi.music_set, "music_set" },
2360 { TYPE_STRING, &ldi.level_filename, "filename" },
2361 { TYPE_STRING, &ldi.level_filetype, "filetype" },
2362 { TYPE_STRING, &ldi.special_flags, "special_flags" },
2363 { TYPE_BOOLEAN, &ldi.handicap, "handicap" },
2364 { TYPE_BOOLEAN, &ldi.skip_levels, "skip_levels" },
2365 { TYPE_BOOLEAN, &ldi.use_emc_tiles, "use_emc_tiles" }
2368 static struct TokenInfo artworkinfo_tokens[] =
2370 // artwork directory info
2371 { TYPE_STRING, &ldi.identifier, "identifier" },
2372 { TYPE_STRING, &ldi.subdir, "subdir" },
2373 { TYPE_STRING, &ldi.name, "name" },
2374 { TYPE_STRING, &ldi.name_sorting, "name_sorting" },
2375 { TYPE_STRING, &ldi.author, "author" },
2376 { TYPE_STRING, &ldi.program_title, "program_title" },
2377 { TYPE_STRING, &ldi.program_copyright, "program_copyright" },
2378 { TYPE_STRING, &ldi.program_company, "program_company" },
2379 { TYPE_INTEGER, &ldi.sort_priority, "sort_priority" },
2380 { TYPE_STRING, &ldi.basepath, "basepath" },
2381 { TYPE_STRING, &ldi.fullpath, "fullpath" },
2382 { TYPE_BOOLEAN, &ldi.in_user_dir, "in_user_dir" },
2383 { TYPE_INTEGER, &ldi.color, "color" },
2384 { TYPE_STRING, &ldi.class_desc, "class_desc" },
2389 static void setTreeInfoToDefaults(TreeInfo *ti, int type)
2393 ti->node_top = (ti->type == TREE_TYPE_LEVEL_DIR ? &leveldir_first :
2394 ti->type == TREE_TYPE_GRAPHICS_DIR ? &artwork.gfx_first :
2395 ti->type == TREE_TYPE_SOUNDS_DIR ? &artwork.snd_first :
2396 ti->type == TREE_TYPE_MUSIC_DIR ? &artwork.mus_first :
2399 ti->node_parent = NULL;
2400 ti->node_group = NULL;
2407 ti->fullpath = NULL;
2408 ti->basepath = NULL;
2409 ti->identifier = NULL;
2410 ti->name = getStringCopy(ANONYMOUS_NAME);
2411 ti->name_sorting = NULL;
2412 ti->author = getStringCopy(ANONYMOUS_NAME);
2415 ti->program_title = NULL;
2416 ti->program_copyright = NULL;
2417 ti->program_company = NULL;
2419 ti->sort_priority = LEVELCLASS_UNDEFINED; // default: least priority
2420 ti->latest_engine = FALSE; // default: get from level
2421 ti->parent_link = FALSE;
2422 ti->in_user_dir = FALSE;
2423 ti->user_defined = FALSE;
2425 ti->class_desc = NULL;
2427 ti->infotext = getStringCopy(TREE_INFOTEXT(ti->type));
2429 if (ti->type == TREE_TYPE_LEVEL_DIR)
2431 ti->imported_from = NULL;
2432 ti->imported_by = NULL;
2433 ti->tested_by = NULL;
2435 ti->graphics_set_ecs = NULL;
2436 ti->graphics_set_aga = NULL;
2437 ti->graphics_set = NULL;
2438 ti->sounds_set = NULL;
2439 ti->music_set = NULL;
2440 ti->graphics_path = getStringCopy(UNDEFINED_FILENAME);
2441 ti->sounds_path = getStringCopy(UNDEFINED_FILENAME);
2442 ti->music_path = getStringCopy(UNDEFINED_FILENAME);
2444 ti->level_filename = NULL;
2445 ti->level_filetype = NULL;
2447 ti->special_flags = NULL;
2450 ti->first_level = 0;
2452 ti->level_group = FALSE;
2453 ti->handicap_level = 0;
2454 ti->readonly = TRUE;
2455 ti->handicap = TRUE;
2456 ti->skip_levels = FALSE;
2458 ti->use_emc_tiles = FALSE;
2462 static void setTreeInfoToDefaultsFromParent(TreeInfo *ti, TreeInfo *parent)
2466 Error(ERR_WARN, "setTreeInfoToDefaultsFromParent(): parent == NULL");
2468 setTreeInfoToDefaults(ti, TREE_TYPE_UNDEFINED);
2473 // copy all values from the parent structure
2475 ti->type = parent->type;
2477 ti->node_top = parent->node_top;
2478 ti->node_parent = parent;
2479 ti->node_group = NULL;
2486 ti->fullpath = NULL;
2487 ti->basepath = NULL;
2488 ti->identifier = NULL;
2489 ti->name = getStringCopy(ANONYMOUS_NAME);
2490 ti->name_sorting = NULL;
2491 ti->author = getStringCopy(parent->author);
2492 ti->year = getStringCopy(parent->year);
2494 ti->program_title = getStringCopy(parent->program_title);
2495 ti->program_copyright = getStringCopy(parent->program_copyright);
2496 ti->program_company = getStringCopy(parent->program_company);
2498 ti->sort_priority = parent->sort_priority;
2499 ti->latest_engine = parent->latest_engine;
2500 ti->parent_link = FALSE;
2501 ti->in_user_dir = parent->in_user_dir;
2502 ti->user_defined = parent->user_defined;
2503 ti->color = parent->color;
2504 ti->class_desc = getStringCopy(parent->class_desc);
2506 ti->infotext = getStringCopy(parent->infotext);
2508 if (ti->type == TREE_TYPE_LEVEL_DIR)
2510 ti->imported_from = getStringCopy(parent->imported_from);
2511 ti->imported_by = getStringCopy(parent->imported_by);
2512 ti->tested_by = getStringCopy(parent->tested_by);
2514 ti->graphics_set_ecs = getStringCopy(parent->graphics_set_ecs);
2515 ti->graphics_set_aga = getStringCopy(parent->graphics_set_aga);
2516 ti->graphics_set = getStringCopy(parent->graphics_set);
2517 ti->sounds_set = getStringCopy(parent->sounds_set);
2518 ti->music_set = getStringCopy(parent->music_set);
2519 ti->graphics_path = getStringCopy(UNDEFINED_FILENAME);
2520 ti->sounds_path = getStringCopy(UNDEFINED_FILENAME);
2521 ti->music_path = getStringCopy(UNDEFINED_FILENAME);
2523 ti->level_filename = getStringCopy(parent->level_filename);
2524 ti->level_filetype = getStringCopy(parent->level_filetype);
2526 ti->special_flags = getStringCopy(parent->special_flags);
2528 ti->levels = parent->levels;
2529 ti->first_level = parent->first_level;
2530 ti->last_level = parent->last_level;
2531 ti->level_group = FALSE;
2532 ti->handicap_level = parent->handicap_level;
2533 ti->readonly = parent->readonly;
2534 ti->handicap = parent->handicap;
2535 ti->skip_levels = parent->skip_levels;
2537 ti->use_emc_tiles = parent->use_emc_tiles;
2541 static TreeInfo *getTreeInfoCopy(TreeInfo *ti)
2543 TreeInfo *ti_copy = newTreeInfo();
2545 // copy all values from the original structure
2547 ti_copy->type = ti->type;
2549 ti_copy->node_top = ti->node_top;
2550 ti_copy->node_parent = ti->node_parent;
2551 ti_copy->node_group = ti->node_group;
2552 ti_copy->next = ti->next;
2554 ti_copy->cl_first = ti->cl_first;
2555 ti_copy->cl_cursor = ti->cl_cursor;
2557 ti_copy->subdir = getStringCopy(ti->subdir);
2558 ti_copy->fullpath = getStringCopy(ti->fullpath);
2559 ti_copy->basepath = getStringCopy(ti->basepath);
2560 ti_copy->identifier = getStringCopy(ti->identifier);
2561 ti_copy->name = getStringCopy(ti->name);
2562 ti_copy->name_sorting = getStringCopy(ti->name_sorting);
2563 ti_copy->author = getStringCopy(ti->author);
2564 ti_copy->year = getStringCopy(ti->year);
2566 ti_copy->program_title = getStringCopy(ti->program_title);
2567 ti_copy->program_copyright = getStringCopy(ti->program_copyright);
2568 ti_copy->program_company = getStringCopy(ti->program_company);
2570 ti_copy->imported_from = getStringCopy(ti->imported_from);
2571 ti_copy->imported_by = getStringCopy(ti->imported_by);
2572 ti_copy->tested_by = getStringCopy(ti->tested_by);
2574 ti_copy->graphics_set_ecs = getStringCopy(ti->graphics_set_ecs);
2575 ti_copy->graphics_set_aga = getStringCopy(ti->graphics_set_aga);
2576 ti_copy->graphics_set = getStringCopy(ti->graphics_set);
2577 ti_copy->sounds_set = getStringCopy(ti->sounds_set);
2578 ti_copy->music_set = getStringCopy(ti->music_set);
2579 ti_copy->graphics_path = getStringCopy(ti->graphics_path);
2580 ti_copy->sounds_path = getStringCopy(ti->sounds_path);
2581 ti_copy->music_path = getStringCopy(ti->music_path);
2583 ti_copy->level_filename = getStringCopy(ti->level_filename);
2584 ti_copy->level_filetype = getStringCopy(ti->level_filetype);
2586 ti_copy->special_flags = getStringCopy(ti->special_flags);
2588 ti_copy->levels = ti->levels;
2589 ti_copy->first_level = ti->first_level;
2590 ti_copy->last_level = ti->last_level;
2591 ti_copy->sort_priority = ti->sort_priority;
2593 ti_copy->latest_engine = ti->latest_engine;
2595 ti_copy->level_group = ti->level_group;
2596 ti_copy->parent_link = ti->parent_link;
2597 ti_copy->in_user_dir = ti->in_user_dir;
2598 ti_copy->user_defined = ti->user_defined;
2599 ti_copy->readonly = ti->readonly;
2600 ti_copy->handicap = ti->handicap;
2601 ti_copy->skip_levels = ti->skip_levels;
2603 ti_copy->use_emc_tiles = ti->use_emc_tiles;
2605 ti_copy->color = ti->color;
2606 ti_copy->class_desc = getStringCopy(ti->class_desc);
2607 ti_copy->handicap_level = ti->handicap_level;
2609 ti_copy->infotext = getStringCopy(ti->infotext);
2614 void freeTreeInfo(TreeInfo *ti)
2619 checked_free(ti->subdir);
2620 checked_free(ti->fullpath);
2621 checked_free(ti->basepath);
2622 checked_free(ti->identifier);
2624 checked_free(ti->name);
2625 checked_free(ti->name_sorting);
2626 checked_free(ti->author);
2627 checked_free(ti->year);
2629 checked_free(ti->program_title);
2630 checked_free(ti->program_copyright);
2631 checked_free(ti->program_company);
2633 checked_free(ti->class_desc);
2635 checked_free(ti->infotext);
2637 if (ti->type == TREE_TYPE_LEVEL_DIR)
2639 checked_free(ti->imported_from);
2640 checked_free(ti->imported_by);
2641 checked_free(ti->tested_by);
2643 checked_free(ti->graphics_set_ecs);
2644 checked_free(ti->graphics_set_aga);
2645 checked_free(ti->graphics_set);
2646 checked_free(ti->sounds_set);
2647 checked_free(ti->music_set);
2649 checked_free(ti->graphics_path);
2650 checked_free(ti->sounds_path);
2651 checked_free(ti->music_path);
2653 checked_free(ti->level_filename);
2654 checked_free(ti->level_filetype);
2656 checked_free(ti->special_flags);
2659 // recursively free child node
2661 freeTreeInfo(ti->node_group);
2663 // recursively free next node
2665 freeTreeInfo(ti->next);
2670 void setSetupInfo(struct TokenInfo *token_info,
2671 int token_nr, char *token_value)
2673 int token_type = token_info[token_nr].type;
2674 void *setup_value = token_info[token_nr].value;
2676 if (token_value == NULL)
2679 // set setup field to corresponding token value
2684 *(boolean *)setup_value = get_boolean_from_string(token_value);
2688 *(int *)setup_value = get_switch3_from_string(token_value);
2692 *(Key *)setup_value = getKeyFromKeyName(token_value);
2696 *(Key *)setup_value = getKeyFromX11KeyName(token_value);
2700 *(int *)setup_value = get_integer_from_string(token_value);
2704 checked_free(*(char **)setup_value);
2705 *(char **)setup_value = getStringCopy(token_value);
2709 *(int *)setup_value = get_player_nr_from_string(token_value);
2717 static int compareTreeInfoEntries(const void *object1, const void *object2)
2719 const TreeInfo *entry1 = *((TreeInfo **)object1);
2720 const TreeInfo *entry2 = *((TreeInfo **)object2);
2721 int class_sorting1 = 0, class_sorting2 = 0;
2724 if (entry1->type == TREE_TYPE_LEVEL_DIR)
2726 class_sorting1 = LEVELSORTING(entry1);
2727 class_sorting2 = LEVELSORTING(entry2);
2729 else if (entry1->type == TREE_TYPE_GRAPHICS_DIR ||
2730 entry1->type == TREE_TYPE_SOUNDS_DIR ||
2731 entry1->type == TREE_TYPE_MUSIC_DIR)
2733 class_sorting1 = ARTWORKSORTING(entry1);
2734 class_sorting2 = ARTWORKSORTING(entry2);
2737 if (entry1->parent_link || entry2->parent_link)
2738 compare_result = (entry1->parent_link ? -1 : +1);
2739 else if (entry1->sort_priority == entry2->sort_priority)
2741 char *name1 = getStringToLower(entry1->name_sorting);
2742 char *name2 = getStringToLower(entry2->name_sorting);
2744 compare_result = strcmp(name1, name2);
2749 else if (class_sorting1 == class_sorting2)
2750 compare_result = entry1->sort_priority - entry2->sort_priority;
2752 compare_result = class_sorting1 - class_sorting2;
2754 return compare_result;
2757 static TreeInfo *createParentTreeInfoNode(TreeInfo *node_parent)
2761 if (node_parent == NULL)
2764 ti_new = newTreeInfo();
2765 setTreeInfoToDefaults(ti_new, node_parent->type);
2767 ti_new->node_parent = node_parent;
2768 ti_new->parent_link = TRUE;
2770 setString(&ti_new->identifier, node_parent->identifier);
2771 setString(&ti_new->name, ".. (parent directory)");
2772 setString(&ti_new->name_sorting, ti_new->name);
2774 setString(&ti_new->subdir, STRING_PARENT_DIRECTORY);
2775 setString(&ti_new->fullpath, node_parent->fullpath);
2777 ti_new->sort_priority = node_parent->sort_priority;
2778 ti_new->latest_engine = node_parent->latest_engine;
2780 setString(&ti_new->class_desc, getLevelClassDescription(ti_new));
2782 pushTreeInfo(&node_parent->node_group, ti_new);
2787 static TreeInfo *createTopTreeInfoNode(TreeInfo *node_first)
2789 TreeInfo *ti_new, *ti_new2;
2791 if (node_first == NULL)
2794 ti_new = newTreeInfo();
2795 setTreeInfoToDefaults(ti_new, TREE_TYPE_LEVEL_DIR);
2797 ti_new->node_parent = NULL;
2798 ti_new->parent_link = FALSE;
2800 setString(&ti_new->identifier, node_first->identifier);
2801 setString(&ti_new->name, "level sets");
2802 setString(&ti_new->name_sorting, ti_new->name);
2804 setString(&ti_new->subdir, STRING_TOP_DIRECTORY);
2805 setString(&ti_new->fullpath, ".");
2807 ti_new->sort_priority = node_first->sort_priority;;
2808 ti_new->latest_engine = node_first->latest_engine;
2810 setString(&ti_new->class_desc, "level sets");
2812 ti_new->node_group = node_first;
2813 ti_new->level_group = TRUE;
2815 ti_new2 = createParentTreeInfoNode(ti_new);
2817 setString(&ti_new2->name, ".. (main menu)");
2818 setString(&ti_new2->name_sorting, ti_new2->name);
2824 // ----------------------------------------------------------------------------
2825 // functions for handling level and custom artwork info cache
2826 // ----------------------------------------------------------------------------
2828 static void LoadArtworkInfoCache(void)
2830 InitCacheDirectory();
2832 if (artworkinfo_cache_old == NULL)
2834 char *filename = getPath2(getCacheDir(), ARTWORKINFO_CACHE_FILE);
2836 // try to load artwork info hash from already existing cache file
2837 artworkinfo_cache_old = loadSetupFileHash(filename);
2839 // if no artwork info cache file was found, start with empty hash
2840 if (artworkinfo_cache_old == NULL)
2841 artworkinfo_cache_old = newSetupFileHash();
2846 if (artworkinfo_cache_new == NULL)
2847 artworkinfo_cache_new = newSetupFileHash();
2850 static void SaveArtworkInfoCache(void)
2852 char *filename = getPath2(getCacheDir(), ARTWORKINFO_CACHE_FILE);
2854 InitCacheDirectory();
2856 saveSetupFileHash(artworkinfo_cache_new, filename);
2861 static char *getCacheTokenPrefix(char *prefix1, char *prefix2)
2863 static char *prefix = NULL;
2865 checked_free(prefix);
2867 prefix = getStringCat2WithSeparator(prefix1, prefix2, ".");
2872 // (identical to above function, but separate string buffer needed -- nasty)
2873 static char *getCacheToken(char *prefix, char *suffix)
2875 static char *token = NULL;
2877 checked_free(token);
2879 token = getStringCat2WithSeparator(prefix, suffix, ".");
2884 static char *getFileTimestampString(char *filename)
2886 return getStringCopy(i_to_a(getFileTimestampEpochSeconds(filename)));
2889 static boolean modifiedFileTimestamp(char *filename, char *timestamp_string)
2891 struct stat file_status;
2893 if (timestamp_string == NULL)
2896 if (stat(filename, &file_status) != 0) // cannot stat file
2899 return (file_status.st_mtime != atoi(timestamp_string));
2902 static TreeInfo *getArtworkInfoCacheEntry(LevelDirTree *level_node, int type)
2904 char *identifier = level_node->subdir;
2905 char *type_string = ARTWORK_DIRECTORY(type);
2906 char *token_prefix = getCacheTokenPrefix(type_string, identifier);
2907 char *token_main = getCacheToken(token_prefix, "CACHED");
2908 char *cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
2909 boolean cached = (cache_entry != NULL && strEqual(cache_entry, "true"));
2910 TreeInfo *artwork_info = NULL;
2912 if (!use_artworkinfo_cache)
2919 artwork_info = newTreeInfo();
2920 setTreeInfoToDefaults(artwork_info, type);
2922 // set all structure fields according to the token/value pairs
2923 ldi = *artwork_info;
2924 for (i = 0; artworkinfo_tokens[i].type != -1; i++)
2926 char *token = getCacheToken(token_prefix, artworkinfo_tokens[i].text);
2927 char *value = getHashEntry(artworkinfo_cache_old, token);
2929 // if defined, use value from cache, else keep default value
2931 setSetupInfo(artworkinfo_tokens, i, value);
2934 *artwork_info = ldi;
2936 char *filename_levelinfo = getPath2(getLevelDirFromTreeInfo(level_node),
2937 LEVELINFO_FILENAME);
2938 char *filename_artworkinfo = getPath2(getSetupArtworkDir(artwork_info),
2939 ARTWORKINFO_FILENAME(type));
2941 // check if corresponding "levelinfo.conf" file has changed
2942 token_main = getCacheToken(token_prefix, "TIMESTAMP_LEVELINFO");
2943 cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
2945 if (modifiedFileTimestamp(filename_levelinfo, cache_entry))
2948 // check if corresponding "<artworkinfo>.conf" file has changed
2949 token_main = getCacheToken(token_prefix, "TIMESTAMP_ARTWORKINFO");
2950 cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
2952 if (modifiedFileTimestamp(filename_artworkinfo, cache_entry))
2955 checked_free(filename_levelinfo);
2956 checked_free(filename_artworkinfo);
2959 if (!cached && artwork_info != NULL)
2961 freeTreeInfo(artwork_info);
2966 return artwork_info;
2969 static void setArtworkInfoCacheEntry(TreeInfo *artwork_info,
2970 LevelDirTree *level_node, int type)
2972 char *identifier = level_node->subdir;
2973 char *type_string = ARTWORK_DIRECTORY(type);
2974 char *token_prefix = getCacheTokenPrefix(type_string, identifier);
2975 char *token_main = getCacheToken(token_prefix, "CACHED");
2976 boolean set_cache_timestamps = TRUE;
2979 setHashEntry(artworkinfo_cache_new, token_main, "true");
2981 if (set_cache_timestamps)
2983 char *filename_levelinfo = getPath2(getLevelDirFromTreeInfo(level_node),
2984 LEVELINFO_FILENAME);
2985 char *filename_artworkinfo = getPath2(getSetupArtworkDir(artwork_info),
2986 ARTWORKINFO_FILENAME(type));
2987 char *timestamp_levelinfo = getFileTimestampString(filename_levelinfo);
2988 char *timestamp_artworkinfo = getFileTimestampString(filename_artworkinfo);
2990 token_main = getCacheToken(token_prefix, "TIMESTAMP_LEVELINFO");
2991 setHashEntry(artworkinfo_cache_new, token_main, timestamp_levelinfo);
2993 token_main = getCacheToken(token_prefix, "TIMESTAMP_ARTWORKINFO");
2994 setHashEntry(artworkinfo_cache_new, token_main, timestamp_artworkinfo);
2996 checked_free(filename_levelinfo);
2997 checked_free(filename_artworkinfo);
2998 checked_free(timestamp_levelinfo);
2999 checked_free(timestamp_artworkinfo);
3002 ldi = *artwork_info;
3003 for (i = 0; artworkinfo_tokens[i].type != -1; i++)
3005 char *token = getCacheToken(token_prefix, artworkinfo_tokens[i].text);
3006 char *value = getSetupValue(artworkinfo_tokens[i].type,
3007 artworkinfo_tokens[i].value);
3009 setHashEntry(artworkinfo_cache_new, token, value);
3014 // ----------------------------------------------------------------------------
3015 // functions for loading level info and custom artwork info
3016 // ----------------------------------------------------------------------------
3018 int GetZipFileTreeType(char *zip_filename)
3020 static char *top_dir_path = NULL;
3021 static char *top_dir_conf_filename[NUM_BASE_TREE_TYPES] = { NULL };
3022 static char *conf_basename[NUM_BASE_TREE_TYPES] =
3024 GRAPHICSINFO_FILENAME,
3025 SOUNDSINFO_FILENAME,
3031 checked_free(top_dir_path);
3032 top_dir_path = NULL;
3034 for (j = 0; j < NUM_BASE_TREE_TYPES; j++)
3036 checked_free(top_dir_conf_filename[j]);
3037 top_dir_conf_filename[j] = NULL;
3040 char **zip_entries = zip_list(zip_filename);
3042 // check if zip file successfully opened
3043 if (zip_entries == NULL || zip_entries[0] == NULL)
3044 return TREE_TYPE_UNDEFINED;
3046 // first zip file entry is expected to be top level directory
3047 char *top_dir = zip_entries[0];
3049 // check if valid top level directory found in zip file
3050 if (!strSuffix(top_dir, "/"))
3051 return TREE_TYPE_UNDEFINED;
3053 // get filenames of valid configuration files in top level directory
3054 for (j = 0; j < NUM_BASE_TREE_TYPES; j++)
3055 top_dir_conf_filename[j] = getStringCat2(top_dir, conf_basename[j]);
3057 int tree_type = TREE_TYPE_UNDEFINED;
3060 while (zip_entries[e] != NULL)
3062 // check if every zip file entry is below top level directory
3063 if (!strPrefix(zip_entries[e], top_dir))
3064 return TREE_TYPE_UNDEFINED;
3066 // check if this zip file entry is a valid configuration filename
3067 for (j = 0; j < NUM_BASE_TREE_TYPES; j++)
3069 if (strEqual(zip_entries[e], top_dir_conf_filename[j]))
3071 // only exactly one valid configuration file allowed
3072 if (tree_type != TREE_TYPE_UNDEFINED)
3073 return TREE_TYPE_UNDEFINED;
3085 static boolean CheckZipFileForDirectory(char *zip_filename, char *directory,
3088 static char *top_dir_path = NULL;
3089 static char *top_dir_conf_filename = NULL;
3091 checked_free(top_dir_path);
3092 checked_free(top_dir_conf_filename);
3094 top_dir_path = NULL;
3095 top_dir_conf_filename = NULL;
3097 char *conf_basename = (tree_type == TREE_TYPE_LEVEL_DIR ? LEVELINFO_FILENAME :
3098 ARTWORKINFO_FILENAME(tree_type));
3100 // check if valid configuration filename determined
3101 if (conf_basename == NULL || strEqual(conf_basename, ""))
3104 char **zip_entries = zip_list(zip_filename);
3106 // check if zip file successfully opened
3107 if (zip_entries == NULL || zip_entries[0] == NULL)
3110 // first zip file entry is expected to be top level directory
3111 char *top_dir = zip_entries[0];
3113 // check if valid top level directory found in zip file
3114 if (!strSuffix(top_dir, "/"))
3117 // get path of extracted top level directory
3118 top_dir_path = getPath2(directory, top_dir);
3120 // remove trailing directory separator from top level directory path
3121 // (required to be able to check for file and directory in next step)
3122 top_dir_path[strlen(top_dir_path) - 1] = '\0';
3124 // check if zip file's top level directory already exists in target directory
3125 if (fileExists(top_dir_path)) // (checks for file and directory)
3128 // get filename of configuration file in top level directory
3129 top_dir_conf_filename = getStringCat2(top_dir, conf_basename);
3131 boolean found_top_dir_conf_filename = FALSE;
3134 while (zip_entries[i] != NULL)
3136 // check if every zip file entry is below top level directory
3137 if (!strPrefix(zip_entries[i], top_dir))
3140 // check if this zip file entry is the configuration filename
3141 if (strEqual(zip_entries[i], top_dir_conf_filename))
3142 found_top_dir_conf_filename = TRUE;
3147 // check if valid configuration filename was found in zip file
3148 if (!found_top_dir_conf_filename)
3154 char *ExtractZipFileIntoDirectory(char *zip_filename, char *directory,
3157 boolean zip_file_valid = CheckZipFileForDirectory(zip_filename, directory,
3160 if (!zip_file_valid)
3162 Error(ERR_WARN, "zip file '%s' rejected!", zip_filename);
3167 char **zip_entries = zip_extract(zip_filename, directory);
3169 if (zip_entries == NULL)
3171 Error(ERR_WARN, "zip file '%s' could not be extracted!", zip_filename);
3176 Error(ERR_INFO, "zip file '%s' successfully extracted!", zip_filename);
3178 // first zip file entry contains top level directory
3179 char *top_dir = zip_entries[0];
3181 // remove trailing directory separator from top level directory
3182 top_dir[strlen(top_dir) - 1] = '\0';
3187 static void ProcessZipFilesInDirectory(char *directory, int tree_type)
3190 DirectoryEntry *dir_entry;
3192 if ((dir = openDirectory(directory)) == NULL)
3194 // display error if directory is main "options.graphics_directory" etc.
3195 if (tree_type == TREE_TYPE_LEVEL_DIR ||
3196 directory == OPTIONS_ARTWORK_DIRECTORY(tree_type))
3197 Error(ERR_WARN, "cannot read directory '%s'", directory);
3202 while ((dir_entry = readDirectory(dir)) != NULL) // loop all entries
3204 // skip non-zip files (and also directories with zip extension)
3205 if (!strSuffixLower(dir_entry->basename, ".zip") || dir_entry->is_directory)
3208 char *zip_filename = getPath2(directory, dir_entry->basename);
3209 char *zip_filename_extracted = getStringCat2(zip_filename, ".extracted");
3210 char *zip_filename_rejected = getStringCat2(zip_filename, ".rejected");
3212 // check if zip file hasn't already been extracted or rejected
3213 if (!fileExists(zip_filename_extracted) &&
3214 !fileExists(zip_filename_rejected))
3216 char *top_dir = ExtractZipFileIntoDirectory(zip_filename, directory,
3218 char *marker_filename = (top_dir != NULL ? zip_filename_extracted :
3219 zip_filename_rejected);
3222 // create empty file to mark zip file as extracted or rejected
3223 if ((marker_file = fopen(marker_filename, MODE_WRITE)))
3224 fclose(marker_file);
3227 free(zip_filename_extracted);
3228 free(zip_filename_rejected);
3232 closeDirectory(dir);
3235 // forward declaration for recursive call by "LoadLevelInfoFromLevelDir()"
3236 static void LoadLevelInfoFromLevelDir(TreeInfo **, TreeInfo *, char *);
3238 static boolean LoadLevelInfoFromLevelConf(TreeInfo **node_first,
3239 TreeInfo *node_parent,
3240 char *level_directory,
3241 char *directory_name)
3243 char *directory_path = getPath2(level_directory, directory_name);
3244 char *filename = getPath2(directory_path, LEVELINFO_FILENAME);
3245 SetupFileHash *setup_file_hash;
3246 LevelDirTree *leveldir_new = NULL;
3249 // unless debugging, silently ignore directories without "levelinfo.conf"
3250 if (!options.debug && !fileExists(filename))
3252 free(directory_path);
3258 setup_file_hash = loadSetupFileHash(filename);
3260 if (setup_file_hash == NULL)
3262 #if DEBUG_NO_CONFIG_FILE
3263 Error(ERR_WARN, "ignoring level directory '%s'", directory_path);
3266 free(directory_path);
3272 leveldir_new = newTreeInfo();
3275 setTreeInfoToDefaultsFromParent(leveldir_new, node_parent);
3277 setTreeInfoToDefaults(leveldir_new, TREE_TYPE_LEVEL_DIR);
3279 leveldir_new->subdir = getStringCopy(directory_name);
3281 // set all structure fields according to the token/value pairs
3282 ldi = *leveldir_new;
3283 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
3284 setSetupInfo(levelinfo_tokens, i,
3285 getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
3286 *leveldir_new = ldi;
3288 if (strEqual(leveldir_new->name, ANONYMOUS_NAME))
3289 setString(&leveldir_new->name, leveldir_new->subdir);
3291 if (leveldir_new->identifier == NULL)
3292 leveldir_new->identifier = getStringCopy(leveldir_new->subdir);
3294 if (leveldir_new->name_sorting == NULL)
3295 leveldir_new->name_sorting = getStringCopy(leveldir_new->name);
3297 if (node_parent == NULL) // top level group
3299 leveldir_new->basepath = getStringCopy(level_directory);
3300 leveldir_new->fullpath = getStringCopy(leveldir_new->subdir);
3302 else // sub level group
3304 leveldir_new->basepath = getStringCopy(node_parent->basepath);
3305 leveldir_new->fullpath = getPath2(node_parent->fullpath, directory_name);
3308 leveldir_new->last_level =
3309 leveldir_new->first_level + leveldir_new->levels - 1;
3311 leveldir_new->in_user_dir =
3312 (!strEqual(leveldir_new->basepath, options.level_directory));
3314 // adjust some settings if user's private level directory was detected
3315 if (leveldir_new->sort_priority == LEVELCLASS_UNDEFINED &&
3316 leveldir_new->in_user_dir &&
3317 (strEqual(leveldir_new->subdir, getLoginName()) ||
3318 strEqual(leveldir_new->name, getLoginName()) ||
3319 strEqual(leveldir_new->author, getRealName())))
3321 leveldir_new->sort_priority = LEVELCLASS_PRIVATE_START;
3322 leveldir_new->readonly = FALSE;
3325 leveldir_new->user_defined =
3326 (leveldir_new->in_user_dir && IS_LEVELCLASS_PRIVATE(leveldir_new));
3328 leveldir_new->color = LEVELCOLOR(leveldir_new);
3330 setString(&leveldir_new->class_desc, getLevelClassDescription(leveldir_new));
3332 leveldir_new->handicap_level = // set handicap to default value
3333 (leveldir_new->user_defined || !leveldir_new->handicap ?
3334 leveldir_new->last_level : leveldir_new->first_level);
3336 DrawInitText(leveldir_new->name, 150, FC_YELLOW);
3338 pushTreeInfo(node_first, leveldir_new);
3340 freeSetupFileHash(setup_file_hash);
3342 if (leveldir_new->level_group)
3344 // create node to link back to current level directory
3345 createParentTreeInfoNode(leveldir_new);
3347 // recursively step into sub-directory and look for more level series
3348 LoadLevelInfoFromLevelDir(&leveldir_new->node_group,
3349 leveldir_new, directory_path);
3352 free(directory_path);
3358 static void LoadLevelInfoFromLevelDir(TreeInfo **node_first,
3359 TreeInfo *node_parent,
3360 char *level_directory)
3362 // ---------- 1st stage: process any level set zip files ----------
3364 ProcessZipFilesInDirectory(level_directory, TREE_TYPE_LEVEL_DIR);
3366 // ---------- 2nd stage: check for level set directories ----------
3369 DirectoryEntry *dir_entry;
3370 boolean valid_entry_found = FALSE;
3372 if ((dir = openDirectory(level_directory)) == NULL)
3374 Error(ERR_WARN, "cannot read level directory '%s'", level_directory);
3379 while ((dir_entry = readDirectory(dir)) != NULL) // loop all entries
3381 char *directory_name = dir_entry->basename;
3382 char *directory_path = getPath2(level_directory, directory_name);
3384 // skip entries for current and parent directory
3385 if (strEqual(directory_name, ".") ||
3386 strEqual(directory_name, ".."))
3388 free(directory_path);
3393 // find out if directory entry is itself a directory
3394 if (!dir_entry->is_directory) // not a directory
3396 free(directory_path);
3401 free(directory_path);
3403 if (strEqual(directory_name, GRAPHICS_DIRECTORY) ||
3404 strEqual(directory_name, SOUNDS_DIRECTORY) ||
3405 strEqual(directory_name, MUSIC_DIRECTORY))
3408 valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
3413 closeDirectory(dir);
3415 // special case: top level directory may directly contain "levelinfo.conf"
3416 if (node_parent == NULL && !valid_entry_found)
3418 // check if this directory directly contains a file "levelinfo.conf"
3419 valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
3420 level_directory, ".");
3423 if (!valid_entry_found)
3424 Error(ERR_WARN, "cannot find any valid level series in directory '%s'",
3428 boolean AdjustGraphicsForEMC(void)
3430 boolean settings_changed = FALSE;
3432 settings_changed |= adjustTreeGraphicsForEMC(leveldir_first_all);
3433 settings_changed |= adjustTreeGraphicsForEMC(leveldir_first);
3435 return settings_changed;
3438 void LoadLevelInfo(void)
3440 InitUserLevelDirectory(getLoginName());
3442 DrawInitText("Loading level series", 120, FC_GREEN);
3444 LoadLevelInfoFromLevelDir(&leveldir_first, NULL, options.level_directory);
3445 LoadLevelInfoFromLevelDir(&leveldir_first, NULL, getUserLevelDir(NULL));
3447 leveldir_first = createTopTreeInfoNode(leveldir_first);
3449 /* after loading all level set information, clone the level directory tree
3450 and remove all level sets without levels (these may still contain artwork
3451 to be offered in the setup menu as "custom artwork", and are therefore
3452 checked for existing artwork in the function "LoadLevelArtworkInfo()") */
3453 leveldir_first_all = leveldir_first;
3454 cloneTree(&leveldir_first, leveldir_first_all, TRUE);
3456 AdjustGraphicsForEMC();
3458 // before sorting, the first entries will be from the user directory
3459 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
3461 if (leveldir_first == NULL)
3462 Error(ERR_EXIT, "cannot find any valid level series in any directory");
3464 sortTreeInfo(&leveldir_first);
3466 #if ENABLE_UNUSED_CODE
3467 dumpTreeInfo(leveldir_first, 0);
3471 static boolean LoadArtworkInfoFromArtworkConf(TreeInfo **node_first,
3472 TreeInfo *node_parent,
3473 char *base_directory,
3474 char *directory_name, int type)
3476 char *directory_path = getPath2(base_directory, directory_name);
3477 char *filename = getPath2(directory_path, ARTWORKINFO_FILENAME(type));
3478 SetupFileHash *setup_file_hash = NULL;
3479 TreeInfo *artwork_new = NULL;
3482 if (fileExists(filename))
3483 setup_file_hash = loadSetupFileHash(filename);
3485 if (setup_file_hash == NULL) // no config file -- look for artwork files
3488 DirectoryEntry *dir_entry;
3489 boolean valid_file_found = FALSE;
3491 if ((dir = openDirectory(directory_path)) != NULL)
3493 while ((dir_entry = readDirectory(dir)) != NULL)
3495 if (FileIsArtworkType(dir_entry->filename, type))
3497 valid_file_found = TRUE;
3503 closeDirectory(dir);
3506 if (!valid_file_found)
3508 #if DEBUG_NO_CONFIG_FILE
3509 if (!strEqual(directory_name, "."))
3510 Error(ERR_WARN, "ignoring artwork directory '%s'", directory_path);
3513 free(directory_path);
3520 artwork_new = newTreeInfo();
3523 setTreeInfoToDefaultsFromParent(artwork_new, node_parent);
3525 setTreeInfoToDefaults(artwork_new, type);
3527 artwork_new->subdir = getStringCopy(directory_name);
3529 if (setup_file_hash) // (before defining ".color" and ".class_desc")
3531 // set all structure fields according to the token/value pairs
3533 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
3534 setSetupInfo(levelinfo_tokens, i,
3535 getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
3538 if (strEqual(artwork_new->name, ANONYMOUS_NAME))
3539 setString(&artwork_new->name, artwork_new->subdir);
3541 if (artwork_new->identifier == NULL)
3542 artwork_new->identifier = getStringCopy(artwork_new->subdir);
3544 if (artwork_new->name_sorting == NULL)
3545 artwork_new->name_sorting = getStringCopy(artwork_new->name);
3548 if (node_parent == NULL) // top level group
3550 artwork_new->basepath = getStringCopy(base_directory);
3551 artwork_new->fullpath = getStringCopy(artwork_new->subdir);
3553 else // sub level group
3555 artwork_new->basepath = getStringCopy(node_parent->basepath);
3556 artwork_new->fullpath = getPath2(node_parent->fullpath, directory_name);
3559 artwork_new->in_user_dir =
3560 (!strEqual(artwork_new->basepath, OPTIONS_ARTWORK_DIRECTORY(type)));
3562 // (may use ".sort_priority" from "setup_file_hash" above)
3563 artwork_new->color = ARTWORKCOLOR(artwork_new);
3565 setString(&artwork_new->class_desc, getLevelClassDescription(artwork_new));
3567 if (setup_file_hash == NULL) // (after determining ".user_defined")
3569 if (strEqual(artwork_new->subdir, "."))
3571 if (artwork_new->user_defined)
3573 setString(&artwork_new->identifier, "private");
3574 artwork_new->sort_priority = ARTWORKCLASS_PRIVATE;
3578 setString(&artwork_new->identifier, "classic");
3579 artwork_new->sort_priority = ARTWORKCLASS_CLASSICS;
3582 // set to new values after changing ".sort_priority"
3583 artwork_new->color = ARTWORKCOLOR(artwork_new);
3585 setString(&artwork_new->class_desc,
3586 getLevelClassDescription(artwork_new));
3590 setString(&artwork_new->identifier, artwork_new->subdir);
3593 setString(&artwork_new->name, artwork_new->identifier);
3594 setString(&artwork_new->name_sorting, artwork_new->name);
3597 pushTreeInfo(node_first, artwork_new);
3599 freeSetupFileHash(setup_file_hash);
3601 free(directory_path);
3607 static void LoadArtworkInfoFromArtworkDir(TreeInfo **node_first,
3608 TreeInfo *node_parent,
3609 char *base_directory, int type)
3611 // ---------- 1st stage: process any artwork set zip files ----------
3613 ProcessZipFilesInDirectory(base_directory, type);
3615 // ---------- 2nd stage: check for artwork set directories ----------
3618 DirectoryEntry *dir_entry;
3619 boolean valid_entry_found = FALSE;
3621 if ((dir = openDirectory(base_directory)) == NULL)
3623 // display error if directory is main "options.graphics_directory" etc.
3624 if (base_directory == OPTIONS_ARTWORK_DIRECTORY(type))
3625 Error(ERR_WARN, "cannot read directory '%s'", base_directory);
3630 while ((dir_entry = readDirectory(dir)) != NULL) // loop all entries
3632 char *directory_name = dir_entry->basename;
3633 char *directory_path = getPath2(base_directory, directory_name);
3635 // skip directory entries for current and parent directory
3636 if (strEqual(directory_name, ".") ||
3637 strEqual(directory_name, ".."))
3639 free(directory_path);
3644 // skip directory entries which are not a directory
3645 if (!dir_entry->is_directory) // not a directory
3647 free(directory_path);
3652 free(directory_path);
3654 // check if this directory contains artwork with or without config file
3655 valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
3657 directory_name, type);
3660 closeDirectory(dir);
3662 // check if this directory directly contains artwork itself
3663 valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
3664 base_directory, ".",
3666 if (!valid_entry_found)
3667 Error(ERR_WARN, "cannot find any valid artwork in directory '%s'",
3671 static TreeInfo *getDummyArtworkInfo(int type)
3673 // this is only needed when there is completely no artwork available
3674 TreeInfo *artwork_new = newTreeInfo();
3676 setTreeInfoToDefaults(artwork_new, type);
3678 setString(&artwork_new->subdir, UNDEFINED_FILENAME);
3679 setString(&artwork_new->fullpath, UNDEFINED_FILENAME);
3680 setString(&artwork_new->basepath, UNDEFINED_FILENAME);
3682 setString(&artwork_new->identifier, UNDEFINED_FILENAME);
3683 setString(&artwork_new->name, UNDEFINED_FILENAME);
3684 setString(&artwork_new->name_sorting, UNDEFINED_FILENAME);
3689 void LoadArtworkInfo(void)
3691 LoadArtworkInfoCache();
3693 DrawInitText("Looking for custom artwork", 120, FC_GREEN);
3695 LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
3696 options.graphics_directory,
3697 TREE_TYPE_GRAPHICS_DIR);
3698 LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
3699 getUserGraphicsDir(),
3700 TREE_TYPE_GRAPHICS_DIR);
3702 LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
3703 options.sounds_directory,
3704 TREE_TYPE_SOUNDS_DIR);
3705 LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
3707 TREE_TYPE_SOUNDS_DIR);
3709 LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
3710 options.music_directory,
3711 TREE_TYPE_MUSIC_DIR);
3712 LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
3714 TREE_TYPE_MUSIC_DIR);
3716 if (artwork.gfx_first == NULL)
3717 artwork.gfx_first = getDummyArtworkInfo(TREE_TYPE_GRAPHICS_DIR);
3718 if (artwork.snd_first == NULL)
3719 artwork.snd_first = getDummyArtworkInfo(TREE_TYPE_SOUNDS_DIR);
3720 if (artwork.mus_first == NULL)
3721 artwork.mus_first = getDummyArtworkInfo(TREE_TYPE_MUSIC_DIR);
3723 // before sorting, the first entries will be from the user directory
3724 artwork.gfx_current =
3725 getTreeInfoFromIdentifier(artwork.gfx_first, setup.graphics_set);
3726 if (artwork.gfx_current == NULL)
3727 artwork.gfx_current =
3728 getTreeInfoFromIdentifier(artwork.gfx_first, GFX_DEFAULT_SUBDIR);
3729 if (artwork.gfx_current == NULL)
3730 artwork.gfx_current = getFirstValidTreeInfoEntry(artwork.gfx_first);
3732 artwork.snd_current =
3733 getTreeInfoFromIdentifier(artwork.snd_first, setup.sounds_set);
3734 if (artwork.snd_current == NULL)
3735 artwork.snd_current =
3736 getTreeInfoFromIdentifier(artwork.snd_first, SND_DEFAULT_SUBDIR);
3737 if (artwork.snd_current == NULL)
3738 artwork.snd_current = getFirstValidTreeInfoEntry(artwork.snd_first);
3740 artwork.mus_current =
3741 getTreeInfoFromIdentifier(artwork.mus_first, setup.music_set);
3742 if (artwork.mus_current == NULL)
3743 artwork.mus_current =
3744 getTreeInfoFromIdentifier(artwork.mus_first, MUS_DEFAULT_SUBDIR);
3745 if (artwork.mus_current == NULL)
3746 artwork.mus_current = getFirstValidTreeInfoEntry(artwork.mus_first);
3748 artwork.gfx_current_identifier = artwork.gfx_current->identifier;
3749 artwork.snd_current_identifier = artwork.snd_current->identifier;
3750 artwork.mus_current_identifier = artwork.mus_current->identifier;
3752 #if ENABLE_UNUSED_CODE
3753 printf("graphics set == %s\n\n", artwork.gfx_current_identifier);
3754 printf("sounds set == %s\n\n", artwork.snd_current_identifier);
3755 printf("music set == %s\n\n", artwork.mus_current_identifier);
3758 sortTreeInfo(&artwork.gfx_first);
3759 sortTreeInfo(&artwork.snd_first);
3760 sortTreeInfo(&artwork.mus_first);
3762 #if ENABLE_UNUSED_CODE
3763 dumpTreeInfo(artwork.gfx_first, 0);
3764 dumpTreeInfo(artwork.snd_first, 0);
3765 dumpTreeInfo(artwork.mus_first, 0);
3769 static void LoadArtworkInfoFromLevelInfo(ArtworkDirTree **artwork_node,
3770 LevelDirTree *level_node)
3772 int type = (*artwork_node)->type;
3774 // recursively check all level directories for artwork sub-directories
3778 // check all tree entries for artwork, but skip parent link entries
3779 if (!level_node->parent_link)
3781 TreeInfo *artwork_new = getArtworkInfoCacheEntry(level_node, type);
3782 boolean cached = (artwork_new != NULL);
3786 pushTreeInfo(artwork_node, artwork_new);
3790 TreeInfo *topnode_last = *artwork_node;
3791 char *path = getPath2(getLevelDirFromTreeInfo(level_node),
3792 ARTWORK_DIRECTORY(type));
3794 LoadArtworkInfoFromArtworkDir(artwork_node, NULL, path, type);
3796 if (topnode_last != *artwork_node) // check for newly added node
3798 artwork_new = *artwork_node;
3800 setString(&artwork_new->identifier, level_node->subdir);
3801 setString(&artwork_new->name, level_node->name);
3802 setString(&artwork_new->name_sorting, level_node->name_sorting);
3804 artwork_new->sort_priority = level_node->sort_priority;
3805 artwork_new->color = LEVELCOLOR(artwork_new);
3811 // insert artwork info (from old cache or filesystem) into new cache
3812 if (artwork_new != NULL)
3813 setArtworkInfoCacheEntry(artwork_new, level_node, type);
3816 DrawInitText(level_node->name, 150, FC_YELLOW);
3818 if (level_node->node_group != NULL)
3819 LoadArtworkInfoFromLevelInfo(artwork_node, level_node->node_group);
3821 level_node = level_node->next;
3825 void LoadLevelArtworkInfo(void)
3827 print_timestamp_init("LoadLevelArtworkInfo");
3829 DrawInitText("Looking for custom level artwork", 120, FC_GREEN);
3831 print_timestamp_time("DrawTimeText");
3833 LoadArtworkInfoFromLevelInfo(&artwork.gfx_first, leveldir_first_all);
3834 print_timestamp_time("LoadArtworkInfoFromLevelInfo (gfx)");
3835 LoadArtworkInfoFromLevelInfo(&artwork.snd_first, leveldir_first_all);
3836 print_timestamp_time("LoadArtworkInfoFromLevelInfo (snd)");
3837 LoadArtworkInfoFromLevelInfo(&artwork.mus_first, leveldir_first_all);
3838 print_timestamp_time("LoadArtworkInfoFromLevelInfo (mus)");
3840 SaveArtworkInfoCache();
3842 print_timestamp_time("SaveArtworkInfoCache");
3844 // needed for reloading level artwork not known at ealier stage
3846 if (!strEqual(artwork.gfx_current_identifier, setup.graphics_set))
3848 artwork.gfx_current =
3849 getTreeInfoFromIdentifier(artwork.gfx_first, setup.graphics_set);
3850 if (artwork.gfx_current == NULL)
3851 artwork.gfx_current =
3852 getTreeInfoFromIdentifier(artwork.gfx_first, GFX_DEFAULT_SUBDIR);
3853 if (artwork.gfx_current == NULL)
3854 artwork.gfx_current = getFirstValidTreeInfoEntry(artwork.gfx_first);
3857 if (!strEqual(artwork.snd_current_identifier, setup.sounds_set))
3859 artwork.snd_current =
3860 getTreeInfoFromIdentifier(artwork.snd_first, setup.sounds_set);
3861 if (artwork.snd_current == NULL)
3862 artwork.snd_current =
3863 getTreeInfoFromIdentifier(artwork.snd_first, SND_DEFAULT_SUBDIR);
3864 if (artwork.snd_current == NULL)
3865 artwork.snd_current = getFirstValidTreeInfoEntry(artwork.snd_first);
3868 if (!strEqual(artwork.mus_current_identifier, setup.music_set))
3870 artwork.mus_current =
3871 getTreeInfoFromIdentifier(artwork.mus_first, setup.music_set);
3872 if (artwork.mus_current == NULL)
3873 artwork.mus_current =
3874 getTreeInfoFromIdentifier(artwork.mus_first, MUS_DEFAULT_SUBDIR);
3875 if (artwork.mus_current == NULL)
3876 artwork.mus_current = getFirstValidTreeInfoEntry(artwork.mus_first);
3879 print_timestamp_time("getTreeInfoFromIdentifier");
3881 sortTreeInfo(&artwork.gfx_first);
3882 sortTreeInfo(&artwork.snd_first);
3883 sortTreeInfo(&artwork.mus_first);
3885 print_timestamp_time("sortTreeInfo");
3887 #if ENABLE_UNUSED_CODE
3888 dumpTreeInfo(artwork.gfx_first, 0);
3889 dumpTreeInfo(artwork.snd_first, 0);
3890 dumpTreeInfo(artwork.mus_first, 0);
3893 print_timestamp_done("LoadLevelArtworkInfo");
3896 static boolean AddTreeSetToTreeInfoExt(TreeInfo *tree_node_old, char *tree_dir,
3897 char *tree_subdir_new, int type)
3899 if (tree_node_old == NULL)
3901 if (type == TREE_TYPE_LEVEL_DIR)
3903 // get level info tree node of personal user level set
3904 tree_node_old = getTreeInfoFromIdentifier(leveldir_first, getLoginName());
3906 // this may happen if "setup.internal.create_user_levelset" is FALSE
3907 // or if file "levelinfo.conf" is missing in personal user level set
3908 if (tree_node_old == NULL)
3909 tree_node_old = leveldir_first->node_group;
3913 // get artwork info tree node of first artwork set
3914 tree_node_old = ARTWORK_FIRST_NODE(artwork, type);
3918 if (tree_dir == NULL)
3919 tree_dir = TREE_USERDIR(type);
3921 if (tree_node_old == NULL ||
3923 tree_subdir_new == NULL) // should not happen
3926 int draw_deactivation_mask = GetDrawDeactivationMask();
3928 // override draw deactivation mask (temporarily disable drawing)
3929 SetDrawDeactivationMask(REDRAW_ALL);
3931 if (type == TREE_TYPE_LEVEL_DIR)
3933 // load new level set config and add it next to first user level set
3934 LoadLevelInfoFromLevelConf(&tree_node_old->next,
3935 tree_node_old->node_parent,
3936 tree_dir, tree_subdir_new);
3940 // load new artwork set config and add it next to first artwork set
3941 LoadArtworkInfoFromArtworkConf(&tree_node_old->next,
3942 tree_node_old->node_parent,
3943 tree_dir, tree_subdir_new, type);
3946 // set draw deactivation mask to previous value
3947 SetDrawDeactivationMask(draw_deactivation_mask);
3949 // get first node of level or artwork info tree
3950 TreeInfo **tree_node_first = TREE_FIRST_NODE_PTR(type);
3952 // get tree info node of newly added level or artwork set
3953 TreeInfo *tree_node_new = getTreeInfoFromIdentifier(*tree_node_first,
3956 if (tree_node_new == NULL) // should not happen
3959 // correct top link and parent node link of newly created tree node
3960 tree_node_new->node_top = tree_node_old->node_top;
3961 tree_node_new->node_parent = tree_node_old->node_parent;
3963 // sort tree info to adjust position of newly added tree set
3964 sortTreeInfo(tree_node_first);
3969 void AddTreeSetToTreeInfo(TreeInfo *tree_node, char *tree_dir,
3970 char *tree_subdir_new, int type)
3972 if (!AddTreeSetToTreeInfoExt(tree_node, tree_dir, tree_subdir_new, type))
3973 Error(ERR_EXIT, "internal tree info structure corrupted -- aborting");
3976 void AddUserLevelSetToLevelInfo(char *level_subdir_new)
3978 AddTreeSetToTreeInfo(NULL, NULL, level_subdir_new, TREE_TYPE_LEVEL_DIR);
3981 char *getArtworkIdentifierForUserLevelSet(int type)
3983 char *classic_artwork_set = getClassicArtworkSet(type);
3985 // check for custom artwork configured in "levelinfo.conf"
3986 char *leveldir_artwork_set =
3987 *LEVELDIR_ARTWORK_SET_PTR(leveldir_current, type);
3988 boolean has_leveldir_artwork_set =
3989 (leveldir_artwork_set != NULL && !strEqual(leveldir_artwork_set,
3990 classic_artwork_set));
3992 // check for custom artwork in sub-directory "graphics" etc.
3993 TreeInfo *artwork_first_node = ARTWORK_FIRST_NODE(artwork, type);
3994 char *leveldir_identifier = leveldir_current->identifier;
3995 boolean has_artwork_subdir =
3996 (getTreeInfoFromIdentifier(artwork_first_node,
3997 leveldir_identifier) != NULL);
3999 return (has_leveldir_artwork_set ? leveldir_artwork_set :
4000 has_artwork_subdir ? leveldir_identifier :
4001 classic_artwork_set);
4004 TreeInfo *getArtworkTreeInfoForUserLevelSet(int type)
4006 char *artwork_set = getArtworkIdentifierForUserLevelSet(type);
4007 TreeInfo *artwork_first_node = ARTWORK_FIRST_NODE(artwork, type);
4008 TreeInfo *ti = getTreeInfoFromIdentifier(artwork_first_node, artwork_set);
4012 ti = getTreeInfoFromIdentifier(artwork_first_node,
4013 ARTWORK_DEFAULT_SUBDIR(type));
4015 Error(ERR_EXIT, "cannot find default graphics -- should not happen");
4021 boolean checkIfCustomArtworkExistsForCurrentLevelSet(void)
4023 char *graphics_set =
4024 getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_GRAPHICS);
4026 getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_SOUNDS);
4028 getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_MUSIC);
4030 return (!strEqual(graphics_set, GFX_CLASSIC_SUBDIR) ||
4031 !strEqual(sounds_set, SND_CLASSIC_SUBDIR) ||
4032 !strEqual(music_set, MUS_CLASSIC_SUBDIR));
4035 boolean UpdateUserLevelSet(char *level_subdir, char *level_name,
4036 char *level_author, int num_levels)
4038 char *filename = getPath2(getUserLevelDir(level_subdir), LEVELINFO_FILENAME);
4039 char *filename_tmp = getStringCat2(filename, ".tmp");
4041 FILE *file_tmp = NULL;
4042 char line[MAX_LINE_LEN];
4043 boolean success = FALSE;
4044 LevelDirTree *leveldir = getTreeInfoFromIdentifier(leveldir_first,
4046 // update values in level directory tree
4048 if (level_name != NULL)
4049 setString(&leveldir->name, level_name);
4051 if (level_author != NULL)
4052 setString(&leveldir->author, level_author);
4054 if (num_levels != -1)
4055 leveldir->levels = num_levels;
4057 // update values that depend on other values
4059 setString(&leveldir->name_sorting, leveldir->name);
4061 leveldir->last_level = leveldir->first_level + leveldir->levels - 1;
4063 // sort order of level sets may have changed
4064 sortTreeInfo(&leveldir_first);
4066 if ((file = fopen(filename, MODE_READ)) &&
4067 (file_tmp = fopen(filename_tmp, MODE_WRITE)))
4069 while (fgets(line, MAX_LINE_LEN, file))
4071 if (strPrefix(line, "name:") && level_name != NULL)
4072 fprintf(file_tmp, "%-32s%s\n", "name:", level_name);
4073 else if (strPrefix(line, "author:") && level_author != NULL)
4074 fprintf(file_tmp, "%-32s%s\n", "author:", level_author);
4075 else if (strPrefix(line, "levels:") && num_levels != -1)
4076 fprintf(file_tmp, "%-32s%d\n", "levels:", num_levels);
4078 fputs(line, file_tmp);
4091 success = (rename(filename_tmp, filename) == 0);
4099 boolean CreateUserLevelSet(char *level_subdir, char *level_name,
4100 char *level_author, int num_levels,
4101 boolean use_artwork_set)
4103 LevelDirTree *level_info;
4108 // create user level sub-directory, if needed
4109 createDirectory(getUserLevelDir(level_subdir), "user level", PERMS_PRIVATE);
4111 filename = getPath2(getUserLevelDir(level_subdir), LEVELINFO_FILENAME);
4113 if (!(file = fopen(filename, MODE_WRITE)))
4115 Error(ERR_WARN, "cannot write level info file '%s'", filename);
4121 level_info = newTreeInfo();
4123 // always start with reliable default values
4124 setTreeInfoToDefaults(level_info, TREE_TYPE_LEVEL_DIR);
4126 setString(&level_info->name, level_name);
4127 setString(&level_info->author, level_author);
4128 level_info->levels = num_levels;
4129 level_info->first_level = 1;
4130 level_info->sort_priority = LEVELCLASS_PRIVATE_START;
4131 level_info->readonly = FALSE;
4133 if (use_artwork_set)
4135 level_info->graphics_set =
4136 getStringCopy(getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_GRAPHICS));
4137 level_info->sounds_set =
4138 getStringCopy(getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_SOUNDS));
4139 level_info->music_set =
4140 getStringCopy(getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_MUSIC));
4143 token_value_position = TOKEN_VALUE_POSITION_SHORT;
4145 fprintFileHeader(file, LEVELINFO_FILENAME);
4148 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
4150 if (i == LEVELINFO_TOKEN_NAME ||
4151 i == LEVELINFO_TOKEN_AUTHOR ||
4152 i == LEVELINFO_TOKEN_LEVELS ||
4153 i == LEVELINFO_TOKEN_FIRST_LEVEL ||
4154 i == LEVELINFO_TOKEN_SORT_PRIORITY ||
4155 i == LEVELINFO_TOKEN_READONLY ||
4156 (use_artwork_set && (i == LEVELINFO_TOKEN_GRAPHICS_SET ||
4157 i == LEVELINFO_TOKEN_SOUNDS_SET ||
4158 i == LEVELINFO_TOKEN_MUSIC_SET)))
4159 fprintf(file, "%s\n", getSetupLine(levelinfo_tokens, "", i));
4161 // just to make things nicer :)
4162 if (i == LEVELINFO_TOKEN_AUTHOR ||
4163 i == LEVELINFO_TOKEN_FIRST_LEVEL ||
4164 (use_artwork_set && i == LEVELINFO_TOKEN_READONLY))
4165 fprintf(file, "\n");
4168 token_value_position = TOKEN_VALUE_POSITION_DEFAULT;
4172 SetFilePermissions(filename, PERMS_PRIVATE);
4174 freeTreeInfo(level_info);
4180 static void SaveUserLevelInfo(void)
4182 CreateUserLevelSet(getLoginName(), getLoginName(), getRealName(), 100, FALSE);
4185 char *getSetupValue(int type, void *value)
4187 static char value_string[MAX_LINE_LEN];
4195 strcpy(value_string, (*(boolean *)value ? "true" : "false"));
4199 strcpy(value_string, (*(boolean *)value ? "on" : "off"));
4203 strcpy(value_string, (*(int *)value == AUTO ? "auto" :
4204 *(int *)value == FALSE ? "off" : "on"));
4208 strcpy(value_string, (*(boolean *)value ? "yes" : "no"));
4211 case TYPE_YES_NO_AUTO:
4212 strcpy(value_string, (*(int *)value == AUTO ? "auto" :
4213 *(int *)value == FALSE ? "no" : "yes"));
4217 strcpy(value_string, (*(boolean *)value ? "AGA" : "ECS"));
4221 strcpy(value_string, getKeyNameFromKey(*(Key *)value));
4225 strcpy(value_string, getX11KeyNameFromKey(*(Key *)value));
4229 sprintf(value_string, "%d", *(int *)value);
4233 if (*(char **)value == NULL)
4236 strcpy(value_string, *(char **)value);
4240 sprintf(value_string, "player_%d", *(int *)value + 1);
4244 value_string[0] = '\0';
4248 if (type & TYPE_GHOSTED)
4249 strcpy(value_string, "n/a");
4251 return value_string;
4254 char *getSetupLine(struct TokenInfo *token_info, char *prefix, int token_nr)
4258 static char token_string[MAX_LINE_LEN];
4259 int token_type = token_info[token_nr].type;
4260 void *setup_value = token_info[token_nr].value;
4261 char *token_text = token_info[token_nr].text;
4262 char *value_string = getSetupValue(token_type, setup_value);
4264 // build complete token string
4265 sprintf(token_string, "%s%s", prefix, token_text);
4267 // build setup entry line
4268 line = getFormattedSetupEntry(token_string, value_string);
4270 if (token_type == TYPE_KEY_X11)
4272 Key key = *(Key *)setup_value;
4273 char *keyname = getKeyNameFromKey(key);
4275 // add comment, if useful
4276 if (!strEqual(keyname, "(undefined)") &&
4277 !strEqual(keyname, "(unknown)"))
4279 // add at least one whitespace
4281 for (i = strlen(line); i < token_comment_position; i++)
4285 strcat(line, keyname);
4292 void LoadLevelSetup_LastSeries(void)
4294 // --------------------------------------------------------------------------
4295 // ~/.<program>/levelsetup.conf
4296 // --------------------------------------------------------------------------
4298 char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
4299 SetupFileHash *level_setup_hash = NULL;
4301 // always start with reliable default values
4302 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
4304 if (!strEqual(DEFAULT_LEVELSET, UNDEFINED_LEVELSET))
4306 leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
4308 if (leveldir_current == NULL)
4309 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
4312 if ((level_setup_hash = loadSetupFileHash(filename)))
4314 char *last_level_series =
4315 getHashEntry(level_setup_hash, TOKEN_STR_LAST_LEVEL_SERIES);
4317 leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
4319 if (leveldir_current == NULL)
4320 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
4322 freeSetupFileHash(level_setup_hash);
4326 Error(ERR_DEBUG, "using default setup values");
4332 static void SaveLevelSetup_LastSeries_Ext(boolean deactivate_last_level_series)
4334 // --------------------------------------------------------------------------
4335 // ~/.<program>/levelsetup.conf
4336 // --------------------------------------------------------------------------
4338 // check if the current level directory structure is available at this point
4339 if (leveldir_current == NULL)
4342 char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
4343 char *level_subdir = leveldir_current->subdir;
4346 InitUserDataDirectory();
4348 if (!(file = fopen(filename, MODE_WRITE)))
4350 Error(ERR_WARN, "cannot write setup file '%s'", filename);
4357 fprintFileHeader(file, LEVELSETUP_FILENAME);
4359 if (deactivate_last_level_series)
4360 fprintf(file, "# %s\n# ", "the following level set may have caused a problem and was deactivated");
4362 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_LEVEL_SERIES,
4367 SetFilePermissions(filename, PERMS_PRIVATE);
4372 void SaveLevelSetup_LastSeries(void)
4374 SaveLevelSetup_LastSeries_Ext(FALSE);
4377 void SaveLevelSetup_LastSeries_Deactivate(void)
4379 SaveLevelSetup_LastSeries_Ext(TRUE);
4382 static void checkSeriesInfo(void)
4384 static char *level_directory = NULL;
4387 DirectoryEntry *dir_entry;
4390 checked_free(level_directory);
4392 // check for more levels besides the 'levels' field of 'levelinfo.conf'
4394 level_directory = getPath2((leveldir_current->in_user_dir ?
4395 getUserLevelDir(NULL) :
4396 options.level_directory),
4397 leveldir_current->fullpath);
4399 if ((dir = openDirectory(level_directory)) == NULL)
4401 Error(ERR_WARN, "cannot read level directory '%s'", level_directory);
4407 while ((dir_entry = readDirectory(dir)) != NULL) // loop all entries
4409 if (strlen(dir_entry->basename) > 4 &&
4410 dir_entry->basename[3] == '.' &&
4411 strEqual(&dir_entry->basename[4], LEVELFILE_EXTENSION))
4413 char levelnum_str[4];
4416 strncpy(levelnum_str, dir_entry->basename, 3);
4417 levelnum_str[3] = '\0';
4419 levelnum_value = atoi(levelnum_str);
4421 if (levelnum_value < leveldir_current->first_level)
4423 Error(ERR_WARN, "additional level %d found", levelnum_value);
4424 leveldir_current->first_level = levelnum_value;
4426 else if (levelnum_value > leveldir_current->last_level)
4428 Error(ERR_WARN, "additional level %d found", levelnum_value);
4429 leveldir_current->last_level = levelnum_value;
4435 closeDirectory(dir);
4438 void LoadLevelSetup_SeriesInfo(void)
4441 SetupFileHash *level_setup_hash = NULL;
4442 char *level_subdir = leveldir_current->subdir;
4445 // always start with reliable default values
4446 level_nr = leveldir_current->first_level;
4448 for (i = 0; i < MAX_LEVELS; i++)
4450 LevelStats_setPlayed(i, 0);
4451 LevelStats_setSolved(i, 0);
4456 // --------------------------------------------------------------------------
4457 // ~/.<program>/levelsetup/<level series>/levelsetup.conf
4458 // --------------------------------------------------------------------------
4460 level_subdir = leveldir_current->subdir;
4462 filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
4464 if ((level_setup_hash = loadSetupFileHash(filename)))
4468 // get last played level in this level set
4470 token_value = getHashEntry(level_setup_hash, TOKEN_STR_LAST_PLAYED_LEVEL);
4474 level_nr = atoi(token_value);
4476 if (level_nr < leveldir_current->first_level)
4477 level_nr = leveldir_current->first_level;
4478 if (level_nr > leveldir_current->last_level)
4479 level_nr = leveldir_current->last_level;
4482 // get handicap level in this level set
4484 token_value = getHashEntry(level_setup_hash, TOKEN_STR_HANDICAP_LEVEL);
4488 int level_nr = atoi(token_value);
4490 if (level_nr < leveldir_current->first_level)
4491 level_nr = leveldir_current->first_level;
4492 if (level_nr > leveldir_current->last_level + 1)
4493 level_nr = leveldir_current->last_level;
4495 if (leveldir_current->user_defined || !leveldir_current->handicap)
4496 level_nr = leveldir_current->last_level;
4498 leveldir_current->handicap_level = level_nr;
4501 // get number of played and solved levels in this level set
4503 BEGIN_HASH_ITERATION(level_setup_hash, itr)
4505 char *token = HASH_ITERATION_TOKEN(itr);
4506 char *value = HASH_ITERATION_VALUE(itr);
4508 if (strlen(token) == 3 &&
4509 token[0] >= '0' && token[0] <= '9' &&
4510 token[1] >= '0' && token[1] <= '9' &&
4511 token[2] >= '0' && token[2] <= '9')
4513 int level_nr = atoi(token);
4516 LevelStats_setPlayed(level_nr, atoi(value)); // read 1st column
4518 value = strchr(value, ' ');
4521 LevelStats_setSolved(level_nr, atoi(value)); // read 2nd column
4524 END_HASH_ITERATION(hash, itr)
4526 freeSetupFileHash(level_setup_hash);
4530 Error(ERR_DEBUG, "using default setup values");
4536 void SaveLevelSetup_SeriesInfo(void)
4539 char *level_subdir = leveldir_current->subdir;
4540 char *level_nr_str = int2str(level_nr, 0);
4541 char *handicap_level_str = int2str(leveldir_current->handicap_level, 0);
4545 // --------------------------------------------------------------------------
4546 // ~/.<program>/levelsetup/<level series>/levelsetup.conf
4547 // --------------------------------------------------------------------------
4549 InitLevelSetupDirectory(level_subdir);
4551 filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
4553 if (!(file = fopen(filename, MODE_WRITE)))
4555 Error(ERR_WARN, "cannot write setup file '%s'", filename);
4560 fprintFileHeader(file, LEVELSETUP_FILENAME);
4562 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_PLAYED_LEVEL,
4564 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_HANDICAP_LEVEL,
4565 handicap_level_str));
4567 for (i = leveldir_current->first_level; i <= leveldir_current->last_level;
4570 if (LevelStats_getPlayed(i) > 0 ||
4571 LevelStats_getSolved(i) > 0)
4576 sprintf(token, "%03d", i);
4577 sprintf(value, "%d %d", LevelStats_getPlayed(i), LevelStats_getSolved(i));
4579 fprintf(file, "%s\n", getFormattedSetupEntry(token, value));
4585 SetFilePermissions(filename, PERMS_PRIVATE);
4590 int LevelStats_getPlayed(int nr)
4592 return (nr >= 0 && nr < MAX_LEVELS ? level_stats[nr].played : 0);
4595 int LevelStats_getSolved(int nr)
4597 return (nr >= 0 && nr < MAX_LEVELS ? level_stats[nr].solved : 0);
4600 void LevelStats_setPlayed(int nr, int value)
4602 if (nr >= 0 && nr < MAX_LEVELS)
4603 level_stats[nr].played = value;
4606 void LevelStats_setSolved(int nr, int value)
4608 if (nr >= 0 && nr < MAX_LEVELS)
4609 level_stats[nr].solved = value;
4612 void LevelStats_incPlayed(int nr)
4614 if (nr >= 0 && nr < MAX_LEVELS)
4615 level_stats[nr].played++;
4618 void LevelStats_incSolved(int nr)
4620 if (nr >= 0 && nr < MAX_LEVELS)
4621 level_stats[nr].solved++;