1 // ============================================================================
2 // Artsoft Retro-Game Library
3 // ----------------------------------------------------------------------------
4 // (c) 1995-2014 by Artsoft Entertainment
7 // https://www.artsoft.org/
8 // ----------------------------------------------------------------------------
10 // ============================================================================
12 #include <sys/types.h>
26 #include "zip/miniunz.h"
29 #define ENABLE_UNUSED_CODE FALSE // for currently unused functions
30 #define DEBUG_NO_CONFIG_FILE FALSE // for extra-verbose debug output
32 #define NUM_LEVELCLASS_DESC 8
34 static char *levelclass_desc[NUM_LEVELCLASS_DESC] =
47 #define LEVELCOLOR(n) (IS_LEVELCLASS_TUTORIAL(n) ? FC_BLUE : \
48 IS_LEVELCLASS_CLASSICS(n) ? FC_RED : \
49 IS_LEVELCLASS_BD(n) ? FC_YELLOW : \
50 IS_LEVELCLASS_EM(n) ? FC_YELLOW : \
51 IS_LEVELCLASS_SP(n) ? FC_YELLOW : \
52 IS_LEVELCLASS_DX(n) ? FC_YELLOW : \
53 IS_LEVELCLASS_SB(n) ? FC_YELLOW : \
54 IS_LEVELCLASS_CONTRIB(n) ? FC_GREEN : \
55 IS_LEVELCLASS_PRIVATE(n) ? FC_RED : \
58 #define LEVELSORTING(n) (IS_LEVELCLASS_TUTORIAL(n) ? 0 : \
59 IS_LEVELCLASS_CLASSICS(n) ? 1 : \
60 IS_LEVELCLASS_BD(n) ? 2 : \
61 IS_LEVELCLASS_EM(n) ? 3 : \
62 IS_LEVELCLASS_SP(n) ? 4 : \
63 IS_LEVELCLASS_DX(n) ? 5 : \
64 IS_LEVELCLASS_SB(n) ? 6 : \
65 IS_LEVELCLASS_CONTRIB(n) ? 7 : \
66 IS_LEVELCLASS_PRIVATE(n) ? 8 : \
69 #define ARTWORKCOLOR(n) (IS_ARTWORKCLASS_CLASSICS(n) ? FC_RED : \
70 IS_ARTWORKCLASS_CONTRIB(n) ? FC_GREEN : \
71 IS_ARTWORKCLASS_PRIVATE(n) ? FC_RED : \
72 IS_ARTWORKCLASS_LEVEL(n) ? FC_YELLOW : \
75 #define ARTWORKSORTING(n) (IS_ARTWORKCLASS_CLASSICS(n) ? 0 : \
76 IS_ARTWORKCLASS_LEVEL(n) ? 1 : \
77 IS_ARTWORKCLASS_CONTRIB(n) ? 2 : \
78 IS_ARTWORKCLASS_PRIVATE(n) ? 3 : \
81 #define TOKEN_VALUE_POSITION_SHORT 32
82 #define TOKEN_VALUE_POSITION_DEFAULT 40
83 #define TOKEN_COMMENT_POSITION_DEFAULT 60
85 #define MAX_COOKIE_LEN 256
88 static void setTreeInfoToDefaults(TreeInfo *, int);
89 static TreeInfo *getTreeInfoCopy(TreeInfo *ti);
90 static int compareTreeInfoEntries(const void *, const void *);
92 static int token_value_position = TOKEN_VALUE_POSITION_DEFAULT;
93 static int token_comment_position = TOKEN_COMMENT_POSITION_DEFAULT;
95 static SetupFileHash *artworkinfo_cache_old = NULL;
96 static SetupFileHash *artworkinfo_cache_new = NULL;
97 static SetupFileHash *optional_tokens_hash = NULL;
98 static boolean use_artworkinfo_cache = TRUE;
101 // ----------------------------------------------------------------------------
103 // ----------------------------------------------------------------------------
105 static char *getLevelClassDescription(TreeInfo *ti)
107 int position = ti->sort_priority / 100;
109 if (position >= 0 && position < NUM_LEVELCLASS_DESC)
110 return levelclass_desc[position];
112 return "Unknown Level Class";
115 static char *getScoreDir(char *level_subdir)
117 static char *score_dir = NULL;
118 static char *score_level_dir = NULL;
119 char *score_subdir = SCORES_DIRECTORY;
121 if (score_dir == NULL)
123 if (program.global_scores)
124 score_dir = getPath2(getCommonDataDir(), score_subdir);
126 score_dir = getPath2(getMainUserGameDataDir(), score_subdir);
129 if (level_subdir != NULL)
131 checked_free(score_level_dir);
133 score_level_dir = getPath2(score_dir, level_subdir);
135 return score_level_dir;
141 static char *getUserSubdir(int nr)
143 static char user_subdir[16] = { 0 };
145 sprintf(user_subdir, "%03d", nr);
150 static char *getUserDir(int nr)
152 static char *user_dir = NULL;
153 char *main_data_dir = getMainUserGameDataDir();
154 char *users_subdir = USERS_DIRECTORY;
155 char *user_subdir = getUserSubdir(nr);
157 checked_free(user_dir);
160 user_dir = getPath3(main_data_dir, users_subdir, user_subdir);
162 user_dir = getPath2(main_data_dir, users_subdir);
167 static char *getLevelSetupDir(char *level_subdir)
169 static char *levelsetup_dir = NULL;
170 char *data_dir = getUserGameDataDir();
171 char *levelsetup_subdir = LEVELSETUP_DIRECTORY;
173 checked_free(levelsetup_dir);
175 if (level_subdir != NULL)
176 levelsetup_dir = getPath3(data_dir, levelsetup_subdir, level_subdir);
178 levelsetup_dir = getPath2(data_dir, levelsetup_subdir);
180 return levelsetup_dir;
183 static char *getCacheDir(void)
185 static char *cache_dir = NULL;
187 if (cache_dir == NULL)
188 cache_dir = getPath2(getMainUserGameDataDir(), CACHE_DIRECTORY);
193 static char *getNetworkDir(void)
195 static char *network_dir = NULL;
197 if (network_dir == NULL)
198 network_dir = getPath2(getMainUserGameDataDir(), NETWORK_DIRECTORY);
203 char *getLevelDirFromTreeInfo(TreeInfo *node)
205 static char *level_dir = NULL;
208 return options.level_directory;
210 checked_free(level_dir);
212 level_dir = getPath2((node->in_user_dir ? getUserLevelDir(NULL) :
213 options.level_directory), node->fullpath);
218 char *getUserLevelDir(char *level_subdir)
220 static char *userlevel_dir = NULL;
221 char *data_dir = getMainUserGameDataDir();
222 char *userlevel_subdir = LEVELS_DIRECTORY;
224 checked_free(userlevel_dir);
226 if (level_subdir != NULL)
227 userlevel_dir = getPath3(data_dir, userlevel_subdir, level_subdir);
229 userlevel_dir = getPath2(data_dir, userlevel_subdir);
231 return userlevel_dir;
234 char *getNetworkLevelDir(char *level_subdir)
236 static char *network_level_dir = NULL;
237 char *data_dir = getNetworkDir();
238 char *networklevel_subdir = LEVELS_DIRECTORY;
240 checked_free(network_level_dir);
242 if (level_subdir != NULL)
243 network_level_dir = getPath3(data_dir, networklevel_subdir, level_subdir);
245 network_level_dir = getPath2(data_dir, networklevel_subdir);
247 return network_level_dir;
250 char *getCurrentLevelDir(void)
252 return getLevelDirFromTreeInfo(leveldir_current);
255 char *getNewUserLevelSubdir(void)
257 static char *new_level_subdir = NULL;
258 char *subdir_prefix = getLoginName();
259 char subdir_suffix[10];
260 int max_suffix_number = 1000;
263 while (++i < max_suffix_number)
265 sprintf(subdir_suffix, "_%d", i);
267 checked_free(new_level_subdir);
268 new_level_subdir = getStringCat2(subdir_prefix, subdir_suffix);
270 if (!directoryExists(getUserLevelDir(new_level_subdir)))
274 return new_level_subdir;
277 static char *getTapeDir(char *level_subdir)
279 static char *tape_dir = NULL;
280 char *data_dir = getUserGameDataDir();
281 char *tape_subdir = TAPES_DIRECTORY;
283 checked_free(tape_dir);
285 if (level_subdir != NULL)
286 tape_dir = getPath3(data_dir, tape_subdir, level_subdir);
288 tape_dir = getPath2(data_dir, tape_subdir);
293 static char *getSolutionTapeDir(void)
295 static char *tape_dir = NULL;
296 char *data_dir = getCurrentLevelDir();
297 char *tape_subdir = TAPES_DIRECTORY;
299 checked_free(tape_dir);
301 tape_dir = getPath2(data_dir, tape_subdir);
306 static char *getDefaultGraphicsDir(char *graphics_subdir)
308 static char *graphics_dir = NULL;
310 if (graphics_subdir == NULL)
311 return options.graphics_directory;
313 checked_free(graphics_dir);
315 graphics_dir = getPath2(options.graphics_directory, graphics_subdir);
320 static char *getDefaultSoundsDir(char *sounds_subdir)
322 static char *sounds_dir = NULL;
324 if (sounds_subdir == NULL)
325 return options.sounds_directory;
327 checked_free(sounds_dir);
329 sounds_dir = getPath2(options.sounds_directory, sounds_subdir);
334 static char *getDefaultMusicDir(char *music_subdir)
336 static char *music_dir = NULL;
338 if (music_subdir == NULL)
339 return options.music_directory;
341 checked_free(music_dir);
343 music_dir = getPath2(options.music_directory, music_subdir);
348 static char *getClassicArtworkSet(int type)
350 return (type == TREE_TYPE_GRAPHICS_DIR ? GFX_CLASSIC_SUBDIR :
351 type == TREE_TYPE_SOUNDS_DIR ? SND_CLASSIC_SUBDIR :
352 type == TREE_TYPE_MUSIC_DIR ? MUS_CLASSIC_SUBDIR : "");
355 static char *getClassicArtworkDir(int type)
357 return (type == TREE_TYPE_GRAPHICS_DIR ?
358 getDefaultGraphicsDir(GFX_CLASSIC_SUBDIR) :
359 type == TREE_TYPE_SOUNDS_DIR ?
360 getDefaultSoundsDir(SND_CLASSIC_SUBDIR) :
361 type == TREE_TYPE_MUSIC_DIR ?
362 getDefaultMusicDir(MUS_CLASSIC_SUBDIR) : "");
365 char *getUserGraphicsDir(void)
367 static char *usergraphics_dir = NULL;
369 if (usergraphics_dir == NULL)
370 usergraphics_dir = getPath2(getMainUserGameDataDir(), GRAPHICS_DIRECTORY);
372 return usergraphics_dir;
375 char *getUserSoundsDir(void)
377 static char *usersounds_dir = NULL;
379 if (usersounds_dir == NULL)
380 usersounds_dir = getPath2(getMainUserGameDataDir(), SOUNDS_DIRECTORY);
382 return usersounds_dir;
385 char *getUserMusicDir(void)
387 static char *usermusic_dir = NULL;
389 if (usermusic_dir == NULL)
390 usermusic_dir = getPath2(getMainUserGameDataDir(), MUSIC_DIRECTORY);
392 return usermusic_dir;
395 static char *getSetupArtworkDir(TreeInfo *ti)
397 static char *artwork_dir = NULL;
402 checked_free(artwork_dir);
404 artwork_dir = getPath2(ti->basepath, ti->fullpath);
409 char *setLevelArtworkDir(TreeInfo *ti)
411 char **artwork_path_ptr, **artwork_set_ptr;
412 TreeInfo *level_artwork;
414 if (ti == NULL || leveldir_current == NULL)
417 artwork_path_ptr = LEVELDIR_ARTWORK_PATH_PTR(leveldir_current, ti->type);
418 artwork_set_ptr = LEVELDIR_ARTWORK_SET_PTR( leveldir_current, ti->type);
420 checked_free(*artwork_path_ptr);
422 if ((level_artwork = getTreeInfoFromIdentifier(ti, *artwork_set_ptr)))
424 *artwork_path_ptr = getStringCopy(getSetupArtworkDir(level_artwork));
429 No (or non-existing) artwork configured in "levelinfo.conf". This would
430 normally result in using the artwork configured in the setup menu. But
431 if an artwork subdirectory exists (which might contain custom artwork
432 or an artwork configuration file), this level artwork must be treated
433 as relative to the default "classic" artwork, not to the artwork that
434 is currently configured in the setup menu.
436 Update: For "special" versions of R'n'D (like "R'n'D jue"), do not use
437 the "default" artwork (which would be "jue0" for "R'n'D jue"), but use
438 the real "classic" artwork from the original R'n'D (like "gfx_classic").
441 char *dir = getPath2(getCurrentLevelDir(), ARTWORK_DIRECTORY(ti->type));
443 checked_free(*artwork_set_ptr);
445 if (directoryExists(dir))
447 *artwork_path_ptr = getStringCopy(getClassicArtworkDir(ti->type));
448 *artwork_set_ptr = getStringCopy(getClassicArtworkSet(ti->type));
452 *artwork_path_ptr = getStringCopy(UNDEFINED_FILENAME);
453 *artwork_set_ptr = NULL;
459 return *artwork_set_ptr;
462 static char *getLevelArtworkSet(int type)
464 if (leveldir_current == NULL)
467 return LEVELDIR_ARTWORK_SET(leveldir_current, type);
470 static char *getLevelArtworkDir(int type)
472 if (leveldir_current == NULL)
473 return UNDEFINED_FILENAME;
475 return LEVELDIR_ARTWORK_PATH(leveldir_current, type);
478 char *getProgramMainDataPath(char *command_filename, char *base_path)
480 // check if the program's main data base directory is configured
481 if (!strEqual(base_path, "."))
482 return getStringCopy(base_path);
484 /* if the program is configured to start from current directory (default),
485 determine program package directory from program binary (some versions
486 of KDE/Konqueror and Mac OS X (especially "Mavericks") apparently do not
487 set the current working directory to the program package directory) */
488 char *main_data_path = getBasePath(command_filename);
490 #if defined(PLATFORM_MACOSX)
491 if (strSuffix(main_data_path, MAC_APP_BINARY_SUBDIR))
493 char *main_data_path_old = main_data_path;
495 // cut relative path to Mac OS X application binary directory from path
496 main_data_path[strlen(main_data_path) -
497 strlen(MAC_APP_BINARY_SUBDIR)] = '\0';
499 // cut trailing path separator from path (but not if path is root directory)
500 if (strSuffix(main_data_path, "/") && !strEqual(main_data_path, "/"))
501 main_data_path[strlen(main_data_path) - 1] = '\0';
503 // replace empty path with current directory
504 if (strEqual(main_data_path, ""))
505 main_data_path = ".";
507 // add relative path to Mac OS X application resources directory to path
508 main_data_path = getPath2(main_data_path, MAC_APP_FILES_SUBDIR);
510 free(main_data_path_old);
514 return main_data_path;
517 char *getProgramConfigFilename(char *command_filename)
519 static char *config_filename_1 = NULL;
520 static char *config_filename_2 = NULL;
521 static char *config_filename_3 = NULL;
522 static boolean initialized = FALSE;
526 char *command_filename_1 = getStringCopy(command_filename);
528 // strip trailing executable suffix from command filename
529 if (strSuffix(command_filename_1, ".exe"))
530 command_filename_1[strlen(command_filename_1) - 4] = '\0';
532 char *ro_base_path = getProgramMainDataPath(command_filename, RO_BASE_PATH);
533 char *conf_directory = getPath2(ro_base_path, CONF_DIRECTORY);
535 char *command_basepath = getBasePath(command_filename);
536 char *command_basename = getBaseNameNoSuffix(command_filename);
537 char *command_filename_2 = getPath2(command_basepath, command_basename);
539 config_filename_1 = getStringCat2(command_filename_1, ".conf");
540 config_filename_2 = getStringCat2(command_filename_2, ".conf");
541 config_filename_3 = getPath2(conf_directory, SETUP_FILENAME);
543 checked_free(ro_base_path);
544 checked_free(conf_directory);
546 checked_free(command_basepath);
547 checked_free(command_basename);
549 checked_free(command_filename_1);
550 checked_free(command_filename_2);
555 // 1st try: look for config file that exactly matches the binary filename
556 if (fileExists(config_filename_1))
557 return config_filename_1;
559 // 2nd try: look for config file that matches binary filename without suffix
560 if (fileExists(config_filename_2))
561 return config_filename_2;
563 // 3rd try: return setup config filename in global program config directory
564 return config_filename_3;
567 char *getTapeFilename(int nr)
569 static char *filename = NULL;
570 char basename[MAX_FILENAME_LEN];
572 checked_free(filename);
574 sprintf(basename, "%03d.%s", nr, TAPEFILE_EXTENSION);
575 filename = getPath2(getTapeDir(leveldir_current->subdir), basename);
580 char *getSolutionTapeFilename(int nr)
582 static char *filename = NULL;
583 char basename[MAX_FILENAME_LEN];
585 checked_free(filename);
587 sprintf(basename, "%03d.%s", nr, TAPEFILE_EXTENSION);
588 filename = getPath2(getSolutionTapeDir(), basename);
590 if (!fileExists(filename))
592 static char *filename_sln = NULL;
594 checked_free(filename_sln);
596 sprintf(basename, "%03d.sln", nr);
597 filename_sln = getPath2(getSolutionTapeDir(), basename);
599 if (fileExists(filename_sln))
606 char *getScoreFilename(int nr)
608 static char *filename = NULL;
609 char basename[MAX_FILENAME_LEN];
611 checked_free(filename);
613 sprintf(basename, "%03d.%s", nr, SCOREFILE_EXTENSION);
615 // used instead of "leveldir_current->subdir" (for network games)
616 filename = getPath2(getScoreDir(levelset.identifier), basename);
621 char *getSetupFilename(void)
623 static char *filename = NULL;
625 checked_free(filename);
627 filename = getPath2(getSetupDir(), SETUP_FILENAME);
632 char *getDefaultSetupFilename(void)
634 return program.config_filename;
637 char *getEditorSetupFilename(void)
639 static char *filename = NULL;
641 checked_free(filename);
642 filename = getPath2(getCurrentLevelDir(), EDITORSETUP_FILENAME);
644 if (fileExists(filename))
647 checked_free(filename);
648 filename = getPath2(getSetupDir(), EDITORSETUP_FILENAME);
653 char *getHelpAnimFilename(void)
655 static char *filename = NULL;
657 checked_free(filename);
659 filename = getPath2(getCurrentLevelDir(), HELPANIM_FILENAME);
664 char *getHelpTextFilename(void)
666 static char *filename = NULL;
668 checked_free(filename);
670 filename = getPath2(getCurrentLevelDir(), HELPTEXT_FILENAME);
675 char *getLevelSetInfoFilename(void)
677 static char *filename = NULL;
692 for (i = 0; basenames[i] != NULL; i++)
694 checked_free(filename);
695 filename = getPath2(getCurrentLevelDir(), basenames[i]);
697 if (fileExists(filename))
704 static char *getLevelSetTitleMessageBasename(int nr, boolean initial)
706 static char basename[32];
708 sprintf(basename, "%s_%d.txt",
709 (initial ? "titlemessage_initial" : "titlemessage"), nr + 1);
714 char *getLevelSetTitleMessageFilename(int nr, boolean initial)
716 static char *filename = NULL;
718 boolean skip_setup_artwork = FALSE;
720 checked_free(filename);
722 basename = getLevelSetTitleMessageBasename(nr, initial);
724 if (!gfx.override_level_graphics)
726 // 1st try: look for special artwork in current level series directory
727 filename = getPath3(getCurrentLevelDir(), GRAPHICS_DIRECTORY, basename);
728 if (fileExists(filename))
733 // 2nd try: look for message file in current level set directory
734 filename = getPath2(getCurrentLevelDir(), basename);
735 if (fileExists(filename))
740 // check if there is special artwork configured in level series config
741 if (getLevelArtworkSet(ARTWORK_TYPE_GRAPHICS) != NULL)
743 // 3rd try: look for special artwork configured in level series config
744 filename = getPath2(getLevelArtworkDir(ARTWORK_TYPE_GRAPHICS), basename);
745 if (fileExists(filename))
750 // take missing artwork configured in level set config from default
751 skip_setup_artwork = TRUE;
755 if (!skip_setup_artwork)
757 // 4th try: look for special artwork in configured artwork directory
758 filename = getPath2(getSetupArtworkDir(artwork.gfx_current), basename);
759 if (fileExists(filename))
765 // 5th try: look for default artwork in new default artwork directory
766 filename = getPath2(getDefaultGraphicsDir(GFX_DEFAULT_SUBDIR), basename);
767 if (fileExists(filename))
772 // 6th try: look for default artwork in old default artwork directory
773 filename = getPath2(options.graphics_directory, basename);
774 if (fileExists(filename))
777 return NULL; // cannot find specified artwork file anywhere
780 static char *getCorrectedArtworkBasename(char *basename)
785 char *getCustomImageFilename(char *basename)
787 static char *filename = NULL;
788 boolean skip_setup_artwork = FALSE;
790 checked_free(filename);
792 basename = getCorrectedArtworkBasename(basename);
794 if (!gfx.override_level_graphics)
796 // 1st try: look for special artwork in current level series directory
797 filename = getImg3(getCurrentLevelDir(), GRAPHICS_DIRECTORY, basename);
798 if (fileExists(filename))
803 // check if there is special artwork configured in level series config
804 if (getLevelArtworkSet(ARTWORK_TYPE_GRAPHICS) != NULL)
806 // 2nd try: look for special artwork configured in level series config
807 filename = getImg2(getLevelArtworkDir(ARTWORK_TYPE_GRAPHICS), basename);
808 if (fileExists(filename))
813 // take missing artwork configured in level set config from default
814 skip_setup_artwork = TRUE;
818 if (!skip_setup_artwork)
820 // 3rd try: look for special artwork in configured artwork directory
821 filename = getImg2(getSetupArtworkDir(artwork.gfx_current), basename);
822 if (fileExists(filename))
828 // 4th try: look for default artwork in new default artwork directory
829 filename = getImg2(getDefaultGraphicsDir(GFX_DEFAULT_SUBDIR), basename);
830 if (fileExists(filename))
835 // 5th try: look for default artwork in old default artwork directory
836 filename = getImg2(options.graphics_directory, basename);
837 if (fileExists(filename))
840 if (!strEqual(GFX_FALLBACK_FILENAME, UNDEFINED_FILENAME))
844 Warn("cannot find artwork file '%s' (using fallback)", basename);
846 // 6th try: look for fallback artwork in old default artwork directory
847 // (needed to prevent errors when trying to access unused artwork files)
848 filename = getImg2(options.graphics_directory, GFX_FALLBACK_FILENAME);
849 if (fileExists(filename))
853 return NULL; // cannot find specified artwork file anywhere
856 char *getCustomSoundFilename(char *basename)
858 static char *filename = NULL;
859 boolean skip_setup_artwork = FALSE;
861 checked_free(filename);
863 basename = getCorrectedArtworkBasename(basename);
865 if (!gfx.override_level_sounds)
867 // 1st try: look for special artwork in current level series directory
868 filename = getPath3(getCurrentLevelDir(), SOUNDS_DIRECTORY, basename);
869 if (fileExists(filename))
874 // check if there is special artwork configured in level series config
875 if (getLevelArtworkSet(ARTWORK_TYPE_SOUNDS) != NULL)
877 // 2nd try: look for special artwork configured in level series config
878 filename = getPath2(getLevelArtworkDir(TREE_TYPE_SOUNDS_DIR), basename);
879 if (fileExists(filename))
884 // take missing artwork configured in level set config from default
885 skip_setup_artwork = TRUE;
889 if (!skip_setup_artwork)
891 // 3rd try: look for special artwork in configured artwork directory
892 filename = getPath2(getSetupArtworkDir(artwork.snd_current), basename);
893 if (fileExists(filename))
899 // 4th try: look for default artwork in new default artwork directory
900 filename = getPath2(getDefaultSoundsDir(SND_DEFAULT_SUBDIR), basename);
901 if (fileExists(filename))
906 // 5th try: look for default artwork in old default artwork directory
907 filename = getPath2(options.sounds_directory, basename);
908 if (fileExists(filename))
911 if (!strEqual(SND_FALLBACK_FILENAME, UNDEFINED_FILENAME))
915 Warn("cannot find artwork file '%s' (using fallback)", basename);
917 // 6th try: look for fallback artwork in old default artwork directory
918 // (needed to prevent errors when trying to access unused artwork files)
919 filename = getPath2(options.sounds_directory, SND_FALLBACK_FILENAME);
920 if (fileExists(filename))
924 return NULL; // cannot find specified artwork file anywhere
927 char *getCustomMusicFilename(char *basename)
929 static char *filename = NULL;
930 boolean skip_setup_artwork = FALSE;
932 checked_free(filename);
934 basename = getCorrectedArtworkBasename(basename);
936 if (!gfx.override_level_music)
938 // 1st try: look for special artwork in current level series directory
939 filename = getPath3(getCurrentLevelDir(), MUSIC_DIRECTORY, basename);
940 if (fileExists(filename))
945 // check if there is special artwork configured in level series config
946 if (getLevelArtworkSet(ARTWORK_TYPE_MUSIC) != NULL)
948 // 2nd try: look for special artwork configured in level series config
949 filename = getPath2(getLevelArtworkDir(TREE_TYPE_MUSIC_DIR), basename);
950 if (fileExists(filename))
955 // take missing artwork configured in level set config from default
956 skip_setup_artwork = TRUE;
960 if (!skip_setup_artwork)
962 // 3rd try: look for special artwork in configured artwork directory
963 filename = getPath2(getSetupArtworkDir(artwork.mus_current), basename);
964 if (fileExists(filename))
970 // 4th try: look for default artwork in new default artwork directory
971 filename = getPath2(getDefaultMusicDir(MUS_DEFAULT_SUBDIR), basename);
972 if (fileExists(filename))
977 // 5th try: look for default artwork in old default artwork directory
978 filename = getPath2(options.music_directory, basename);
979 if (fileExists(filename))
982 if (!strEqual(MUS_FALLBACK_FILENAME, UNDEFINED_FILENAME))
986 Warn("cannot find artwork file '%s' (using fallback)", basename);
988 // 6th try: look for fallback artwork in old default artwork directory
989 // (needed to prevent errors when trying to access unused artwork files)
990 filename = getPath2(options.music_directory, MUS_FALLBACK_FILENAME);
991 if (fileExists(filename))
995 return NULL; // cannot find specified artwork file anywhere
998 char *getCustomArtworkFilename(char *basename, int type)
1000 if (type == ARTWORK_TYPE_GRAPHICS)
1001 return getCustomImageFilename(basename);
1002 else if (type == ARTWORK_TYPE_SOUNDS)
1003 return getCustomSoundFilename(basename);
1004 else if (type == ARTWORK_TYPE_MUSIC)
1005 return getCustomMusicFilename(basename);
1007 return UNDEFINED_FILENAME;
1010 char *getCustomArtworkConfigFilename(int type)
1012 return getCustomArtworkFilename(ARTWORKINFO_FILENAME(type), type);
1015 char *getCustomArtworkLevelConfigFilename(int type)
1017 static char *filename = NULL;
1019 checked_free(filename);
1021 filename = getPath2(getLevelArtworkDir(type), ARTWORKINFO_FILENAME(type));
1026 char *getCustomMusicDirectory(void)
1028 static char *directory = NULL;
1029 boolean skip_setup_artwork = FALSE;
1031 checked_free(directory);
1033 if (!gfx.override_level_music)
1035 // 1st try: look for special artwork in current level series directory
1036 directory = getPath2(getCurrentLevelDir(), MUSIC_DIRECTORY);
1037 if (directoryExists(directory))
1042 // check if there is special artwork configured in level series config
1043 if (getLevelArtworkSet(ARTWORK_TYPE_MUSIC) != NULL)
1045 // 2nd try: look for special artwork configured in level series config
1046 directory = getStringCopy(getLevelArtworkDir(TREE_TYPE_MUSIC_DIR));
1047 if (directoryExists(directory))
1052 // take missing artwork configured in level set config from default
1053 skip_setup_artwork = TRUE;
1057 if (!skip_setup_artwork)
1059 // 3rd try: look for special artwork in configured artwork directory
1060 directory = getStringCopy(getSetupArtworkDir(artwork.mus_current));
1061 if (directoryExists(directory))
1067 // 4th try: look for default artwork in new default artwork directory
1068 directory = getStringCopy(getDefaultMusicDir(MUS_DEFAULT_SUBDIR));
1069 if (directoryExists(directory))
1074 // 5th try: look for default artwork in old default artwork directory
1075 directory = getStringCopy(options.music_directory);
1076 if (directoryExists(directory))
1079 return NULL; // cannot find specified artwork file anywhere
1082 void InitTapeDirectory(char *level_subdir)
1084 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
1085 createDirectory(getTapeDir(NULL), "main tape", PERMS_PRIVATE);
1086 createDirectory(getTapeDir(level_subdir), "level tape", PERMS_PRIVATE);
1089 void InitScoreDirectory(char *level_subdir)
1091 int permissions = (program.global_scores ? PERMS_PUBLIC : PERMS_PRIVATE);
1093 if (program.global_scores)
1094 createDirectory(getCommonDataDir(), "common data", permissions);
1096 createDirectory(getMainUserGameDataDir(), "main user data", permissions);
1098 createDirectory(getScoreDir(NULL), "main score", permissions);
1099 createDirectory(getScoreDir(level_subdir), "level score", permissions);
1102 static void SaveUserLevelInfo(void);
1104 void InitUserLevelDirectory(char *level_subdir)
1106 if (!directoryExists(getUserLevelDir(level_subdir)))
1108 createDirectory(getMainUserGameDataDir(), "main user data", PERMS_PRIVATE);
1109 createDirectory(getUserLevelDir(NULL), "main user level", PERMS_PRIVATE);
1110 createDirectory(getUserLevelDir(level_subdir), "user level", PERMS_PRIVATE);
1112 if (setup.internal.create_user_levelset)
1113 SaveUserLevelInfo();
1117 void InitNetworkLevelDirectory(char *level_subdir)
1119 if (!directoryExists(getNetworkLevelDir(level_subdir)))
1121 createDirectory(getMainUserGameDataDir(), "main user data", PERMS_PRIVATE);
1122 createDirectory(getNetworkDir(), "network data", PERMS_PRIVATE);
1123 createDirectory(getNetworkLevelDir(NULL), "main network level", PERMS_PRIVATE);
1124 createDirectory(getNetworkLevelDir(level_subdir), "network level", PERMS_PRIVATE);
1128 void InitLevelSetupDirectory(char *level_subdir)
1130 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
1131 createDirectory(getLevelSetupDir(NULL), "main level setup", PERMS_PRIVATE);
1132 createDirectory(getLevelSetupDir(level_subdir), "level setup", PERMS_PRIVATE);
1135 static void InitCacheDirectory(void)
1137 createDirectory(getMainUserGameDataDir(), "main user data", PERMS_PRIVATE);
1138 createDirectory(getCacheDir(), "cache data", PERMS_PRIVATE);
1142 // ----------------------------------------------------------------------------
1143 // some functions to handle lists of level and artwork directories
1144 // ----------------------------------------------------------------------------
1146 TreeInfo *newTreeInfo(void)
1148 return checked_calloc(sizeof(TreeInfo));
1151 TreeInfo *newTreeInfo_setDefaults(int type)
1153 TreeInfo *ti = newTreeInfo();
1155 setTreeInfoToDefaults(ti, type);
1160 void pushTreeInfo(TreeInfo **node_first, TreeInfo *node_new)
1162 node_new->next = *node_first;
1163 *node_first = node_new;
1166 int numTreeInfo(TreeInfo *node)
1179 boolean validLevelSeries(TreeInfo *node)
1181 return (node != NULL && !node->node_group && !node->parent_link);
1184 TreeInfo *getFirstValidTreeInfoEntry(TreeInfo *node)
1189 if (node->node_group) // enter level group (step down into tree)
1190 return getFirstValidTreeInfoEntry(node->node_group);
1191 else if (node->parent_link) // skip start entry of level group
1193 if (node->next) // get first real level series entry
1194 return getFirstValidTreeInfoEntry(node->next);
1195 else // leave empty level group and go on
1196 return getFirstValidTreeInfoEntry(node->node_parent->next);
1198 else // this seems to be a regular level series
1202 TreeInfo *getTreeInfoFirstGroupEntry(TreeInfo *node)
1207 if (node->node_parent == NULL) // top level group
1208 return *node->node_top;
1209 else // sub level group
1210 return node->node_parent->node_group;
1213 int numTreeInfoInGroup(TreeInfo *node)
1215 return numTreeInfo(getTreeInfoFirstGroupEntry(node));
1218 int getPosFromTreeInfo(TreeInfo *node)
1220 TreeInfo *node_cmp = getTreeInfoFirstGroupEntry(node);
1225 if (node_cmp == node)
1229 node_cmp = node_cmp->next;
1235 TreeInfo *getTreeInfoFromPos(TreeInfo *node, int pos)
1237 TreeInfo *node_default = node;
1249 return node_default;
1252 static TreeInfo *getTreeInfoFromIdentifierExt(TreeInfo *node, char *identifier,
1253 boolean include_node_groups)
1255 if (identifier == NULL)
1260 if (node->node_group)
1262 if (include_node_groups && strEqual(identifier, node->identifier))
1265 TreeInfo *node_group = getTreeInfoFromIdentifierExt(node->node_group,
1267 include_node_groups);
1271 else if (!node->parent_link)
1273 if (strEqual(identifier, node->identifier))
1283 TreeInfo *getTreeInfoFromIdentifier(TreeInfo *node, char *identifier)
1285 return getTreeInfoFromIdentifierExt(node, identifier, FALSE);
1288 static TreeInfo *cloneTreeNode(TreeInfo **node_top, TreeInfo *node_parent,
1289 TreeInfo *node, boolean skip_sets_without_levels)
1296 if (!node->parent_link && !node->level_group &&
1297 skip_sets_without_levels && node->levels == 0)
1298 return cloneTreeNode(node_top, node_parent, node->next,
1299 skip_sets_without_levels);
1301 node_new = getTreeInfoCopy(node); // copy complete node
1303 node_new->node_top = node_top; // correct top node link
1304 node_new->node_parent = node_parent; // correct parent node link
1306 if (node->level_group)
1307 node_new->node_group = cloneTreeNode(node_top, node_new, node->node_group,
1308 skip_sets_without_levels);
1310 node_new->next = cloneTreeNode(node_top, node_parent, node->next,
1311 skip_sets_without_levels);
1316 static void cloneTree(TreeInfo **ti_new, TreeInfo *ti, boolean skip_empty_sets)
1318 TreeInfo *ti_cloned = cloneTreeNode(ti_new, NULL, ti, skip_empty_sets);
1320 *ti_new = ti_cloned;
1323 static boolean adjustTreeGraphicsForEMC(TreeInfo *node)
1325 boolean settings_changed = FALSE;
1329 boolean want_ecs = (setup.prefer_aga_graphics == FALSE);
1330 boolean want_aga = (setup.prefer_aga_graphics == TRUE);
1331 boolean has_only_ecs = (!node->graphics_set && !node->graphics_set_aga);
1332 boolean has_only_aga = (!node->graphics_set && !node->graphics_set_ecs);
1333 char *graphics_set = NULL;
1335 if (node->graphics_set_ecs && (want_ecs || has_only_ecs))
1336 graphics_set = node->graphics_set_ecs;
1338 if (node->graphics_set_aga && (want_aga || has_only_aga))
1339 graphics_set = node->graphics_set_aga;
1341 if (graphics_set && !strEqual(node->graphics_set, graphics_set))
1343 setString(&node->graphics_set, graphics_set);
1344 settings_changed = TRUE;
1347 if (node->node_group != NULL)
1348 settings_changed |= adjustTreeGraphicsForEMC(node->node_group);
1353 return settings_changed;
1356 static boolean adjustTreeSoundsForEMC(TreeInfo *node)
1358 boolean settings_changed = FALSE;
1362 boolean want_default = (setup.prefer_lowpass_sounds == FALSE);
1363 boolean want_lowpass = (setup.prefer_lowpass_sounds == TRUE);
1364 boolean has_only_default = (!node->sounds_set && !node->sounds_set_lowpass);
1365 boolean has_only_lowpass = (!node->sounds_set && !node->sounds_set_default);
1366 char *sounds_set = NULL;
1368 if (node->sounds_set_default && (want_default || has_only_default))
1369 sounds_set = node->sounds_set_default;
1371 if (node->sounds_set_lowpass && (want_lowpass || has_only_lowpass))
1372 sounds_set = node->sounds_set_lowpass;
1374 if (sounds_set && !strEqual(node->sounds_set, sounds_set))
1376 setString(&node->sounds_set, sounds_set);
1377 settings_changed = TRUE;
1380 if (node->node_group != NULL)
1381 settings_changed |= adjustTreeSoundsForEMC(node->node_group);
1386 return settings_changed;
1389 void dumpTreeInfo(TreeInfo *node, int depth)
1391 char bullet_list[] = { '-', '*', 'o' };
1395 Debug("tree", "Dumping TreeInfo:");
1399 char bullet = bullet_list[depth % ARRAY_SIZE(bullet_list)];
1401 for (i = 0; i < depth * 2; i++)
1402 DebugContinued("", " ");
1404 DebugContinued("tree", "%c '%s' ['%s] [PARENT: '%s'] %s\n",
1405 bullet, node->name, node->identifier,
1406 (node->node_parent ? node->node_parent->identifier : "-"),
1407 (node->node_group ? "[GROUP]" : ""));
1410 // use for dumping artwork info tree
1411 Debug("tree", "subdir == '%s' ['%s', '%s'] [%d])",
1412 node->subdir, node->fullpath, node->basepath, node->in_user_dir);
1415 if (node->node_group != NULL)
1416 dumpTreeInfo(node->node_group, depth + 1);
1422 void sortTreeInfoBySortFunction(TreeInfo **node_first,
1423 int (*compare_function)(const void *,
1426 int num_nodes = numTreeInfo(*node_first);
1427 TreeInfo **sort_array;
1428 TreeInfo *node = *node_first;
1434 // allocate array for sorting structure pointers
1435 sort_array = checked_calloc(num_nodes * sizeof(TreeInfo *));
1437 // writing structure pointers to sorting array
1438 while (i < num_nodes && node) // double boundary check...
1440 sort_array[i] = node;
1446 // sorting the structure pointers in the sorting array
1447 qsort(sort_array, num_nodes, sizeof(TreeInfo *),
1450 // update the linkage of list elements with the sorted node array
1451 for (i = 0; i < num_nodes - 1; i++)
1452 sort_array[i]->next = sort_array[i + 1];
1453 sort_array[num_nodes - 1]->next = NULL;
1455 // update the linkage of the main list anchor pointer
1456 *node_first = sort_array[0];
1460 // now recursively sort the level group structures
1464 if (node->node_group != NULL)
1465 sortTreeInfoBySortFunction(&node->node_group, compare_function);
1471 void sortTreeInfo(TreeInfo **node_first)
1473 sortTreeInfoBySortFunction(node_first, compareTreeInfoEntries);
1477 // ============================================================================
1478 // some stuff from "files.c"
1479 // ============================================================================
1481 #if defined(PLATFORM_WIN32)
1483 #define S_IRGRP S_IRUSR
1486 #define S_IROTH S_IRUSR
1489 #define S_IWGRP S_IWUSR
1492 #define S_IWOTH S_IWUSR
1495 #define S_IXGRP S_IXUSR
1498 #define S_IXOTH S_IXUSR
1501 #define S_IRWXG (S_IRGRP | S_IWGRP | S_IXGRP)
1506 #endif // PLATFORM_WIN32
1508 // file permissions for newly written files
1509 #define MODE_R_ALL (S_IRUSR | S_IRGRP | S_IROTH)
1510 #define MODE_W_ALL (S_IWUSR | S_IWGRP | S_IWOTH)
1511 #define MODE_X_ALL (S_IXUSR | S_IXGRP | S_IXOTH)
1513 #define MODE_W_PRIVATE (S_IWUSR)
1514 #define MODE_W_PUBLIC_FILE (S_IWUSR | S_IWGRP)
1515 #define MODE_W_PUBLIC_DIR (S_IWUSR | S_IWGRP | S_ISGID)
1517 #define DIR_PERMS_PRIVATE (MODE_R_ALL | MODE_X_ALL | MODE_W_PRIVATE)
1518 #define DIR_PERMS_PUBLIC (MODE_R_ALL | MODE_X_ALL | MODE_W_PUBLIC_DIR)
1519 #define DIR_PERMS_PUBLIC_ALL (MODE_R_ALL | MODE_X_ALL | MODE_W_ALL)
1521 #define FILE_PERMS_PRIVATE (MODE_R_ALL | MODE_W_PRIVATE)
1522 #define FILE_PERMS_PUBLIC (MODE_R_ALL | MODE_W_PUBLIC_FILE)
1523 #define FILE_PERMS_PUBLIC_ALL (MODE_R_ALL | MODE_W_ALL)
1526 char *getHomeDir(void)
1528 static char *dir = NULL;
1530 #if defined(PLATFORM_WIN32)
1533 dir = checked_malloc(MAX_PATH + 1);
1535 if (!SUCCEEDED(SHGetFolderPath(NULL, CSIDL_PERSONAL, NULL, 0, dir)))
1538 #elif defined(PLATFORM_UNIX)
1541 if ((dir = getenv("HOME")) == NULL)
1543 dir = getUnixHomeDir();
1546 dir = getStringCopy(dir);
1558 char *getCommonDataDir(void)
1560 static char *common_data_dir = NULL;
1562 #if defined(PLATFORM_WIN32)
1563 if (common_data_dir == NULL)
1565 char *dir = checked_malloc(MAX_PATH + 1);
1567 if (SUCCEEDED(SHGetFolderPath(NULL, CSIDL_COMMON_DOCUMENTS, NULL, 0, dir))
1568 && !strEqual(dir, "")) // empty for Windows 95/98
1569 common_data_dir = getPath2(dir, program.userdata_subdir);
1571 common_data_dir = options.rw_base_directory;
1574 if (common_data_dir == NULL)
1575 common_data_dir = options.rw_base_directory;
1578 return common_data_dir;
1581 char *getPersonalDataDir(void)
1583 static char *personal_data_dir = NULL;
1585 #if defined(PLATFORM_MACOSX)
1586 if (personal_data_dir == NULL)
1587 personal_data_dir = getPath2(getHomeDir(), "Documents");
1589 if (personal_data_dir == NULL)
1590 personal_data_dir = getHomeDir();
1593 return personal_data_dir;
1596 char *getMainUserGameDataDir(void)
1598 static char *main_user_data_dir = NULL;
1600 #if defined(PLATFORM_ANDROID)
1601 if (main_user_data_dir == NULL)
1602 main_user_data_dir = (char *)(SDL_AndroidGetExternalStorageState() &
1603 SDL_ANDROID_EXTERNAL_STORAGE_WRITE ?
1604 SDL_AndroidGetExternalStoragePath() :
1605 SDL_AndroidGetInternalStoragePath());
1607 if (main_user_data_dir == NULL)
1608 main_user_data_dir = getPath2(getPersonalDataDir(),
1609 program.userdata_subdir);
1612 return main_user_data_dir;
1615 char *getUserGameDataDir(void)
1618 return getMainUserGameDataDir();
1620 return getUserDir(user.nr);
1623 char *getSetupDir(void)
1625 return getUserGameDataDir();
1628 static mode_t posix_umask(mode_t mask)
1630 #if defined(PLATFORM_UNIX)
1637 static int posix_mkdir(const char *pathname, mode_t mode)
1639 #if defined(PLATFORM_WIN32)
1640 return mkdir(pathname);
1642 return mkdir(pathname, mode);
1646 static boolean posix_process_running_setgid(void)
1648 #if defined(PLATFORM_UNIX)
1649 return (getgid() != getegid());
1655 void createDirectory(char *dir, char *text, int permission_class)
1657 if (directoryExists(dir))
1660 // leave "other" permissions in umask untouched, but ensure group parts
1661 // of USERDATA_DIR_MODE are not masked
1662 mode_t dir_mode = (permission_class == PERMS_PRIVATE ?
1663 DIR_PERMS_PRIVATE : DIR_PERMS_PUBLIC);
1664 mode_t last_umask = posix_umask(0);
1665 mode_t group_umask = ~(dir_mode & S_IRWXG);
1666 int running_setgid = posix_process_running_setgid();
1668 if (permission_class == PERMS_PUBLIC)
1670 // if we're setgid, protect files against "other"
1671 // else keep umask(0) to make the dir world-writable
1674 posix_umask(last_umask & group_umask);
1676 dir_mode = DIR_PERMS_PUBLIC_ALL;
1679 if (posix_mkdir(dir, dir_mode) != 0)
1680 Warn("cannot create %s directory '%s': %s", text, dir, strerror(errno));
1682 if (permission_class == PERMS_PUBLIC && !running_setgid)
1683 chmod(dir, dir_mode);
1685 posix_umask(last_umask); // restore previous umask
1688 void InitMainUserDataDirectory(void)
1690 createDirectory(getMainUserGameDataDir(), "main user data", PERMS_PRIVATE);
1693 void InitUserDataDirectory(void)
1695 createDirectory(getMainUserGameDataDir(), "main user data", PERMS_PRIVATE);
1699 createDirectory(getUserDir(-1), "users", PERMS_PRIVATE);
1700 createDirectory(getUserDir(user.nr), "user data", PERMS_PRIVATE);
1704 void SetFilePermissions(char *filename, int permission_class)
1706 int running_setgid = posix_process_running_setgid();
1707 int perms = (permission_class == PERMS_PRIVATE ?
1708 FILE_PERMS_PRIVATE : FILE_PERMS_PUBLIC);
1710 if (permission_class == PERMS_PUBLIC && !running_setgid)
1711 perms = FILE_PERMS_PUBLIC_ALL;
1713 chmod(filename, perms);
1716 char *getCookie(char *file_type)
1718 static char cookie[MAX_COOKIE_LEN + 1];
1720 if (strlen(program.cookie_prefix) + 1 +
1721 strlen(file_type) + strlen("_FILE_VERSION_x.x") > MAX_COOKIE_LEN)
1722 return "[COOKIE ERROR]"; // should never happen
1724 sprintf(cookie, "%s_%s_FILE_VERSION_%d.%d",
1725 program.cookie_prefix, file_type,
1726 program.version_super, program.version_major);
1731 void fprintFileHeader(FILE *file, char *basename)
1733 char *prefix = "# ";
1736 fprintf_line_with_prefix(file, prefix, sep1, 77);
1737 fprintf(file, "%s%s\n", prefix, basename);
1738 fprintf_line_with_prefix(file, prefix, sep1, 77);
1739 fprintf(file, "\n");
1742 int getFileVersionFromCookieString(const char *cookie)
1744 const char *ptr_cookie1, *ptr_cookie2;
1745 const char *pattern1 = "_FILE_VERSION_";
1746 const char *pattern2 = "?.?";
1747 const int len_cookie = strlen(cookie);
1748 const int len_pattern1 = strlen(pattern1);
1749 const int len_pattern2 = strlen(pattern2);
1750 const int len_pattern = len_pattern1 + len_pattern2;
1751 int version_super, version_major;
1753 if (len_cookie <= len_pattern)
1756 ptr_cookie1 = &cookie[len_cookie - len_pattern];
1757 ptr_cookie2 = &cookie[len_cookie - len_pattern2];
1759 if (strncmp(ptr_cookie1, pattern1, len_pattern1) != 0)
1762 if (ptr_cookie2[0] < '0' || ptr_cookie2[0] > '9' ||
1763 ptr_cookie2[1] != '.' ||
1764 ptr_cookie2[2] < '0' || ptr_cookie2[2] > '9')
1767 version_super = ptr_cookie2[0] - '0';
1768 version_major = ptr_cookie2[2] - '0';
1770 return VERSION_IDENT(version_super, version_major, 0, 0);
1773 boolean checkCookieString(const char *cookie, const char *template)
1775 const char *pattern = "_FILE_VERSION_?.?";
1776 const int len_cookie = strlen(cookie);
1777 const int len_template = strlen(template);
1778 const int len_pattern = strlen(pattern);
1780 if (len_cookie != len_template)
1783 if (strncmp(cookie, template, len_cookie - len_pattern) != 0)
1790 // ----------------------------------------------------------------------------
1791 // setup file list and hash handling functions
1792 // ----------------------------------------------------------------------------
1794 char *getFormattedSetupEntry(char *token, char *value)
1797 static char entry[MAX_LINE_LEN];
1799 // if value is an empty string, just return token without value
1803 // start with the token and some spaces to format output line
1804 sprintf(entry, "%s:", token);
1805 for (i = strlen(entry); i < token_value_position; i++)
1808 // continue with the token's value
1809 strcat(entry, value);
1814 SetupFileList *newSetupFileList(char *token, char *value)
1816 SetupFileList *new = checked_malloc(sizeof(SetupFileList));
1818 new->token = getStringCopy(token);
1819 new->value = getStringCopy(value);
1826 void freeSetupFileList(SetupFileList *list)
1831 checked_free(list->token);
1832 checked_free(list->value);
1835 freeSetupFileList(list->next);
1840 char *getListEntry(SetupFileList *list, char *token)
1845 if (strEqual(list->token, token))
1848 return getListEntry(list->next, token);
1851 SetupFileList *setListEntry(SetupFileList *list, char *token, char *value)
1856 if (strEqual(list->token, token))
1858 checked_free(list->value);
1860 list->value = getStringCopy(value);
1864 else if (list->next == NULL)
1865 return (list->next = newSetupFileList(token, value));
1867 return setListEntry(list->next, token, value);
1870 SetupFileList *addListEntry(SetupFileList *list, char *token, char *value)
1875 if (list->next == NULL)
1876 return (list->next = newSetupFileList(token, value));
1878 return addListEntry(list->next, token, value);
1881 #if ENABLE_UNUSED_CODE
1883 static void printSetupFileList(SetupFileList *list)
1888 Debug("setup:printSetupFileList", "token: '%s'", list->token);
1889 Debug("setup:printSetupFileList", "value: '%s'", list->value);
1891 printSetupFileList(list->next);
1897 DEFINE_HASHTABLE_INSERT(insert_hash_entry, char, char);
1898 DEFINE_HASHTABLE_SEARCH(search_hash_entry, char, char);
1899 DEFINE_HASHTABLE_CHANGE(change_hash_entry, char, char);
1900 DEFINE_HASHTABLE_REMOVE(remove_hash_entry, char, char);
1902 #define insert_hash_entry hashtable_insert
1903 #define search_hash_entry hashtable_search
1904 #define change_hash_entry hashtable_change
1905 #define remove_hash_entry hashtable_remove
1908 unsigned int get_hash_from_key(void *key)
1913 This algorithm (k=33) was first reported by Dan Bernstein many years ago in
1914 'comp.lang.c'. Another version of this algorithm (now favored by Bernstein)
1915 uses XOR: hash(i) = hash(i - 1) * 33 ^ str[i]; the magic of number 33 (why
1916 it works better than many other constants, prime or not) has never been
1917 adequately explained.
1919 If you just want to have a good hash function, and cannot wait, djb2
1920 is one of the best string hash functions i know. It has excellent
1921 distribution and speed on many different sets of keys and table sizes.
1922 You are not likely to do better with one of the "well known" functions
1923 such as PJW, K&R, etc.
1925 Ozan (oz) Yigit [http://www.cs.yorku.ca/~oz/hash.html]
1928 char *str = (char *)key;
1929 unsigned int hash = 5381;
1932 while ((c = *str++))
1933 hash = ((hash << 5) + hash) + c; // hash * 33 + c
1938 static int keys_are_equal(void *key1, void *key2)
1940 return (strEqual((char *)key1, (char *)key2));
1943 SetupFileHash *newSetupFileHash(void)
1945 SetupFileHash *new_hash =
1946 create_hashtable(16, 0.75, get_hash_from_key, keys_are_equal);
1948 if (new_hash == NULL)
1949 Fail("create_hashtable() failed -- out of memory");
1954 void freeSetupFileHash(SetupFileHash *hash)
1959 hashtable_destroy(hash, 1); // 1 == also free values stored in hash
1962 char *getHashEntry(SetupFileHash *hash, char *token)
1967 return search_hash_entry(hash, token);
1970 void setHashEntry(SetupFileHash *hash, char *token, char *value)
1977 value_copy = getStringCopy(value);
1979 // change value; if it does not exist, insert it as new
1980 if (!change_hash_entry(hash, token, value_copy))
1981 if (!insert_hash_entry(hash, getStringCopy(token), value_copy))
1982 Fail("cannot insert into hash -- aborting");
1985 char *removeHashEntry(SetupFileHash *hash, char *token)
1990 return remove_hash_entry(hash, token);
1993 #if ENABLE_UNUSED_CODE
1995 static void printSetupFileHash(SetupFileHash *hash)
1997 BEGIN_HASH_ITERATION(hash, itr)
1999 Debug("setup:printSetupFileHash", "token: '%s'", HASH_ITERATION_TOKEN(itr));
2000 Debug("setup:printSetupFileHash", "value: '%s'", HASH_ITERATION_VALUE(itr));
2002 END_HASH_ITERATION(hash, itr)
2007 #define ALLOW_TOKEN_VALUE_SEPARATOR_BEING_WHITESPACE 1
2008 #define CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING 0
2009 #define CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH 0
2011 static boolean token_value_separator_found = FALSE;
2012 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2013 static boolean token_value_separator_warning = FALSE;
2015 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2016 static boolean token_already_exists_warning = FALSE;
2019 static boolean getTokenValueFromSetupLineExt(char *line,
2020 char **token_ptr, char **value_ptr,
2021 char *filename, char *line_raw,
2023 boolean separator_required)
2025 static char line_copy[MAX_LINE_LEN + 1], line_raw_copy[MAX_LINE_LEN + 1];
2026 char *token, *value, *line_ptr;
2028 // when externally invoked via ReadTokenValueFromLine(), copy line buffers
2029 if (line_raw == NULL)
2031 strncpy(line_copy, line, MAX_LINE_LEN);
2032 line_copy[MAX_LINE_LEN] = '\0';
2035 strcpy(line_raw_copy, line_copy);
2036 line_raw = line_raw_copy;
2039 // cut trailing comment from input line
2040 for (line_ptr = line; *line_ptr; line_ptr++)
2042 if (*line_ptr == '#')
2049 // cut trailing whitespaces from input line
2050 for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
2051 if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
2054 // ignore empty lines
2058 // cut leading whitespaces from token
2059 for (token = line; *token; token++)
2060 if (*token != ' ' && *token != '\t')
2063 // start with empty value as reliable default
2066 token_value_separator_found = FALSE;
2068 // find end of token to determine start of value
2069 for (line_ptr = token; *line_ptr; line_ptr++)
2071 // first look for an explicit token/value separator, like ':' or '='
2072 if (*line_ptr == ':' || *line_ptr == '=')
2074 *line_ptr = '\0'; // terminate token string
2075 value = line_ptr + 1; // set beginning of value
2077 token_value_separator_found = TRUE;
2083 #if ALLOW_TOKEN_VALUE_SEPARATOR_BEING_WHITESPACE
2084 // fallback: if no token/value separator found, also allow whitespaces
2085 if (!token_value_separator_found && !separator_required)
2087 for (line_ptr = token; *line_ptr; line_ptr++)
2089 if (*line_ptr == ' ' || *line_ptr == '\t')
2091 *line_ptr = '\0'; // terminate token string
2092 value = line_ptr + 1; // set beginning of value
2094 token_value_separator_found = TRUE;
2100 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2101 if (token_value_separator_found)
2103 if (!token_value_separator_warning)
2105 Debug("setup", "---");
2107 if (filename != NULL)
2109 Debug("setup", "missing token/value separator(s) in config file:");
2110 Debug("setup", "- config file: '%s'", filename);
2114 Debug("setup", "missing token/value separator(s):");
2117 token_value_separator_warning = TRUE;
2120 if (filename != NULL)
2121 Debug("setup", "- line %d: '%s'", line_nr, line_raw);
2123 Debug("setup", "- line: '%s'", line_raw);
2129 // cut trailing whitespaces from token
2130 for (line_ptr = &token[strlen(token)]; line_ptr >= token; line_ptr--)
2131 if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
2134 // cut leading whitespaces from value
2135 for (; *value; value++)
2136 if (*value != ' ' && *value != '\t')
2145 boolean getTokenValueFromSetupLine(char *line, char **token, char **value)
2147 // while the internal (old) interface does not require a token/value
2148 // separator (for downwards compatibility with existing files which
2149 // don't use them), it is mandatory for the external (new) interface
2151 return getTokenValueFromSetupLineExt(line, token, value, NULL, NULL, 0, TRUE);
2154 static boolean loadSetupFileData(void *setup_file_data, char *filename,
2155 boolean top_recursion_level, boolean is_hash)
2157 static SetupFileHash *include_filename_hash = NULL;
2158 char line[MAX_LINE_LEN], line_raw[MAX_LINE_LEN], previous_line[MAX_LINE_LEN];
2159 char *token, *value, *line_ptr;
2160 void *insert_ptr = NULL;
2161 boolean read_continued_line = FALSE;
2163 int line_nr = 0, token_count = 0, include_count = 0;
2165 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2166 token_value_separator_warning = FALSE;
2169 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2170 token_already_exists_warning = FALSE;
2173 if (!(file = openFile(filename, MODE_READ)))
2175 #if DEBUG_NO_CONFIG_FILE
2176 Debug("setup", "cannot open configuration file '%s'", filename);
2182 // use "insert pointer" to store list end for constant insertion complexity
2184 insert_ptr = setup_file_data;
2186 // on top invocation, create hash to mark included files (to prevent loops)
2187 if (top_recursion_level)
2188 include_filename_hash = newSetupFileHash();
2190 // mark this file as already included (to prevent including it again)
2191 setHashEntry(include_filename_hash, getBaseNamePtr(filename), "true");
2193 while (!checkEndOfFile(file))
2195 // read next line of input file
2196 if (!getStringFromFile(file, line, MAX_LINE_LEN))
2199 // check if line was completely read and is terminated by line break
2200 if (strlen(line) > 0 && line[strlen(line) - 1] == '\n')
2203 // cut trailing line break (this can be newline and/or carriage return)
2204 for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
2205 if ((*line_ptr == '\n' || *line_ptr == '\r') && *(line_ptr + 1) == '\0')
2208 // copy raw input line for later use (mainly debugging output)
2209 strcpy(line_raw, line);
2211 if (read_continued_line)
2213 // append new line to existing line, if there is enough space
2214 if (strlen(previous_line) + strlen(line_ptr) < MAX_LINE_LEN)
2215 strcat(previous_line, line_ptr);
2217 strcpy(line, previous_line); // copy storage buffer to line
2219 read_continued_line = FALSE;
2222 // if the last character is '\', continue at next line
2223 if (strlen(line) > 0 && line[strlen(line) - 1] == '\\')
2225 line[strlen(line) - 1] = '\0'; // cut off trailing backslash
2226 strcpy(previous_line, line); // copy line to storage buffer
2228 read_continued_line = TRUE;
2233 if (!getTokenValueFromSetupLineExt(line, &token, &value, filename,
2234 line_raw, line_nr, FALSE))
2239 if (strEqual(token, "include"))
2241 if (getHashEntry(include_filename_hash, value) == NULL)
2243 char *basepath = getBasePath(filename);
2244 char *basename = getBaseName(value);
2245 char *filename_include = getPath2(basepath, basename);
2247 loadSetupFileData(setup_file_data, filename_include, FALSE, is_hash);
2251 free(filename_include);
2257 Warn("ignoring already processed file '%s'", value);
2264 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2266 getHashEntry((SetupFileHash *)setup_file_data, token);
2268 if (old_value != NULL)
2270 if (!token_already_exists_warning)
2272 Debug("setup", "---");
2273 Debug("setup", "duplicate token(s) found in config file:");
2274 Debug("setup", "- config file: '%s'", filename);
2276 token_already_exists_warning = TRUE;
2279 Debug("setup", "- token: '%s' (in line %d)", token, line_nr);
2280 Debug("setup", " old value: '%s'", old_value);
2281 Debug("setup", " new value: '%s'", value);
2285 setHashEntry((SetupFileHash *)setup_file_data, token, value);
2289 insert_ptr = addListEntry((SetupFileList *)insert_ptr, token, value);
2299 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2300 if (token_value_separator_warning)
2301 Debug("setup", "---");
2304 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2305 if (token_already_exists_warning)
2306 Debug("setup", "---");
2309 if (token_count == 0 && include_count == 0)
2310 Warn("configuration file '%s' is empty", filename);
2312 if (top_recursion_level)
2313 freeSetupFileHash(include_filename_hash);
2318 static void saveSetupFileHash(SetupFileHash *hash, char *filename)
2322 if (!(file = fopen(filename, MODE_WRITE)))
2324 Warn("cannot write configuration file '%s'", filename);
2329 BEGIN_HASH_ITERATION(hash, itr)
2331 fprintf(file, "%s\n", getFormattedSetupEntry(HASH_ITERATION_TOKEN(itr),
2332 HASH_ITERATION_VALUE(itr)));
2334 END_HASH_ITERATION(hash, itr)
2339 SetupFileList *loadSetupFileList(char *filename)
2341 SetupFileList *setup_file_list = newSetupFileList("", "");
2342 SetupFileList *first_valid_list_entry;
2344 if (!loadSetupFileData(setup_file_list, filename, TRUE, FALSE))
2346 freeSetupFileList(setup_file_list);
2351 first_valid_list_entry = setup_file_list->next;
2353 // free empty list header
2354 setup_file_list->next = NULL;
2355 freeSetupFileList(setup_file_list);
2357 return first_valid_list_entry;
2360 SetupFileHash *loadSetupFileHash(char *filename)
2362 SetupFileHash *setup_file_hash = newSetupFileHash();
2364 if (!loadSetupFileData(setup_file_hash, filename, TRUE, TRUE))
2366 freeSetupFileHash(setup_file_hash);
2371 return setup_file_hash;
2375 // ============================================================================
2377 // ============================================================================
2379 #define TOKEN_STR_LAST_LEVEL_SERIES "last_level_series"
2380 #define TOKEN_STR_LAST_PLAYED_LEVEL "last_played_level"
2381 #define TOKEN_STR_HANDICAP_LEVEL "handicap_level"
2382 #define TOKEN_STR_LAST_USER "last_user"
2384 // level directory info
2385 #define LEVELINFO_TOKEN_IDENTIFIER 0
2386 #define LEVELINFO_TOKEN_NAME 1
2387 #define LEVELINFO_TOKEN_NAME_SORTING 2
2388 #define LEVELINFO_TOKEN_AUTHOR 3
2389 #define LEVELINFO_TOKEN_YEAR 4
2390 #define LEVELINFO_TOKEN_PROGRAM_TITLE 5
2391 #define LEVELINFO_TOKEN_PROGRAM_COPYRIGHT 6
2392 #define LEVELINFO_TOKEN_PROGRAM_COMPANY 7
2393 #define LEVELINFO_TOKEN_IMPORTED_FROM 8
2394 #define LEVELINFO_TOKEN_IMPORTED_BY 9
2395 #define LEVELINFO_TOKEN_TESTED_BY 10
2396 #define LEVELINFO_TOKEN_LEVELS 11
2397 #define LEVELINFO_TOKEN_FIRST_LEVEL 12
2398 #define LEVELINFO_TOKEN_SORT_PRIORITY 13
2399 #define LEVELINFO_TOKEN_LATEST_ENGINE 14
2400 #define LEVELINFO_TOKEN_LEVEL_GROUP 15
2401 #define LEVELINFO_TOKEN_READONLY 16
2402 #define LEVELINFO_TOKEN_GRAPHICS_SET_ECS 17
2403 #define LEVELINFO_TOKEN_GRAPHICS_SET_AGA 18
2404 #define LEVELINFO_TOKEN_GRAPHICS_SET 19
2405 #define LEVELINFO_TOKEN_SOUNDS_SET_DEFAULT 20
2406 #define LEVELINFO_TOKEN_SOUNDS_SET_LOWPASS 21
2407 #define LEVELINFO_TOKEN_SOUNDS_SET 22
2408 #define LEVELINFO_TOKEN_MUSIC_SET 23
2409 #define LEVELINFO_TOKEN_FILENAME 24
2410 #define LEVELINFO_TOKEN_FILETYPE 25
2411 #define LEVELINFO_TOKEN_SPECIAL_FLAGS 26
2412 #define LEVELINFO_TOKEN_HANDICAP 27
2413 #define LEVELINFO_TOKEN_SKIP_LEVELS 28
2414 #define LEVELINFO_TOKEN_USE_EMC_TILES 29
2416 #define NUM_LEVELINFO_TOKENS 30
2418 static LevelDirTree ldi;
2420 static struct TokenInfo levelinfo_tokens[] =
2422 // level directory info
2423 { TYPE_STRING, &ldi.identifier, "identifier" },
2424 { TYPE_STRING, &ldi.name, "name" },
2425 { TYPE_STRING, &ldi.name_sorting, "name_sorting" },
2426 { TYPE_STRING, &ldi.author, "author" },
2427 { TYPE_STRING, &ldi.year, "year" },
2428 { TYPE_STRING, &ldi.program_title, "program_title" },
2429 { TYPE_STRING, &ldi.program_copyright, "program_copyright" },
2430 { TYPE_STRING, &ldi.program_company, "program_company" },
2431 { TYPE_STRING, &ldi.imported_from, "imported_from" },
2432 { TYPE_STRING, &ldi.imported_by, "imported_by" },
2433 { TYPE_STRING, &ldi.tested_by, "tested_by" },
2434 { TYPE_INTEGER, &ldi.levels, "levels" },
2435 { TYPE_INTEGER, &ldi.first_level, "first_level" },
2436 { TYPE_INTEGER, &ldi.sort_priority, "sort_priority" },
2437 { TYPE_BOOLEAN, &ldi.latest_engine, "latest_engine" },
2438 { TYPE_BOOLEAN, &ldi.level_group, "level_group" },
2439 { TYPE_BOOLEAN, &ldi.readonly, "readonly" },
2440 { TYPE_STRING, &ldi.graphics_set_ecs, "graphics_set.ecs" },
2441 { TYPE_STRING, &ldi.graphics_set_aga, "graphics_set.aga" },
2442 { TYPE_STRING, &ldi.graphics_set, "graphics_set" },
2443 { TYPE_STRING, &ldi.sounds_set_default,"sounds_set.default" },
2444 { TYPE_STRING, &ldi.sounds_set_lowpass,"sounds_set.lowpass" },
2445 { TYPE_STRING, &ldi.sounds_set, "sounds_set" },
2446 { TYPE_STRING, &ldi.music_set, "music_set" },
2447 { TYPE_STRING, &ldi.level_filename, "filename" },
2448 { TYPE_STRING, &ldi.level_filetype, "filetype" },
2449 { TYPE_STRING, &ldi.special_flags, "special_flags" },
2450 { TYPE_BOOLEAN, &ldi.handicap, "handicap" },
2451 { TYPE_BOOLEAN, &ldi.skip_levels, "skip_levels" },
2452 { TYPE_BOOLEAN, &ldi.use_emc_tiles, "use_emc_tiles" }
2455 static struct TokenInfo artworkinfo_tokens[] =
2457 // artwork directory info
2458 { TYPE_STRING, &ldi.identifier, "identifier" },
2459 { TYPE_STRING, &ldi.subdir, "subdir" },
2460 { TYPE_STRING, &ldi.name, "name" },
2461 { TYPE_STRING, &ldi.name_sorting, "name_sorting" },
2462 { TYPE_STRING, &ldi.author, "author" },
2463 { TYPE_STRING, &ldi.program_title, "program_title" },
2464 { TYPE_STRING, &ldi.program_copyright, "program_copyright" },
2465 { TYPE_STRING, &ldi.program_company, "program_company" },
2466 { TYPE_INTEGER, &ldi.sort_priority, "sort_priority" },
2467 { TYPE_STRING, &ldi.basepath, "basepath" },
2468 { TYPE_STRING, &ldi.fullpath, "fullpath" },
2469 { TYPE_BOOLEAN, &ldi.in_user_dir, "in_user_dir" },
2470 { TYPE_INTEGER, &ldi.color, "color" },
2471 { TYPE_STRING, &ldi.class_desc, "class_desc" },
2476 static char *optional_tokens[] =
2479 "program_copyright",
2485 static void setTreeInfoToDefaults(TreeInfo *ti, int type)
2489 ti->node_top = (ti->type == TREE_TYPE_LEVEL_DIR ? &leveldir_first :
2490 ti->type == TREE_TYPE_GRAPHICS_DIR ? &artwork.gfx_first :
2491 ti->type == TREE_TYPE_SOUNDS_DIR ? &artwork.snd_first :
2492 ti->type == TREE_TYPE_MUSIC_DIR ? &artwork.mus_first :
2495 ti->node_parent = NULL;
2496 ti->node_group = NULL;
2503 ti->fullpath = NULL;
2504 ti->basepath = NULL;
2505 ti->identifier = NULL;
2506 ti->name = getStringCopy(ANONYMOUS_NAME);
2507 ti->name_sorting = NULL;
2508 ti->author = getStringCopy(ANONYMOUS_NAME);
2511 ti->program_title = NULL;
2512 ti->program_copyright = NULL;
2513 ti->program_company = NULL;
2515 ti->sort_priority = LEVELCLASS_UNDEFINED; // default: least priority
2516 ti->latest_engine = FALSE; // default: get from level
2517 ti->parent_link = FALSE;
2518 ti->in_user_dir = FALSE;
2519 ti->user_defined = FALSE;
2521 ti->class_desc = NULL;
2523 ti->infotext = getStringCopy(TREE_INFOTEXT(ti->type));
2525 if (ti->type == TREE_TYPE_LEVEL_DIR)
2527 ti->imported_from = NULL;
2528 ti->imported_by = NULL;
2529 ti->tested_by = NULL;
2531 ti->graphics_set_ecs = NULL;
2532 ti->graphics_set_aga = NULL;
2533 ti->graphics_set = NULL;
2534 ti->sounds_set_default = NULL;
2535 ti->sounds_set_lowpass = NULL;
2536 ti->sounds_set = NULL;
2537 ti->music_set = NULL;
2538 ti->graphics_path = getStringCopy(UNDEFINED_FILENAME);
2539 ti->sounds_path = getStringCopy(UNDEFINED_FILENAME);
2540 ti->music_path = getStringCopy(UNDEFINED_FILENAME);
2542 ti->level_filename = NULL;
2543 ti->level_filetype = NULL;
2545 ti->special_flags = NULL;
2548 ti->first_level = 0;
2550 ti->level_group = FALSE;
2551 ti->handicap_level = 0;
2552 ti->readonly = TRUE;
2553 ti->handicap = TRUE;
2554 ti->skip_levels = FALSE;
2556 ti->use_emc_tiles = FALSE;
2560 static void setTreeInfoToDefaultsFromParent(TreeInfo *ti, TreeInfo *parent)
2564 Warn("setTreeInfoToDefaultsFromParent(): parent == NULL");
2566 setTreeInfoToDefaults(ti, TREE_TYPE_UNDEFINED);
2571 // copy all values from the parent structure
2573 ti->type = parent->type;
2575 ti->node_top = parent->node_top;
2576 ti->node_parent = parent;
2577 ti->node_group = NULL;
2584 ti->fullpath = NULL;
2585 ti->basepath = NULL;
2586 ti->identifier = NULL;
2587 ti->name = getStringCopy(ANONYMOUS_NAME);
2588 ti->name_sorting = NULL;
2589 ti->author = getStringCopy(parent->author);
2590 ti->year = getStringCopy(parent->year);
2592 ti->program_title = getStringCopy(parent->program_title);
2593 ti->program_copyright = getStringCopy(parent->program_copyright);
2594 ti->program_company = getStringCopy(parent->program_company);
2596 ti->sort_priority = parent->sort_priority;
2597 ti->latest_engine = parent->latest_engine;
2598 ti->parent_link = FALSE;
2599 ti->in_user_dir = parent->in_user_dir;
2600 ti->user_defined = parent->user_defined;
2601 ti->color = parent->color;
2602 ti->class_desc = getStringCopy(parent->class_desc);
2604 ti->infotext = getStringCopy(parent->infotext);
2606 if (ti->type == TREE_TYPE_LEVEL_DIR)
2608 ti->imported_from = getStringCopy(parent->imported_from);
2609 ti->imported_by = getStringCopy(parent->imported_by);
2610 ti->tested_by = getStringCopy(parent->tested_by);
2612 ti->graphics_set_ecs = getStringCopy(parent->graphics_set_ecs);
2613 ti->graphics_set_aga = getStringCopy(parent->graphics_set_aga);
2614 ti->graphics_set = getStringCopy(parent->graphics_set);
2615 ti->sounds_set_default = getStringCopy(parent->sounds_set_default);
2616 ti->sounds_set_lowpass = getStringCopy(parent->sounds_set_lowpass);
2617 ti->sounds_set = getStringCopy(parent->sounds_set);
2618 ti->music_set = getStringCopy(parent->music_set);
2619 ti->graphics_path = getStringCopy(UNDEFINED_FILENAME);
2620 ti->sounds_path = getStringCopy(UNDEFINED_FILENAME);
2621 ti->music_path = getStringCopy(UNDEFINED_FILENAME);
2623 ti->level_filename = getStringCopy(parent->level_filename);
2624 ti->level_filetype = getStringCopy(parent->level_filetype);
2626 ti->special_flags = getStringCopy(parent->special_flags);
2628 ti->levels = parent->levels;
2629 ti->first_level = parent->first_level;
2630 ti->last_level = parent->last_level;
2631 ti->level_group = FALSE;
2632 ti->handicap_level = parent->handicap_level;
2633 ti->readonly = parent->readonly;
2634 ti->handicap = parent->handicap;
2635 ti->skip_levels = parent->skip_levels;
2637 ti->use_emc_tiles = parent->use_emc_tiles;
2641 static TreeInfo *getTreeInfoCopy(TreeInfo *ti)
2643 TreeInfo *ti_copy = newTreeInfo();
2645 // copy all values from the original structure
2647 ti_copy->type = ti->type;
2649 ti_copy->node_top = ti->node_top;
2650 ti_copy->node_parent = ti->node_parent;
2651 ti_copy->node_group = ti->node_group;
2652 ti_copy->next = ti->next;
2654 ti_copy->cl_first = ti->cl_first;
2655 ti_copy->cl_cursor = ti->cl_cursor;
2657 ti_copy->subdir = getStringCopy(ti->subdir);
2658 ti_copy->fullpath = getStringCopy(ti->fullpath);
2659 ti_copy->basepath = getStringCopy(ti->basepath);
2660 ti_copy->identifier = getStringCopy(ti->identifier);
2661 ti_copy->name = getStringCopy(ti->name);
2662 ti_copy->name_sorting = getStringCopy(ti->name_sorting);
2663 ti_copy->author = getStringCopy(ti->author);
2664 ti_copy->year = getStringCopy(ti->year);
2666 ti_copy->program_title = getStringCopy(ti->program_title);
2667 ti_copy->program_copyright = getStringCopy(ti->program_copyright);
2668 ti_copy->program_company = getStringCopy(ti->program_company);
2670 ti_copy->imported_from = getStringCopy(ti->imported_from);
2671 ti_copy->imported_by = getStringCopy(ti->imported_by);
2672 ti_copy->tested_by = getStringCopy(ti->tested_by);
2674 ti_copy->graphics_set_ecs = getStringCopy(ti->graphics_set_ecs);
2675 ti_copy->graphics_set_aga = getStringCopy(ti->graphics_set_aga);
2676 ti_copy->graphics_set = getStringCopy(ti->graphics_set);
2677 ti_copy->sounds_set_default = getStringCopy(ti->sounds_set_default);
2678 ti_copy->sounds_set_lowpass = getStringCopy(ti->sounds_set_lowpass);
2679 ti_copy->sounds_set = getStringCopy(ti->sounds_set);
2680 ti_copy->music_set = getStringCopy(ti->music_set);
2681 ti_copy->graphics_path = getStringCopy(ti->graphics_path);
2682 ti_copy->sounds_path = getStringCopy(ti->sounds_path);
2683 ti_copy->music_path = getStringCopy(ti->music_path);
2685 ti_copy->level_filename = getStringCopy(ti->level_filename);
2686 ti_copy->level_filetype = getStringCopy(ti->level_filetype);
2688 ti_copy->special_flags = getStringCopy(ti->special_flags);
2690 ti_copy->levels = ti->levels;
2691 ti_copy->first_level = ti->first_level;
2692 ti_copy->last_level = ti->last_level;
2693 ti_copy->sort_priority = ti->sort_priority;
2695 ti_copy->latest_engine = ti->latest_engine;
2697 ti_copy->level_group = ti->level_group;
2698 ti_copy->parent_link = ti->parent_link;
2699 ti_copy->in_user_dir = ti->in_user_dir;
2700 ti_copy->user_defined = ti->user_defined;
2701 ti_copy->readonly = ti->readonly;
2702 ti_copy->handicap = ti->handicap;
2703 ti_copy->skip_levels = ti->skip_levels;
2705 ti_copy->use_emc_tiles = ti->use_emc_tiles;
2707 ti_copy->color = ti->color;
2708 ti_copy->class_desc = getStringCopy(ti->class_desc);
2709 ti_copy->handicap_level = ti->handicap_level;
2711 ti_copy->infotext = getStringCopy(ti->infotext);
2716 void freeTreeInfo(TreeInfo *ti)
2721 checked_free(ti->subdir);
2722 checked_free(ti->fullpath);
2723 checked_free(ti->basepath);
2724 checked_free(ti->identifier);
2726 checked_free(ti->name);
2727 checked_free(ti->name_sorting);
2728 checked_free(ti->author);
2729 checked_free(ti->year);
2731 checked_free(ti->program_title);
2732 checked_free(ti->program_copyright);
2733 checked_free(ti->program_company);
2735 checked_free(ti->class_desc);
2737 checked_free(ti->infotext);
2739 if (ti->type == TREE_TYPE_LEVEL_DIR)
2741 checked_free(ti->imported_from);
2742 checked_free(ti->imported_by);
2743 checked_free(ti->tested_by);
2745 checked_free(ti->graphics_set_ecs);
2746 checked_free(ti->graphics_set_aga);
2747 checked_free(ti->graphics_set);
2748 checked_free(ti->sounds_set_default);
2749 checked_free(ti->sounds_set_lowpass);
2750 checked_free(ti->sounds_set);
2751 checked_free(ti->music_set);
2753 checked_free(ti->graphics_path);
2754 checked_free(ti->sounds_path);
2755 checked_free(ti->music_path);
2757 checked_free(ti->level_filename);
2758 checked_free(ti->level_filetype);
2760 checked_free(ti->special_flags);
2763 // recursively free child node
2765 freeTreeInfo(ti->node_group);
2767 // recursively free next node
2769 freeTreeInfo(ti->next);
2774 void setSetupInfo(struct TokenInfo *token_info,
2775 int token_nr, char *token_value)
2777 int token_type = token_info[token_nr].type;
2778 void *setup_value = token_info[token_nr].value;
2780 if (token_value == NULL)
2783 // set setup field to corresponding token value
2788 *(boolean *)setup_value = get_boolean_from_string(token_value);
2792 *(int *)setup_value = get_switch3_from_string(token_value);
2796 *(Key *)setup_value = getKeyFromKeyName(token_value);
2800 *(Key *)setup_value = getKeyFromX11KeyName(token_value);
2804 *(int *)setup_value = get_integer_from_string(token_value);
2808 checked_free(*(char **)setup_value);
2809 *(char **)setup_value = getStringCopy(token_value);
2813 *(int *)setup_value = get_player_nr_from_string(token_value);
2821 static int compareTreeInfoEntries(const void *object1, const void *object2)
2823 const TreeInfo *entry1 = *((TreeInfo **)object1);
2824 const TreeInfo *entry2 = *((TreeInfo **)object2);
2825 int class_sorting1 = 0, class_sorting2 = 0;
2828 if (entry1->type == TREE_TYPE_LEVEL_DIR)
2830 class_sorting1 = LEVELSORTING(entry1);
2831 class_sorting2 = LEVELSORTING(entry2);
2833 else if (entry1->type == TREE_TYPE_GRAPHICS_DIR ||
2834 entry1->type == TREE_TYPE_SOUNDS_DIR ||
2835 entry1->type == TREE_TYPE_MUSIC_DIR)
2837 class_sorting1 = ARTWORKSORTING(entry1);
2838 class_sorting2 = ARTWORKSORTING(entry2);
2841 if (entry1->parent_link || entry2->parent_link)
2842 compare_result = (entry1->parent_link ? -1 : +1);
2843 else if (entry1->sort_priority == entry2->sort_priority)
2845 char *name1 = getStringToLower(entry1->name_sorting);
2846 char *name2 = getStringToLower(entry2->name_sorting);
2848 compare_result = strcmp(name1, name2);
2853 else if (class_sorting1 == class_sorting2)
2854 compare_result = entry1->sort_priority - entry2->sort_priority;
2856 compare_result = class_sorting1 - class_sorting2;
2858 return compare_result;
2861 static TreeInfo *createParentTreeInfoNode(TreeInfo *node_parent)
2865 if (node_parent == NULL)
2868 ti_new = newTreeInfo();
2869 setTreeInfoToDefaults(ti_new, node_parent->type);
2871 ti_new->node_parent = node_parent;
2872 ti_new->parent_link = TRUE;
2874 setString(&ti_new->identifier, node_parent->identifier);
2875 setString(&ti_new->name, ".. (parent directory)");
2876 setString(&ti_new->name_sorting, ti_new->name);
2878 setString(&ti_new->subdir, STRING_PARENT_DIRECTORY);
2879 setString(&ti_new->fullpath, node_parent->fullpath);
2881 ti_new->sort_priority = node_parent->sort_priority;
2882 ti_new->latest_engine = node_parent->latest_engine;
2884 setString(&ti_new->class_desc, getLevelClassDescription(ti_new));
2886 pushTreeInfo(&node_parent->node_group, ti_new);
2891 static TreeInfo *createTopTreeInfoNode(TreeInfo *node_first)
2893 TreeInfo *ti_new, *ti_new2;
2895 if (node_first == NULL)
2898 ti_new = newTreeInfo();
2899 setTreeInfoToDefaults(ti_new, TREE_TYPE_LEVEL_DIR);
2901 ti_new->node_parent = NULL;
2902 ti_new->parent_link = FALSE;
2904 setString(&ti_new->identifier, node_first->identifier);
2905 setString(&ti_new->name, "level sets");
2906 setString(&ti_new->name_sorting, ti_new->name);
2908 setString(&ti_new->subdir, STRING_TOP_DIRECTORY);
2909 setString(&ti_new->fullpath, ".");
2911 ti_new->sort_priority = node_first->sort_priority;;
2912 ti_new->latest_engine = node_first->latest_engine;
2914 setString(&ti_new->class_desc, "level sets");
2916 ti_new->node_group = node_first;
2917 ti_new->level_group = TRUE;
2919 ti_new2 = createParentTreeInfoNode(ti_new);
2921 setString(&ti_new2->name, ".. (main menu)");
2922 setString(&ti_new2->name_sorting, ti_new2->name);
2928 // ----------------------------------------------------------------------------
2929 // functions for handling level and custom artwork info cache
2930 // ----------------------------------------------------------------------------
2932 static void LoadArtworkInfoCache(void)
2934 InitCacheDirectory();
2936 if (artworkinfo_cache_old == NULL)
2938 char *filename = getPath2(getCacheDir(), ARTWORKINFO_CACHE_FILE);
2940 // try to load artwork info hash from already existing cache file
2941 artworkinfo_cache_old = loadSetupFileHash(filename);
2943 // if no artwork info cache file was found, start with empty hash
2944 if (artworkinfo_cache_old == NULL)
2945 artworkinfo_cache_old = newSetupFileHash();
2950 if (artworkinfo_cache_new == NULL)
2951 artworkinfo_cache_new = newSetupFileHash();
2954 static void SaveArtworkInfoCache(void)
2956 char *filename = getPath2(getCacheDir(), ARTWORKINFO_CACHE_FILE);
2958 InitCacheDirectory();
2960 saveSetupFileHash(artworkinfo_cache_new, filename);
2965 static char *getCacheTokenPrefix(char *prefix1, char *prefix2)
2967 static char *prefix = NULL;
2969 checked_free(prefix);
2971 prefix = getStringCat2WithSeparator(prefix1, prefix2, ".");
2976 // (identical to above function, but separate string buffer needed -- nasty)
2977 static char *getCacheToken(char *prefix, char *suffix)
2979 static char *token = NULL;
2981 checked_free(token);
2983 token = getStringCat2WithSeparator(prefix, suffix, ".");
2988 static char *getFileTimestampString(char *filename)
2990 return getStringCopy(i_to_a(getFileTimestampEpochSeconds(filename)));
2993 static boolean modifiedFileTimestamp(char *filename, char *timestamp_string)
2995 struct stat file_status;
2997 if (timestamp_string == NULL)
3000 if (stat(filename, &file_status) != 0) // cannot stat file
3003 return (file_status.st_mtime != atoi(timestamp_string));
3006 static TreeInfo *getArtworkInfoCacheEntry(LevelDirTree *level_node, int type)
3008 char *identifier = level_node->subdir;
3009 char *type_string = ARTWORK_DIRECTORY(type);
3010 char *token_prefix = getCacheTokenPrefix(type_string, identifier);
3011 char *token_main = getCacheToken(token_prefix, "CACHED");
3012 char *cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
3013 boolean cached = (cache_entry != NULL && strEqual(cache_entry, "true"));
3014 TreeInfo *artwork_info = NULL;
3016 if (!use_artworkinfo_cache)
3019 if (optional_tokens_hash == NULL)
3023 // create hash from list of optional tokens (for quick access)
3024 optional_tokens_hash = newSetupFileHash();
3025 for (i = 0; optional_tokens[i] != NULL; i++)
3026 setHashEntry(optional_tokens_hash, optional_tokens[i], "");
3033 artwork_info = newTreeInfo();
3034 setTreeInfoToDefaults(artwork_info, type);
3036 // set all structure fields according to the token/value pairs
3037 ldi = *artwork_info;
3038 for (i = 0; artworkinfo_tokens[i].type != -1; i++)
3040 char *token_suffix = artworkinfo_tokens[i].text;
3041 char *token = getCacheToken(token_prefix, token_suffix);
3042 char *value = getHashEntry(artworkinfo_cache_old, token);
3044 (getHashEntry(optional_tokens_hash, token_suffix) != NULL);
3046 setSetupInfo(artworkinfo_tokens, i, value);
3048 // check if cache entry for this item is mandatory, but missing
3049 if (value == NULL && !optional)
3051 Warn("missing cache entry '%s'", token);
3057 *artwork_info = ldi;
3062 char *filename_levelinfo = getPath2(getLevelDirFromTreeInfo(level_node),
3063 LEVELINFO_FILENAME);
3064 char *filename_artworkinfo = getPath2(getSetupArtworkDir(artwork_info),
3065 ARTWORKINFO_FILENAME(type));
3067 // check if corresponding "levelinfo.conf" file has changed
3068 token_main = getCacheToken(token_prefix, "TIMESTAMP_LEVELINFO");
3069 cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
3071 if (modifiedFileTimestamp(filename_levelinfo, cache_entry))
3074 // check if corresponding "<artworkinfo>.conf" file has changed
3075 token_main = getCacheToken(token_prefix, "TIMESTAMP_ARTWORKINFO");
3076 cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
3078 if (modifiedFileTimestamp(filename_artworkinfo, cache_entry))
3081 checked_free(filename_levelinfo);
3082 checked_free(filename_artworkinfo);
3085 if (!cached && artwork_info != NULL)
3087 freeTreeInfo(artwork_info);
3092 return artwork_info;
3095 static void setArtworkInfoCacheEntry(TreeInfo *artwork_info,
3096 LevelDirTree *level_node, int type)
3098 char *identifier = level_node->subdir;
3099 char *type_string = ARTWORK_DIRECTORY(type);
3100 char *token_prefix = getCacheTokenPrefix(type_string, identifier);
3101 char *token_main = getCacheToken(token_prefix, "CACHED");
3102 boolean set_cache_timestamps = TRUE;
3105 setHashEntry(artworkinfo_cache_new, token_main, "true");
3107 if (set_cache_timestamps)
3109 char *filename_levelinfo = getPath2(getLevelDirFromTreeInfo(level_node),
3110 LEVELINFO_FILENAME);
3111 char *filename_artworkinfo = getPath2(getSetupArtworkDir(artwork_info),
3112 ARTWORKINFO_FILENAME(type));
3113 char *timestamp_levelinfo = getFileTimestampString(filename_levelinfo);
3114 char *timestamp_artworkinfo = getFileTimestampString(filename_artworkinfo);
3116 token_main = getCacheToken(token_prefix, "TIMESTAMP_LEVELINFO");
3117 setHashEntry(artworkinfo_cache_new, token_main, timestamp_levelinfo);
3119 token_main = getCacheToken(token_prefix, "TIMESTAMP_ARTWORKINFO");
3120 setHashEntry(artworkinfo_cache_new, token_main, timestamp_artworkinfo);
3122 checked_free(filename_levelinfo);
3123 checked_free(filename_artworkinfo);
3124 checked_free(timestamp_levelinfo);
3125 checked_free(timestamp_artworkinfo);
3128 ldi = *artwork_info;
3129 for (i = 0; artworkinfo_tokens[i].type != -1; i++)
3131 char *token = getCacheToken(token_prefix, artworkinfo_tokens[i].text);
3132 char *value = getSetupValue(artworkinfo_tokens[i].type,
3133 artworkinfo_tokens[i].value);
3135 setHashEntry(artworkinfo_cache_new, token, value);
3140 // ----------------------------------------------------------------------------
3141 // functions for loading level info and custom artwork info
3142 // ----------------------------------------------------------------------------
3144 int GetZipFileTreeType(char *zip_filename)
3146 static char *top_dir_path = NULL;
3147 static char *top_dir_conf_filename[NUM_BASE_TREE_TYPES] = { NULL };
3148 static char *conf_basename[NUM_BASE_TREE_TYPES] =
3150 GRAPHICSINFO_FILENAME,
3151 SOUNDSINFO_FILENAME,
3157 checked_free(top_dir_path);
3158 top_dir_path = NULL;
3160 for (j = 0; j < NUM_BASE_TREE_TYPES; j++)
3162 checked_free(top_dir_conf_filename[j]);
3163 top_dir_conf_filename[j] = NULL;
3166 char **zip_entries = zip_list(zip_filename);
3168 // check if zip file successfully opened
3169 if (zip_entries == NULL || zip_entries[0] == NULL)
3170 return TREE_TYPE_UNDEFINED;
3172 // first zip file entry is expected to be top level directory
3173 char *top_dir = zip_entries[0];
3175 // check if valid top level directory found in zip file
3176 if (!strSuffix(top_dir, "/"))
3177 return TREE_TYPE_UNDEFINED;
3179 // get filenames of valid configuration files in top level directory
3180 for (j = 0; j < NUM_BASE_TREE_TYPES; j++)
3181 top_dir_conf_filename[j] = getStringCat2(top_dir, conf_basename[j]);
3183 int tree_type = TREE_TYPE_UNDEFINED;
3186 while (zip_entries[e] != NULL)
3188 // check if every zip file entry is below top level directory
3189 if (!strPrefix(zip_entries[e], top_dir))
3190 return TREE_TYPE_UNDEFINED;
3192 // check if this zip file entry is a valid configuration filename
3193 for (j = 0; j < NUM_BASE_TREE_TYPES; j++)
3195 if (strEqual(zip_entries[e], top_dir_conf_filename[j]))
3197 // only exactly one valid configuration file allowed
3198 if (tree_type != TREE_TYPE_UNDEFINED)
3199 return TREE_TYPE_UNDEFINED;
3211 static boolean CheckZipFileForDirectory(char *zip_filename, char *directory,
3214 static char *top_dir_path = NULL;
3215 static char *top_dir_conf_filename = NULL;
3217 checked_free(top_dir_path);
3218 checked_free(top_dir_conf_filename);
3220 top_dir_path = NULL;
3221 top_dir_conf_filename = NULL;
3223 char *conf_basename = (tree_type == TREE_TYPE_LEVEL_DIR ? LEVELINFO_FILENAME :
3224 ARTWORKINFO_FILENAME(tree_type));
3226 // check if valid configuration filename determined
3227 if (conf_basename == NULL || strEqual(conf_basename, ""))
3230 char **zip_entries = zip_list(zip_filename);
3232 // check if zip file successfully opened
3233 if (zip_entries == NULL || zip_entries[0] == NULL)
3236 // first zip file entry is expected to be top level directory
3237 char *top_dir = zip_entries[0];
3239 // check if valid top level directory found in zip file
3240 if (!strSuffix(top_dir, "/"))
3243 // get path of extracted top level directory
3244 top_dir_path = getPath2(directory, top_dir);
3246 // remove trailing directory separator from top level directory path
3247 // (required to be able to check for file and directory in next step)
3248 top_dir_path[strlen(top_dir_path) - 1] = '\0';
3250 // check if zip file's top level directory already exists in target directory
3251 if (fileExists(top_dir_path)) // (checks for file and directory)
3254 // get filename of configuration file in top level directory
3255 top_dir_conf_filename = getStringCat2(top_dir, conf_basename);
3257 boolean found_top_dir_conf_filename = FALSE;
3260 while (zip_entries[i] != NULL)
3262 // check if every zip file entry is below top level directory
3263 if (!strPrefix(zip_entries[i], top_dir))
3266 // check if this zip file entry is the configuration filename
3267 if (strEqual(zip_entries[i], top_dir_conf_filename))
3268 found_top_dir_conf_filename = TRUE;
3273 // check if valid configuration filename was found in zip file
3274 if (!found_top_dir_conf_filename)
3280 char *ExtractZipFileIntoDirectory(char *zip_filename, char *directory,
3283 boolean zip_file_valid = CheckZipFileForDirectory(zip_filename, directory,
3286 if (!zip_file_valid)
3288 Warn("zip file '%s' rejected!", zip_filename);
3293 char **zip_entries = zip_extract(zip_filename, directory);
3295 if (zip_entries == NULL)
3297 Warn("zip file '%s' could not be extracted!", zip_filename);
3302 Info("zip file '%s' successfully extracted!", zip_filename);
3304 // first zip file entry contains top level directory
3305 char *top_dir = zip_entries[0];
3307 // remove trailing directory separator from top level directory
3308 top_dir[strlen(top_dir) - 1] = '\0';
3313 static void ProcessZipFilesInDirectory(char *directory, int tree_type)
3316 DirectoryEntry *dir_entry;
3318 if ((dir = openDirectory(directory)) == NULL)
3320 // display error if directory is main "options.graphics_directory" etc.
3321 if (tree_type == TREE_TYPE_LEVEL_DIR ||
3322 directory == OPTIONS_ARTWORK_DIRECTORY(tree_type))
3323 Warn("cannot read directory '%s'", directory);
3328 while ((dir_entry = readDirectory(dir)) != NULL) // loop all entries
3330 // skip non-zip files (and also directories with zip extension)
3331 if (!strSuffixLower(dir_entry->basename, ".zip") || dir_entry->is_directory)
3334 char *zip_filename = getPath2(directory, dir_entry->basename);
3335 char *zip_filename_extracted = getStringCat2(zip_filename, ".extracted");
3336 char *zip_filename_rejected = getStringCat2(zip_filename, ".rejected");
3338 // check if zip file hasn't already been extracted or rejected
3339 if (!fileExists(zip_filename_extracted) &&
3340 !fileExists(zip_filename_rejected))
3342 char *top_dir = ExtractZipFileIntoDirectory(zip_filename, directory,
3344 char *marker_filename = (top_dir != NULL ? zip_filename_extracted :
3345 zip_filename_rejected);
3348 // create empty file to mark zip file as extracted or rejected
3349 if ((marker_file = fopen(marker_filename, MODE_WRITE)))
3350 fclose(marker_file);
3353 free(zip_filename_extracted);
3354 free(zip_filename_rejected);
3358 closeDirectory(dir);
3361 // forward declaration for recursive call by "LoadLevelInfoFromLevelDir()"
3362 static void LoadLevelInfoFromLevelDir(TreeInfo **, TreeInfo *, char *);
3364 static boolean LoadLevelInfoFromLevelConf(TreeInfo **node_first,
3365 TreeInfo *node_parent,
3366 char *level_directory,
3367 char *directory_name)
3369 char *directory_path = getPath2(level_directory, directory_name);
3370 char *filename = getPath2(directory_path, LEVELINFO_FILENAME);
3371 SetupFileHash *setup_file_hash;
3372 LevelDirTree *leveldir_new = NULL;
3375 // unless debugging, silently ignore directories without "levelinfo.conf"
3376 if (!options.debug && !fileExists(filename))
3378 free(directory_path);
3384 setup_file_hash = loadSetupFileHash(filename);
3386 if (setup_file_hash == NULL)
3388 #if DEBUG_NO_CONFIG_FILE
3389 Debug("setup", "ignoring level directory '%s'", directory_path);
3392 free(directory_path);
3398 leveldir_new = newTreeInfo();
3401 setTreeInfoToDefaultsFromParent(leveldir_new, node_parent);
3403 setTreeInfoToDefaults(leveldir_new, TREE_TYPE_LEVEL_DIR);
3405 leveldir_new->subdir = getStringCopy(directory_name);
3407 // set all structure fields according to the token/value pairs
3408 ldi = *leveldir_new;
3409 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
3410 setSetupInfo(levelinfo_tokens, i,
3411 getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
3412 *leveldir_new = ldi;
3414 if (strEqual(leveldir_new->name, ANONYMOUS_NAME))
3415 setString(&leveldir_new->name, leveldir_new->subdir);
3417 if (leveldir_new->identifier == NULL)
3418 leveldir_new->identifier = getStringCopy(leveldir_new->subdir);
3420 if (leveldir_new->name_sorting == NULL)
3421 leveldir_new->name_sorting = getStringCopy(leveldir_new->name);
3423 if (node_parent == NULL) // top level group
3425 leveldir_new->basepath = getStringCopy(level_directory);
3426 leveldir_new->fullpath = getStringCopy(leveldir_new->subdir);
3428 else // sub level group
3430 leveldir_new->basepath = getStringCopy(node_parent->basepath);
3431 leveldir_new->fullpath = getPath2(node_parent->fullpath, directory_name);
3434 leveldir_new->last_level =
3435 leveldir_new->first_level + leveldir_new->levels - 1;
3437 leveldir_new->in_user_dir =
3438 (!strEqual(leveldir_new->basepath, options.level_directory));
3440 // adjust some settings if user's private level directory was detected
3441 if (leveldir_new->sort_priority == LEVELCLASS_UNDEFINED &&
3442 leveldir_new->in_user_dir &&
3443 (strEqual(leveldir_new->subdir, getLoginName()) ||
3444 strEqual(leveldir_new->name, getLoginName()) ||
3445 strEqual(leveldir_new->author, getRealName())))
3447 leveldir_new->sort_priority = LEVELCLASS_PRIVATE_START;
3448 leveldir_new->readonly = FALSE;
3451 leveldir_new->user_defined =
3452 (leveldir_new->in_user_dir && IS_LEVELCLASS_PRIVATE(leveldir_new));
3454 leveldir_new->color = LEVELCOLOR(leveldir_new);
3456 setString(&leveldir_new->class_desc, getLevelClassDescription(leveldir_new));
3458 leveldir_new->handicap_level = // set handicap to default value
3459 (leveldir_new->user_defined || !leveldir_new->handicap ?
3460 leveldir_new->last_level : leveldir_new->first_level);
3462 DrawInitText(leveldir_new->name, 150, FC_YELLOW);
3464 pushTreeInfo(node_first, leveldir_new);
3466 freeSetupFileHash(setup_file_hash);
3468 if (leveldir_new->level_group)
3470 // create node to link back to current level directory
3471 createParentTreeInfoNode(leveldir_new);
3473 // recursively step into sub-directory and look for more level series
3474 LoadLevelInfoFromLevelDir(&leveldir_new->node_group,
3475 leveldir_new, directory_path);
3478 free(directory_path);
3484 static void LoadLevelInfoFromLevelDir(TreeInfo **node_first,
3485 TreeInfo *node_parent,
3486 char *level_directory)
3488 // ---------- 1st stage: process any level set zip files ----------
3490 ProcessZipFilesInDirectory(level_directory, TREE_TYPE_LEVEL_DIR);
3492 // ---------- 2nd stage: check for level set directories ----------
3495 DirectoryEntry *dir_entry;
3496 boolean valid_entry_found = FALSE;
3498 if ((dir = openDirectory(level_directory)) == NULL)
3500 Warn("cannot read level directory '%s'", level_directory);
3505 while ((dir_entry = readDirectory(dir)) != NULL) // loop all entries
3507 char *directory_name = dir_entry->basename;
3508 char *directory_path = getPath2(level_directory, directory_name);
3510 // skip entries for current and parent directory
3511 if (strEqual(directory_name, ".") ||
3512 strEqual(directory_name, ".."))
3514 free(directory_path);
3519 // find out if directory entry is itself a directory
3520 if (!dir_entry->is_directory) // not a directory
3522 free(directory_path);
3527 free(directory_path);
3529 if (strEqual(directory_name, GRAPHICS_DIRECTORY) ||
3530 strEqual(directory_name, SOUNDS_DIRECTORY) ||
3531 strEqual(directory_name, MUSIC_DIRECTORY))
3534 valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
3539 closeDirectory(dir);
3541 // special case: top level directory may directly contain "levelinfo.conf"
3542 if (node_parent == NULL && !valid_entry_found)
3544 // check if this directory directly contains a file "levelinfo.conf"
3545 valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
3546 level_directory, ".");
3549 if (!valid_entry_found)
3550 Warn("cannot find any valid level series in directory '%s'",
3554 boolean AdjustGraphicsForEMC(void)
3556 boolean settings_changed = FALSE;
3558 settings_changed |= adjustTreeGraphicsForEMC(leveldir_first_all);
3559 settings_changed |= adjustTreeGraphicsForEMC(leveldir_first);
3561 return settings_changed;
3564 boolean AdjustSoundsForEMC(void)
3566 boolean settings_changed = FALSE;
3568 settings_changed |= adjustTreeSoundsForEMC(leveldir_first_all);
3569 settings_changed |= adjustTreeSoundsForEMC(leveldir_first);
3571 return settings_changed;
3574 void LoadLevelInfo(void)
3576 InitUserLevelDirectory(getLoginName());
3578 DrawInitText("Loading level series", 120, FC_GREEN);
3580 LoadLevelInfoFromLevelDir(&leveldir_first, NULL, options.level_directory);
3581 LoadLevelInfoFromLevelDir(&leveldir_first, NULL, getUserLevelDir(NULL));
3583 leveldir_first = createTopTreeInfoNode(leveldir_first);
3585 /* after loading all level set information, clone the level directory tree
3586 and remove all level sets without levels (these may still contain artwork
3587 to be offered in the setup menu as "custom artwork", and are therefore
3588 checked for existing artwork in the function "LoadLevelArtworkInfo()") */
3589 leveldir_first_all = leveldir_first;
3590 cloneTree(&leveldir_first, leveldir_first_all, TRUE);
3592 AdjustGraphicsForEMC();
3593 AdjustSoundsForEMC();
3595 // before sorting, the first entries will be from the user directory
3596 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
3598 if (leveldir_first == NULL)
3599 Fail("cannot find any valid level series in any directory");
3601 sortTreeInfo(&leveldir_first);
3603 #if ENABLE_UNUSED_CODE
3604 dumpTreeInfo(leveldir_first, 0);
3608 static boolean LoadArtworkInfoFromArtworkConf(TreeInfo **node_first,
3609 TreeInfo *node_parent,
3610 char *base_directory,
3611 char *directory_name, int type)
3613 char *directory_path = getPath2(base_directory, directory_name);
3614 char *filename = getPath2(directory_path, ARTWORKINFO_FILENAME(type));
3615 SetupFileHash *setup_file_hash = NULL;
3616 TreeInfo *artwork_new = NULL;
3619 if (fileExists(filename))
3620 setup_file_hash = loadSetupFileHash(filename);
3622 if (setup_file_hash == NULL) // no config file -- look for artwork files
3625 DirectoryEntry *dir_entry;
3626 boolean valid_file_found = FALSE;
3628 if ((dir = openDirectory(directory_path)) != NULL)
3630 while ((dir_entry = readDirectory(dir)) != NULL)
3632 if (FileIsArtworkType(dir_entry->filename, type))
3634 valid_file_found = TRUE;
3640 closeDirectory(dir);
3643 if (!valid_file_found)
3645 #if DEBUG_NO_CONFIG_FILE
3646 if (!strEqual(directory_name, "."))
3647 Debug("setup", "ignoring artwork directory '%s'", directory_path);
3650 free(directory_path);
3657 artwork_new = newTreeInfo();
3660 setTreeInfoToDefaultsFromParent(artwork_new, node_parent);
3662 setTreeInfoToDefaults(artwork_new, type);
3664 artwork_new->subdir = getStringCopy(directory_name);
3666 if (setup_file_hash) // (before defining ".color" and ".class_desc")
3668 // set all structure fields according to the token/value pairs
3670 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
3671 setSetupInfo(levelinfo_tokens, i,
3672 getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
3675 if (strEqual(artwork_new->name, ANONYMOUS_NAME))
3676 setString(&artwork_new->name, artwork_new->subdir);
3678 if (artwork_new->identifier == NULL)
3679 artwork_new->identifier = getStringCopy(artwork_new->subdir);
3681 if (artwork_new->name_sorting == NULL)
3682 artwork_new->name_sorting = getStringCopy(artwork_new->name);
3685 if (node_parent == NULL) // top level group
3687 artwork_new->basepath = getStringCopy(base_directory);
3688 artwork_new->fullpath = getStringCopy(artwork_new->subdir);
3690 else // sub level group
3692 artwork_new->basepath = getStringCopy(node_parent->basepath);
3693 artwork_new->fullpath = getPath2(node_parent->fullpath, directory_name);
3696 artwork_new->in_user_dir =
3697 (!strEqual(artwork_new->basepath, OPTIONS_ARTWORK_DIRECTORY(type)));
3699 // (may use ".sort_priority" from "setup_file_hash" above)
3700 artwork_new->color = ARTWORKCOLOR(artwork_new);
3702 setString(&artwork_new->class_desc, getLevelClassDescription(artwork_new));
3704 if (setup_file_hash == NULL) // (after determining ".user_defined")
3706 if (strEqual(artwork_new->subdir, "."))
3708 if (artwork_new->user_defined)
3710 setString(&artwork_new->identifier, "private");
3711 artwork_new->sort_priority = ARTWORKCLASS_PRIVATE;
3715 setString(&artwork_new->identifier, "classic");
3716 artwork_new->sort_priority = ARTWORKCLASS_CLASSICS;
3719 // set to new values after changing ".sort_priority"
3720 artwork_new->color = ARTWORKCOLOR(artwork_new);
3722 setString(&artwork_new->class_desc,
3723 getLevelClassDescription(artwork_new));
3727 setString(&artwork_new->identifier, artwork_new->subdir);
3730 setString(&artwork_new->name, artwork_new->identifier);
3731 setString(&artwork_new->name_sorting, artwork_new->name);
3734 pushTreeInfo(node_first, artwork_new);
3736 freeSetupFileHash(setup_file_hash);
3738 free(directory_path);
3744 static void LoadArtworkInfoFromArtworkDir(TreeInfo **node_first,
3745 TreeInfo *node_parent,
3746 char *base_directory, int type)
3748 // ---------- 1st stage: process any artwork set zip files ----------
3750 ProcessZipFilesInDirectory(base_directory, type);
3752 // ---------- 2nd stage: check for artwork set directories ----------
3755 DirectoryEntry *dir_entry;
3756 boolean valid_entry_found = FALSE;
3758 if ((dir = openDirectory(base_directory)) == NULL)
3760 // display error if directory is main "options.graphics_directory" etc.
3761 if (base_directory == OPTIONS_ARTWORK_DIRECTORY(type))
3762 Warn("cannot read directory '%s'", base_directory);
3767 while ((dir_entry = readDirectory(dir)) != NULL) // loop all entries
3769 char *directory_name = dir_entry->basename;
3770 char *directory_path = getPath2(base_directory, directory_name);
3772 // skip directory entries for current and parent directory
3773 if (strEqual(directory_name, ".") ||
3774 strEqual(directory_name, ".."))
3776 free(directory_path);
3781 // skip directory entries which are not a directory
3782 if (!dir_entry->is_directory) // not a directory
3784 free(directory_path);
3789 free(directory_path);
3791 // check if this directory contains artwork with or without config file
3792 valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
3794 directory_name, type);
3797 closeDirectory(dir);
3799 // check if this directory directly contains artwork itself
3800 valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
3801 base_directory, ".",
3803 if (!valid_entry_found)
3804 Warn("cannot find any valid artwork in directory '%s'", base_directory);
3807 static TreeInfo *getDummyArtworkInfo(int type)
3809 // this is only needed when there is completely no artwork available
3810 TreeInfo *artwork_new = newTreeInfo();
3812 setTreeInfoToDefaults(artwork_new, type);
3814 setString(&artwork_new->subdir, UNDEFINED_FILENAME);
3815 setString(&artwork_new->fullpath, UNDEFINED_FILENAME);
3816 setString(&artwork_new->basepath, UNDEFINED_FILENAME);
3818 setString(&artwork_new->identifier, UNDEFINED_FILENAME);
3819 setString(&artwork_new->name, UNDEFINED_FILENAME);
3820 setString(&artwork_new->name_sorting, UNDEFINED_FILENAME);
3825 void SetCurrentArtwork(int type)
3827 ArtworkDirTree **current_ptr = ARTWORK_CURRENT_PTR(artwork, type);
3828 ArtworkDirTree *first_node = ARTWORK_FIRST_NODE(artwork, type);
3829 char *setup_set = SETUP_ARTWORK_SET(setup, type);
3830 char *default_subdir = ARTWORK_DEFAULT_SUBDIR(type);
3832 // set current artwork to artwork configured in setup menu
3833 *current_ptr = getTreeInfoFromIdentifier(first_node, setup_set);
3835 // if not found, set current artwork to default artwork
3836 if (*current_ptr == NULL)
3837 *current_ptr = getTreeInfoFromIdentifier(first_node, default_subdir);
3839 // if not found, set current artwork to first artwork in tree
3840 if (*current_ptr == NULL)
3841 *current_ptr = getFirstValidTreeInfoEntry(first_node);
3844 void ChangeCurrentArtworkIfNeeded(int type)
3846 char *current_identifier = ARTWORK_CURRENT_IDENTIFIER(artwork, type);
3847 char *setup_set = SETUP_ARTWORK_SET(setup, type);
3849 if (!strEqual(current_identifier, setup_set))
3850 SetCurrentArtwork(type);
3853 void LoadArtworkInfo(void)
3855 LoadArtworkInfoCache();
3857 DrawInitText("Looking for custom artwork", 120, FC_GREEN);
3859 LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
3860 options.graphics_directory,
3861 TREE_TYPE_GRAPHICS_DIR);
3862 LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
3863 getUserGraphicsDir(),
3864 TREE_TYPE_GRAPHICS_DIR);
3866 LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
3867 options.sounds_directory,
3868 TREE_TYPE_SOUNDS_DIR);
3869 LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
3871 TREE_TYPE_SOUNDS_DIR);
3873 LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
3874 options.music_directory,
3875 TREE_TYPE_MUSIC_DIR);
3876 LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
3878 TREE_TYPE_MUSIC_DIR);
3880 if (artwork.gfx_first == NULL)
3881 artwork.gfx_first = getDummyArtworkInfo(TREE_TYPE_GRAPHICS_DIR);
3882 if (artwork.snd_first == NULL)
3883 artwork.snd_first = getDummyArtworkInfo(TREE_TYPE_SOUNDS_DIR);
3884 if (artwork.mus_first == NULL)
3885 artwork.mus_first = getDummyArtworkInfo(TREE_TYPE_MUSIC_DIR);
3887 // before sorting, the first entries will be from the user directory
3888 SetCurrentArtwork(ARTWORK_TYPE_GRAPHICS);
3889 SetCurrentArtwork(ARTWORK_TYPE_SOUNDS);
3890 SetCurrentArtwork(ARTWORK_TYPE_MUSIC);
3892 artwork.gfx_current_identifier = artwork.gfx_current->identifier;
3893 artwork.snd_current_identifier = artwork.snd_current->identifier;
3894 artwork.mus_current_identifier = artwork.mus_current->identifier;
3896 #if ENABLE_UNUSED_CODE
3897 Debug("setup:LoadArtworkInfo", "graphics set == %s",
3898 artwork.gfx_current_identifier);
3899 Debug("setup:LoadArtworkInfo", "sounds set == %s",
3900 artwork.snd_current_identifier);
3901 Debug("setup:LoadArtworkInfo", "music set == %s",
3902 artwork.mus_current_identifier);
3905 sortTreeInfo(&artwork.gfx_first);
3906 sortTreeInfo(&artwork.snd_first);
3907 sortTreeInfo(&artwork.mus_first);
3909 #if ENABLE_UNUSED_CODE
3910 dumpTreeInfo(artwork.gfx_first, 0);
3911 dumpTreeInfo(artwork.snd_first, 0);
3912 dumpTreeInfo(artwork.mus_first, 0);
3916 static void LoadArtworkInfoFromLevelInfo(ArtworkDirTree **artwork_node,
3917 ArtworkDirTree *node_parent,
3918 LevelDirTree *level_node)
3920 int type = (*artwork_node)->type;
3922 // recursively check all level directories for artwork sub-directories
3926 // check all tree entries for artwork, but skip parent link entries
3927 if (!level_node->parent_link)
3929 TreeInfo *artwork_new = getArtworkInfoCacheEntry(level_node, type);
3930 boolean cached = (artwork_new != NULL);
3934 pushTreeInfo(artwork_node, artwork_new);
3938 TreeInfo *topnode_last = *artwork_node;
3939 char *path = getPath2(getLevelDirFromTreeInfo(level_node),
3940 ARTWORK_DIRECTORY(type));
3942 LoadArtworkInfoFromArtworkDir(artwork_node, NULL, path, type);
3944 if (topnode_last != *artwork_node) // check for newly added node
3946 artwork_new = *artwork_node;
3948 setString(&artwork_new->identifier, level_node->subdir);
3949 setString(&artwork_new->name, level_node->name);
3950 setString(&artwork_new->name_sorting, level_node->name_sorting);
3952 artwork_new->sort_priority = level_node->sort_priority;
3953 artwork_new->color = LEVELCOLOR(artwork_new);
3959 // insert artwork info (from old cache or filesystem) into new cache
3960 if (artwork_new != NULL)
3961 setArtworkInfoCacheEntry(artwork_new, level_node, type);
3964 DrawInitText(level_node->name, 150, FC_YELLOW);
3966 if (level_node->node_group != NULL)
3968 TreeInfo *artwork_new = newTreeInfo();
3971 setTreeInfoToDefaultsFromParent(artwork_new, node_parent);
3973 setTreeInfoToDefaults(artwork_new, type);
3975 artwork_new->level_group = TRUE;
3977 setString(&artwork_new->identifier, level_node->subdir);
3978 setString(&artwork_new->name, level_node->name);
3979 setString(&artwork_new->name_sorting, level_node->name_sorting);
3981 pushTreeInfo(artwork_node, artwork_new);
3983 // create node to link back to current custom artwork directory
3984 createParentTreeInfoNode(artwork_new);
3986 // recursively step into sub-directory and look for more custom artwork
3987 LoadArtworkInfoFromLevelInfo(&artwork_new->node_group, artwork_new,
3988 level_node->node_group);
3990 // if sub-tree has no custom artwork at all, remove it
3991 if (artwork_new->node_group->next == NULL)
3993 *artwork_node = artwork_new->next;
3994 artwork_new->next = NULL;
3996 freeTreeInfo(artwork_new);
4000 level_node = level_node->next;
4004 void LoadLevelArtworkInfo(void)
4006 print_timestamp_init("LoadLevelArtworkInfo");
4008 DrawInitText("Looking for custom level artwork", 120, FC_GREEN);
4010 print_timestamp_time("DrawTimeText");
4012 LoadArtworkInfoFromLevelInfo(&artwork.gfx_first, NULL, leveldir_first_all);
4013 print_timestamp_time("LoadArtworkInfoFromLevelInfo (gfx)");
4014 LoadArtworkInfoFromLevelInfo(&artwork.snd_first, NULL, leveldir_first_all);
4015 print_timestamp_time("LoadArtworkInfoFromLevelInfo (snd)");
4016 LoadArtworkInfoFromLevelInfo(&artwork.mus_first, NULL, leveldir_first_all);
4017 print_timestamp_time("LoadArtworkInfoFromLevelInfo (mus)");
4019 SaveArtworkInfoCache();
4021 print_timestamp_time("SaveArtworkInfoCache");
4023 // needed for reloading level artwork not known at ealier stage
4024 ChangeCurrentArtworkIfNeeded(ARTWORK_TYPE_GRAPHICS);
4025 ChangeCurrentArtworkIfNeeded(ARTWORK_TYPE_SOUNDS);
4026 ChangeCurrentArtworkIfNeeded(ARTWORK_TYPE_MUSIC);
4028 print_timestamp_time("getTreeInfoFromIdentifier");
4030 sortTreeInfo(&artwork.gfx_first);
4031 sortTreeInfo(&artwork.snd_first);
4032 sortTreeInfo(&artwork.mus_first);
4034 print_timestamp_time("sortTreeInfo");
4036 #if ENABLE_UNUSED_CODE
4037 dumpTreeInfo(artwork.gfx_first, 0);
4038 dumpTreeInfo(artwork.snd_first, 0);
4039 dumpTreeInfo(artwork.mus_first, 0);
4042 print_timestamp_done("LoadLevelArtworkInfo");
4045 static boolean AddTreeSetToTreeInfoExt(TreeInfo *tree_node_old, char *tree_dir,
4046 char *tree_subdir_new, int type)
4048 if (tree_node_old == NULL)
4050 if (type == TREE_TYPE_LEVEL_DIR)
4052 // get level info tree node of personal user level set
4053 tree_node_old = getTreeInfoFromIdentifier(leveldir_first, getLoginName());
4055 // this may happen if "setup.internal.create_user_levelset" is FALSE
4056 // or if file "levelinfo.conf" is missing in personal user level set
4057 if (tree_node_old == NULL)
4058 tree_node_old = leveldir_first->node_group;
4062 // get artwork info tree node of first artwork set
4063 tree_node_old = ARTWORK_FIRST_NODE(artwork, type);
4067 if (tree_dir == NULL)
4068 tree_dir = TREE_USERDIR(type);
4070 if (tree_node_old == NULL ||
4072 tree_subdir_new == NULL) // should not happen
4075 int draw_deactivation_mask = GetDrawDeactivationMask();
4077 // override draw deactivation mask (temporarily disable drawing)
4078 SetDrawDeactivationMask(REDRAW_ALL);
4080 if (type == TREE_TYPE_LEVEL_DIR)
4082 // load new level set config and add it next to first user level set
4083 LoadLevelInfoFromLevelConf(&tree_node_old->next,
4084 tree_node_old->node_parent,
4085 tree_dir, tree_subdir_new);
4089 // load new artwork set config and add it next to first artwork set
4090 LoadArtworkInfoFromArtworkConf(&tree_node_old->next,
4091 tree_node_old->node_parent,
4092 tree_dir, tree_subdir_new, type);
4095 // set draw deactivation mask to previous value
4096 SetDrawDeactivationMask(draw_deactivation_mask);
4098 // get first node of level or artwork info tree
4099 TreeInfo **tree_node_first = TREE_FIRST_NODE_PTR(type);
4101 // get tree info node of newly added level or artwork set
4102 TreeInfo *tree_node_new = getTreeInfoFromIdentifier(*tree_node_first,
4105 if (tree_node_new == NULL) // should not happen
4108 // correct top link and parent node link of newly created tree node
4109 tree_node_new->node_top = tree_node_old->node_top;
4110 tree_node_new->node_parent = tree_node_old->node_parent;
4112 // sort tree info to adjust position of newly added tree set
4113 sortTreeInfo(tree_node_first);
4118 void AddTreeSetToTreeInfo(TreeInfo *tree_node, char *tree_dir,
4119 char *tree_subdir_new, int type)
4121 if (!AddTreeSetToTreeInfoExt(tree_node, tree_dir, tree_subdir_new, type))
4122 Fail("internal tree info structure corrupted -- aborting");
4125 void AddUserLevelSetToLevelInfo(char *level_subdir_new)
4127 AddTreeSetToTreeInfo(NULL, NULL, level_subdir_new, TREE_TYPE_LEVEL_DIR);
4130 char *getArtworkIdentifierForUserLevelSet(int type)
4132 char *classic_artwork_set = getClassicArtworkSet(type);
4134 // check for custom artwork configured in "levelinfo.conf"
4135 char *leveldir_artwork_set =
4136 *LEVELDIR_ARTWORK_SET_PTR(leveldir_current, type);
4137 boolean has_leveldir_artwork_set =
4138 (leveldir_artwork_set != NULL && !strEqual(leveldir_artwork_set,
4139 classic_artwork_set));
4141 // check for custom artwork in sub-directory "graphics" etc.
4142 TreeInfo *artwork_first_node = ARTWORK_FIRST_NODE(artwork, type);
4143 char *leveldir_identifier = leveldir_current->identifier;
4144 boolean has_artwork_subdir =
4145 (getTreeInfoFromIdentifier(artwork_first_node,
4146 leveldir_identifier) != NULL);
4148 return (has_leveldir_artwork_set ? leveldir_artwork_set :
4149 has_artwork_subdir ? leveldir_identifier :
4150 classic_artwork_set);
4153 TreeInfo *getArtworkTreeInfoForUserLevelSet(int type)
4155 char *artwork_set = getArtworkIdentifierForUserLevelSet(type);
4156 TreeInfo *artwork_first_node = ARTWORK_FIRST_NODE(artwork, type);
4157 TreeInfo *ti = getTreeInfoFromIdentifier(artwork_first_node, artwork_set);
4161 ti = getTreeInfoFromIdentifier(artwork_first_node,
4162 ARTWORK_DEFAULT_SUBDIR(type));
4164 Fail("cannot find default graphics -- should not happen");
4170 boolean checkIfCustomArtworkExistsForCurrentLevelSet(void)
4172 char *graphics_set =
4173 getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_GRAPHICS);
4175 getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_SOUNDS);
4177 getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_MUSIC);
4179 return (!strEqual(graphics_set, GFX_CLASSIC_SUBDIR) ||
4180 !strEqual(sounds_set, SND_CLASSIC_SUBDIR) ||
4181 !strEqual(music_set, MUS_CLASSIC_SUBDIR));
4184 boolean UpdateUserLevelSet(char *level_subdir, char *level_name,
4185 char *level_author, int num_levels)
4187 char *filename = getPath2(getUserLevelDir(level_subdir), LEVELINFO_FILENAME);
4188 char *filename_tmp = getStringCat2(filename, ".tmp");
4190 FILE *file_tmp = NULL;
4191 char line[MAX_LINE_LEN];
4192 boolean success = FALSE;
4193 LevelDirTree *leveldir = getTreeInfoFromIdentifier(leveldir_first,
4195 // update values in level directory tree
4197 if (level_name != NULL)
4198 setString(&leveldir->name, level_name);
4200 if (level_author != NULL)
4201 setString(&leveldir->author, level_author);
4203 if (num_levels != -1)
4204 leveldir->levels = num_levels;
4206 // update values that depend on other values
4208 setString(&leveldir->name_sorting, leveldir->name);
4210 leveldir->last_level = leveldir->first_level + leveldir->levels - 1;
4212 // sort order of level sets may have changed
4213 sortTreeInfo(&leveldir_first);
4215 if ((file = fopen(filename, MODE_READ)) &&
4216 (file_tmp = fopen(filename_tmp, MODE_WRITE)))
4218 while (fgets(line, MAX_LINE_LEN, file))
4220 if (strPrefix(line, "name:") && level_name != NULL)
4221 fprintf(file_tmp, "%-32s%s\n", "name:", level_name);
4222 else if (strPrefix(line, "author:") && level_author != NULL)
4223 fprintf(file_tmp, "%-32s%s\n", "author:", level_author);
4224 else if (strPrefix(line, "levels:") && num_levels != -1)
4225 fprintf(file_tmp, "%-32s%d\n", "levels:", num_levels);
4227 fputs(line, file_tmp);
4240 success = (rename(filename_tmp, filename) == 0);
4248 boolean CreateUserLevelSet(char *level_subdir, char *level_name,
4249 char *level_author, int num_levels,
4250 boolean use_artwork_set)
4252 LevelDirTree *level_info;
4257 // create user level sub-directory, if needed
4258 createDirectory(getUserLevelDir(level_subdir), "user level", PERMS_PRIVATE);
4260 filename = getPath2(getUserLevelDir(level_subdir), LEVELINFO_FILENAME);
4262 if (!(file = fopen(filename, MODE_WRITE)))
4264 Warn("cannot write level info file '%s'", filename);
4271 level_info = newTreeInfo();
4273 // always start with reliable default values
4274 setTreeInfoToDefaults(level_info, TREE_TYPE_LEVEL_DIR);
4276 setString(&level_info->name, level_name);
4277 setString(&level_info->author, level_author);
4278 level_info->levels = num_levels;
4279 level_info->first_level = 1;
4280 level_info->sort_priority = LEVELCLASS_PRIVATE_START;
4281 level_info->readonly = FALSE;
4283 if (use_artwork_set)
4285 level_info->graphics_set =
4286 getStringCopy(getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_GRAPHICS));
4287 level_info->sounds_set =
4288 getStringCopy(getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_SOUNDS));
4289 level_info->music_set =
4290 getStringCopy(getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_MUSIC));
4293 token_value_position = TOKEN_VALUE_POSITION_SHORT;
4295 fprintFileHeader(file, LEVELINFO_FILENAME);
4298 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
4300 if (i == LEVELINFO_TOKEN_NAME ||
4301 i == LEVELINFO_TOKEN_AUTHOR ||
4302 i == LEVELINFO_TOKEN_LEVELS ||
4303 i == LEVELINFO_TOKEN_FIRST_LEVEL ||
4304 i == LEVELINFO_TOKEN_SORT_PRIORITY ||
4305 i == LEVELINFO_TOKEN_READONLY ||
4306 (use_artwork_set && (i == LEVELINFO_TOKEN_GRAPHICS_SET ||
4307 i == LEVELINFO_TOKEN_SOUNDS_SET ||
4308 i == LEVELINFO_TOKEN_MUSIC_SET)))
4309 fprintf(file, "%s\n", getSetupLine(levelinfo_tokens, "", i));
4311 // just to make things nicer :)
4312 if (i == LEVELINFO_TOKEN_AUTHOR ||
4313 i == LEVELINFO_TOKEN_FIRST_LEVEL ||
4314 (use_artwork_set && i == LEVELINFO_TOKEN_READONLY))
4315 fprintf(file, "\n");
4318 token_value_position = TOKEN_VALUE_POSITION_DEFAULT;
4322 SetFilePermissions(filename, PERMS_PRIVATE);
4324 freeTreeInfo(level_info);
4330 static void SaveUserLevelInfo(void)
4332 CreateUserLevelSet(getLoginName(), getLoginName(), getRealName(), 100, FALSE);
4335 char *getSetupValue(int type, void *value)
4337 static char value_string[MAX_LINE_LEN];
4345 strcpy(value_string, (*(boolean *)value ? "true" : "false"));
4349 strcpy(value_string, (*(boolean *)value ? "on" : "off"));
4353 strcpy(value_string, (*(int *)value == AUTO ? "auto" :
4354 *(int *)value == FALSE ? "off" : "on"));
4358 strcpy(value_string, (*(boolean *)value ? "yes" : "no"));
4361 case TYPE_YES_NO_AUTO:
4362 strcpy(value_string, (*(int *)value == AUTO ? "auto" :
4363 *(int *)value == FALSE ? "no" : "yes"));
4367 strcpy(value_string, (*(boolean *)value ? "AGA" : "ECS"));
4371 strcpy(value_string, getKeyNameFromKey(*(Key *)value));
4375 strcpy(value_string, getX11KeyNameFromKey(*(Key *)value));
4379 sprintf(value_string, "%d", *(int *)value);
4383 if (*(char **)value == NULL)
4386 strcpy(value_string, *(char **)value);
4390 sprintf(value_string, "player_%d", *(int *)value + 1);
4394 value_string[0] = '\0';
4398 if (type & TYPE_GHOSTED)
4399 strcpy(value_string, "n/a");
4401 return value_string;
4404 char *getSetupLine(struct TokenInfo *token_info, char *prefix, int token_nr)
4408 static char token_string[MAX_LINE_LEN];
4409 int token_type = token_info[token_nr].type;
4410 void *setup_value = token_info[token_nr].value;
4411 char *token_text = token_info[token_nr].text;
4412 char *value_string = getSetupValue(token_type, setup_value);
4414 // build complete token string
4415 sprintf(token_string, "%s%s", prefix, token_text);
4417 // build setup entry line
4418 line = getFormattedSetupEntry(token_string, value_string);
4420 if (token_type == TYPE_KEY_X11)
4422 Key key = *(Key *)setup_value;
4423 char *keyname = getKeyNameFromKey(key);
4425 // add comment, if useful
4426 if (!strEqual(keyname, "(undefined)") &&
4427 !strEqual(keyname, "(unknown)"))
4429 // add at least one whitespace
4431 for (i = strlen(line); i < token_comment_position; i++)
4435 strcat(line, keyname);
4442 static void InitLastPlayedLevels_ParentNode(void)
4444 LevelDirTree **leveldir_top = &leveldir_first->node_group->next;
4445 LevelDirTree *leveldir_new = NULL;
4447 // check if parent node for last played levels already exists
4448 if (strEqual((*leveldir_top)->identifier, TOKEN_STR_LAST_LEVEL_SERIES))
4451 leveldir_new = newTreeInfo();
4453 setTreeInfoToDefaultsFromParent(leveldir_new, leveldir_first);
4455 leveldir_new->level_group = TRUE;
4457 setString(&leveldir_new->identifier, TOKEN_STR_LAST_LEVEL_SERIES);
4458 setString(&leveldir_new->name, "<< (last played level sets)");
4460 pushTreeInfo(leveldir_top, leveldir_new);
4462 // create node to link back to current level directory
4463 createParentTreeInfoNode(leveldir_new);
4466 void UpdateLastPlayedLevels_TreeInfo(void)
4468 char **last_level_series = setup.level_setup.last_level_series;
4469 boolean reset_leveldir_current = FALSE;
4470 LevelDirTree *leveldir_last;
4471 TreeInfo **node_new = NULL;
4474 if (last_level_series[0] == NULL)
4477 InitLastPlayedLevels_ParentNode();
4479 // check if current level set is from "last played" sub-tree to be rebuilt
4480 reset_leveldir_current = strEqual(leveldir_current->node_parent->identifier,
4481 TOKEN_STR_LAST_LEVEL_SERIES);
4483 leveldir_last = getTreeInfoFromIdentifierExt(leveldir_first,
4484 TOKEN_STR_LAST_LEVEL_SERIES,
4486 if (leveldir_last == NULL)
4489 node_new = &leveldir_last->node_group->next;
4491 freeTreeInfo(*node_new);
4493 for (i = 0; last_level_series[i] != NULL; i++)
4495 LevelDirTree *node_last = getTreeInfoFromIdentifier(leveldir_first,
4496 last_level_series[i]);
4498 *node_new = getTreeInfoCopy(node_last); // copy complete node
4500 (*node_new)->node_top = &leveldir_first; // correct top node link
4501 (*node_new)->node_parent = leveldir_last; // correct parent node link
4503 (*node_new)->node_group = NULL;
4504 (*node_new)->next = NULL;
4506 (*node_new)->cl_first = -1; // force setting tree cursor
4508 node_new = &((*node_new)->next);
4511 if (reset_leveldir_current)
4512 leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
4513 last_level_series[0]);
4516 static void UpdateLastPlayedLevels_List(void)
4518 char **last_level_series = setup.level_setup.last_level_series;
4519 int pos = MAX_LEVELDIR_HISTORY - 1;
4522 // search for potentially already existing entry in list of level sets
4523 for (i = 0; last_level_series[i] != NULL; i++)
4524 if (strEqual(last_level_series[i], leveldir_current->identifier))
4527 // move list of level sets one entry down (using potentially free entry)
4528 for (i = pos; i > 0; i--)
4529 setString(&last_level_series[i], last_level_series[i - 1]);
4531 // put last played level set at top position
4532 setString(&last_level_series[0], leveldir_current->identifier);
4535 void LoadLevelSetup_LastSeries(void)
4537 // --------------------------------------------------------------------------
4538 // ~/.<program>/levelsetup.conf
4539 // --------------------------------------------------------------------------
4541 char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
4542 SetupFileHash *level_setup_hash = NULL;
4546 // always start with reliable default values
4547 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
4549 // start with empty history of last played level sets
4550 setString(&setup.level_setup.last_level_series[0], NULL);
4552 if (!strEqual(DEFAULT_LEVELSET, UNDEFINED_LEVELSET))
4554 leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
4556 if (leveldir_current == NULL)
4557 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
4560 if ((level_setup_hash = loadSetupFileHash(filename)))
4562 char *last_level_series =
4563 getHashEntry(level_setup_hash, TOKEN_STR_LAST_LEVEL_SERIES);
4565 leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
4567 if (leveldir_current == NULL)
4568 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
4570 for (i = 0; i < MAX_LEVELDIR_HISTORY; i++)
4572 char token[strlen(TOKEN_STR_LAST_LEVEL_SERIES) + 10];
4573 LevelDirTree *leveldir_last;
4575 sprintf(token, "%s.%03d", TOKEN_STR_LAST_LEVEL_SERIES, i);
4577 last_level_series = getHashEntry(level_setup_hash, token);
4579 leveldir_last = getTreeInfoFromIdentifier(leveldir_first,
4581 if (leveldir_last != NULL)
4582 setString(&setup.level_setup.last_level_series[pos++],
4586 setString(&setup.level_setup.last_level_series[pos], NULL);
4588 freeSetupFileHash(level_setup_hash);
4592 Debug("setup", "using default setup values");
4598 static void SaveLevelSetup_LastSeries_Ext(boolean deactivate_last_level_series)
4600 // --------------------------------------------------------------------------
4601 // ~/.<program>/levelsetup.conf
4602 // --------------------------------------------------------------------------
4604 // check if the current level directory structure is available at this point
4605 if (leveldir_current == NULL)
4608 char **last_level_series = setup.level_setup.last_level_series;
4609 char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
4613 InitUserDataDirectory();
4615 UpdateLastPlayedLevels_List();
4617 if (!(file = fopen(filename, MODE_WRITE)))
4619 Warn("cannot write setup file '%s'", filename);
4626 fprintFileHeader(file, LEVELSETUP_FILENAME);
4628 if (deactivate_last_level_series)
4629 fprintf(file, "# %s\n# ", "the following level set may have caused a problem and was deactivated");
4631 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_LAST_LEVEL_SERIES,
4632 leveldir_current->identifier));
4634 for (i = 0; last_level_series[i] != NULL; i++)
4636 char token[strlen(TOKEN_STR_LAST_LEVEL_SERIES) + 10];
4638 sprintf(token, "%s.%03d", TOKEN_STR_LAST_LEVEL_SERIES, i);
4640 fprintf(file, "%s\n", getFormattedSetupEntry(token, last_level_series[i]));
4645 SetFilePermissions(filename, PERMS_PRIVATE);
4650 void SaveLevelSetup_LastSeries(void)
4652 SaveLevelSetup_LastSeries_Ext(FALSE);
4655 void SaveLevelSetup_LastSeries_Deactivate(void)
4657 SaveLevelSetup_LastSeries_Ext(TRUE);
4660 static void checkSeriesInfo(void)
4662 static char *level_directory = NULL;
4665 DirectoryEntry *dir_entry;
4668 checked_free(level_directory);
4670 // check for more levels besides the 'levels' field of 'levelinfo.conf'
4672 level_directory = getPath2((leveldir_current->in_user_dir ?
4673 getUserLevelDir(NULL) :
4674 options.level_directory),
4675 leveldir_current->fullpath);
4677 if ((dir = openDirectory(level_directory)) == NULL)
4679 Warn("cannot read level directory '%s'", level_directory);
4685 while ((dir_entry = readDirectory(dir)) != NULL) // loop all entries
4687 if (strlen(dir_entry->basename) > 4 &&
4688 dir_entry->basename[3] == '.' &&
4689 strEqual(&dir_entry->basename[4], LEVELFILE_EXTENSION))
4691 char levelnum_str[4];
4694 strncpy(levelnum_str, dir_entry->basename, 3);
4695 levelnum_str[3] = '\0';
4697 levelnum_value = atoi(levelnum_str);
4699 if (levelnum_value < leveldir_current->first_level)
4701 Warn("additional level %d found", levelnum_value);
4703 leveldir_current->first_level = levelnum_value;
4705 else if (levelnum_value > leveldir_current->last_level)
4707 Warn("additional level %d found", levelnum_value);
4709 leveldir_current->last_level = levelnum_value;
4715 closeDirectory(dir);
4718 void LoadLevelSetup_SeriesInfo(void)
4721 SetupFileHash *level_setup_hash = NULL;
4722 char *level_subdir = leveldir_current->subdir;
4725 // always start with reliable default values
4726 level_nr = leveldir_current->first_level;
4728 for (i = 0; i < MAX_LEVELS; i++)
4730 LevelStats_setPlayed(i, 0);
4731 LevelStats_setSolved(i, 0);
4736 // --------------------------------------------------------------------------
4737 // ~/.<program>/levelsetup/<level series>/levelsetup.conf
4738 // --------------------------------------------------------------------------
4740 level_subdir = leveldir_current->subdir;
4742 filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
4744 if ((level_setup_hash = loadSetupFileHash(filename)))
4748 // get last played level in this level set
4750 token_value = getHashEntry(level_setup_hash, TOKEN_STR_LAST_PLAYED_LEVEL);
4754 level_nr = atoi(token_value);
4756 if (level_nr < leveldir_current->first_level)
4757 level_nr = leveldir_current->first_level;
4758 if (level_nr > leveldir_current->last_level)
4759 level_nr = leveldir_current->last_level;
4762 // get handicap level in this level set
4764 token_value = getHashEntry(level_setup_hash, TOKEN_STR_HANDICAP_LEVEL);
4768 int level_nr = atoi(token_value);
4770 if (level_nr < leveldir_current->first_level)
4771 level_nr = leveldir_current->first_level;
4772 if (level_nr > leveldir_current->last_level + 1)
4773 level_nr = leveldir_current->last_level;
4775 if (leveldir_current->user_defined || !leveldir_current->handicap)
4776 level_nr = leveldir_current->last_level;
4778 leveldir_current->handicap_level = level_nr;
4781 // get number of played and solved levels in this level set
4783 BEGIN_HASH_ITERATION(level_setup_hash, itr)
4785 char *token = HASH_ITERATION_TOKEN(itr);
4786 char *value = HASH_ITERATION_VALUE(itr);
4788 if (strlen(token) == 3 &&
4789 token[0] >= '0' && token[0] <= '9' &&
4790 token[1] >= '0' && token[1] <= '9' &&
4791 token[2] >= '0' && token[2] <= '9')
4793 int level_nr = atoi(token);
4796 LevelStats_setPlayed(level_nr, atoi(value)); // read 1st column
4798 value = strchr(value, ' ');
4801 LevelStats_setSolved(level_nr, atoi(value)); // read 2nd column
4804 END_HASH_ITERATION(hash, itr)
4806 freeSetupFileHash(level_setup_hash);
4810 Debug("setup", "using default setup values");
4816 void SaveLevelSetup_SeriesInfo(void)
4819 char *level_subdir = leveldir_current->subdir;
4820 char *level_nr_str = int2str(level_nr, 0);
4821 char *handicap_level_str = int2str(leveldir_current->handicap_level, 0);
4825 // --------------------------------------------------------------------------
4826 // ~/.<program>/levelsetup/<level series>/levelsetup.conf
4827 // --------------------------------------------------------------------------
4829 InitLevelSetupDirectory(level_subdir);
4831 filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
4833 if (!(file = fopen(filename, MODE_WRITE)))
4835 Warn("cannot write setup file '%s'", filename);
4842 fprintFileHeader(file, LEVELSETUP_FILENAME);
4844 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_PLAYED_LEVEL,
4846 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_HANDICAP_LEVEL,
4847 handicap_level_str));
4849 for (i = leveldir_current->first_level; i <= leveldir_current->last_level;
4852 if (LevelStats_getPlayed(i) > 0 ||
4853 LevelStats_getSolved(i) > 0)
4858 sprintf(token, "%03d", i);
4859 sprintf(value, "%d %d", LevelStats_getPlayed(i), LevelStats_getSolved(i));
4861 fprintf(file, "%s\n", getFormattedSetupEntry(token, value));
4867 SetFilePermissions(filename, PERMS_PRIVATE);
4872 int LevelStats_getPlayed(int nr)
4874 return (nr >= 0 && nr < MAX_LEVELS ? level_stats[nr].played : 0);
4877 int LevelStats_getSolved(int nr)
4879 return (nr >= 0 && nr < MAX_LEVELS ? level_stats[nr].solved : 0);
4882 void LevelStats_setPlayed(int nr, int value)
4884 if (nr >= 0 && nr < MAX_LEVELS)
4885 level_stats[nr].played = value;
4888 void LevelStats_setSolved(int nr, int value)
4890 if (nr >= 0 && nr < MAX_LEVELS)
4891 level_stats[nr].solved = value;
4894 void LevelStats_incPlayed(int nr)
4896 if (nr >= 0 && nr < MAX_LEVELS)
4897 level_stats[nr].played++;
4900 void LevelStats_incSolved(int nr)
4902 if (nr >= 0 && nr < MAX_LEVELS)
4903 level_stats[nr].solved++;
4906 void LoadUserSetup(void)
4908 // --------------------------------------------------------------------------
4909 // ~/.<program>/usersetup.conf
4910 // --------------------------------------------------------------------------
4912 char *filename = getPath2(getMainUserGameDataDir(), USERSETUP_FILENAME);
4913 SetupFileHash *user_setup_hash = NULL;
4915 // always start with reliable default values
4918 if ((user_setup_hash = loadSetupFileHash(filename)))
4922 // get last selected user number
4923 token_value = getHashEntry(user_setup_hash, TOKEN_STR_LAST_USER);
4926 user.nr = MIN(MAX(0, atoi(token_value)), MAX_PLAYER_NAMES - 1);
4928 freeSetupFileHash(user_setup_hash);
4932 Debug("setup", "using default setup values");
4938 void SaveUserSetup(void)
4940 // --------------------------------------------------------------------------
4941 // ~/.<program>/usersetup.conf
4942 // --------------------------------------------------------------------------
4944 char *filename = getPath2(getMainUserGameDataDir(), USERSETUP_FILENAME);
4947 InitMainUserDataDirectory();
4949 if (!(file = fopen(filename, MODE_WRITE)))
4951 Warn("cannot write setup file '%s'", filename);
4958 fprintFileHeader(file, USERSETUP_FILENAME);
4960 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_USER,
4964 SetFilePermissions(filename, PERMS_PRIVATE);