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>
33 #define ENABLE_UNUSED_CODE FALSE /* for currently unused functions */
35 #define NUM_LEVELCLASS_DESC 8
37 static char *levelclass_desc[NUM_LEVELCLASS_DESC] =
50 #define LEVELCOLOR(n) (IS_LEVELCLASS_TUTORIAL(n) ? FC_BLUE : \
51 IS_LEVELCLASS_CLASSICS(n) ? FC_RED : \
52 IS_LEVELCLASS_BD(n) ? FC_YELLOW : \
53 IS_LEVELCLASS_EM(n) ? FC_YELLOW : \
54 IS_LEVELCLASS_SP(n) ? FC_YELLOW : \
55 IS_LEVELCLASS_DX(n) ? FC_YELLOW : \
56 IS_LEVELCLASS_SB(n) ? FC_YELLOW : \
57 IS_LEVELCLASS_CONTRIB(n) ? FC_GREEN : \
58 IS_LEVELCLASS_PRIVATE(n) ? FC_RED : \
61 #define LEVELSORTING(n) (IS_LEVELCLASS_TUTORIAL(n) ? 0 : \
62 IS_LEVELCLASS_CLASSICS(n) ? 1 : \
63 IS_LEVELCLASS_BD(n) ? 2 : \
64 IS_LEVELCLASS_EM(n) ? 3 : \
65 IS_LEVELCLASS_SP(n) ? 4 : \
66 IS_LEVELCLASS_DX(n) ? 5 : \
67 IS_LEVELCLASS_SB(n) ? 6 : \
68 IS_LEVELCLASS_CONTRIB(n) ? 7 : \
69 IS_LEVELCLASS_PRIVATE(n) ? 8 : \
72 #define ARTWORKCOLOR(n) (IS_ARTWORKCLASS_CLASSICS(n) ? FC_RED : \
73 IS_ARTWORKCLASS_CONTRIB(n) ? FC_GREEN : \
74 IS_ARTWORKCLASS_PRIVATE(n) ? FC_RED : \
75 IS_ARTWORKCLASS_LEVEL(n) ? FC_YELLOW : \
78 #define ARTWORKSORTING(n) (IS_ARTWORKCLASS_CLASSICS(n) ? 0 : \
79 IS_ARTWORKCLASS_LEVEL(n) ? 1 : \
80 IS_ARTWORKCLASS_CONTRIB(n) ? 2 : \
81 IS_ARTWORKCLASS_PRIVATE(n) ? 3 : \
84 #define TOKEN_VALUE_POSITION_SHORT 32
85 #define TOKEN_VALUE_POSITION_DEFAULT 40
86 #define TOKEN_COMMENT_POSITION_DEFAULT 60
88 #define MAX_COOKIE_LEN 256
91 static void setTreeInfoToDefaults(TreeInfo *, int);
92 static TreeInfo *getTreeInfoCopy(TreeInfo *ti);
93 static int compareTreeInfoEntries(const void *, const void *);
95 static int token_value_position = TOKEN_VALUE_POSITION_DEFAULT;
96 static int token_comment_position = TOKEN_COMMENT_POSITION_DEFAULT;
98 static SetupFileHash *artworkinfo_cache_old = NULL;
99 static SetupFileHash *artworkinfo_cache_new = NULL;
100 static boolean use_artworkinfo_cache = TRUE;
103 /* ------------------------------------------------------------------------- */
105 /* ------------------------------------------------------------------------- */
107 static char *getLevelClassDescription(TreeInfo *ti)
109 int position = ti->sort_priority / 100;
111 if (position >= 0 && position < NUM_LEVELCLASS_DESC)
112 return levelclass_desc[position];
114 return "Unknown Level Class";
117 static char *getUserLevelDir(char *level_subdir)
119 static char *userlevel_dir = NULL;
120 char *data_dir = getUserGameDataDir();
121 char *userlevel_subdir = LEVELS_DIRECTORY;
123 checked_free(userlevel_dir);
125 if (level_subdir != NULL)
126 userlevel_dir = getPath3(data_dir, userlevel_subdir, level_subdir);
128 userlevel_dir = getPath2(data_dir, userlevel_subdir);
130 return userlevel_dir;
133 static char *getScoreDir(char *level_subdir)
135 static char *score_dir = NULL;
136 static char *score_level_dir = NULL;
137 char *score_subdir = SCORES_DIRECTORY;
139 if (score_dir == NULL)
141 if (program.global_scores)
142 score_dir = getPath2(getCommonDataDir(), score_subdir);
144 score_dir = getPath2(getUserGameDataDir(), score_subdir);
147 if (level_subdir != NULL)
149 checked_free(score_level_dir);
151 score_level_dir = getPath2(score_dir, level_subdir);
153 return score_level_dir;
159 static char *getLevelSetupDir(char *level_subdir)
161 static char *levelsetup_dir = NULL;
162 char *data_dir = getUserGameDataDir();
163 char *levelsetup_subdir = LEVELSETUP_DIRECTORY;
165 checked_free(levelsetup_dir);
167 if (level_subdir != NULL)
168 levelsetup_dir = getPath3(data_dir, levelsetup_subdir, level_subdir);
170 levelsetup_dir = getPath2(data_dir, levelsetup_subdir);
172 return levelsetup_dir;
175 static char *getCacheDir()
177 static char *cache_dir = NULL;
179 if (cache_dir == NULL)
180 cache_dir = getPath2(getUserGameDataDir(), CACHE_DIRECTORY);
185 static char *getLevelDirFromTreeInfo(TreeInfo *node)
187 static char *level_dir = NULL;
190 return options.level_directory;
192 checked_free(level_dir);
194 level_dir = getPath2((node->in_user_dir ? getUserLevelDir(NULL) :
195 options.level_directory), node->fullpath);
200 char *getCurrentLevelDir()
202 return getLevelDirFromTreeInfo(leveldir_current);
205 char *getNewUserLevelSubdir()
207 static char *new_level_subdir = NULL;
208 char *subdir_prefix = getLoginName();
209 char subdir_suffix[10];
210 int max_suffix_number = 1000;
213 while (++i < max_suffix_number)
215 sprintf(subdir_suffix, "_%d", i);
217 checked_free(new_level_subdir);
218 new_level_subdir = getStringCat2(subdir_prefix, subdir_suffix);
220 if (!directoryExists(getUserLevelDir(new_level_subdir)))
224 return new_level_subdir;
227 static char *getTapeDir(char *level_subdir)
229 static char *tape_dir = NULL;
230 char *data_dir = getUserGameDataDir();
231 char *tape_subdir = TAPES_DIRECTORY;
233 checked_free(tape_dir);
235 if (level_subdir != NULL)
236 tape_dir = getPath3(data_dir, tape_subdir, level_subdir);
238 tape_dir = getPath2(data_dir, tape_subdir);
243 static char *getSolutionTapeDir()
245 static char *tape_dir = NULL;
246 char *data_dir = getCurrentLevelDir();
247 char *tape_subdir = TAPES_DIRECTORY;
249 checked_free(tape_dir);
251 tape_dir = getPath2(data_dir, tape_subdir);
256 static char *getDefaultGraphicsDir(char *graphics_subdir)
258 static char *graphics_dir = NULL;
260 if (graphics_subdir == NULL)
261 return options.graphics_directory;
263 checked_free(graphics_dir);
265 graphics_dir = getPath2(options.graphics_directory, graphics_subdir);
270 static char *getDefaultSoundsDir(char *sounds_subdir)
272 static char *sounds_dir = NULL;
274 if (sounds_subdir == NULL)
275 return options.sounds_directory;
277 checked_free(sounds_dir);
279 sounds_dir = getPath2(options.sounds_directory, sounds_subdir);
284 static char *getDefaultMusicDir(char *music_subdir)
286 static char *music_dir = NULL;
288 if (music_subdir == NULL)
289 return options.music_directory;
291 checked_free(music_dir);
293 music_dir = getPath2(options.music_directory, music_subdir);
298 static char *getClassicArtworkSet(int type)
300 return (type == TREE_TYPE_GRAPHICS_DIR ? GFX_CLASSIC_SUBDIR :
301 type == TREE_TYPE_SOUNDS_DIR ? SND_CLASSIC_SUBDIR :
302 type == TREE_TYPE_MUSIC_DIR ? MUS_CLASSIC_SUBDIR : "");
305 static char *getClassicArtworkDir(int type)
307 return (type == TREE_TYPE_GRAPHICS_DIR ?
308 getDefaultGraphicsDir(GFX_CLASSIC_SUBDIR) :
309 type == TREE_TYPE_SOUNDS_DIR ?
310 getDefaultSoundsDir(SND_CLASSIC_SUBDIR) :
311 type == TREE_TYPE_MUSIC_DIR ?
312 getDefaultMusicDir(MUS_CLASSIC_SUBDIR) : "");
315 static char *getUserGraphicsDir()
317 static char *usergraphics_dir = NULL;
319 if (usergraphics_dir == NULL)
320 usergraphics_dir = getPath2(getUserGameDataDir(), GRAPHICS_DIRECTORY);
322 return usergraphics_dir;
325 static char *getUserSoundsDir()
327 static char *usersounds_dir = NULL;
329 if (usersounds_dir == NULL)
330 usersounds_dir = getPath2(getUserGameDataDir(), SOUNDS_DIRECTORY);
332 return usersounds_dir;
335 static char *getUserMusicDir()
337 static char *usermusic_dir = NULL;
339 if (usermusic_dir == NULL)
340 usermusic_dir = getPath2(getUserGameDataDir(), MUSIC_DIRECTORY);
342 return usermusic_dir;
345 static char *getSetupArtworkDir(TreeInfo *ti)
347 static char *artwork_dir = NULL;
352 checked_free(artwork_dir);
354 artwork_dir = getPath2(ti->basepath, ti->fullpath);
359 char *setLevelArtworkDir(TreeInfo *ti)
361 char **artwork_path_ptr, **artwork_set_ptr;
362 TreeInfo *level_artwork;
364 if (ti == NULL || leveldir_current == NULL)
367 artwork_path_ptr = LEVELDIR_ARTWORK_PATH_PTR(leveldir_current, ti->type);
368 artwork_set_ptr = LEVELDIR_ARTWORK_SET_PTR( leveldir_current, ti->type);
370 checked_free(*artwork_path_ptr);
372 if ((level_artwork = getTreeInfoFromIdentifier(ti, *artwork_set_ptr)))
374 *artwork_path_ptr = getStringCopy(getSetupArtworkDir(level_artwork));
379 No (or non-existing) artwork configured in "levelinfo.conf". This would
380 normally result in using the artwork configured in the setup menu. But
381 if an artwork subdirectory exists (which might contain custom artwork
382 or an artwork configuration file), this level artwork must be treated
383 as relative to the default "classic" artwork, not to the artwork that
384 is currently configured in the setup menu.
386 Update: For "special" versions of R'n'D (like "R'n'D jue"), do not use
387 the "default" artwork (which would be "jue0" for "R'n'D jue"), but use
388 the real "classic" artwork from the original R'n'D (like "gfx_classic").
391 char *dir = getPath2(getCurrentLevelDir(), ARTWORK_DIRECTORY(ti->type));
393 checked_free(*artwork_set_ptr);
395 if (directoryExists(dir))
397 *artwork_path_ptr = getStringCopy(getClassicArtworkDir(ti->type));
398 *artwork_set_ptr = getStringCopy(getClassicArtworkSet(ti->type));
402 *artwork_path_ptr = getStringCopy(UNDEFINED_FILENAME);
403 *artwork_set_ptr = NULL;
409 return *artwork_set_ptr;
412 inline static char *getLevelArtworkSet(int type)
414 if (leveldir_current == NULL)
417 return LEVELDIR_ARTWORK_SET(leveldir_current, type);
420 inline static char *getLevelArtworkDir(int type)
422 if (leveldir_current == NULL)
423 return UNDEFINED_FILENAME;
425 return LEVELDIR_ARTWORK_PATH(leveldir_current, type);
428 char *getProgramMainDataPath(char *command_filename, char *base_path)
430 /* check if the program's main data base directory is configured */
431 if (!strEqual(base_path, "."))
434 /* if the program is configured to start from current directory (default),
435 determine program package directory from program binary (some versions
436 of KDE/Konqueror and Mac OS X (especially "Mavericks") apparently do not
437 set the current working directory to the program package directory) */
438 char *main_data_path = getBasePath(command_filename);
440 #if defined(PLATFORM_MACOSX)
441 if (strSuffix(main_data_path, MAC_APP_BINARY_SUBDIR))
443 char *main_data_path_old = main_data_path;
445 // cut relative path to Mac OS X application binary directory from path
446 main_data_path[strlen(main_data_path) -
447 strlen(MAC_APP_BINARY_SUBDIR)] = '\0';
449 // cut trailing path separator from path (but not if path is root directory)
450 if (strSuffix(main_data_path, "/") && !strEqual(main_data_path, "/"))
451 main_data_path[strlen(main_data_path) - 1] = '\0';
453 // replace empty path with current directory
454 if (strEqual(main_data_path, ""))
455 main_data_path = ".";
457 // add relative path to Mac OS X application resources directory to path
458 main_data_path = getPath2(main_data_path, MAC_APP_FILES_SUBDIR);
460 free(main_data_path_old);
464 return main_data_path;
467 char *getProgramConfigFilename(char *command_filename_ptr)
469 char *command_filename_1 = getStringCopy(command_filename_ptr);
471 // strip trailing executable suffix from command filename
472 if (strSuffix(command_filename_1, ".exe"))
473 command_filename_1[strlen(command_filename_1) - 4] = '\0';
475 char *command_basepath = getBasePath(command_filename_ptr);
476 char *command_basename = getBaseNameNoSuffix(command_filename_ptr);
477 char *command_filename_2 = getPath2(command_basepath, command_basename);
479 char *config_filename_1 = getStringCat2(command_filename_1, ".conf");
480 char *config_filename_2 = getStringCat2(command_filename_2, ".conf");
482 // 1st try: look for config file that exactly matches the binary filename
483 if (fileExists(config_filename_1))
484 return config_filename_1;
486 // 2nd try: return config filename that matches binary filename without suffix
487 return config_filename_2;
490 char *getTapeFilename(int nr)
492 static char *filename = NULL;
493 char basename[MAX_FILENAME_LEN];
495 checked_free(filename);
497 sprintf(basename, "%03d.%s", nr, TAPEFILE_EXTENSION);
498 filename = getPath2(getTapeDir(leveldir_current->subdir), basename);
503 char *getSolutionTapeFilename(int nr)
505 static char *filename = NULL;
506 char basename[MAX_FILENAME_LEN];
508 checked_free(filename);
510 sprintf(basename, "%03d.%s", nr, TAPEFILE_EXTENSION);
511 filename = getPath2(getSolutionTapeDir(), basename);
513 if (!fileExists(filename))
515 static char *filename_sln = NULL;
517 checked_free(filename_sln);
519 sprintf(basename, "%03d.sln", nr);
520 filename_sln = getPath2(getSolutionTapeDir(), basename);
522 if (fileExists(filename_sln))
529 char *getScoreFilename(int nr)
531 static char *filename = NULL;
532 char basename[MAX_FILENAME_LEN];
534 checked_free(filename);
536 sprintf(basename, "%03d.%s", nr, SCOREFILE_EXTENSION);
537 filename = getPath2(getScoreDir(leveldir_current->subdir), basename);
542 char *getSetupFilename()
544 static char *filename = NULL;
546 checked_free(filename);
548 filename = getPath2(getSetupDir(), SETUP_FILENAME);
553 char *getDefaultSetupFilename()
555 return program.config_filename;
558 char *getEditorSetupFilename()
560 static char *filename = NULL;
562 checked_free(filename);
563 filename = getPath2(getCurrentLevelDir(), EDITORSETUP_FILENAME);
565 if (fileExists(filename))
568 checked_free(filename);
569 filename = getPath2(getSetupDir(), EDITORSETUP_FILENAME);
574 char *getHelpAnimFilename()
576 static char *filename = NULL;
578 checked_free(filename);
580 filename = getPath2(getCurrentLevelDir(), HELPANIM_FILENAME);
585 char *getHelpTextFilename()
587 static char *filename = NULL;
589 checked_free(filename);
591 filename = getPath2(getCurrentLevelDir(), HELPTEXT_FILENAME);
596 char *getLevelSetInfoFilename()
598 static char *filename = NULL;
613 for (i = 0; basenames[i] != NULL; i++)
615 checked_free(filename);
616 filename = getPath2(getCurrentLevelDir(), basenames[i]);
618 if (fileExists(filename))
625 char *getLevelSetTitleMessageBasename(int nr, boolean initial)
627 static char basename[32];
629 sprintf(basename, "%s_%d.txt",
630 (initial ? "titlemessage_initial" : "titlemessage"), nr + 1);
635 char *getLevelSetTitleMessageFilename(int nr, boolean initial)
637 static char *filename = NULL;
639 boolean skip_setup_artwork = FALSE;
641 checked_free(filename);
643 basename = getLevelSetTitleMessageBasename(nr, initial);
645 if (!gfx.override_level_graphics)
647 /* 1st try: look for special artwork in current level series directory */
648 filename = getPath3(getCurrentLevelDir(), GRAPHICS_DIRECTORY, basename);
649 if (fileExists(filename))
654 /* 2nd try: look for message file in current level set directory */
655 filename = getPath2(getCurrentLevelDir(), basename);
656 if (fileExists(filename))
661 /* check if there is special artwork configured in level series config */
662 if (getLevelArtworkSet(ARTWORK_TYPE_GRAPHICS) != NULL)
664 /* 3rd try: look for special artwork configured in level series config */
665 filename = getPath2(getLevelArtworkDir(ARTWORK_TYPE_GRAPHICS), basename);
666 if (fileExists(filename))
671 /* take missing artwork configured in level set config from default */
672 skip_setup_artwork = TRUE;
676 if (!skip_setup_artwork)
678 /* 4th try: look for special artwork in configured artwork directory */
679 filename = getPath2(getSetupArtworkDir(artwork.gfx_current), basename);
680 if (fileExists(filename))
686 /* 5th try: look for default artwork in new default artwork directory */
687 filename = getPath2(getDefaultGraphicsDir(GFX_DEFAULT_SUBDIR), basename);
688 if (fileExists(filename))
693 /* 6th try: look for default artwork in old default artwork directory */
694 filename = getPath2(options.graphics_directory, basename);
695 if (fileExists(filename))
698 return NULL; /* cannot find specified artwork file anywhere */
701 static char *getCorrectedArtworkBasename(char *basename)
706 char *getCustomImageFilename(char *basename)
708 static char *filename = NULL;
709 boolean skip_setup_artwork = FALSE;
711 checked_free(filename);
713 basename = getCorrectedArtworkBasename(basename);
715 if (!gfx.override_level_graphics)
717 /* 1st try: look for special artwork in current level series directory */
718 filename = getImg3(getCurrentLevelDir(), GRAPHICS_DIRECTORY, basename);
719 if (fileExists(filename))
724 /* check if there is special artwork configured in level series config */
725 if (getLevelArtworkSet(ARTWORK_TYPE_GRAPHICS) != NULL)
727 /* 2nd try: look for special artwork configured in level series config */
728 filename = getImg2(getLevelArtworkDir(ARTWORK_TYPE_GRAPHICS), basename);
729 if (fileExists(filename))
734 /* take missing artwork configured in level set config from default */
735 skip_setup_artwork = TRUE;
739 if (!skip_setup_artwork)
741 /* 3rd try: look for special artwork in configured artwork directory */
742 filename = getImg2(getSetupArtworkDir(artwork.gfx_current), basename);
743 if (fileExists(filename))
749 /* 4th try: look for default artwork in new default artwork directory */
750 filename = getImg2(getDefaultGraphicsDir(GFX_DEFAULT_SUBDIR), basename);
751 if (fileExists(filename))
756 /* 5th try: look for default artwork in old default artwork directory */
757 filename = getImg2(options.graphics_directory, basename);
758 if (fileExists(filename))
761 if (!strEqual(GFX_FALLBACK_FILENAME, UNDEFINED_FILENAME))
766 Error(ERR_WARN, "cannot find artwork file '%s' (using fallback)",
769 /* 6th try: look for fallback artwork in old default artwork directory */
770 /* (needed to prevent errors when trying to access unused artwork files) */
771 filename = getImg2(options.graphics_directory, GFX_FALLBACK_FILENAME);
772 if (fileExists(filename))
776 return NULL; /* cannot find specified artwork file anywhere */
779 char *getCustomSoundFilename(char *basename)
781 static char *filename = NULL;
782 boolean skip_setup_artwork = FALSE;
784 checked_free(filename);
786 basename = getCorrectedArtworkBasename(basename);
788 if (!gfx.override_level_sounds)
790 /* 1st try: look for special artwork in current level series directory */
791 filename = getPath3(getCurrentLevelDir(), SOUNDS_DIRECTORY, basename);
792 if (fileExists(filename))
797 /* check if there is special artwork configured in level series config */
798 if (getLevelArtworkSet(ARTWORK_TYPE_SOUNDS) != NULL)
800 /* 2nd try: look for special artwork configured in level series config */
801 filename = getPath2(getLevelArtworkDir(TREE_TYPE_SOUNDS_DIR), basename);
802 if (fileExists(filename))
807 /* take missing artwork configured in level set config from default */
808 skip_setup_artwork = TRUE;
812 if (!skip_setup_artwork)
814 /* 3rd try: look for special artwork in configured artwork directory */
815 filename = getPath2(getSetupArtworkDir(artwork.snd_current), basename);
816 if (fileExists(filename))
822 /* 4th try: look for default artwork in new default artwork directory */
823 filename = getPath2(getDefaultSoundsDir(SND_DEFAULT_SUBDIR), basename);
824 if (fileExists(filename))
829 /* 5th try: look for default artwork in old default artwork directory */
830 filename = getPath2(options.sounds_directory, basename);
831 if (fileExists(filename))
834 if (!strEqual(SND_FALLBACK_FILENAME, UNDEFINED_FILENAME))
839 Error(ERR_WARN, "cannot find artwork file '%s' (using fallback)",
842 /* 6th try: look for fallback artwork in old default artwork directory */
843 /* (needed to prevent errors when trying to access unused artwork files) */
844 filename = getPath2(options.sounds_directory, SND_FALLBACK_FILENAME);
845 if (fileExists(filename))
849 return NULL; /* cannot find specified artwork file anywhere */
852 char *getCustomMusicFilename(char *basename)
854 static char *filename = NULL;
855 boolean skip_setup_artwork = FALSE;
857 checked_free(filename);
859 basename = getCorrectedArtworkBasename(basename);
861 if (!gfx.override_level_music)
863 /* 1st try: look for special artwork in current level series directory */
864 filename = getPath3(getCurrentLevelDir(), MUSIC_DIRECTORY, basename);
865 if (fileExists(filename))
870 /* check if there is special artwork configured in level series config */
871 if (getLevelArtworkSet(ARTWORK_TYPE_MUSIC) != NULL)
873 /* 2nd try: look for special artwork configured in level series config */
874 filename = getPath2(getLevelArtworkDir(TREE_TYPE_MUSIC_DIR), basename);
875 if (fileExists(filename))
880 /* take missing artwork configured in level set config from default */
881 skip_setup_artwork = TRUE;
885 if (!skip_setup_artwork)
887 /* 3rd try: look for special artwork in configured artwork directory */
888 filename = getPath2(getSetupArtworkDir(artwork.mus_current), basename);
889 if (fileExists(filename))
895 /* 4th try: look for default artwork in new default artwork directory */
896 filename = getPath2(getDefaultMusicDir(MUS_DEFAULT_SUBDIR), basename);
897 if (fileExists(filename))
902 /* 5th try: look for default artwork in old default artwork directory */
903 filename = getPath2(options.music_directory, basename);
904 if (fileExists(filename))
907 if (!strEqual(MUS_FALLBACK_FILENAME, UNDEFINED_FILENAME))
912 Error(ERR_WARN, "cannot find artwork file '%s' (using fallback)",
915 /* 6th try: look for fallback artwork in old default artwork directory */
916 /* (needed to prevent errors when trying to access unused artwork files) */
917 filename = getPath2(options.music_directory, MUS_FALLBACK_FILENAME);
918 if (fileExists(filename))
922 return NULL; /* cannot find specified artwork file anywhere */
925 char *getCustomArtworkFilename(char *basename, int type)
927 if (type == ARTWORK_TYPE_GRAPHICS)
928 return getCustomImageFilename(basename);
929 else if (type == ARTWORK_TYPE_SOUNDS)
930 return getCustomSoundFilename(basename);
931 else if (type == ARTWORK_TYPE_MUSIC)
932 return getCustomMusicFilename(basename);
934 return UNDEFINED_FILENAME;
937 char *getCustomArtworkConfigFilename(int type)
939 return getCustomArtworkFilename(ARTWORKINFO_FILENAME(type), type);
942 char *getCustomArtworkLevelConfigFilename(int type)
944 static char *filename = NULL;
946 checked_free(filename);
948 filename = getPath2(getLevelArtworkDir(type), ARTWORKINFO_FILENAME(type));
953 char *getCustomMusicDirectory(void)
955 static char *directory = NULL;
956 boolean skip_setup_artwork = FALSE;
958 checked_free(directory);
960 if (!gfx.override_level_music)
962 /* 1st try: look for special artwork in current level series directory */
963 directory = getPath2(getCurrentLevelDir(), MUSIC_DIRECTORY);
964 if (directoryExists(directory))
969 /* check if there is special artwork configured in level series config */
970 if (getLevelArtworkSet(ARTWORK_TYPE_MUSIC) != NULL)
972 /* 2nd try: look for special artwork configured in level series config */
973 directory = getStringCopy(getLevelArtworkDir(TREE_TYPE_MUSIC_DIR));
974 if (directoryExists(directory))
979 /* take missing artwork configured in level set config from default */
980 skip_setup_artwork = TRUE;
984 if (!skip_setup_artwork)
986 /* 3rd try: look for special artwork in configured artwork directory */
987 directory = getStringCopy(getSetupArtworkDir(artwork.mus_current));
988 if (directoryExists(directory))
994 /* 4th try: look for default artwork in new default artwork directory */
995 directory = getStringCopy(getDefaultMusicDir(MUS_DEFAULT_SUBDIR));
996 if (directoryExists(directory))
1001 /* 5th try: look for default artwork in old default artwork directory */
1002 directory = getStringCopy(options.music_directory);
1003 if (directoryExists(directory))
1006 return NULL; /* cannot find specified artwork file anywhere */
1009 void InitTapeDirectory(char *level_subdir)
1011 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
1012 createDirectory(getTapeDir(NULL), "main tape", PERMS_PRIVATE);
1013 createDirectory(getTapeDir(level_subdir), "level tape", PERMS_PRIVATE);
1016 void InitScoreDirectory(char *level_subdir)
1018 int permissions = (program.global_scores ? PERMS_PUBLIC : PERMS_PRIVATE);
1020 if (program.global_scores)
1021 createDirectory(getCommonDataDir(), "common data", permissions);
1023 createDirectory(getUserGameDataDir(), "user data", permissions);
1025 createDirectory(getScoreDir(NULL), "main score", permissions);
1026 createDirectory(getScoreDir(level_subdir), "level score", permissions);
1029 static void SaveUserLevelInfo();
1031 void InitUserLevelDirectory(char *level_subdir)
1033 if (!directoryExists(getUserLevelDir(level_subdir)))
1035 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
1036 createDirectory(getUserLevelDir(NULL), "main user level", PERMS_PRIVATE);
1037 createDirectory(getUserLevelDir(level_subdir), "user level", PERMS_PRIVATE);
1039 SaveUserLevelInfo();
1043 void InitLevelSetupDirectory(char *level_subdir)
1045 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
1046 createDirectory(getLevelSetupDir(NULL), "main level setup", PERMS_PRIVATE);
1047 createDirectory(getLevelSetupDir(level_subdir), "level setup", PERMS_PRIVATE);
1050 void InitCacheDirectory()
1052 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
1053 createDirectory(getCacheDir(), "cache data", PERMS_PRIVATE);
1057 /* ------------------------------------------------------------------------- */
1058 /* some functions to handle lists of level and artwork directories */
1059 /* ------------------------------------------------------------------------- */
1061 TreeInfo *newTreeInfo()
1063 return checked_calloc(sizeof(TreeInfo));
1066 TreeInfo *newTreeInfo_setDefaults(int type)
1068 TreeInfo *ti = newTreeInfo();
1070 setTreeInfoToDefaults(ti, type);
1075 void pushTreeInfo(TreeInfo **node_first, TreeInfo *node_new)
1077 node_new->next = *node_first;
1078 *node_first = node_new;
1081 int numTreeInfo(TreeInfo *node)
1094 boolean validLevelSeries(TreeInfo *node)
1096 return (node != NULL && !node->node_group && !node->parent_link);
1099 TreeInfo *getFirstValidTreeInfoEntry(TreeInfo *node)
1104 if (node->node_group) /* enter level group (step down into tree) */
1105 return getFirstValidTreeInfoEntry(node->node_group);
1106 else if (node->parent_link) /* skip start entry of level group */
1108 if (node->next) /* get first real level series entry */
1109 return getFirstValidTreeInfoEntry(node->next);
1110 else /* leave empty level group and go on */
1111 return getFirstValidTreeInfoEntry(node->node_parent->next);
1113 else /* this seems to be a regular level series */
1117 TreeInfo *getTreeInfoFirstGroupEntry(TreeInfo *node)
1122 if (node->node_parent == NULL) /* top level group */
1123 return *node->node_top;
1124 else /* sub level group */
1125 return node->node_parent->node_group;
1128 int numTreeInfoInGroup(TreeInfo *node)
1130 return numTreeInfo(getTreeInfoFirstGroupEntry(node));
1133 int posTreeInfo(TreeInfo *node)
1135 TreeInfo *node_cmp = getTreeInfoFirstGroupEntry(node);
1140 if (node_cmp == node)
1144 node_cmp = node_cmp->next;
1150 TreeInfo *getTreeInfoFromPos(TreeInfo *node, int pos)
1152 TreeInfo *node_default = node;
1164 return node_default;
1167 TreeInfo *getTreeInfoFromIdentifier(TreeInfo *node, char *identifier)
1169 if (identifier == NULL)
1174 if (node->node_group)
1176 TreeInfo *node_group;
1178 node_group = getTreeInfoFromIdentifier(node->node_group, identifier);
1183 else if (!node->parent_link)
1185 if (strEqual(identifier, node->identifier))
1195 TreeInfo *cloneTreeNode(TreeInfo **node_top, TreeInfo *node_parent,
1196 TreeInfo *node, boolean skip_sets_without_levels)
1203 if (!node->parent_link && !node->level_group &&
1204 skip_sets_without_levels && node->levels == 0)
1205 return cloneTreeNode(node_top, node_parent, node->next,
1206 skip_sets_without_levels);
1208 node_new = getTreeInfoCopy(node); /* copy complete node */
1210 node_new->node_top = node_top; /* correct top node link */
1211 node_new->node_parent = node_parent; /* correct parent node link */
1213 if (node->level_group)
1214 node_new->node_group = cloneTreeNode(node_top, node_new, node->node_group,
1215 skip_sets_without_levels);
1217 node_new->next = cloneTreeNode(node_top, node_parent, node->next,
1218 skip_sets_without_levels);
1223 void cloneTree(TreeInfo **ti_new, TreeInfo *ti, boolean skip_empty_sets)
1225 TreeInfo *ti_cloned = cloneTreeNode(ti_new, NULL, ti, skip_empty_sets);
1227 *ti_new = ti_cloned;
1230 static boolean adjustTreeGraphicsForEMC(TreeInfo *node)
1232 boolean settings_changed = FALSE;
1236 if (node->graphics_set_ecs && !setup.prefer_aga_graphics &&
1237 !strEqual(node->graphics_set, node->graphics_set_ecs))
1239 setString(&node->graphics_set, node->graphics_set_ecs);
1240 settings_changed = TRUE;
1242 else if (node->graphics_set_aga && setup.prefer_aga_graphics &&
1243 !strEqual(node->graphics_set, node->graphics_set_aga))
1245 setString(&node->graphics_set, node->graphics_set_aga);
1246 settings_changed = TRUE;
1249 if (node->node_group != NULL)
1250 settings_changed |= adjustTreeGraphicsForEMC(node->node_group);
1255 return settings_changed;
1258 void dumpTreeInfo(TreeInfo *node, int depth)
1262 printf("Dumping TreeInfo:\n");
1266 for (i = 0; i < (depth + 1) * 3; i++)
1269 printf("'%s' / '%s'\n", node->identifier, node->name);
1272 // use for dumping artwork info tree
1273 printf("subdir == '%s' ['%s', '%s'] [%d])\n",
1274 node->subdir, node->fullpath, node->basepath, node->in_user_dir);
1277 if (node->node_group != NULL)
1278 dumpTreeInfo(node->node_group, depth + 1);
1284 void sortTreeInfoBySortFunction(TreeInfo **node_first,
1285 int (*compare_function)(const void *,
1288 int num_nodes = numTreeInfo(*node_first);
1289 TreeInfo **sort_array;
1290 TreeInfo *node = *node_first;
1296 /* allocate array for sorting structure pointers */
1297 sort_array = checked_calloc(num_nodes * sizeof(TreeInfo *));
1299 /* writing structure pointers to sorting array */
1300 while (i < num_nodes && node) /* double boundary check... */
1302 sort_array[i] = node;
1308 /* sorting the structure pointers in the sorting array */
1309 qsort(sort_array, num_nodes, sizeof(TreeInfo *),
1312 /* update the linkage of list elements with the sorted node array */
1313 for (i = 0; i < num_nodes - 1; i++)
1314 sort_array[i]->next = sort_array[i + 1];
1315 sort_array[num_nodes - 1]->next = NULL;
1317 /* update the linkage of the main list anchor pointer */
1318 *node_first = sort_array[0];
1322 /* now recursively sort the level group structures */
1326 if (node->node_group != NULL)
1327 sortTreeInfoBySortFunction(&node->node_group, compare_function);
1333 void sortTreeInfo(TreeInfo **node_first)
1335 sortTreeInfoBySortFunction(node_first, compareTreeInfoEntries);
1339 /* ========================================================================= */
1340 /* some stuff from "files.c" */
1341 /* ========================================================================= */
1343 #if defined(PLATFORM_WIN32)
1345 #define S_IRGRP S_IRUSR
1348 #define S_IROTH S_IRUSR
1351 #define S_IWGRP S_IWUSR
1354 #define S_IWOTH S_IWUSR
1357 #define S_IXGRP S_IXUSR
1360 #define S_IXOTH S_IXUSR
1363 #define S_IRWXG (S_IRGRP | S_IWGRP | S_IXGRP)
1368 #endif /* PLATFORM_WIN32 */
1370 /* file permissions for newly written files */
1371 #define MODE_R_ALL (S_IRUSR | S_IRGRP | S_IROTH)
1372 #define MODE_W_ALL (S_IWUSR | S_IWGRP | S_IWOTH)
1373 #define MODE_X_ALL (S_IXUSR | S_IXGRP | S_IXOTH)
1375 #define MODE_W_PRIVATE (S_IWUSR)
1376 #define MODE_W_PUBLIC_FILE (S_IWUSR | S_IWGRP)
1377 #define MODE_W_PUBLIC_DIR (S_IWUSR | S_IWGRP | S_ISGID)
1379 #define DIR_PERMS_PRIVATE (MODE_R_ALL | MODE_X_ALL | MODE_W_PRIVATE)
1380 #define DIR_PERMS_PUBLIC (MODE_R_ALL | MODE_X_ALL | MODE_W_PUBLIC_DIR)
1381 #define DIR_PERMS_PUBLIC_ALL (MODE_R_ALL | MODE_X_ALL | MODE_W_ALL)
1383 #define FILE_PERMS_PRIVATE (MODE_R_ALL | MODE_W_PRIVATE)
1384 #define FILE_PERMS_PUBLIC (MODE_R_ALL | MODE_W_PUBLIC_FILE)
1385 #define FILE_PERMS_PUBLIC_ALL (MODE_R_ALL | MODE_W_ALL)
1390 static char *dir = NULL;
1392 #if defined(PLATFORM_WIN32)
1395 dir = checked_malloc(MAX_PATH + 1);
1397 if (!SUCCEEDED(SHGetFolderPath(NULL, CSIDL_PERSONAL, NULL, 0, dir)))
1400 #elif defined(PLATFORM_UNIX)
1403 if ((dir = getenv("HOME")) == NULL)
1407 if ((pwd = getpwuid(getuid())) != NULL)
1408 dir = getStringCopy(pwd->pw_dir);
1420 char *getCommonDataDir(void)
1422 static char *common_data_dir = NULL;
1424 #if defined(PLATFORM_WIN32)
1425 if (common_data_dir == NULL)
1427 char *dir = checked_malloc(MAX_PATH + 1);
1429 if (SUCCEEDED(SHGetFolderPath(NULL, CSIDL_COMMON_DOCUMENTS, NULL, 0, dir))
1430 && !strEqual(dir, "")) /* empty for Windows 95/98 */
1431 common_data_dir = getPath2(dir, program.userdata_subdir);
1433 common_data_dir = options.rw_base_directory;
1436 if (common_data_dir == NULL)
1437 common_data_dir = options.rw_base_directory;
1440 return common_data_dir;
1443 char *getPersonalDataDir(void)
1445 static char *personal_data_dir = NULL;
1447 #if defined(PLATFORM_MACOSX)
1448 if (personal_data_dir == NULL)
1449 personal_data_dir = getPath2(getHomeDir(), "Documents");
1451 if (personal_data_dir == NULL)
1452 personal_data_dir = getHomeDir();
1455 return personal_data_dir;
1458 char *getUserGameDataDir(void)
1460 static char *user_game_data_dir = NULL;
1462 #if defined(PLATFORM_ANDROID)
1463 if (user_game_data_dir == NULL)
1464 user_game_data_dir = (char *)(SDL_AndroidGetExternalStorageState() &
1465 SDL_ANDROID_EXTERNAL_STORAGE_WRITE ?
1466 SDL_AndroidGetExternalStoragePath() :
1467 SDL_AndroidGetInternalStoragePath());
1469 if (user_game_data_dir == NULL)
1470 user_game_data_dir = getPath2(getPersonalDataDir(),
1471 program.userdata_subdir);
1474 return user_game_data_dir;
1479 return getUserGameDataDir();
1482 static mode_t posix_umask(mode_t mask)
1484 #if defined(PLATFORM_UNIX)
1491 static int posix_mkdir(const char *pathname, mode_t mode)
1493 #if defined(PLATFORM_WIN32)
1494 return mkdir(pathname);
1496 return mkdir(pathname, mode);
1500 static boolean posix_process_running_setgid()
1502 #if defined(PLATFORM_UNIX)
1503 return (getgid() != getegid());
1509 void createDirectory(char *dir, char *text, int permission_class)
1511 if (directoryExists(dir))
1514 /* leave "other" permissions in umask untouched, but ensure group parts
1515 of USERDATA_DIR_MODE are not masked */
1516 mode_t dir_mode = (permission_class == PERMS_PRIVATE ?
1517 DIR_PERMS_PRIVATE : DIR_PERMS_PUBLIC);
1518 mode_t last_umask = posix_umask(0);
1519 mode_t group_umask = ~(dir_mode & S_IRWXG);
1520 int running_setgid = posix_process_running_setgid();
1522 if (permission_class == PERMS_PUBLIC)
1524 /* if we're setgid, protect files against "other" */
1525 /* else keep umask(0) to make the dir world-writable */
1528 posix_umask(last_umask & group_umask);
1530 dir_mode = DIR_PERMS_PUBLIC_ALL;
1533 if (posix_mkdir(dir, dir_mode) != 0)
1534 Error(ERR_WARN, "cannot create %s directory '%s': %s",
1535 text, dir, strerror(errno));
1537 if (permission_class == PERMS_PUBLIC && !running_setgid)
1538 chmod(dir, dir_mode);
1540 posix_umask(last_umask); /* restore previous umask */
1543 void InitUserDataDirectory()
1545 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
1548 void SetFilePermissions(char *filename, int permission_class)
1550 int running_setgid = posix_process_running_setgid();
1551 int perms = (permission_class == PERMS_PRIVATE ?
1552 FILE_PERMS_PRIVATE : FILE_PERMS_PUBLIC);
1554 if (permission_class == PERMS_PUBLIC && !running_setgid)
1555 perms = FILE_PERMS_PUBLIC_ALL;
1557 chmod(filename, perms);
1560 char *getCookie(char *file_type)
1562 static char cookie[MAX_COOKIE_LEN + 1];
1564 if (strlen(program.cookie_prefix) + 1 +
1565 strlen(file_type) + strlen("_FILE_VERSION_x.x") > MAX_COOKIE_LEN)
1566 return "[COOKIE ERROR]"; /* should never happen */
1568 sprintf(cookie, "%s_%s_FILE_VERSION_%d.%d",
1569 program.cookie_prefix, file_type,
1570 program.version_major, program.version_minor);
1575 void fprintFileHeader(FILE *file, char *basename)
1577 char *prefix = "# ";
1580 fprintf_line_with_prefix(file, prefix, sep1, 77);
1581 fprintf(file, "%s%s\n", prefix, basename);
1582 fprintf_line_with_prefix(file, prefix, sep1, 77);
1583 fprintf(file, "\n");
1586 int getFileVersionFromCookieString(const char *cookie)
1588 const char *ptr_cookie1, *ptr_cookie2;
1589 const char *pattern1 = "_FILE_VERSION_";
1590 const char *pattern2 = "?.?";
1591 const int len_cookie = strlen(cookie);
1592 const int len_pattern1 = strlen(pattern1);
1593 const int len_pattern2 = strlen(pattern2);
1594 const int len_pattern = len_pattern1 + len_pattern2;
1595 int version_major, version_minor;
1597 if (len_cookie <= len_pattern)
1600 ptr_cookie1 = &cookie[len_cookie - len_pattern];
1601 ptr_cookie2 = &cookie[len_cookie - len_pattern2];
1603 if (strncmp(ptr_cookie1, pattern1, len_pattern1) != 0)
1606 if (ptr_cookie2[0] < '0' || ptr_cookie2[0] > '9' ||
1607 ptr_cookie2[1] != '.' ||
1608 ptr_cookie2[2] < '0' || ptr_cookie2[2] > '9')
1611 version_major = ptr_cookie2[0] - '0';
1612 version_minor = ptr_cookie2[2] - '0';
1614 return VERSION_IDENT(version_major, version_minor, 0, 0);
1617 boolean checkCookieString(const char *cookie, const char *template)
1619 const char *pattern = "_FILE_VERSION_?.?";
1620 const int len_cookie = strlen(cookie);
1621 const int len_template = strlen(template);
1622 const int len_pattern = strlen(pattern);
1624 if (len_cookie != len_template)
1627 if (strncmp(cookie, template, len_cookie - len_pattern) != 0)
1634 /* ------------------------------------------------------------------------- */
1635 /* setup file list and hash handling functions */
1636 /* ------------------------------------------------------------------------- */
1638 char *getFormattedSetupEntry(char *token, char *value)
1641 static char entry[MAX_LINE_LEN];
1643 /* if value is an empty string, just return token without value */
1647 /* start with the token and some spaces to format output line */
1648 sprintf(entry, "%s:", token);
1649 for (i = strlen(entry); i < token_value_position; i++)
1652 /* continue with the token's value */
1653 strcat(entry, value);
1658 SetupFileList *newSetupFileList(char *token, char *value)
1660 SetupFileList *new = checked_malloc(sizeof(SetupFileList));
1662 new->token = getStringCopy(token);
1663 new->value = getStringCopy(value);
1670 void freeSetupFileList(SetupFileList *list)
1675 checked_free(list->token);
1676 checked_free(list->value);
1679 freeSetupFileList(list->next);
1684 char *getListEntry(SetupFileList *list, char *token)
1689 if (strEqual(list->token, token))
1692 return getListEntry(list->next, token);
1695 SetupFileList *setListEntry(SetupFileList *list, char *token, char *value)
1700 if (strEqual(list->token, token))
1702 checked_free(list->value);
1704 list->value = getStringCopy(value);
1708 else if (list->next == NULL)
1709 return (list->next = newSetupFileList(token, value));
1711 return setListEntry(list->next, token, value);
1714 SetupFileList *addListEntry(SetupFileList *list, char *token, char *value)
1719 if (list->next == NULL)
1720 return (list->next = newSetupFileList(token, value));
1722 return addListEntry(list->next, token, value);
1725 #if ENABLE_UNUSED_CODE
1727 static void printSetupFileList(SetupFileList *list)
1732 printf("token: '%s'\n", list->token);
1733 printf("value: '%s'\n", list->value);
1735 printSetupFileList(list->next);
1741 DEFINE_HASHTABLE_INSERT(insert_hash_entry, char, char);
1742 DEFINE_HASHTABLE_SEARCH(search_hash_entry, char, char);
1743 DEFINE_HASHTABLE_CHANGE(change_hash_entry, char, char);
1744 DEFINE_HASHTABLE_REMOVE(remove_hash_entry, char, char);
1746 #define insert_hash_entry hashtable_insert
1747 #define search_hash_entry hashtable_search
1748 #define change_hash_entry hashtable_change
1749 #define remove_hash_entry hashtable_remove
1752 unsigned int get_hash_from_key(void *key)
1757 This algorithm (k=33) was first reported by Dan Bernstein many years ago in
1758 'comp.lang.c'. Another version of this algorithm (now favored by Bernstein)
1759 uses XOR: hash(i) = hash(i - 1) * 33 ^ str[i]; the magic of number 33 (why
1760 it works better than many other constants, prime or not) has never been
1761 adequately explained.
1763 If you just want to have a good hash function, and cannot wait, djb2
1764 is one of the best string hash functions i know. It has excellent
1765 distribution and speed on many different sets of keys and table sizes.
1766 You are not likely to do better with one of the "well known" functions
1767 such as PJW, K&R, etc.
1769 Ozan (oz) Yigit [http://www.cs.yorku.ca/~oz/hash.html]
1772 char *str = (char *)key;
1773 unsigned int hash = 5381;
1776 while ((c = *str++))
1777 hash = ((hash << 5) + hash) + c; /* hash * 33 + c */
1782 static int keys_are_equal(void *key1, void *key2)
1784 return (strEqual((char *)key1, (char *)key2));
1787 SetupFileHash *newSetupFileHash()
1789 SetupFileHash *new_hash =
1790 create_hashtable(16, 0.75, get_hash_from_key, keys_are_equal);
1792 if (new_hash == NULL)
1793 Error(ERR_EXIT, "create_hashtable() failed -- out of memory");
1798 void freeSetupFileHash(SetupFileHash *hash)
1803 hashtable_destroy(hash, 1); /* 1 == also free values stored in hash */
1806 char *getHashEntry(SetupFileHash *hash, char *token)
1811 return search_hash_entry(hash, token);
1814 void setHashEntry(SetupFileHash *hash, char *token, char *value)
1821 value_copy = getStringCopy(value);
1823 /* change value; if it does not exist, insert it as new */
1824 if (!change_hash_entry(hash, token, value_copy))
1825 if (!insert_hash_entry(hash, getStringCopy(token), value_copy))
1826 Error(ERR_EXIT, "cannot insert into hash -- aborting");
1829 char *removeHashEntry(SetupFileHash *hash, char *token)
1834 return remove_hash_entry(hash, token);
1837 #if ENABLE_UNUSED_CODE
1839 static void printSetupFileHash(SetupFileHash *hash)
1841 BEGIN_HASH_ITERATION(hash, itr)
1843 printf("token: '%s'\n", HASH_ITERATION_TOKEN(itr));
1844 printf("value: '%s'\n", HASH_ITERATION_VALUE(itr));
1846 END_HASH_ITERATION(hash, itr)
1851 #define ALLOW_TOKEN_VALUE_SEPARATOR_BEING_WHITESPACE 1
1852 #define CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING 0
1853 #define CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH 0
1855 static boolean token_value_separator_found = FALSE;
1856 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
1857 static boolean token_value_separator_warning = FALSE;
1859 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
1860 static boolean token_already_exists_warning = FALSE;
1863 static boolean getTokenValueFromSetupLineExt(char *line,
1864 char **token_ptr, char **value_ptr,
1865 char *filename, char *line_raw,
1867 boolean separator_required)
1869 static char line_copy[MAX_LINE_LEN + 1], line_raw_copy[MAX_LINE_LEN + 1];
1870 char *token, *value, *line_ptr;
1872 /* when externally invoked via ReadTokenValueFromLine(), copy line buffers */
1873 if (line_raw == NULL)
1875 strncpy(line_copy, line, MAX_LINE_LEN);
1876 line_copy[MAX_LINE_LEN] = '\0';
1879 strcpy(line_raw_copy, line_copy);
1880 line_raw = line_raw_copy;
1883 /* cut trailing comment from input line */
1884 for (line_ptr = line; *line_ptr; line_ptr++)
1886 if (*line_ptr == '#')
1893 /* cut trailing whitespaces from input line */
1894 for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
1895 if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
1898 /* ignore empty lines */
1902 /* cut leading whitespaces from token */
1903 for (token = line; *token; token++)
1904 if (*token != ' ' && *token != '\t')
1907 /* start with empty value as reliable default */
1910 token_value_separator_found = FALSE;
1912 /* find end of token to determine start of value */
1913 for (line_ptr = token; *line_ptr; line_ptr++)
1915 /* first look for an explicit token/value separator, like ':' or '=' */
1916 if (*line_ptr == ':' || *line_ptr == '=')
1918 *line_ptr = '\0'; /* terminate token string */
1919 value = line_ptr + 1; /* set beginning of value */
1921 token_value_separator_found = TRUE;
1927 #if ALLOW_TOKEN_VALUE_SEPARATOR_BEING_WHITESPACE
1928 /* fallback: if no token/value separator found, also allow whitespaces */
1929 if (!token_value_separator_found && !separator_required)
1931 for (line_ptr = token; *line_ptr; line_ptr++)
1933 if (*line_ptr == ' ' || *line_ptr == '\t')
1935 *line_ptr = '\0'; /* terminate token string */
1936 value = line_ptr + 1; /* set beginning of value */
1938 token_value_separator_found = TRUE;
1944 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
1945 if (token_value_separator_found)
1947 if (!token_value_separator_warning)
1949 Error(ERR_INFO_LINE, "-");
1951 if (filename != NULL)
1953 Error(ERR_WARN, "missing token/value separator(s) in config file:");
1954 Error(ERR_INFO, "- config file: '%s'", filename);
1958 Error(ERR_WARN, "missing token/value separator(s):");
1961 token_value_separator_warning = TRUE;
1964 if (filename != NULL)
1965 Error(ERR_INFO, "- line %d: '%s'", line_nr, line_raw);
1967 Error(ERR_INFO, "- line: '%s'", line_raw);
1973 /* cut trailing whitespaces from token */
1974 for (line_ptr = &token[strlen(token)]; line_ptr >= token; line_ptr--)
1975 if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
1978 /* cut leading whitespaces from value */
1979 for (; *value; value++)
1980 if (*value != ' ' && *value != '\t')
1989 boolean getTokenValueFromSetupLine(char *line, char **token, char **value)
1991 /* while the internal (old) interface does not require a token/value
1992 separator (for downwards compatibility with existing files which
1993 don't use them), it is mandatory for the external (new) interface */
1995 return getTokenValueFromSetupLineExt(line, token, value, NULL, NULL, 0, TRUE);
1998 static boolean loadSetupFileData(void *setup_file_data, char *filename,
1999 boolean top_recursion_level, boolean is_hash)
2001 static SetupFileHash *include_filename_hash = NULL;
2002 char line[MAX_LINE_LEN], line_raw[MAX_LINE_LEN], previous_line[MAX_LINE_LEN];
2003 char *token, *value, *line_ptr;
2004 void *insert_ptr = NULL;
2005 boolean read_continued_line = FALSE;
2007 int line_nr = 0, token_count = 0, include_count = 0;
2009 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2010 token_value_separator_warning = FALSE;
2013 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2014 token_already_exists_warning = FALSE;
2017 if (!(file = openFile(filename, MODE_READ)))
2019 Error(ERR_DEBUG, "cannot open configuration file '%s'", filename);
2024 /* use "insert pointer" to store list end for constant insertion complexity */
2026 insert_ptr = setup_file_data;
2028 /* on top invocation, create hash to mark included files (to prevent loops) */
2029 if (top_recursion_level)
2030 include_filename_hash = newSetupFileHash();
2032 /* mark this file as already included (to prevent including it again) */
2033 setHashEntry(include_filename_hash, getBaseNamePtr(filename), "true");
2035 while (!checkEndOfFile(file))
2037 /* read next line of input file */
2038 if (!getStringFromFile(file, line, MAX_LINE_LEN))
2041 /* check if line was completely read and is terminated by line break */
2042 if (strlen(line) > 0 && line[strlen(line) - 1] == '\n')
2045 /* cut trailing line break (this can be newline and/or carriage return) */
2046 for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
2047 if ((*line_ptr == '\n' || *line_ptr == '\r') && *(line_ptr + 1) == '\0')
2050 /* copy raw input line for later use (mainly debugging output) */
2051 strcpy(line_raw, line);
2053 if (read_continued_line)
2055 /* append new line to existing line, if there is enough space */
2056 if (strlen(previous_line) + strlen(line_ptr) < MAX_LINE_LEN)
2057 strcat(previous_line, line_ptr);
2059 strcpy(line, previous_line); /* copy storage buffer to line */
2061 read_continued_line = FALSE;
2064 /* if the last character is '\', continue at next line */
2065 if (strlen(line) > 0 && line[strlen(line) - 1] == '\\')
2067 line[strlen(line) - 1] = '\0'; /* cut off trailing backslash */
2068 strcpy(previous_line, line); /* copy line to storage buffer */
2070 read_continued_line = TRUE;
2075 if (!getTokenValueFromSetupLineExt(line, &token, &value, filename,
2076 line_raw, line_nr, FALSE))
2081 if (strEqual(token, "include"))
2083 if (getHashEntry(include_filename_hash, value) == NULL)
2085 char *basepath = getBasePath(filename);
2086 char *basename = getBaseName(value);
2087 char *filename_include = getPath2(basepath, basename);
2089 loadSetupFileData(setup_file_data, filename_include, FALSE, is_hash);
2093 free(filename_include);
2099 Error(ERR_WARN, "ignoring already processed file '%s'", value);
2106 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2108 getHashEntry((SetupFileHash *)setup_file_data, token);
2110 if (old_value != NULL)
2112 if (!token_already_exists_warning)
2114 Error(ERR_INFO_LINE, "-");
2115 Error(ERR_WARN, "duplicate token(s) found in config file:");
2116 Error(ERR_INFO, "- config file: '%s'", filename);
2118 token_already_exists_warning = TRUE;
2121 Error(ERR_INFO, "- token: '%s' (in line %d)", token, line_nr);
2122 Error(ERR_INFO, " old value: '%s'", old_value);
2123 Error(ERR_INFO, " new value: '%s'", value);
2127 setHashEntry((SetupFileHash *)setup_file_data, token, value);
2131 insert_ptr = addListEntry((SetupFileList *)insert_ptr, token, value);
2141 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2142 if (token_value_separator_warning)
2143 Error(ERR_INFO_LINE, "-");
2146 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2147 if (token_already_exists_warning)
2148 Error(ERR_INFO_LINE, "-");
2151 if (token_count == 0 && include_count == 0)
2152 Error(ERR_WARN, "configuration file '%s' is empty", filename);
2154 if (top_recursion_level)
2155 freeSetupFileHash(include_filename_hash);
2160 void saveSetupFileHash(SetupFileHash *hash, char *filename)
2164 if (!(file = fopen(filename, MODE_WRITE)))
2166 Error(ERR_WARN, "cannot write configuration file '%s'", filename);
2171 BEGIN_HASH_ITERATION(hash, itr)
2173 fprintf(file, "%s\n", getFormattedSetupEntry(HASH_ITERATION_TOKEN(itr),
2174 HASH_ITERATION_VALUE(itr)));
2176 END_HASH_ITERATION(hash, itr)
2181 SetupFileList *loadSetupFileList(char *filename)
2183 SetupFileList *setup_file_list = newSetupFileList("", "");
2184 SetupFileList *first_valid_list_entry;
2186 if (!loadSetupFileData(setup_file_list, filename, TRUE, FALSE))
2188 freeSetupFileList(setup_file_list);
2193 first_valid_list_entry = setup_file_list->next;
2195 /* free empty list header */
2196 setup_file_list->next = NULL;
2197 freeSetupFileList(setup_file_list);
2199 return first_valid_list_entry;
2202 SetupFileHash *loadSetupFileHash(char *filename)
2204 SetupFileHash *setup_file_hash = newSetupFileHash();
2206 if (!loadSetupFileData(setup_file_hash, filename, TRUE, TRUE))
2208 freeSetupFileHash(setup_file_hash);
2213 return setup_file_hash;
2217 /* ========================================================================= */
2218 /* setup file stuff */
2219 /* ========================================================================= */
2221 #define TOKEN_STR_LAST_LEVEL_SERIES "last_level_series"
2222 #define TOKEN_STR_LAST_PLAYED_LEVEL "last_played_level"
2223 #define TOKEN_STR_HANDICAP_LEVEL "handicap_level"
2225 /* level directory info */
2226 #define LEVELINFO_TOKEN_IDENTIFIER 0
2227 #define LEVELINFO_TOKEN_NAME 1
2228 #define LEVELINFO_TOKEN_NAME_SORTING 2
2229 #define LEVELINFO_TOKEN_AUTHOR 3
2230 #define LEVELINFO_TOKEN_YEAR 4
2231 #define LEVELINFO_TOKEN_IMPORTED_FROM 5
2232 #define LEVELINFO_TOKEN_IMPORTED_BY 6
2233 #define LEVELINFO_TOKEN_TESTED_BY 7
2234 #define LEVELINFO_TOKEN_LEVELS 8
2235 #define LEVELINFO_TOKEN_FIRST_LEVEL 9
2236 #define LEVELINFO_TOKEN_SORT_PRIORITY 10
2237 #define LEVELINFO_TOKEN_LATEST_ENGINE 11
2238 #define LEVELINFO_TOKEN_LEVEL_GROUP 12
2239 #define LEVELINFO_TOKEN_READONLY 13
2240 #define LEVELINFO_TOKEN_GRAPHICS_SET_ECS 14
2241 #define LEVELINFO_TOKEN_GRAPHICS_SET_AGA 15
2242 #define LEVELINFO_TOKEN_GRAPHICS_SET 16
2243 #define LEVELINFO_TOKEN_SOUNDS_SET 17
2244 #define LEVELINFO_TOKEN_MUSIC_SET 18
2245 #define LEVELINFO_TOKEN_FILENAME 19
2246 #define LEVELINFO_TOKEN_FILETYPE 20
2247 #define LEVELINFO_TOKEN_SPECIAL_FLAGS 21
2248 #define LEVELINFO_TOKEN_HANDICAP 22
2249 #define LEVELINFO_TOKEN_SKIP_LEVELS 23
2251 #define NUM_LEVELINFO_TOKENS 24
2253 static LevelDirTree ldi;
2255 static struct TokenInfo levelinfo_tokens[] =
2257 /* level directory info */
2258 { TYPE_STRING, &ldi.identifier, "identifier" },
2259 { TYPE_STRING, &ldi.name, "name" },
2260 { TYPE_STRING, &ldi.name_sorting, "name_sorting" },
2261 { TYPE_STRING, &ldi.author, "author" },
2262 { TYPE_STRING, &ldi.year, "year" },
2263 { TYPE_STRING, &ldi.imported_from, "imported_from" },
2264 { TYPE_STRING, &ldi.imported_by, "imported_by" },
2265 { TYPE_STRING, &ldi.tested_by, "tested_by" },
2266 { TYPE_INTEGER, &ldi.levels, "levels" },
2267 { TYPE_INTEGER, &ldi.first_level, "first_level" },
2268 { TYPE_INTEGER, &ldi.sort_priority, "sort_priority" },
2269 { TYPE_BOOLEAN, &ldi.latest_engine, "latest_engine" },
2270 { TYPE_BOOLEAN, &ldi.level_group, "level_group" },
2271 { TYPE_BOOLEAN, &ldi.readonly, "readonly" },
2272 { TYPE_STRING, &ldi.graphics_set_ecs, "graphics_set.ecs" },
2273 { TYPE_STRING, &ldi.graphics_set_aga, "graphics_set.aga" },
2274 { TYPE_STRING, &ldi.graphics_set, "graphics_set" },
2275 { TYPE_STRING, &ldi.sounds_set, "sounds_set" },
2276 { TYPE_STRING, &ldi.music_set, "music_set" },
2277 { TYPE_STRING, &ldi.level_filename, "filename" },
2278 { TYPE_STRING, &ldi.level_filetype, "filetype" },
2279 { TYPE_STRING, &ldi.special_flags, "special_flags" },
2280 { TYPE_BOOLEAN, &ldi.handicap, "handicap" },
2281 { TYPE_BOOLEAN, &ldi.skip_levels, "skip_levels" }
2284 static struct TokenInfo artworkinfo_tokens[] =
2286 /* artwork directory info */
2287 { TYPE_STRING, &ldi.identifier, "identifier" },
2288 { TYPE_STRING, &ldi.subdir, "subdir" },
2289 { TYPE_STRING, &ldi.name, "name" },
2290 { TYPE_STRING, &ldi.name_sorting, "name_sorting" },
2291 { TYPE_STRING, &ldi.author, "author" },
2292 { TYPE_INTEGER, &ldi.sort_priority, "sort_priority" },
2293 { TYPE_STRING, &ldi.basepath, "basepath" },
2294 { TYPE_STRING, &ldi.fullpath, "fullpath" },
2295 { TYPE_BOOLEAN, &ldi.in_user_dir, "in_user_dir" },
2296 { TYPE_INTEGER, &ldi.color, "color" },
2297 { TYPE_STRING, &ldi.class_desc, "class_desc" },
2302 static void setTreeInfoToDefaults(TreeInfo *ti, int type)
2306 ti->node_top = (ti->type == TREE_TYPE_LEVEL_DIR ? &leveldir_first :
2307 ti->type == TREE_TYPE_GRAPHICS_DIR ? &artwork.gfx_first :
2308 ti->type == TREE_TYPE_SOUNDS_DIR ? &artwork.snd_first :
2309 ti->type == TREE_TYPE_MUSIC_DIR ? &artwork.mus_first :
2312 ti->node_parent = NULL;
2313 ti->node_group = NULL;
2320 ti->fullpath = NULL;
2321 ti->basepath = NULL;
2322 ti->identifier = NULL;
2323 ti->name = getStringCopy(ANONYMOUS_NAME);
2324 ti->name_sorting = NULL;
2325 ti->author = getStringCopy(ANONYMOUS_NAME);
2328 ti->sort_priority = LEVELCLASS_UNDEFINED; /* default: least priority */
2329 ti->latest_engine = FALSE; /* default: get from level */
2330 ti->parent_link = FALSE;
2331 ti->in_user_dir = FALSE;
2332 ti->user_defined = FALSE;
2334 ti->class_desc = NULL;
2336 ti->infotext = getStringCopy(TREE_INFOTEXT(ti->type));
2338 if (ti->type == TREE_TYPE_LEVEL_DIR)
2340 ti->imported_from = NULL;
2341 ti->imported_by = NULL;
2342 ti->tested_by = NULL;
2344 ti->graphics_set_ecs = NULL;
2345 ti->graphics_set_aga = NULL;
2346 ti->graphics_set = NULL;
2347 ti->sounds_set = NULL;
2348 ti->music_set = NULL;
2349 ti->graphics_path = getStringCopy(UNDEFINED_FILENAME);
2350 ti->sounds_path = getStringCopy(UNDEFINED_FILENAME);
2351 ti->music_path = getStringCopy(UNDEFINED_FILENAME);
2353 ti->level_filename = NULL;
2354 ti->level_filetype = NULL;
2356 ti->special_flags = NULL;
2359 ti->first_level = 0;
2361 ti->level_group = FALSE;
2362 ti->handicap_level = 0;
2363 ti->readonly = TRUE;
2364 ti->handicap = TRUE;
2365 ti->skip_levels = FALSE;
2369 static void setTreeInfoToDefaultsFromParent(TreeInfo *ti, TreeInfo *parent)
2373 Error(ERR_WARN, "setTreeInfoToDefaultsFromParent(): parent == NULL");
2375 setTreeInfoToDefaults(ti, TREE_TYPE_UNDEFINED);
2380 /* copy all values from the parent structure */
2382 ti->type = parent->type;
2384 ti->node_top = parent->node_top;
2385 ti->node_parent = parent;
2386 ti->node_group = NULL;
2393 ti->fullpath = NULL;
2394 ti->basepath = NULL;
2395 ti->identifier = NULL;
2396 ti->name = getStringCopy(ANONYMOUS_NAME);
2397 ti->name_sorting = NULL;
2398 ti->author = getStringCopy(parent->author);
2399 ti->year = getStringCopy(parent->year);
2401 ti->sort_priority = parent->sort_priority;
2402 ti->latest_engine = parent->latest_engine;
2403 ti->parent_link = FALSE;
2404 ti->in_user_dir = parent->in_user_dir;
2405 ti->user_defined = parent->user_defined;
2406 ti->color = parent->color;
2407 ti->class_desc = getStringCopy(parent->class_desc);
2409 ti->infotext = getStringCopy(parent->infotext);
2411 if (ti->type == TREE_TYPE_LEVEL_DIR)
2413 ti->imported_from = getStringCopy(parent->imported_from);
2414 ti->imported_by = getStringCopy(parent->imported_by);
2415 ti->tested_by = getStringCopy(parent->tested_by);
2417 ti->graphics_set_ecs = getStringCopy(parent->graphics_set_ecs);
2418 ti->graphics_set_aga = getStringCopy(parent->graphics_set_aga);
2419 ti->graphics_set = getStringCopy(parent->graphics_set);
2420 ti->sounds_set = getStringCopy(parent->sounds_set);
2421 ti->music_set = getStringCopy(parent->music_set);
2422 ti->graphics_path = getStringCopy(UNDEFINED_FILENAME);
2423 ti->sounds_path = getStringCopy(UNDEFINED_FILENAME);
2424 ti->music_path = getStringCopy(UNDEFINED_FILENAME);
2426 ti->level_filename = getStringCopy(parent->level_filename);
2427 ti->level_filetype = getStringCopy(parent->level_filetype);
2429 ti->special_flags = getStringCopy(parent->special_flags);
2431 ti->levels = parent->levels;
2432 ti->first_level = parent->first_level;
2433 ti->last_level = parent->last_level;
2434 ti->level_group = FALSE;
2435 ti->handicap_level = parent->handicap_level;
2436 ti->readonly = parent->readonly;
2437 ti->handicap = parent->handicap;
2438 ti->skip_levels = parent->skip_levels;
2442 static TreeInfo *getTreeInfoCopy(TreeInfo *ti)
2444 TreeInfo *ti_copy = newTreeInfo();
2446 /* copy all values from the original structure */
2448 ti_copy->type = ti->type;
2450 ti_copy->node_top = ti->node_top;
2451 ti_copy->node_parent = ti->node_parent;
2452 ti_copy->node_group = ti->node_group;
2453 ti_copy->next = ti->next;
2455 ti_copy->cl_first = ti->cl_first;
2456 ti_copy->cl_cursor = ti->cl_cursor;
2458 ti_copy->subdir = getStringCopy(ti->subdir);
2459 ti_copy->fullpath = getStringCopy(ti->fullpath);
2460 ti_copy->basepath = getStringCopy(ti->basepath);
2461 ti_copy->identifier = getStringCopy(ti->identifier);
2462 ti_copy->name = getStringCopy(ti->name);
2463 ti_copy->name_sorting = getStringCopy(ti->name_sorting);
2464 ti_copy->author = getStringCopy(ti->author);
2465 ti_copy->year = getStringCopy(ti->year);
2466 ti_copy->imported_from = getStringCopy(ti->imported_from);
2467 ti_copy->imported_by = getStringCopy(ti->imported_by);
2468 ti_copy->tested_by = getStringCopy(ti->tested_by);
2470 ti_copy->graphics_set_ecs = getStringCopy(ti->graphics_set_ecs);
2471 ti_copy->graphics_set_aga = getStringCopy(ti->graphics_set_aga);
2472 ti_copy->graphics_set = getStringCopy(ti->graphics_set);
2473 ti_copy->sounds_set = getStringCopy(ti->sounds_set);
2474 ti_copy->music_set = getStringCopy(ti->music_set);
2475 ti_copy->graphics_path = getStringCopy(ti->graphics_path);
2476 ti_copy->sounds_path = getStringCopy(ti->sounds_path);
2477 ti_copy->music_path = getStringCopy(ti->music_path);
2479 ti_copy->level_filename = getStringCopy(ti->level_filename);
2480 ti_copy->level_filetype = getStringCopy(ti->level_filetype);
2482 ti_copy->special_flags = getStringCopy(ti->special_flags);
2484 ti_copy->levels = ti->levels;
2485 ti_copy->first_level = ti->first_level;
2486 ti_copy->last_level = ti->last_level;
2487 ti_copy->sort_priority = ti->sort_priority;
2489 ti_copy->latest_engine = ti->latest_engine;
2491 ti_copy->level_group = ti->level_group;
2492 ti_copy->parent_link = ti->parent_link;
2493 ti_copy->in_user_dir = ti->in_user_dir;
2494 ti_copy->user_defined = ti->user_defined;
2495 ti_copy->readonly = ti->readonly;
2496 ti_copy->handicap = ti->handicap;
2497 ti_copy->skip_levels = ti->skip_levels;
2499 ti_copy->color = ti->color;
2500 ti_copy->class_desc = getStringCopy(ti->class_desc);
2501 ti_copy->handicap_level = ti->handicap_level;
2503 ti_copy->infotext = getStringCopy(ti->infotext);
2508 void freeTreeInfo(TreeInfo *ti)
2513 checked_free(ti->subdir);
2514 checked_free(ti->fullpath);
2515 checked_free(ti->basepath);
2516 checked_free(ti->identifier);
2518 checked_free(ti->name);
2519 checked_free(ti->name_sorting);
2520 checked_free(ti->author);
2521 checked_free(ti->year);
2523 checked_free(ti->class_desc);
2525 checked_free(ti->infotext);
2527 if (ti->type == TREE_TYPE_LEVEL_DIR)
2529 checked_free(ti->imported_from);
2530 checked_free(ti->imported_by);
2531 checked_free(ti->tested_by);
2533 checked_free(ti->graphics_set_ecs);
2534 checked_free(ti->graphics_set_aga);
2535 checked_free(ti->graphics_set);
2536 checked_free(ti->sounds_set);
2537 checked_free(ti->music_set);
2539 checked_free(ti->graphics_path);
2540 checked_free(ti->sounds_path);
2541 checked_free(ti->music_path);
2543 checked_free(ti->level_filename);
2544 checked_free(ti->level_filetype);
2546 checked_free(ti->special_flags);
2549 // recursively free child node
2551 freeTreeInfo(ti->node_group);
2553 // recursively free next node
2555 freeTreeInfo(ti->next);
2560 void setSetupInfo(struct TokenInfo *token_info,
2561 int token_nr, char *token_value)
2563 int token_type = token_info[token_nr].type;
2564 void *setup_value = token_info[token_nr].value;
2566 if (token_value == NULL)
2569 /* set setup field to corresponding token value */
2574 *(boolean *)setup_value = get_boolean_from_string(token_value);
2578 *(int *)setup_value = get_switch3_from_string(token_value);
2582 *(Key *)setup_value = getKeyFromKeyName(token_value);
2586 *(Key *)setup_value = getKeyFromX11KeyName(token_value);
2590 *(int *)setup_value = get_integer_from_string(token_value);
2594 checked_free(*(char **)setup_value);
2595 *(char **)setup_value = getStringCopy(token_value);
2603 static int compareTreeInfoEntries(const void *object1, const void *object2)
2605 const TreeInfo *entry1 = *((TreeInfo **)object1);
2606 const TreeInfo *entry2 = *((TreeInfo **)object2);
2607 int class_sorting1 = 0, class_sorting2 = 0;
2610 if (entry1->type == TREE_TYPE_LEVEL_DIR)
2612 class_sorting1 = LEVELSORTING(entry1);
2613 class_sorting2 = LEVELSORTING(entry2);
2615 else if (entry1->type == TREE_TYPE_GRAPHICS_DIR ||
2616 entry1->type == TREE_TYPE_SOUNDS_DIR ||
2617 entry1->type == TREE_TYPE_MUSIC_DIR)
2619 class_sorting1 = ARTWORKSORTING(entry1);
2620 class_sorting2 = ARTWORKSORTING(entry2);
2623 if (entry1->parent_link || entry2->parent_link)
2624 compare_result = (entry1->parent_link ? -1 : +1);
2625 else if (entry1->sort_priority == entry2->sort_priority)
2627 char *name1 = getStringToLower(entry1->name_sorting);
2628 char *name2 = getStringToLower(entry2->name_sorting);
2630 compare_result = strcmp(name1, name2);
2635 else if (class_sorting1 == class_sorting2)
2636 compare_result = entry1->sort_priority - entry2->sort_priority;
2638 compare_result = class_sorting1 - class_sorting2;
2640 return compare_result;
2643 static TreeInfo *createParentTreeInfoNode(TreeInfo *node_parent)
2647 if (node_parent == NULL)
2650 ti_new = newTreeInfo();
2651 setTreeInfoToDefaults(ti_new, node_parent->type);
2653 ti_new->node_parent = node_parent;
2654 ti_new->parent_link = TRUE;
2656 setString(&ti_new->identifier, node_parent->identifier);
2657 setString(&ti_new->name, ".. (parent directory)");
2658 setString(&ti_new->name_sorting, ti_new->name);
2660 setString(&ti_new->subdir, STRING_PARENT_DIRECTORY);
2661 setString(&ti_new->fullpath, node_parent->fullpath);
2663 ti_new->sort_priority = node_parent->sort_priority;
2664 ti_new->latest_engine = node_parent->latest_engine;
2666 setString(&ti_new->class_desc, getLevelClassDescription(ti_new));
2668 pushTreeInfo(&node_parent->node_group, ti_new);
2673 static TreeInfo *createTopTreeInfoNode(TreeInfo *node_first)
2675 TreeInfo *ti_new, *ti_new2;
2677 if (node_first == NULL)
2680 ti_new = newTreeInfo();
2681 setTreeInfoToDefaults(ti_new, TREE_TYPE_LEVEL_DIR);
2683 ti_new->node_parent = NULL;
2684 ti_new->parent_link = FALSE;
2686 setString(&ti_new->identifier, node_first->identifier);
2687 setString(&ti_new->name, "level sets");
2688 setString(&ti_new->name_sorting, ti_new->name);
2690 setString(&ti_new->subdir, STRING_TOP_DIRECTORY);
2691 setString(&ti_new->fullpath, ".");
2693 ti_new->sort_priority = node_first->sort_priority;;
2694 ti_new->latest_engine = node_first->latest_engine;
2696 setString(&ti_new->class_desc, "level sets");
2698 ti_new->node_group = node_first;
2699 ti_new->level_group = TRUE;
2701 ti_new2 = createParentTreeInfoNode(ti_new);
2703 setString(&ti_new2->name, ".. (main menu)");
2704 setString(&ti_new2->name_sorting, ti_new2->name);
2710 /* -------------------------------------------------------------------------- */
2711 /* functions for handling level and custom artwork info cache */
2712 /* -------------------------------------------------------------------------- */
2714 static void LoadArtworkInfoCache()
2716 InitCacheDirectory();
2718 if (artworkinfo_cache_old == NULL)
2720 char *filename = getPath2(getCacheDir(), ARTWORKINFO_CACHE_FILE);
2722 /* try to load artwork info hash from already existing cache file */
2723 artworkinfo_cache_old = loadSetupFileHash(filename);
2725 /* if no artwork info cache file was found, start with empty hash */
2726 if (artworkinfo_cache_old == NULL)
2727 artworkinfo_cache_old = newSetupFileHash();
2732 if (artworkinfo_cache_new == NULL)
2733 artworkinfo_cache_new = newSetupFileHash();
2736 static void SaveArtworkInfoCache()
2738 char *filename = getPath2(getCacheDir(), ARTWORKINFO_CACHE_FILE);
2740 InitCacheDirectory();
2742 saveSetupFileHash(artworkinfo_cache_new, filename);
2747 static char *getCacheTokenPrefix(char *prefix1, char *prefix2)
2749 static char *prefix = NULL;
2751 checked_free(prefix);
2753 prefix = getStringCat2WithSeparator(prefix1, prefix2, ".");
2758 /* (identical to above function, but separate string buffer needed -- nasty) */
2759 static char *getCacheToken(char *prefix, char *suffix)
2761 static char *token = NULL;
2763 checked_free(token);
2765 token = getStringCat2WithSeparator(prefix, suffix, ".");
2770 static char *getFileTimestampString(char *filename)
2772 return getStringCopy(i_to_a(getFileTimestampEpochSeconds(filename)));
2775 static boolean modifiedFileTimestamp(char *filename, char *timestamp_string)
2777 struct stat file_status;
2779 if (timestamp_string == NULL)
2782 if (stat(filename, &file_status) != 0) /* cannot stat file */
2785 return (file_status.st_mtime != atoi(timestamp_string));
2788 static TreeInfo *getArtworkInfoCacheEntry(LevelDirTree *level_node, int type)
2790 char *identifier = level_node->subdir;
2791 char *type_string = ARTWORK_DIRECTORY(type);
2792 char *token_prefix = getCacheTokenPrefix(type_string, identifier);
2793 char *token_main = getCacheToken(token_prefix, "CACHED");
2794 char *cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
2795 boolean cached = (cache_entry != NULL && strEqual(cache_entry, "true"));
2796 TreeInfo *artwork_info = NULL;
2798 if (!use_artworkinfo_cache)
2805 artwork_info = newTreeInfo();
2806 setTreeInfoToDefaults(artwork_info, type);
2808 /* set all structure fields according to the token/value pairs */
2809 ldi = *artwork_info;
2810 for (i = 0; artworkinfo_tokens[i].type != -1; i++)
2812 char *token = getCacheToken(token_prefix, artworkinfo_tokens[i].text);
2813 char *value = getHashEntry(artworkinfo_cache_old, token);
2815 setSetupInfo(artworkinfo_tokens, i, value);
2817 /* check if cache entry for this item is invalid or incomplete */
2820 Error(ERR_WARN, "cache entry '%s' invalid", token);
2826 *artwork_info = ldi;
2831 char *filename_levelinfo = getPath2(getLevelDirFromTreeInfo(level_node),
2832 LEVELINFO_FILENAME);
2833 char *filename_artworkinfo = getPath2(getSetupArtworkDir(artwork_info),
2834 ARTWORKINFO_FILENAME(type));
2836 /* check if corresponding "levelinfo.conf" file has changed */
2837 token_main = getCacheToken(token_prefix, "TIMESTAMP_LEVELINFO");
2838 cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
2840 if (modifiedFileTimestamp(filename_levelinfo, cache_entry))
2843 /* check if corresponding "<artworkinfo>.conf" file has changed */
2844 token_main = getCacheToken(token_prefix, "TIMESTAMP_ARTWORKINFO");
2845 cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
2847 if (modifiedFileTimestamp(filename_artworkinfo, cache_entry))
2850 checked_free(filename_levelinfo);
2851 checked_free(filename_artworkinfo);
2854 if (!cached && artwork_info != NULL)
2856 freeTreeInfo(artwork_info);
2861 return artwork_info;
2864 static void setArtworkInfoCacheEntry(TreeInfo *artwork_info,
2865 LevelDirTree *level_node, int type)
2867 char *identifier = level_node->subdir;
2868 char *type_string = ARTWORK_DIRECTORY(type);
2869 char *token_prefix = getCacheTokenPrefix(type_string, identifier);
2870 char *token_main = getCacheToken(token_prefix, "CACHED");
2871 boolean set_cache_timestamps = TRUE;
2874 setHashEntry(artworkinfo_cache_new, token_main, "true");
2876 if (set_cache_timestamps)
2878 char *filename_levelinfo = getPath2(getLevelDirFromTreeInfo(level_node),
2879 LEVELINFO_FILENAME);
2880 char *filename_artworkinfo = getPath2(getSetupArtworkDir(artwork_info),
2881 ARTWORKINFO_FILENAME(type));
2882 char *timestamp_levelinfo = getFileTimestampString(filename_levelinfo);
2883 char *timestamp_artworkinfo = getFileTimestampString(filename_artworkinfo);
2885 token_main = getCacheToken(token_prefix, "TIMESTAMP_LEVELINFO");
2886 setHashEntry(artworkinfo_cache_new, token_main, timestamp_levelinfo);
2888 token_main = getCacheToken(token_prefix, "TIMESTAMP_ARTWORKINFO");
2889 setHashEntry(artworkinfo_cache_new, token_main, timestamp_artworkinfo);
2891 checked_free(filename_levelinfo);
2892 checked_free(filename_artworkinfo);
2893 checked_free(timestamp_levelinfo);
2894 checked_free(timestamp_artworkinfo);
2897 ldi = *artwork_info;
2898 for (i = 0; artworkinfo_tokens[i].type != -1; i++)
2900 char *token = getCacheToken(token_prefix, artworkinfo_tokens[i].text);
2901 char *value = getSetupValue(artworkinfo_tokens[i].type,
2902 artworkinfo_tokens[i].value);
2904 setHashEntry(artworkinfo_cache_new, token, value);
2909 /* -------------------------------------------------------------------------- */
2910 /* functions for loading level info and custom artwork info */
2911 /* -------------------------------------------------------------------------- */
2913 /* forward declaration for recursive call by "LoadLevelInfoFromLevelDir()" */
2914 static void LoadLevelInfoFromLevelDir(TreeInfo **, TreeInfo *, char *);
2916 static boolean LoadLevelInfoFromLevelConf(TreeInfo **node_first,
2917 TreeInfo *node_parent,
2918 char *level_directory,
2919 char *directory_name)
2921 char *directory_path = getPath2(level_directory, directory_name);
2922 char *filename = getPath2(directory_path, LEVELINFO_FILENAME);
2923 SetupFileHash *setup_file_hash;
2924 LevelDirTree *leveldir_new = NULL;
2927 /* unless debugging, silently ignore directories without "levelinfo.conf" */
2928 if (!options.debug && !fileExists(filename))
2930 free(directory_path);
2936 setup_file_hash = loadSetupFileHash(filename);
2938 if (setup_file_hash == NULL)
2940 Error(ERR_WARN, "ignoring level directory '%s'", directory_path);
2942 free(directory_path);
2948 leveldir_new = newTreeInfo();
2951 setTreeInfoToDefaultsFromParent(leveldir_new, node_parent);
2953 setTreeInfoToDefaults(leveldir_new, TREE_TYPE_LEVEL_DIR);
2955 leveldir_new->subdir = getStringCopy(directory_name);
2957 /* set all structure fields according to the token/value pairs */
2958 ldi = *leveldir_new;
2959 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
2960 setSetupInfo(levelinfo_tokens, i,
2961 getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
2962 *leveldir_new = ldi;
2964 if (strEqual(leveldir_new->name, ANONYMOUS_NAME))
2965 setString(&leveldir_new->name, leveldir_new->subdir);
2967 if (leveldir_new->identifier == NULL)
2968 leveldir_new->identifier = getStringCopy(leveldir_new->subdir);
2970 if (leveldir_new->name_sorting == NULL)
2971 leveldir_new->name_sorting = getStringCopy(leveldir_new->name);
2973 if (node_parent == NULL) /* top level group */
2975 leveldir_new->basepath = getStringCopy(level_directory);
2976 leveldir_new->fullpath = getStringCopy(leveldir_new->subdir);
2978 else /* sub level group */
2980 leveldir_new->basepath = getStringCopy(node_parent->basepath);
2981 leveldir_new->fullpath = getPath2(node_parent->fullpath, directory_name);
2984 leveldir_new->last_level =
2985 leveldir_new->first_level + leveldir_new->levels - 1;
2987 leveldir_new->in_user_dir =
2988 (!strEqual(leveldir_new->basepath, options.level_directory));
2990 /* adjust some settings if user's private level directory was detected */
2991 if (leveldir_new->sort_priority == LEVELCLASS_UNDEFINED &&
2992 leveldir_new->in_user_dir &&
2993 (strEqual(leveldir_new->subdir, getLoginName()) ||
2994 strEqual(leveldir_new->name, getLoginName()) ||
2995 strEqual(leveldir_new->author, getRealName())))
2997 leveldir_new->sort_priority = LEVELCLASS_PRIVATE_START;
2998 leveldir_new->readonly = FALSE;
3001 leveldir_new->user_defined =
3002 (leveldir_new->in_user_dir && IS_LEVELCLASS_PRIVATE(leveldir_new));
3004 leveldir_new->color = LEVELCOLOR(leveldir_new);
3006 setString(&leveldir_new->class_desc, getLevelClassDescription(leveldir_new));
3008 leveldir_new->handicap_level = /* set handicap to default value */
3009 (leveldir_new->user_defined || !leveldir_new->handicap ?
3010 leveldir_new->last_level : leveldir_new->first_level);
3012 DrawInitText(leveldir_new->name, 150, FC_YELLOW);
3014 pushTreeInfo(node_first, leveldir_new);
3016 freeSetupFileHash(setup_file_hash);
3018 if (leveldir_new->level_group)
3020 /* create node to link back to current level directory */
3021 createParentTreeInfoNode(leveldir_new);
3023 /* recursively step into sub-directory and look for more level series */
3024 LoadLevelInfoFromLevelDir(&leveldir_new->node_group,
3025 leveldir_new, directory_path);
3028 free(directory_path);
3034 static void LoadLevelInfoFromLevelDir(TreeInfo **node_first,
3035 TreeInfo *node_parent,
3036 char *level_directory)
3039 DirectoryEntry *dir_entry;
3040 boolean valid_entry_found = FALSE;
3042 if ((dir = openDirectory(level_directory)) == NULL)
3044 Error(ERR_WARN, "cannot read level directory '%s'", level_directory);
3049 while ((dir_entry = readDirectory(dir)) != NULL) /* loop all entries */
3051 char *directory_name = dir_entry->basename;
3052 char *directory_path = getPath2(level_directory, directory_name);
3054 /* skip entries for current and parent directory */
3055 if (strEqual(directory_name, ".") ||
3056 strEqual(directory_name, ".."))
3058 free(directory_path);
3063 /* find out if directory entry is itself a directory */
3064 if (!dir_entry->is_directory) /* not a directory */
3066 free(directory_path);
3071 free(directory_path);
3073 if (strEqual(directory_name, GRAPHICS_DIRECTORY) ||
3074 strEqual(directory_name, SOUNDS_DIRECTORY) ||
3075 strEqual(directory_name, MUSIC_DIRECTORY))
3078 valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
3083 closeDirectory(dir);
3085 /* special case: top level directory may directly contain "levelinfo.conf" */
3086 if (node_parent == NULL && !valid_entry_found)
3088 /* check if this directory directly contains a file "levelinfo.conf" */
3089 valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
3090 level_directory, ".");
3093 if (!valid_entry_found)
3094 Error(ERR_WARN, "cannot find any valid level series in directory '%s'",
3098 boolean AdjustGraphicsForEMC()
3100 boolean settings_changed = FALSE;
3102 settings_changed |= adjustTreeGraphicsForEMC(leveldir_first_all);
3103 settings_changed |= adjustTreeGraphicsForEMC(leveldir_first);
3105 return settings_changed;
3108 void LoadLevelInfo()
3110 InitUserLevelDirectory(getLoginName());
3112 DrawInitText("Loading level series", 120, FC_GREEN);
3114 LoadLevelInfoFromLevelDir(&leveldir_first, NULL, options.level_directory);
3115 LoadLevelInfoFromLevelDir(&leveldir_first, NULL, getUserLevelDir(NULL));
3117 leveldir_first = createTopTreeInfoNode(leveldir_first);
3119 /* after loading all level set information, clone the level directory tree
3120 and remove all level sets without levels (these may still contain artwork
3121 to be offered in the setup menu as "custom artwork", and are therefore
3122 checked for existing artwork in the function "LoadLevelArtworkInfo()") */
3123 leveldir_first_all = leveldir_first;
3124 cloneTree(&leveldir_first, leveldir_first_all, TRUE);
3126 AdjustGraphicsForEMC();
3128 /* before sorting, the first entries will be from the user directory */
3129 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
3131 if (leveldir_first == NULL)
3132 Error(ERR_EXIT, "cannot find any valid level series in any directory");
3134 sortTreeInfo(&leveldir_first);
3136 #if ENABLE_UNUSED_CODE
3137 dumpTreeInfo(leveldir_first, 0);
3141 static boolean LoadArtworkInfoFromArtworkConf(TreeInfo **node_first,
3142 TreeInfo *node_parent,
3143 char *base_directory,
3144 char *directory_name, int type)
3146 char *directory_path = getPath2(base_directory, directory_name);
3147 char *filename = getPath2(directory_path, ARTWORKINFO_FILENAME(type));
3148 SetupFileHash *setup_file_hash = NULL;
3149 TreeInfo *artwork_new = NULL;
3152 if (fileExists(filename))
3153 setup_file_hash = loadSetupFileHash(filename);
3155 if (setup_file_hash == NULL) /* no config file -- look for artwork files */
3158 DirectoryEntry *dir_entry;
3159 boolean valid_file_found = FALSE;
3161 if ((dir = openDirectory(directory_path)) != NULL)
3163 while ((dir_entry = readDirectory(dir)) != NULL)
3165 if (FileIsArtworkType(dir_entry->filename, type))
3167 valid_file_found = TRUE;
3173 closeDirectory(dir);
3176 if (!valid_file_found)
3178 if (!strEqual(directory_name, "."))
3179 Error(ERR_WARN, "ignoring artwork directory '%s'", directory_path);
3181 free(directory_path);
3188 artwork_new = newTreeInfo();
3191 setTreeInfoToDefaultsFromParent(artwork_new, node_parent);
3193 setTreeInfoToDefaults(artwork_new, type);
3195 artwork_new->subdir = getStringCopy(directory_name);
3197 if (setup_file_hash) /* (before defining ".color" and ".class_desc") */
3199 /* set all structure fields according to the token/value pairs */
3201 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
3202 setSetupInfo(levelinfo_tokens, i,
3203 getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
3206 if (strEqual(artwork_new->name, ANONYMOUS_NAME))
3207 setString(&artwork_new->name, artwork_new->subdir);
3209 if (artwork_new->identifier == NULL)
3210 artwork_new->identifier = getStringCopy(artwork_new->subdir);
3212 if (artwork_new->name_sorting == NULL)
3213 artwork_new->name_sorting = getStringCopy(artwork_new->name);
3216 if (node_parent == NULL) /* top level group */
3218 artwork_new->basepath = getStringCopy(base_directory);
3219 artwork_new->fullpath = getStringCopy(artwork_new->subdir);
3221 else /* sub level group */
3223 artwork_new->basepath = getStringCopy(node_parent->basepath);
3224 artwork_new->fullpath = getPath2(node_parent->fullpath, directory_name);
3227 artwork_new->in_user_dir =
3228 (!strEqual(artwork_new->basepath, OPTIONS_ARTWORK_DIRECTORY(type)));
3230 /* (may use ".sort_priority" from "setup_file_hash" above) */
3231 artwork_new->color = ARTWORKCOLOR(artwork_new);
3233 setString(&artwork_new->class_desc, getLevelClassDescription(artwork_new));
3235 if (setup_file_hash == NULL) /* (after determining ".user_defined") */
3237 if (strEqual(artwork_new->subdir, "."))
3239 if (artwork_new->user_defined)
3241 setString(&artwork_new->identifier, "private");
3242 artwork_new->sort_priority = ARTWORKCLASS_PRIVATE;
3246 setString(&artwork_new->identifier, "classic");
3247 artwork_new->sort_priority = ARTWORKCLASS_CLASSICS;
3250 /* set to new values after changing ".sort_priority" */
3251 artwork_new->color = ARTWORKCOLOR(artwork_new);
3253 setString(&artwork_new->class_desc,
3254 getLevelClassDescription(artwork_new));
3258 setString(&artwork_new->identifier, artwork_new->subdir);
3261 setString(&artwork_new->name, artwork_new->identifier);
3262 setString(&artwork_new->name_sorting, artwork_new->name);
3265 pushTreeInfo(node_first, artwork_new);
3267 freeSetupFileHash(setup_file_hash);
3269 free(directory_path);
3275 static void LoadArtworkInfoFromArtworkDir(TreeInfo **node_first,
3276 TreeInfo *node_parent,
3277 char *base_directory, int type)
3280 DirectoryEntry *dir_entry;
3281 boolean valid_entry_found = FALSE;
3283 if ((dir = openDirectory(base_directory)) == NULL)
3285 /* display error if directory is main "options.graphics_directory" etc. */
3286 if (base_directory == OPTIONS_ARTWORK_DIRECTORY(type))
3287 Error(ERR_WARN, "cannot read directory '%s'", base_directory);
3292 while ((dir_entry = readDirectory(dir)) != NULL) /* loop all entries */
3294 char *directory_name = dir_entry->basename;
3295 char *directory_path = getPath2(base_directory, directory_name);
3297 /* skip directory entries for current and parent directory */
3298 if (strEqual(directory_name, ".") ||
3299 strEqual(directory_name, ".."))
3301 free(directory_path);
3306 /* skip directory entries which are not a directory */
3307 if (!dir_entry->is_directory) /* not a directory */
3309 free(directory_path);
3314 free(directory_path);
3316 /* check if this directory contains artwork with or without config file */
3317 valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
3319 directory_name, type);
3322 closeDirectory(dir);
3324 /* check if this directory directly contains artwork itself */
3325 valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
3326 base_directory, ".",
3328 if (!valid_entry_found)
3329 Error(ERR_WARN, "cannot find any valid artwork in directory '%s'",
3333 static TreeInfo *getDummyArtworkInfo(int type)
3335 /* this is only needed when there is completely no artwork available */
3336 TreeInfo *artwork_new = newTreeInfo();
3338 setTreeInfoToDefaults(artwork_new, type);
3340 setString(&artwork_new->subdir, UNDEFINED_FILENAME);
3341 setString(&artwork_new->fullpath, UNDEFINED_FILENAME);
3342 setString(&artwork_new->basepath, UNDEFINED_FILENAME);
3344 setString(&artwork_new->identifier, UNDEFINED_FILENAME);
3345 setString(&artwork_new->name, UNDEFINED_FILENAME);
3346 setString(&artwork_new->name_sorting, UNDEFINED_FILENAME);
3351 void LoadArtworkInfo()
3353 LoadArtworkInfoCache();
3355 DrawInitText("Looking for custom artwork", 120, FC_GREEN);
3357 LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
3358 options.graphics_directory,
3359 TREE_TYPE_GRAPHICS_DIR);
3360 LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
3361 getUserGraphicsDir(),
3362 TREE_TYPE_GRAPHICS_DIR);
3364 LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
3365 options.sounds_directory,
3366 TREE_TYPE_SOUNDS_DIR);
3367 LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
3369 TREE_TYPE_SOUNDS_DIR);
3371 LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
3372 options.music_directory,
3373 TREE_TYPE_MUSIC_DIR);
3374 LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
3376 TREE_TYPE_MUSIC_DIR);
3378 if (artwork.gfx_first == NULL)
3379 artwork.gfx_first = getDummyArtworkInfo(TREE_TYPE_GRAPHICS_DIR);
3380 if (artwork.snd_first == NULL)
3381 artwork.snd_first = getDummyArtworkInfo(TREE_TYPE_SOUNDS_DIR);
3382 if (artwork.mus_first == NULL)
3383 artwork.mus_first = getDummyArtworkInfo(TREE_TYPE_MUSIC_DIR);
3385 /* before sorting, the first entries will be from the user directory */
3386 artwork.gfx_current =
3387 getTreeInfoFromIdentifier(artwork.gfx_first, setup.graphics_set);
3388 if (artwork.gfx_current == NULL)
3389 artwork.gfx_current =
3390 getTreeInfoFromIdentifier(artwork.gfx_first, GFX_DEFAULT_SUBDIR);
3391 if (artwork.gfx_current == NULL)
3392 artwork.gfx_current = getFirstValidTreeInfoEntry(artwork.gfx_first);
3394 artwork.snd_current =
3395 getTreeInfoFromIdentifier(artwork.snd_first, setup.sounds_set);
3396 if (artwork.snd_current == NULL)
3397 artwork.snd_current =
3398 getTreeInfoFromIdentifier(artwork.snd_first, SND_DEFAULT_SUBDIR);
3399 if (artwork.snd_current == NULL)
3400 artwork.snd_current = getFirstValidTreeInfoEntry(artwork.snd_first);
3402 artwork.mus_current =
3403 getTreeInfoFromIdentifier(artwork.mus_first, setup.music_set);
3404 if (artwork.mus_current == NULL)
3405 artwork.mus_current =
3406 getTreeInfoFromIdentifier(artwork.mus_first, MUS_DEFAULT_SUBDIR);
3407 if (artwork.mus_current == NULL)
3408 artwork.mus_current = getFirstValidTreeInfoEntry(artwork.mus_first);
3410 artwork.gfx_current_identifier = artwork.gfx_current->identifier;
3411 artwork.snd_current_identifier = artwork.snd_current->identifier;
3412 artwork.mus_current_identifier = artwork.mus_current->identifier;
3414 #if ENABLE_UNUSED_CODE
3415 printf("graphics set == %s\n\n", artwork.gfx_current_identifier);
3416 printf("sounds set == %s\n\n", artwork.snd_current_identifier);
3417 printf("music set == %s\n\n", artwork.mus_current_identifier);
3420 sortTreeInfo(&artwork.gfx_first);
3421 sortTreeInfo(&artwork.snd_first);
3422 sortTreeInfo(&artwork.mus_first);
3424 #if ENABLE_UNUSED_CODE
3425 dumpTreeInfo(artwork.gfx_first, 0);
3426 dumpTreeInfo(artwork.snd_first, 0);
3427 dumpTreeInfo(artwork.mus_first, 0);
3431 void LoadArtworkInfoFromLevelInfo(ArtworkDirTree **artwork_node,
3432 LevelDirTree *level_node)
3434 int type = (*artwork_node)->type;
3436 /* recursively check all level directories for artwork sub-directories */
3440 /* check all tree entries for artwork, but skip parent link entries */
3441 if (!level_node->parent_link)
3443 TreeInfo *artwork_new = getArtworkInfoCacheEntry(level_node, type);
3444 boolean cached = (artwork_new != NULL);
3448 pushTreeInfo(artwork_node, artwork_new);
3452 TreeInfo *topnode_last = *artwork_node;
3453 char *path = getPath2(getLevelDirFromTreeInfo(level_node),
3454 ARTWORK_DIRECTORY(type));
3456 LoadArtworkInfoFromArtworkDir(artwork_node, NULL, path, type);
3458 if (topnode_last != *artwork_node) /* check for newly added node */
3460 artwork_new = *artwork_node;
3462 setString(&artwork_new->identifier, level_node->subdir);
3463 setString(&artwork_new->name, level_node->name);
3464 setString(&artwork_new->name_sorting, level_node->name_sorting);
3466 artwork_new->sort_priority = level_node->sort_priority;
3467 artwork_new->color = LEVELCOLOR(artwork_new);
3473 /* insert artwork info (from old cache or filesystem) into new cache */
3474 if (artwork_new != NULL)
3475 setArtworkInfoCacheEntry(artwork_new, level_node, type);
3478 DrawInitText(level_node->name, 150, FC_YELLOW);
3480 if (level_node->node_group != NULL)
3481 LoadArtworkInfoFromLevelInfo(artwork_node, level_node->node_group);
3483 level_node = level_node->next;
3487 void LoadLevelArtworkInfo()
3489 print_timestamp_init("LoadLevelArtworkInfo");
3491 DrawInitText("Looking for custom level artwork", 120, FC_GREEN);
3493 print_timestamp_time("DrawTimeText");
3495 LoadArtworkInfoFromLevelInfo(&artwork.gfx_first, leveldir_first_all);
3496 print_timestamp_time("LoadArtworkInfoFromLevelInfo (gfx)");
3497 LoadArtworkInfoFromLevelInfo(&artwork.snd_first, leveldir_first_all);
3498 print_timestamp_time("LoadArtworkInfoFromLevelInfo (snd)");
3499 LoadArtworkInfoFromLevelInfo(&artwork.mus_first, leveldir_first_all);
3500 print_timestamp_time("LoadArtworkInfoFromLevelInfo (mus)");
3502 SaveArtworkInfoCache();
3504 print_timestamp_time("SaveArtworkInfoCache");
3506 /* needed for reloading level artwork not known at ealier stage */
3508 if (!strEqual(artwork.gfx_current_identifier, setup.graphics_set))
3510 artwork.gfx_current =
3511 getTreeInfoFromIdentifier(artwork.gfx_first, setup.graphics_set);
3512 if (artwork.gfx_current == NULL)
3513 artwork.gfx_current =
3514 getTreeInfoFromIdentifier(artwork.gfx_first, GFX_DEFAULT_SUBDIR);
3515 if (artwork.gfx_current == NULL)
3516 artwork.gfx_current = getFirstValidTreeInfoEntry(artwork.gfx_first);
3519 if (!strEqual(artwork.snd_current_identifier, setup.sounds_set))
3521 artwork.snd_current =
3522 getTreeInfoFromIdentifier(artwork.snd_first, setup.sounds_set);
3523 if (artwork.snd_current == NULL)
3524 artwork.snd_current =
3525 getTreeInfoFromIdentifier(artwork.snd_first, SND_DEFAULT_SUBDIR);
3526 if (artwork.snd_current == NULL)
3527 artwork.snd_current = getFirstValidTreeInfoEntry(artwork.snd_first);
3530 if (!strEqual(artwork.mus_current_identifier, setup.music_set))
3532 artwork.mus_current =
3533 getTreeInfoFromIdentifier(artwork.mus_first, setup.music_set);
3534 if (artwork.mus_current == NULL)
3535 artwork.mus_current =
3536 getTreeInfoFromIdentifier(artwork.mus_first, MUS_DEFAULT_SUBDIR);
3537 if (artwork.mus_current == NULL)
3538 artwork.mus_current = getFirstValidTreeInfoEntry(artwork.mus_first);
3541 print_timestamp_time("getTreeInfoFromIdentifier");
3543 sortTreeInfo(&artwork.gfx_first);
3544 sortTreeInfo(&artwork.snd_first);
3545 sortTreeInfo(&artwork.mus_first);
3547 print_timestamp_time("sortTreeInfo");
3549 #if ENABLE_UNUSED_CODE
3550 dumpTreeInfo(artwork.gfx_first, 0);
3551 dumpTreeInfo(artwork.snd_first, 0);
3552 dumpTreeInfo(artwork.mus_first, 0);
3555 print_timestamp_done("LoadLevelArtworkInfo");
3558 static boolean AddUserLevelSetToLevelInfoExt(char *level_subdir_new)
3560 // get level info tree node of first (original) user level set
3561 char *level_subdir_old = getLoginName();
3562 LevelDirTree *leveldir_old = getTreeInfoFromIdentifier(leveldir_first,
3564 if (leveldir_old == NULL) // should not happen
3567 int draw_deactivation_mask = GetDrawDeactivationMask();
3569 // override draw deactivation mask (temporarily disable drawing)
3570 SetDrawDeactivationMask(REDRAW_ALL);
3572 // load new level set config and add it next to first user level set
3573 LoadLevelInfoFromLevelConf(&leveldir_old->next, NULL,
3574 leveldir_old->basepath, level_subdir_new);
3576 // set draw deactivation mask to previous value
3577 SetDrawDeactivationMask(draw_deactivation_mask);
3579 // get level info tree node of newly added user level set
3580 LevelDirTree *leveldir_new = getTreeInfoFromIdentifier(leveldir_first,
3582 if (leveldir_new == NULL) // should not happen
3585 // correct top link and parent node link of newly created tree node
3586 leveldir_new->node_top = leveldir_old->node_top;
3587 leveldir_new->node_parent = leveldir_old->node_parent;
3589 // sort level info tree to adjust position of newly added level set
3590 sortTreeInfo(&leveldir_first);
3595 void AddUserLevelSetToLevelInfo(char *level_subdir_new)
3597 if (!AddUserLevelSetToLevelInfoExt(level_subdir_new))
3598 Error(ERR_EXIT, "internal level set structure corrupted -- aborting");
3601 boolean UpdateUserLevelSet(char *level_subdir, char *level_name,
3602 char *level_author, int num_levels)
3604 char *filename = getPath2(getUserLevelDir(level_subdir), LEVELINFO_FILENAME);
3605 char *filename_tmp = getStringCat2(filename, ".tmp");
3607 FILE *file_tmp = NULL;
3608 char line[MAX_LINE_LEN];
3609 boolean success = FALSE;
3610 LevelDirTree *leveldir = getTreeInfoFromIdentifier(leveldir_first,
3612 // update values in level directory tree
3614 if (level_name != NULL)
3615 setString(&leveldir->name, level_name);
3617 if (level_author != NULL)
3618 setString(&leveldir->author, level_author);
3620 if (num_levels != -1)
3621 leveldir->levels = num_levels;
3623 // update values that depend on other values
3625 setString(&leveldir->name_sorting, leveldir->name);
3627 leveldir->last_level = leveldir->first_level + leveldir->levels - 1;
3629 // sort order of level sets may have changed
3630 sortTreeInfo(&leveldir_first);
3632 if ((file = fopen(filename, MODE_READ)) &&
3633 (file_tmp = fopen(filename_tmp, MODE_WRITE)))
3635 while (fgets(line, MAX_LINE_LEN, file))
3637 if (strPrefix(line, "name:") && level_name != NULL)
3638 fprintf(file_tmp, "%-32s%s\n", "name:", level_name);
3639 else if (strPrefix(line, "author:") && level_author != NULL)
3640 fprintf(file_tmp, "%-32s%s\n", "author:", level_author);
3641 else if (strPrefix(line, "levels:") && num_levels != -1)
3642 fprintf(file_tmp, "%-32s%d\n", "levels:", num_levels);
3644 fputs(line, file_tmp);
3657 success = (rename(filename_tmp, filename) == 0);
3665 boolean CreateUserLevelSet(char *level_subdir, char *level_name,
3666 char *level_author, int num_levels)
3668 LevelDirTree *level_info;
3673 // create user level sub-directory, if needed
3674 createDirectory(getUserLevelDir(level_subdir), "user level", PERMS_PRIVATE);
3676 filename = getPath2(getUserLevelDir(level_subdir), LEVELINFO_FILENAME);
3678 if (!(file = fopen(filename, MODE_WRITE)))
3680 Error(ERR_WARN, "cannot write level info file '%s'", filename);
3686 level_info = newTreeInfo();
3688 /* always start with reliable default values */
3689 setTreeInfoToDefaults(level_info, TREE_TYPE_LEVEL_DIR);
3691 setString(&level_info->name, level_name);
3692 setString(&level_info->author, level_author);
3693 level_info->levels = num_levels;
3694 level_info->first_level = 1;
3695 level_info->sort_priority = LEVELCLASS_PRIVATE_START;
3696 level_info->readonly = FALSE;
3698 token_value_position = TOKEN_VALUE_POSITION_SHORT;
3700 fprintFileHeader(file, LEVELINFO_FILENAME);
3703 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
3705 if (i == LEVELINFO_TOKEN_NAME ||
3706 i == LEVELINFO_TOKEN_AUTHOR ||
3707 i == LEVELINFO_TOKEN_LEVELS ||
3708 i == LEVELINFO_TOKEN_FIRST_LEVEL ||
3709 i == LEVELINFO_TOKEN_SORT_PRIORITY ||
3710 i == LEVELINFO_TOKEN_READONLY)
3711 fprintf(file, "%s\n", getSetupLine(levelinfo_tokens, "", i));
3713 /* just to make things nicer :) */
3714 if (i == LEVELINFO_TOKEN_AUTHOR ||
3715 i == LEVELINFO_TOKEN_FIRST_LEVEL)
3716 fprintf(file, "\n");
3719 token_value_position = TOKEN_VALUE_POSITION_DEFAULT;
3723 SetFilePermissions(filename, PERMS_PRIVATE);
3725 freeTreeInfo(level_info);
3731 static void SaveUserLevelInfo()
3733 CreateUserLevelSet(getLoginName(), getLoginName(), getRealName(), 100);
3736 char *getSetupValue(int type, void *value)
3738 static char value_string[MAX_LINE_LEN];
3746 strcpy(value_string, (*(boolean *)value ? "true" : "false"));
3750 strcpy(value_string, (*(boolean *)value ? "on" : "off"));
3754 strcpy(value_string, (*(int *)value == AUTO ? "auto" :
3755 *(int *)value == FALSE ? "off" : "on"));
3759 strcpy(value_string, (*(boolean *)value ? "yes" : "no"));
3762 case TYPE_YES_NO_AUTO:
3763 strcpy(value_string, (*(int *)value == AUTO ? "auto" :
3764 *(int *)value == FALSE ? "no" : "yes"));
3768 strcpy(value_string, (*(boolean *)value ? "AGA" : "ECS"));
3772 strcpy(value_string, getKeyNameFromKey(*(Key *)value));
3776 strcpy(value_string, getX11KeyNameFromKey(*(Key *)value));
3780 sprintf(value_string, "%d", *(int *)value);
3784 if (*(char **)value == NULL)
3787 strcpy(value_string, *(char **)value);
3791 value_string[0] = '\0';
3795 if (type & TYPE_GHOSTED)
3796 strcpy(value_string, "n/a");
3798 return value_string;
3801 char *getSetupLine(struct TokenInfo *token_info, char *prefix, int token_nr)
3805 static char token_string[MAX_LINE_LEN];
3806 int token_type = token_info[token_nr].type;
3807 void *setup_value = token_info[token_nr].value;
3808 char *token_text = token_info[token_nr].text;
3809 char *value_string = getSetupValue(token_type, setup_value);
3811 /* build complete token string */
3812 sprintf(token_string, "%s%s", prefix, token_text);
3814 /* build setup entry line */
3815 line = getFormattedSetupEntry(token_string, value_string);
3817 if (token_type == TYPE_KEY_X11)
3819 Key key = *(Key *)setup_value;
3820 char *keyname = getKeyNameFromKey(key);
3822 /* add comment, if useful */
3823 if (!strEqual(keyname, "(undefined)") &&
3824 !strEqual(keyname, "(unknown)"))
3826 /* add at least one whitespace */
3828 for (i = strlen(line); i < token_comment_position; i++)
3832 strcat(line, keyname);
3839 void LoadLevelSetup_LastSeries()
3841 /* ----------------------------------------------------------------------- */
3842 /* ~/.<program>/levelsetup.conf */
3843 /* ----------------------------------------------------------------------- */
3845 char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
3846 SetupFileHash *level_setup_hash = NULL;
3848 /* always start with reliable default values */
3849 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
3851 if (!strEqual(DEFAULT_LEVELSET, UNDEFINED_LEVELSET))
3853 leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
3855 if (leveldir_current == NULL)
3856 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
3859 if ((level_setup_hash = loadSetupFileHash(filename)))
3861 char *last_level_series =
3862 getHashEntry(level_setup_hash, TOKEN_STR_LAST_LEVEL_SERIES);
3864 leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
3866 if (leveldir_current == NULL)
3867 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
3869 freeSetupFileHash(level_setup_hash);
3873 Error(ERR_DEBUG, "using default setup values");
3879 static void SaveLevelSetup_LastSeries_Ext(boolean deactivate_last_level_series)
3881 /* ----------------------------------------------------------------------- */
3882 /* ~/.<program>/levelsetup.conf */
3883 /* ----------------------------------------------------------------------- */
3885 // check if the current level directory structure is available at this point
3886 if (leveldir_current == NULL)
3889 char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
3890 char *level_subdir = leveldir_current->subdir;
3893 InitUserDataDirectory();
3895 if (!(file = fopen(filename, MODE_WRITE)))
3897 Error(ERR_WARN, "cannot write setup file '%s'", filename);
3904 fprintFileHeader(file, LEVELSETUP_FILENAME);
3906 if (deactivate_last_level_series)
3907 fprintf(file, "# %s\n# ", "the following level set may have caused a problem and was deactivated");
3909 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_LEVEL_SERIES,
3914 SetFilePermissions(filename, PERMS_PRIVATE);
3919 void SaveLevelSetup_LastSeries()
3921 SaveLevelSetup_LastSeries_Ext(FALSE);
3924 void SaveLevelSetup_LastSeries_Deactivate()
3926 SaveLevelSetup_LastSeries_Ext(TRUE);
3929 static void checkSeriesInfo()
3931 static char *level_directory = NULL;
3934 /* check for more levels besides the 'levels' field of 'levelinfo.conf' */
3936 level_directory = getPath2((leveldir_current->in_user_dir ?
3937 getUserLevelDir(NULL) :
3938 options.level_directory),
3939 leveldir_current->fullpath);
3941 if ((dir = openDirectory(level_directory)) == NULL)
3943 Error(ERR_WARN, "cannot read level directory '%s'", level_directory);
3948 closeDirectory(dir);
3951 void LoadLevelSetup_SeriesInfo()
3954 SetupFileHash *level_setup_hash = NULL;
3955 char *level_subdir = leveldir_current->subdir;
3958 /* always start with reliable default values */
3959 level_nr = leveldir_current->first_level;
3961 for (i = 0; i < MAX_LEVELS; i++)
3963 LevelStats_setPlayed(i, 0);
3964 LevelStats_setSolved(i, 0);
3969 /* ----------------------------------------------------------------------- */
3970 /* ~/.<program>/levelsetup/<level series>/levelsetup.conf */
3971 /* ----------------------------------------------------------------------- */
3973 level_subdir = leveldir_current->subdir;
3975 filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
3977 if ((level_setup_hash = loadSetupFileHash(filename)))
3981 /* get last played level in this level set */
3983 token_value = getHashEntry(level_setup_hash, TOKEN_STR_LAST_PLAYED_LEVEL);
3987 level_nr = atoi(token_value);
3989 if (level_nr < leveldir_current->first_level)
3990 level_nr = leveldir_current->first_level;
3991 if (level_nr > leveldir_current->last_level)
3992 level_nr = leveldir_current->last_level;
3995 /* get handicap level in this level set */
3997 token_value = getHashEntry(level_setup_hash, TOKEN_STR_HANDICAP_LEVEL);
4001 int level_nr = atoi(token_value);
4003 if (level_nr < leveldir_current->first_level)
4004 level_nr = leveldir_current->first_level;
4005 if (level_nr > leveldir_current->last_level + 1)
4006 level_nr = leveldir_current->last_level;
4008 if (leveldir_current->user_defined || !leveldir_current->handicap)
4009 level_nr = leveldir_current->last_level;
4011 leveldir_current->handicap_level = level_nr;
4014 /* get number of played and solved levels in this level set */
4016 BEGIN_HASH_ITERATION(level_setup_hash, itr)
4018 char *token = HASH_ITERATION_TOKEN(itr);
4019 char *value = HASH_ITERATION_VALUE(itr);
4021 if (strlen(token) == 3 &&
4022 token[0] >= '0' && token[0] <= '9' &&
4023 token[1] >= '0' && token[1] <= '9' &&
4024 token[2] >= '0' && token[2] <= '9')
4026 int level_nr = atoi(token);
4029 LevelStats_setPlayed(level_nr, atoi(value)); /* read 1st column */
4031 value = strchr(value, ' ');
4034 LevelStats_setSolved(level_nr, atoi(value)); /* read 2nd column */
4037 END_HASH_ITERATION(hash, itr)
4039 freeSetupFileHash(level_setup_hash);
4043 Error(ERR_DEBUG, "using default setup values");
4049 void SaveLevelSetup_SeriesInfo()
4052 char *level_subdir = leveldir_current->subdir;
4053 char *level_nr_str = int2str(level_nr, 0);
4054 char *handicap_level_str = int2str(leveldir_current->handicap_level, 0);
4058 /* ----------------------------------------------------------------------- */
4059 /* ~/.<program>/levelsetup/<level series>/levelsetup.conf */
4060 /* ----------------------------------------------------------------------- */
4062 InitLevelSetupDirectory(level_subdir);
4064 filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
4066 if (!(file = fopen(filename, MODE_WRITE)))
4068 Error(ERR_WARN, "cannot write setup file '%s'", filename);
4073 fprintFileHeader(file, LEVELSETUP_FILENAME);
4075 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_PLAYED_LEVEL,
4077 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_HANDICAP_LEVEL,
4078 handicap_level_str));
4080 for (i = leveldir_current->first_level; i <= leveldir_current->last_level;
4083 if (LevelStats_getPlayed(i) > 0 ||
4084 LevelStats_getSolved(i) > 0)
4089 sprintf(token, "%03d", i);
4090 sprintf(value, "%d %d", LevelStats_getPlayed(i), LevelStats_getSolved(i));
4092 fprintf(file, "%s\n", getFormattedSetupEntry(token, value));
4098 SetFilePermissions(filename, PERMS_PRIVATE);
4103 int LevelStats_getPlayed(int nr)
4105 return (nr >= 0 && nr < MAX_LEVELS ? level_stats[nr].played : 0);
4108 int LevelStats_getSolved(int nr)
4110 return (nr >= 0 && nr < MAX_LEVELS ? level_stats[nr].solved : 0);
4113 void LevelStats_setPlayed(int nr, int value)
4115 if (nr >= 0 && nr < MAX_LEVELS)
4116 level_stats[nr].played = value;
4119 void LevelStats_setSolved(int nr, int value)
4121 if (nr >= 0 && nr < MAX_LEVELS)
4122 level_stats[nr].solved = value;
4125 void LevelStats_incPlayed(int nr)
4127 if (nr >= 0 && nr < MAX_LEVELS)
4128 level_stats[nr].played++;
4131 void LevelStats_incSolved(int nr)
4133 if (nr >= 0 && nr < MAX_LEVELS)
4134 level_stats[nr].solved++;