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)
1393 Debug("tree", "Dumping TreeInfo:");
1397 for (i = 0; i < (depth + 1) * 3; i++)
1398 DebugContinued("", " ");
1400 DebugContinued("tree", "'%s' / '%s'\n", node->identifier, node->name);
1403 // use for dumping artwork info tree
1404 Debug("tree", "subdir == '%s' ['%s', '%s'] [%d])",
1405 node->subdir, node->fullpath, node->basepath, node->in_user_dir);
1408 if (node->node_group != NULL)
1409 dumpTreeInfo(node->node_group, depth + 1);
1415 void sortTreeInfoBySortFunction(TreeInfo **node_first,
1416 int (*compare_function)(const void *,
1419 int num_nodes = numTreeInfo(*node_first);
1420 TreeInfo **sort_array;
1421 TreeInfo *node = *node_first;
1427 // allocate array for sorting structure pointers
1428 sort_array = checked_calloc(num_nodes * sizeof(TreeInfo *));
1430 // writing structure pointers to sorting array
1431 while (i < num_nodes && node) // double boundary check...
1433 sort_array[i] = node;
1439 // sorting the structure pointers in the sorting array
1440 qsort(sort_array, num_nodes, sizeof(TreeInfo *),
1443 // update the linkage of list elements with the sorted node array
1444 for (i = 0; i < num_nodes - 1; i++)
1445 sort_array[i]->next = sort_array[i + 1];
1446 sort_array[num_nodes - 1]->next = NULL;
1448 // update the linkage of the main list anchor pointer
1449 *node_first = sort_array[0];
1453 // now recursively sort the level group structures
1457 if (node->node_group != NULL)
1458 sortTreeInfoBySortFunction(&node->node_group, compare_function);
1464 void sortTreeInfo(TreeInfo **node_first)
1466 sortTreeInfoBySortFunction(node_first, compareTreeInfoEntries);
1470 // ============================================================================
1471 // some stuff from "files.c"
1472 // ============================================================================
1474 #if defined(PLATFORM_WIN32)
1476 #define S_IRGRP S_IRUSR
1479 #define S_IROTH S_IRUSR
1482 #define S_IWGRP S_IWUSR
1485 #define S_IWOTH S_IWUSR
1488 #define S_IXGRP S_IXUSR
1491 #define S_IXOTH S_IXUSR
1494 #define S_IRWXG (S_IRGRP | S_IWGRP | S_IXGRP)
1499 #endif // PLATFORM_WIN32
1501 // file permissions for newly written files
1502 #define MODE_R_ALL (S_IRUSR | S_IRGRP | S_IROTH)
1503 #define MODE_W_ALL (S_IWUSR | S_IWGRP | S_IWOTH)
1504 #define MODE_X_ALL (S_IXUSR | S_IXGRP | S_IXOTH)
1506 #define MODE_W_PRIVATE (S_IWUSR)
1507 #define MODE_W_PUBLIC_FILE (S_IWUSR | S_IWGRP)
1508 #define MODE_W_PUBLIC_DIR (S_IWUSR | S_IWGRP | S_ISGID)
1510 #define DIR_PERMS_PRIVATE (MODE_R_ALL | MODE_X_ALL | MODE_W_PRIVATE)
1511 #define DIR_PERMS_PUBLIC (MODE_R_ALL | MODE_X_ALL | MODE_W_PUBLIC_DIR)
1512 #define DIR_PERMS_PUBLIC_ALL (MODE_R_ALL | MODE_X_ALL | MODE_W_ALL)
1514 #define FILE_PERMS_PRIVATE (MODE_R_ALL | MODE_W_PRIVATE)
1515 #define FILE_PERMS_PUBLIC (MODE_R_ALL | MODE_W_PUBLIC_FILE)
1516 #define FILE_PERMS_PUBLIC_ALL (MODE_R_ALL | MODE_W_ALL)
1519 char *getHomeDir(void)
1521 static char *dir = NULL;
1523 #if defined(PLATFORM_WIN32)
1526 dir = checked_malloc(MAX_PATH + 1);
1528 if (!SUCCEEDED(SHGetFolderPath(NULL, CSIDL_PERSONAL, NULL, 0, dir)))
1531 #elif defined(PLATFORM_UNIX)
1534 if ((dir = getenv("HOME")) == NULL)
1536 dir = getUnixHomeDir();
1539 dir = getStringCopy(dir);
1551 char *getCommonDataDir(void)
1553 static char *common_data_dir = NULL;
1555 #if defined(PLATFORM_WIN32)
1556 if (common_data_dir == NULL)
1558 char *dir = checked_malloc(MAX_PATH + 1);
1560 if (SUCCEEDED(SHGetFolderPath(NULL, CSIDL_COMMON_DOCUMENTS, NULL, 0, dir))
1561 && !strEqual(dir, "")) // empty for Windows 95/98
1562 common_data_dir = getPath2(dir, program.userdata_subdir);
1564 common_data_dir = options.rw_base_directory;
1567 if (common_data_dir == NULL)
1568 common_data_dir = options.rw_base_directory;
1571 return common_data_dir;
1574 char *getPersonalDataDir(void)
1576 static char *personal_data_dir = NULL;
1578 #if defined(PLATFORM_MACOSX)
1579 if (personal_data_dir == NULL)
1580 personal_data_dir = getPath2(getHomeDir(), "Documents");
1582 if (personal_data_dir == NULL)
1583 personal_data_dir = getHomeDir();
1586 return personal_data_dir;
1589 char *getMainUserGameDataDir(void)
1591 static char *main_user_data_dir = NULL;
1593 #if defined(PLATFORM_ANDROID)
1594 if (main_user_data_dir == NULL)
1595 main_user_data_dir = (char *)(SDL_AndroidGetExternalStorageState() &
1596 SDL_ANDROID_EXTERNAL_STORAGE_WRITE ?
1597 SDL_AndroidGetExternalStoragePath() :
1598 SDL_AndroidGetInternalStoragePath());
1600 if (main_user_data_dir == NULL)
1601 main_user_data_dir = getPath2(getPersonalDataDir(),
1602 program.userdata_subdir);
1605 return main_user_data_dir;
1608 char *getUserGameDataDir(void)
1611 return getMainUserGameDataDir();
1613 return getUserDir(user.nr);
1616 char *getSetupDir(void)
1618 return getUserGameDataDir();
1621 static mode_t posix_umask(mode_t mask)
1623 #if defined(PLATFORM_UNIX)
1630 static int posix_mkdir(const char *pathname, mode_t mode)
1632 #if defined(PLATFORM_WIN32)
1633 return mkdir(pathname);
1635 return mkdir(pathname, mode);
1639 static boolean posix_process_running_setgid(void)
1641 #if defined(PLATFORM_UNIX)
1642 return (getgid() != getegid());
1648 void createDirectory(char *dir, char *text, int permission_class)
1650 if (directoryExists(dir))
1653 // leave "other" permissions in umask untouched, but ensure group parts
1654 // of USERDATA_DIR_MODE are not masked
1655 mode_t dir_mode = (permission_class == PERMS_PRIVATE ?
1656 DIR_PERMS_PRIVATE : DIR_PERMS_PUBLIC);
1657 mode_t last_umask = posix_umask(0);
1658 mode_t group_umask = ~(dir_mode & S_IRWXG);
1659 int running_setgid = posix_process_running_setgid();
1661 if (permission_class == PERMS_PUBLIC)
1663 // if we're setgid, protect files against "other"
1664 // else keep umask(0) to make the dir world-writable
1667 posix_umask(last_umask & group_umask);
1669 dir_mode = DIR_PERMS_PUBLIC_ALL;
1672 if (posix_mkdir(dir, dir_mode) != 0)
1673 Warn("cannot create %s directory '%s': %s", text, dir, strerror(errno));
1675 if (permission_class == PERMS_PUBLIC && !running_setgid)
1676 chmod(dir, dir_mode);
1678 posix_umask(last_umask); // restore previous umask
1681 void InitMainUserDataDirectory(void)
1683 createDirectory(getMainUserGameDataDir(), "main user data", PERMS_PRIVATE);
1686 void InitUserDataDirectory(void)
1688 createDirectory(getMainUserGameDataDir(), "main user data", PERMS_PRIVATE);
1692 createDirectory(getUserDir(-1), "users", PERMS_PRIVATE);
1693 createDirectory(getUserDir(user.nr), "user data", PERMS_PRIVATE);
1697 void SetFilePermissions(char *filename, int permission_class)
1699 int running_setgid = posix_process_running_setgid();
1700 int perms = (permission_class == PERMS_PRIVATE ?
1701 FILE_PERMS_PRIVATE : FILE_PERMS_PUBLIC);
1703 if (permission_class == PERMS_PUBLIC && !running_setgid)
1704 perms = FILE_PERMS_PUBLIC_ALL;
1706 chmod(filename, perms);
1709 char *getCookie(char *file_type)
1711 static char cookie[MAX_COOKIE_LEN + 1];
1713 if (strlen(program.cookie_prefix) + 1 +
1714 strlen(file_type) + strlen("_FILE_VERSION_x.x") > MAX_COOKIE_LEN)
1715 return "[COOKIE ERROR]"; // should never happen
1717 sprintf(cookie, "%s_%s_FILE_VERSION_%d.%d",
1718 program.cookie_prefix, file_type,
1719 program.version_super, program.version_major);
1724 void fprintFileHeader(FILE *file, char *basename)
1726 char *prefix = "# ";
1729 fprintf_line_with_prefix(file, prefix, sep1, 77);
1730 fprintf(file, "%s%s\n", prefix, basename);
1731 fprintf_line_with_prefix(file, prefix, sep1, 77);
1732 fprintf(file, "\n");
1735 int getFileVersionFromCookieString(const char *cookie)
1737 const char *ptr_cookie1, *ptr_cookie2;
1738 const char *pattern1 = "_FILE_VERSION_";
1739 const char *pattern2 = "?.?";
1740 const int len_cookie = strlen(cookie);
1741 const int len_pattern1 = strlen(pattern1);
1742 const int len_pattern2 = strlen(pattern2);
1743 const int len_pattern = len_pattern1 + len_pattern2;
1744 int version_super, version_major;
1746 if (len_cookie <= len_pattern)
1749 ptr_cookie1 = &cookie[len_cookie - len_pattern];
1750 ptr_cookie2 = &cookie[len_cookie - len_pattern2];
1752 if (strncmp(ptr_cookie1, pattern1, len_pattern1) != 0)
1755 if (ptr_cookie2[0] < '0' || ptr_cookie2[0] > '9' ||
1756 ptr_cookie2[1] != '.' ||
1757 ptr_cookie2[2] < '0' || ptr_cookie2[2] > '9')
1760 version_super = ptr_cookie2[0] - '0';
1761 version_major = ptr_cookie2[2] - '0';
1763 return VERSION_IDENT(version_super, version_major, 0, 0);
1766 boolean checkCookieString(const char *cookie, const char *template)
1768 const char *pattern = "_FILE_VERSION_?.?";
1769 const int len_cookie = strlen(cookie);
1770 const int len_template = strlen(template);
1771 const int len_pattern = strlen(pattern);
1773 if (len_cookie != len_template)
1776 if (strncmp(cookie, template, len_cookie - len_pattern) != 0)
1783 // ----------------------------------------------------------------------------
1784 // setup file list and hash handling functions
1785 // ----------------------------------------------------------------------------
1787 char *getFormattedSetupEntry(char *token, char *value)
1790 static char entry[MAX_LINE_LEN];
1792 // if value is an empty string, just return token without value
1796 // start with the token and some spaces to format output line
1797 sprintf(entry, "%s:", token);
1798 for (i = strlen(entry); i < token_value_position; i++)
1801 // continue with the token's value
1802 strcat(entry, value);
1807 SetupFileList *newSetupFileList(char *token, char *value)
1809 SetupFileList *new = checked_malloc(sizeof(SetupFileList));
1811 new->token = getStringCopy(token);
1812 new->value = getStringCopy(value);
1819 void freeSetupFileList(SetupFileList *list)
1824 checked_free(list->token);
1825 checked_free(list->value);
1828 freeSetupFileList(list->next);
1833 char *getListEntry(SetupFileList *list, char *token)
1838 if (strEqual(list->token, token))
1841 return getListEntry(list->next, token);
1844 SetupFileList *setListEntry(SetupFileList *list, char *token, char *value)
1849 if (strEqual(list->token, token))
1851 checked_free(list->value);
1853 list->value = getStringCopy(value);
1857 else if (list->next == NULL)
1858 return (list->next = newSetupFileList(token, value));
1860 return setListEntry(list->next, token, value);
1863 SetupFileList *addListEntry(SetupFileList *list, char *token, char *value)
1868 if (list->next == NULL)
1869 return (list->next = newSetupFileList(token, value));
1871 return addListEntry(list->next, token, value);
1874 #if ENABLE_UNUSED_CODE
1876 static void printSetupFileList(SetupFileList *list)
1881 Debug("setup:printSetupFileList", "token: '%s'", list->token);
1882 Debug("setup:printSetupFileList", "value: '%s'", list->value);
1884 printSetupFileList(list->next);
1890 DEFINE_HASHTABLE_INSERT(insert_hash_entry, char, char);
1891 DEFINE_HASHTABLE_SEARCH(search_hash_entry, char, char);
1892 DEFINE_HASHTABLE_CHANGE(change_hash_entry, char, char);
1893 DEFINE_HASHTABLE_REMOVE(remove_hash_entry, char, char);
1895 #define insert_hash_entry hashtable_insert
1896 #define search_hash_entry hashtable_search
1897 #define change_hash_entry hashtable_change
1898 #define remove_hash_entry hashtable_remove
1901 unsigned int get_hash_from_key(void *key)
1906 This algorithm (k=33) was first reported by Dan Bernstein many years ago in
1907 'comp.lang.c'. Another version of this algorithm (now favored by Bernstein)
1908 uses XOR: hash(i) = hash(i - 1) * 33 ^ str[i]; the magic of number 33 (why
1909 it works better than many other constants, prime or not) has never been
1910 adequately explained.
1912 If you just want to have a good hash function, and cannot wait, djb2
1913 is one of the best string hash functions i know. It has excellent
1914 distribution and speed on many different sets of keys and table sizes.
1915 You are not likely to do better with one of the "well known" functions
1916 such as PJW, K&R, etc.
1918 Ozan (oz) Yigit [http://www.cs.yorku.ca/~oz/hash.html]
1921 char *str = (char *)key;
1922 unsigned int hash = 5381;
1925 while ((c = *str++))
1926 hash = ((hash << 5) + hash) + c; // hash * 33 + c
1931 static int keys_are_equal(void *key1, void *key2)
1933 return (strEqual((char *)key1, (char *)key2));
1936 SetupFileHash *newSetupFileHash(void)
1938 SetupFileHash *new_hash =
1939 create_hashtable(16, 0.75, get_hash_from_key, keys_are_equal);
1941 if (new_hash == NULL)
1942 Fail("create_hashtable() failed -- out of memory");
1947 void freeSetupFileHash(SetupFileHash *hash)
1952 hashtable_destroy(hash, 1); // 1 == also free values stored in hash
1955 char *getHashEntry(SetupFileHash *hash, char *token)
1960 return search_hash_entry(hash, token);
1963 void setHashEntry(SetupFileHash *hash, char *token, char *value)
1970 value_copy = getStringCopy(value);
1972 // change value; if it does not exist, insert it as new
1973 if (!change_hash_entry(hash, token, value_copy))
1974 if (!insert_hash_entry(hash, getStringCopy(token), value_copy))
1975 Fail("cannot insert into hash -- aborting");
1978 char *removeHashEntry(SetupFileHash *hash, char *token)
1983 return remove_hash_entry(hash, token);
1986 #if ENABLE_UNUSED_CODE
1988 static void printSetupFileHash(SetupFileHash *hash)
1990 BEGIN_HASH_ITERATION(hash, itr)
1992 Debug("setup:printSetupFileHash", "token: '%s'", HASH_ITERATION_TOKEN(itr));
1993 Debug("setup:printSetupFileHash", "value: '%s'", HASH_ITERATION_VALUE(itr));
1995 END_HASH_ITERATION(hash, itr)
2000 #define ALLOW_TOKEN_VALUE_SEPARATOR_BEING_WHITESPACE 1
2001 #define CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING 0
2002 #define CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH 0
2004 static boolean token_value_separator_found = FALSE;
2005 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2006 static boolean token_value_separator_warning = FALSE;
2008 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2009 static boolean token_already_exists_warning = FALSE;
2012 static boolean getTokenValueFromSetupLineExt(char *line,
2013 char **token_ptr, char **value_ptr,
2014 char *filename, char *line_raw,
2016 boolean separator_required)
2018 static char line_copy[MAX_LINE_LEN + 1], line_raw_copy[MAX_LINE_LEN + 1];
2019 char *token, *value, *line_ptr;
2021 // when externally invoked via ReadTokenValueFromLine(), copy line buffers
2022 if (line_raw == NULL)
2024 strncpy(line_copy, line, MAX_LINE_LEN);
2025 line_copy[MAX_LINE_LEN] = '\0';
2028 strcpy(line_raw_copy, line_copy);
2029 line_raw = line_raw_copy;
2032 // cut trailing comment from input line
2033 for (line_ptr = line; *line_ptr; line_ptr++)
2035 if (*line_ptr == '#')
2042 // cut trailing whitespaces from input line
2043 for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
2044 if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
2047 // ignore empty lines
2051 // cut leading whitespaces from token
2052 for (token = line; *token; token++)
2053 if (*token != ' ' && *token != '\t')
2056 // start with empty value as reliable default
2059 token_value_separator_found = FALSE;
2061 // find end of token to determine start of value
2062 for (line_ptr = token; *line_ptr; line_ptr++)
2064 // first look for an explicit token/value separator, like ':' or '='
2065 if (*line_ptr == ':' || *line_ptr == '=')
2067 *line_ptr = '\0'; // terminate token string
2068 value = line_ptr + 1; // set beginning of value
2070 token_value_separator_found = TRUE;
2076 #if ALLOW_TOKEN_VALUE_SEPARATOR_BEING_WHITESPACE
2077 // fallback: if no token/value separator found, also allow whitespaces
2078 if (!token_value_separator_found && !separator_required)
2080 for (line_ptr = token; *line_ptr; line_ptr++)
2082 if (*line_ptr == ' ' || *line_ptr == '\t')
2084 *line_ptr = '\0'; // terminate token string
2085 value = line_ptr + 1; // set beginning of value
2087 token_value_separator_found = TRUE;
2093 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2094 if (token_value_separator_found)
2096 if (!token_value_separator_warning)
2098 Debug("setup", "---");
2100 if (filename != NULL)
2102 Debug("setup", "missing token/value separator(s) in config file:");
2103 Debug("setup", "- config file: '%s'", filename);
2107 Debug("setup", "missing token/value separator(s):");
2110 token_value_separator_warning = TRUE;
2113 if (filename != NULL)
2114 Debug("setup", "- line %d: '%s'", line_nr, line_raw);
2116 Debug("setup", "- line: '%s'", line_raw);
2122 // cut trailing whitespaces from token
2123 for (line_ptr = &token[strlen(token)]; line_ptr >= token; line_ptr--)
2124 if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
2127 // cut leading whitespaces from value
2128 for (; *value; value++)
2129 if (*value != ' ' && *value != '\t')
2138 boolean getTokenValueFromSetupLine(char *line, char **token, char **value)
2140 // while the internal (old) interface does not require a token/value
2141 // separator (for downwards compatibility with existing files which
2142 // don't use them), it is mandatory for the external (new) interface
2144 return getTokenValueFromSetupLineExt(line, token, value, NULL, NULL, 0, TRUE);
2147 static boolean loadSetupFileData(void *setup_file_data, char *filename,
2148 boolean top_recursion_level, boolean is_hash)
2150 static SetupFileHash *include_filename_hash = NULL;
2151 char line[MAX_LINE_LEN], line_raw[MAX_LINE_LEN], previous_line[MAX_LINE_LEN];
2152 char *token, *value, *line_ptr;
2153 void *insert_ptr = NULL;
2154 boolean read_continued_line = FALSE;
2156 int line_nr = 0, token_count = 0, include_count = 0;
2158 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2159 token_value_separator_warning = FALSE;
2162 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2163 token_already_exists_warning = FALSE;
2166 if (!(file = openFile(filename, MODE_READ)))
2168 #if DEBUG_NO_CONFIG_FILE
2169 Debug("setup", "cannot open configuration file '%s'", filename);
2175 // use "insert pointer" to store list end for constant insertion complexity
2177 insert_ptr = setup_file_data;
2179 // on top invocation, create hash to mark included files (to prevent loops)
2180 if (top_recursion_level)
2181 include_filename_hash = newSetupFileHash();
2183 // mark this file as already included (to prevent including it again)
2184 setHashEntry(include_filename_hash, getBaseNamePtr(filename), "true");
2186 while (!checkEndOfFile(file))
2188 // read next line of input file
2189 if (!getStringFromFile(file, line, MAX_LINE_LEN))
2192 // check if line was completely read and is terminated by line break
2193 if (strlen(line) > 0 && line[strlen(line) - 1] == '\n')
2196 // cut trailing line break (this can be newline and/or carriage return)
2197 for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
2198 if ((*line_ptr == '\n' || *line_ptr == '\r') && *(line_ptr + 1) == '\0')
2201 // copy raw input line for later use (mainly debugging output)
2202 strcpy(line_raw, line);
2204 if (read_continued_line)
2206 // append new line to existing line, if there is enough space
2207 if (strlen(previous_line) + strlen(line_ptr) < MAX_LINE_LEN)
2208 strcat(previous_line, line_ptr);
2210 strcpy(line, previous_line); // copy storage buffer to line
2212 read_continued_line = FALSE;
2215 // if the last character is '\', continue at next line
2216 if (strlen(line) > 0 && line[strlen(line) - 1] == '\\')
2218 line[strlen(line) - 1] = '\0'; // cut off trailing backslash
2219 strcpy(previous_line, line); // copy line to storage buffer
2221 read_continued_line = TRUE;
2226 if (!getTokenValueFromSetupLineExt(line, &token, &value, filename,
2227 line_raw, line_nr, FALSE))
2232 if (strEqual(token, "include"))
2234 if (getHashEntry(include_filename_hash, value) == NULL)
2236 char *basepath = getBasePath(filename);
2237 char *basename = getBaseName(value);
2238 char *filename_include = getPath2(basepath, basename);
2240 loadSetupFileData(setup_file_data, filename_include, FALSE, is_hash);
2244 free(filename_include);
2250 Warn("ignoring already processed file '%s'", value);
2257 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2259 getHashEntry((SetupFileHash *)setup_file_data, token);
2261 if (old_value != NULL)
2263 if (!token_already_exists_warning)
2265 Debug("setup", "---");
2266 Debug("setup", "duplicate token(s) found in config file:");
2267 Debug("setup", "- config file: '%s'", filename);
2269 token_already_exists_warning = TRUE;
2272 Debug("setup", "- token: '%s' (in line %d)", token, line_nr);
2273 Debug("setup", " old value: '%s'", old_value);
2274 Debug("setup", " new value: '%s'", value);
2278 setHashEntry((SetupFileHash *)setup_file_data, token, value);
2282 insert_ptr = addListEntry((SetupFileList *)insert_ptr, token, value);
2292 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2293 if (token_value_separator_warning)
2294 Debug("setup", "---");
2297 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2298 if (token_already_exists_warning)
2299 Debug("setup", "---");
2302 if (token_count == 0 && include_count == 0)
2303 Warn("configuration file '%s' is empty", filename);
2305 if (top_recursion_level)
2306 freeSetupFileHash(include_filename_hash);
2311 static void saveSetupFileHash(SetupFileHash *hash, char *filename)
2315 if (!(file = fopen(filename, MODE_WRITE)))
2317 Warn("cannot write configuration file '%s'", filename);
2322 BEGIN_HASH_ITERATION(hash, itr)
2324 fprintf(file, "%s\n", getFormattedSetupEntry(HASH_ITERATION_TOKEN(itr),
2325 HASH_ITERATION_VALUE(itr)));
2327 END_HASH_ITERATION(hash, itr)
2332 SetupFileList *loadSetupFileList(char *filename)
2334 SetupFileList *setup_file_list = newSetupFileList("", "");
2335 SetupFileList *first_valid_list_entry;
2337 if (!loadSetupFileData(setup_file_list, filename, TRUE, FALSE))
2339 freeSetupFileList(setup_file_list);
2344 first_valid_list_entry = setup_file_list->next;
2346 // free empty list header
2347 setup_file_list->next = NULL;
2348 freeSetupFileList(setup_file_list);
2350 return first_valid_list_entry;
2353 SetupFileHash *loadSetupFileHash(char *filename)
2355 SetupFileHash *setup_file_hash = newSetupFileHash();
2357 if (!loadSetupFileData(setup_file_hash, filename, TRUE, TRUE))
2359 freeSetupFileHash(setup_file_hash);
2364 return setup_file_hash;
2368 // ============================================================================
2370 // ============================================================================
2372 #define TOKEN_STR_LAST_LEVEL_SERIES "last_level_series"
2373 #define TOKEN_STR_LAST_PLAYED_LEVEL "last_played_level"
2374 #define TOKEN_STR_HANDICAP_LEVEL "handicap_level"
2375 #define TOKEN_STR_LAST_USER "last_user"
2377 // level directory info
2378 #define LEVELINFO_TOKEN_IDENTIFIER 0
2379 #define LEVELINFO_TOKEN_NAME 1
2380 #define LEVELINFO_TOKEN_NAME_SORTING 2
2381 #define LEVELINFO_TOKEN_AUTHOR 3
2382 #define LEVELINFO_TOKEN_YEAR 4
2383 #define LEVELINFO_TOKEN_PROGRAM_TITLE 5
2384 #define LEVELINFO_TOKEN_PROGRAM_COPYRIGHT 6
2385 #define LEVELINFO_TOKEN_PROGRAM_COMPANY 7
2386 #define LEVELINFO_TOKEN_IMPORTED_FROM 8
2387 #define LEVELINFO_TOKEN_IMPORTED_BY 9
2388 #define LEVELINFO_TOKEN_TESTED_BY 10
2389 #define LEVELINFO_TOKEN_LEVELS 11
2390 #define LEVELINFO_TOKEN_FIRST_LEVEL 12
2391 #define LEVELINFO_TOKEN_SORT_PRIORITY 13
2392 #define LEVELINFO_TOKEN_LATEST_ENGINE 14
2393 #define LEVELINFO_TOKEN_LEVEL_GROUP 15
2394 #define LEVELINFO_TOKEN_READONLY 16
2395 #define LEVELINFO_TOKEN_GRAPHICS_SET_ECS 17
2396 #define LEVELINFO_TOKEN_GRAPHICS_SET_AGA 18
2397 #define LEVELINFO_TOKEN_GRAPHICS_SET 19
2398 #define LEVELINFO_TOKEN_SOUNDS_SET_DEFAULT 20
2399 #define LEVELINFO_TOKEN_SOUNDS_SET_LOWPASS 21
2400 #define LEVELINFO_TOKEN_SOUNDS_SET 22
2401 #define LEVELINFO_TOKEN_MUSIC_SET 23
2402 #define LEVELINFO_TOKEN_FILENAME 24
2403 #define LEVELINFO_TOKEN_FILETYPE 25
2404 #define LEVELINFO_TOKEN_SPECIAL_FLAGS 26
2405 #define LEVELINFO_TOKEN_HANDICAP 27
2406 #define LEVELINFO_TOKEN_SKIP_LEVELS 28
2407 #define LEVELINFO_TOKEN_USE_EMC_TILES 29
2409 #define NUM_LEVELINFO_TOKENS 30
2411 static LevelDirTree ldi;
2413 static struct TokenInfo levelinfo_tokens[] =
2415 // level directory info
2416 { TYPE_STRING, &ldi.identifier, "identifier" },
2417 { TYPE_STRING, &ldi.name, "name" },
2418 { TYPE_STRING, &ldi.name_sorting, "name_sorting" },
2419 { TYPE_STRING, &ldi.author, "author" },
2420 { TYPE_STRING, &ldi.year, "year" },
2421 { TYPE_STRING, &ldi.program_title, "program_title" },
2422 { TYPE_STRING, &ldi.program_copyright, "program_copyright" },
2423 { TYPE_STRING, &ldi.program_company, "program_company" },
2424 { TYPE_STRING, &ldi.imported_from, "imported_from" },
2425 { TYPE_STRING, &ldi.imported_by, "imported_by" },
2426 { TYPE_STRING, &ldi.tested_by, "tested_by" },
2427 { TYPE_INTEGER, &ldi.levels, "levels" },
2428 { TYPE_INTEGER, &ldi.first_level, "first_level" },
2429 { TYPE_INTEGER, &ldi.sort_priority, "sort_priority" },
2430 { TYPE_BOOLEAN, &ldi.latest_engine, "latest_engine" },
2431 { TYPE_BOOLEAN, &ldi.level_group, "level_group" },
2432 { TYPE_BOOLEAN, &ldi.readonly, "readonly" },
2433 { TYPE_STRING, &ldi.graphics_set_ecs, "graphics_set.ecs" },
2434 { TYPE_STRING, &ldi.graphics_set_aga, "graphics_set.aga" },
2435 { TYPE_STRING, &ldi.graphics_set, "graphics_set" },
2436 { TYPE_STRING, &ldi.sounds_set_default,"sounds_set.default" },
2437 { TYPE_STRING, &ldi.sounds_set_lowpass,"sounds_set.lowpass" },
2438 { TYPE_STRING, &ldi.sounds_set, "sounds_set" },
2439 { TYPE_STRING, &ldi.music_set, "music_set" },
2440 { TYPE_STRING, &ldi.level_filename, "filename" },
2441 { TYPE_STRING, &ldi.level_filetype, "filetype" },
2442 { TYPE_STRING, &ldi.special_flags, "special_flags" },
2443 { TYPE_BOOLEAN, &ldi.handicap, "handicap" },
2444 { TYPE_BOOLEAN, &ldi.skip_levels, "skip_levels" },
2445 { TYPE_BOOLEAN, &ldi.use_emc_tiles, "use_emc_tiles" }
2448 static struct TokenInfo artworkinfo_tokens[] =
2450 // artwork directory info
2451 { TYPE_STRING, &ldi.identifier, "identifier" },
2452 { TYPE_STRING, &ldi.subdir, "subdir" },
2453 { TYPE_STRING, &ldi.name, "name" },
2454 { TYPE_STRING, &ldi.name_sorting, "name_sorting" },
2455 { TYPE_STRING, &ldi.author, "author" },
2456 { TYPE_STRING, &ldi.program_title, "program_title" },
2457 { TYPE_STRING, &ldi.program_copyright, "program_copyright" },
2458 { TYPE_STRING, &ldi.program_company, "program_company" },
2459 { TYPE_INTEGER, &ldi.sort_priority, "sort_priority" },
2460 { TYPE_STRING, &ldi.basepath, "basepath" },
2461 { TYPE_STRING, &ldi.fullpath, "fullpath" },
2462 { TYPE_BOOLEAN, &ldi.in_user_dir, "in_user_dir" },
2463 { TYPE_INTEGER, &ldi.color, "color" },
2464 { TYPE_STRING, &ldi.class_desc, "class_desc" },
2469 static char *optional_tokens[] =
2472 "program_copyright",
2478 static void setTreeInfoToDefaults(TreeInfo *ti, int type)
2482 ti->node_top = (ti->type == TREE_TYPE_LEVEL_DIR ? &leveldir_first :
2483 ti->type == TREE_TYPE_GRAPHICS_DIR ? &artwork.gfx_first :
2484 ti->type == TREE_TYPE_SOUNDS_DIR ? &artwork.snd_first :
2485 ti->type == TREE_TYPE_MUSIC_DIR ? &artwork.mus_first :
2488 ti->node_parent = NULL;
2489 ti->node_group = NULL;
2496 ti->fullpath = NULL;
2497 ti->basepath = NULL;
2498 ti->identifier = NULL;
2499 ti->name = getStringCopy(ANONYMOUS_NAME);
2500 ti->name_sorting = NULL;
2501 ti->author = getStringCopy(ANONYMOUS_NAME);
2504 ti->program_title = NULL;
2505 ti->program_copyright = NULL;
2506 ti->program_company = NULL;
2508 ti->sort_priority = LEVELCLASS_UNDEFINED; // default: least priority
2509 ti->latest_engine = FALSE; // default: get from level
2510 ti->parent_link = FALSE;
2511 ti->in_user_dir = FALSE;
2512 ti->user_defined = FALSE;
2514 ti->class_desc = NULL;
2516 ti->infotext = getStringCopy(TREE_INFOTEXT(ti->type));
2518 if (ti->type == TREE_TYPE_LEVEL_DIR)
2520 ti->imported_from = NULL;
2521 ti->imported_by = NULL;
2522 ti->tested_by = NULL;
2524 ti->graphics_set_ecs = NULL;
2525 ti->graphics_set_aga = NULL;
2526 ti->graphics_set = NULL;
2527 ti->sounds_set_default = NULL;
2528 ti->sounds_set_lowpass = NULL;
2529 ti->sounds_set = NULL;
2530 ti->music_set = NULL;
2531 ti->graphics_path = getStringCopy(UNDEFINED_FILENAME);
2532 ti->sounds_path = getStringCopy(UNDEFINED_FILENAME);
2533 ti->music_path = getStringCopy(UNDEFINED_FILENAME);
2535 ti->level_filename = NULL;
2536 ti->level_filetype = NULL;
2538 ti->special_flags = NULL;
2541 ti->first_level = 0;
2543 ti->level_group = FALSE;
2544 ti->handicap_level = 0;
2545 ti->readonly = TRUE;
2546 ti->handicap = TRUE;
2547 ti->skip_levels = FALSE;
2549 ti->use_emc_tiles = FALSE;
2553 static void setTreeInfoToDefaultsFromParent(TreeInfo *ti, TreeInfo *parent)
2557 Warn("setTreeInfoToDefaultsFromParent(): parent == NULL");
2559 setTreeInfoToDefaults(ti, TREE_TYPE_UNDEFINED);
2564 // copy all values from the parent structure
2566 ti->type = parent->type;
2568 ti->node_top = parent->node_top;
2569 ti->node_parent = parent;
2570 ti->node_group = NULL;
2577 ti->fullpath = NULL;
2578 ti->basepath = NULL;
2579 ti->identifier = NULL;
2580 ti->name = getStringCopy(ANONYMOUS_NAME);
2581 ti->name_sorting = NULL;
2582 ti->author = getStringCopy(parent->author);
2583 ti->year = getStringCopy(parent->year);
2585 ti->program_title = getStringCopy(parent->program_title);
2586 ti->program_copyright = getStringCopy(parent->program_copyright);
2587 ti->program_company = getStringCopy(parent->program_company);
2589 ti->sort_priority = parent->sort_priority;
2590 ti->latest_engine = parent->latest_engine;
2591 ti->parent_link = FALSE;
2592 ti->in_user_dir = parent->in_user_dir;
2593 ti->user_defined = parent->user_defined;
2594 ti->color = parent->color;
2595 ti->class_desc = getStringCopy(parent->class_desc);
2597 ti->infotext = getStringCopy(parent->infotext);
2599 if (ti->type == TREE_TYPE_LEVEL_DIR)
2601 ti->imported_from = getStringCopy(parent->imported_from);
2602 ti->imported_by = getStringCopy(parent->imported_by);
2603 ti->tested_by = getStringCopy(parent->tested_by);
2605 ti->graphics_set_ecs = getStringCopy(parent->graphics_set_ecs);
2606 ti->graphics_set_aga = getStringCopy(parent->graphics_set_aga);
2607 ti->graphics_set = getStringCopy(parent->graphics_set);
2608 ti->sounds_set_default = getStringCopy(parent->sounds_set_default);
2609 ti->sounds_set_lowpass = getStringCopy(parent->sounds_set_lowpass);
2610 ti->sounds_set = getStringCopy(parent->sounds_set);
2611 ti->music_set = getStringCopy(parent->music_set);
2612 ti->graphics_path = getStringCopy(UNDEFINED_FILENAME);
2613 ti->sounds_path = getStringCopy(UNDEFINED_FILENAME);
2614 ti->music_path = getStringCopy(UNDEFINED_FILENAME);
2616 ti->level_filename = getStringCopy(parent->level_filename);
2617 ti->level_filetype = getStringCopy(parent->level_filetype);
2619 ti->special_flags = getStringCopy(parent->special_flags);
2621 ti->levels = parent->levels;
2622 ti->first_level = parent->first_level;
2623 ti->last_level = parent->last_level;
2624 ti->level_group = FALSE;
2625 ti->handicap_level = parent->handicap_level;
2626 ti->readonly = parent->readonly;
2627 ti->handicap = parent->handicap;
2628 ti->skip_levels = parent->skip_levels;
2630 ti->use_emc_tiles = parent->use_emc_tiles;
2634 static TreeInfo *getTreeInfoCopy(TreeInfo *ti)
2636 TreeInfo *ti_copy = newTreeInfo();
2638 // copy all values from the original structure
2640 ti_copy->type = ti->type;
2642 ti_copy->node_top = ti->node_top;
2643 ti_copy->node_parent = ti->node_parent;
2644 ti_copy->node_group = ti->node_group;
2645 ti_copy->next = ti->next;
2647 ti_copy->cl_first = ti->cl_first;
2648 ti_copy->cl_cursor = ti->cl_cursor;
2650 ti_copy->subdir = getStringCopy(ti->subdir);
2651 ti_copy->fullpath = getStringCopy(ti->fullpath);
2652 ti_copy->basepath = getStringCopy(ti->basepath);
2653 ti_copy->identifier = getStringCopy(ti->identifier);
2654 ti_copy->name = getStringCopy(ti->name);
2655 ti_copy->name_sorting = getStringCopy(ti->name_sorting);
2656 ti_copy->author = getStringCopy(ti->author);
2657 ti_copy->year = getStringCopy(ti->year);
2659 ti_copy->program_title = getStringCopy(ti->program_title);
2660 ti_copy->program_copyright = getStringCopy(ti->program_copyright);
2661 ti_copy->program_company = getStringCopy(ti->program_company);
2663 ti_copy->imported_from = getStringCopy(ti->imported_from);
2664 ti_copy->imported_by = getStringCopy(ti->imported_by);
2665 ti_copy->tested_by = getStringCopy(ti->tested_by);
2667 ti_copy->graphics_set_ecs = getStringCopy(ti->graphics_set_ecs);
2668 ti_copy->graphics_set_aga = getStringCopy(ti->graphics_set_aga);
2669 ti_copy->graphics_set = getStringCopy(ti->graphics_set);
2670 ti_copy->sounds_set_default = getStringCopy(ti->sounds_set_default);
2671 ti_copy->sounds_set_lowpass = getStringCopy(ti->sounds_set_lowpass);
2672 ti_copy->sounds_set = getStringCopy(ti->sounds_set);
2673 ti_copy->music_set = getStringCopy(ti->music_set);
2674 ti_copy->graphics_path = getStringCopy(ti->graphics_path);
2675 ti_copy->sounds_path = getStringCopy(ti->sounds_path);
2676 ti_copy->music_path = getStringCopy(ti->music_path);
2678 ti_copy->level_filename = getStringCopy(ti->level_filename);
2679 ti_copy->level_filetype = getStringCopy(ti->level_filetype);
2681 ti_copy->special_flags = getStringCopy(ti->special_flags);
2683 ti_copy->levels = ti->levels;
2684 ti_copy->first_level = ti->first_level;
2685 ti_copy->last_level = ti->last_level;
2686 ti_copy->sort_priority = ti->sort_priority;
2688 ti_copy->latest_engine = ti->latest_engine;
2690 ti_copy->level_group = ti->level_group;
2691 ti_copy->parent_link = ti->parent_link;
2692 ti_copy->in_user_dir = ti->in_user_dir;
2693 ti_copy->user_defined = ti->user_defined;
2694 ti_copy->readonly = ti->readonly;
2695 ti_copy->handicap = ti->handicap;
2696 ti_copy->skip_levels = ti->skip_levels;
2698 ti_copy->use_emc_tiles = ti->use_emc_tiles;
2700 ti_copy->color = ti->color;
2701 ti_copy->class_desc = getStringCopy(ti->class_desc);
2702 ti_copy->handicap_level = ti->handicap_level;
2704 ti_copy->infotext = getStringCopy(ti->infotext);
2709 void freeTreeInfo(TreeInfo *ti)
2714 checked_free(ti->subdir);
2715 checked_free(ti->fullpath);
2716 checked_free(ti->basepath);
2717 checked_free(ti->identifier);
2719 checked_free(ti->name);
2720 checked_free(ti->name_sorting);
2721 checked_free(ti->author);
2722 checked_free(ti->year);
2724 checked_free(ti->program_title);
2725 checked_free(ti->program_copyright);
2726 checked_free(ti->program_company);
2728 checked_free(ti->class_desc);
2730 checked_free(ti->infotext);
2732 if (ti->type == TREE_TYPE_LEVEL_DIR)
2734 checked_free(ti->imported_from);
2735 checked_free(ti->imported_by);
2736 checked_free(ti->tested_by);
2738 checked_free(ti->graphics_set_ecs);
2739 checked_free(ti->graphics_set_aga);
2740 checked_free(ti->graphics_set);
2741 checked_free(ti->sounds_set_default);
2742 checked_free(ti->sounds_set_lowpass);
2743 checked_free(ti->sounds_set);
2744 checked_free(ti->music_set);
2746 checked_free(ti->graphics_path);
2747 checked_free(ti->sounds_path);
2748 checked_free(ti->music_path);
2750 checked_free(ti->level_filename);
2751 checked_free(ti->level_filetype);
2753 checked_free(ti->special_flags);
2756 // recursively free child node
2758 freeTreeInfo(ti->node_group);
2760 // recursively free next node
2762 freeTreeInfo(ti->next);
2767 void setSetupInfo(struct TokenInfo *token_info,
2768 int token_nr, char *token_value)
2770 int token_type = token_info[token_nr].type;
2771 void *setup_value = token_info[token_nr].value;
2773 if (token_value == NULL)
2776 // set setup field to corresponding token value
2781 *(boolean *)setup_value = get_boolean_from_string(token_value);
2785 *(int *)setup_value = get_switch3_from_string(token_value);
2789 *(Key *)setup_value = getKeyFromKeyName(token_value);
2793 *(Key *)setup_value = getKeyFromX11KeyName(token_value);
2797 *(int *)setup_value = get_integer_from_string(token_value);
2801 checked_free(*(char **)setup_value);
2802 *(char **)setup_value = getStringCopy(token_value);
2806 *(int *)setup_value = get_player_nr_from_string(token_value);
2814 static int compareTreeInfoEntries(const void *object1, const void *object2)
2816 const TreeInfo *entry1 = *((TreeInfo **)object1);
2817 const TreeInfo *entry2 = *((TreeInfo **)object2);
2818 int class_sorting1 = 0, class_sorting2 = 0;
2821 if (entry1->type == TREE_TYPE_LEVEL_DIR)
2823 class_sorting1 = LEVELSORTING(entry1);
2824 class_sorting2 = LEVELSORTING(entry2);
2826 else if (entry1->type == TREE_TYPE_GRAPHICS_DIR ||
2827 entry1->type == TREE_TYPE_SOUNDS_DIR ||
2828 entry1->type == TREE_TYPE_MUSIC_DIR)
2830 class_sorting1 = ARTWORKSORTING(entry1);
2831 class_sorting2 = ARTWORKSORTING(entry2);
2834 if (entry1->parent_link || entry2->parent_link)
2835 compare_result = (entry1->parent_link ? -1 : +1);
2836 else if (entry1->sort_priority == entry2->sort_priority)
2838 char *name1 = getStringToLower(entry1->name_sorting);
2839 char *name2 = getStringToLower(entry2->name_sorting);
2841 compare_result = strcmp(name1, name2);
2846 else if (class_sorting1 == class_sorting2)
2847 compare_result = entry1->sort_priority - entry2->sort_priority;
2849 compare_result = class_sorting1 - class_sorting2;
2851 return compare_result;
2854 static TreeInfo *createParentTreeInfoNode(TreeInfo *node_parent)
2858 if (node_parent == NULL)
2861 ti_new = newTreeInfo();
2862 setTreeInfoToDefaults(ti_new, node_parent->type);
2864 ti_new->node_parent = node_parent;
2865 ti_new->parent_link = TRUE;
2867 setString(&ti_new->identifier, node_parent->identifier);
2868 setString(&ti_new->name, ".. (parent directory)");
2869 setString(&ti_new->name_sorting, ti_new->name);
2871 setString(&ti_new->subdir, STRING_PARENT_DIRECTORY);
2872 setString(&ti_new->fullpath, node_parent->fullpath);
2874 ti_new->sort_priority = node_parent->sort_priority;
2875 ti_new->latest_engine = node_parent->latest_engine;
2877 setString(&ti_new->class_desc, getLevelClassDescription(ti_new));
2879 pushTreeInfo(&node_parent->node_group, ti_new);
2884 static TreeInfo *createTopTreeInfoNode(TreeInfo *node_first)
2886 TreeInfo *ti_new, *ti_new2;
2888 if (node_first == NULL)
2891 ti_new = newTreeInfo();
2892 setTreeInfoToDefaults(ti_new, TREE_TYPE_LEVEL_DIR);
2894 ti_new->node_parent = NULL;
2895 ti_new->parent_link = FALSE;
2897 setString(&ti_new->identifier, node_first->identifier);
2898 setString(&ti_new->name, "level sets");
2899 setString(&ti_new->name_sorting, ti_new->name);
2901 setString(&ti_new->subdir, STRING_TOP_DIRECTORY);
2902 setString(&ti_new->fullpath, ".");
2904 ti_new->sort_priority = node_first->sort_priority;;
2905 ti_new->latest_engine = node_first->latest_engine;
2907 setString(&ti_new->class_desc, "level sets");
2909 ti_new->node_group = node_first;
2910 ti_new->level_group = TRUE;
2912 ti_new2 = createParentTreeInfoNode(ti_new);
2914 setString(&ti_new2->name, ".. (main menu)");
2915 setString(&ti_new2->name_sorting, ti_new2->name);
2921 // ----------------------------------------------------------------------------
2922 // functions for handling level and custom artwork info cache
2923 // ----------------------------------------------------------------------------
2925 static void LoadArtworkInfoCache(void)
2927 InitCacheDirectory();
2929 if (artworkinfo_cache_old == NULL)
2931 char *filename = getPath2(getCacheDir(), ARTWORKINFO_CACHE_FILE);
2933 // try to load artwork info hash from already existing cache file
2934 artworkinfo_cache_old = loadSetupFileHash(filename);
2936 // if no artwork info cache file was found, start with empty hash
2937 if (artworkinfo_cache_old == NULL)
2938 artworkinfo_cache_old = newSetupFileHash();
2943 if (artworkinfo_cache_new == NULL)
2944 artworkinfo_cache_new = newSetupFileHash();
2947 static void SaveArtworkInfoCache(void)
2949 char *filename = getPath2(getCacheDir(), ARTWORKINFO_CACHE_FILE);
2951 InitCacheDirectory();
2953 saveSetupFileHash(artworkinfo_cache_new, filename);
2958 static char *getCacheTokenPrefix(char *prefix1, char *prefix2)
2960 static char *prefix = NULL;
2962 checked_free(prefix);
2964 prefix = getStringCat2WithSeparator(prefix1, prefix2, ".");
2969 // (identical to above function, but separate string buffer needed -- nasty)
2970 static char *getCacheToken(char *prefix, char *suffix)
2972 static char *token = NULL;
2974 checked_free(token);
2976 token = getStringCat2WithSeparator(prefix, suffix, ".");
2981 static char *getFileTimestampString(char *filename)
2983 return getStringCopy(i_to_a(getFileTimestampEpochSeconds(filename)));
2986 static boolean modifiedFileTimestamp(char *filename, char *timestamp_string)
2988 struct stat file_status;
2990 if (timestamp_string == NULL)
2993 if (stat(filename, &file_status) != 0) // cannot stat file
2996 return (file_status.st_mtime != atoi(timestamp_string));
2999 static TreeInfo *getArtworkInfoCacheEntry(LevelDirTree *level_node, int type)
3001 char *identifier = level_node->subdir;
3002 char *type_string = ARTWORK_DIRECTORY(type);
3003 char *token_prefix = getCacheTokenPrefix(type_string, identifier);
3004 char *token_main = getCacheToken(token_prefix, "CACHED");
3005 char *cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
3006 boolean cached = (cache_entry != NULL && strEqual(cache_entry, "true"));
3007 TreeInfo *artwork_info = NULL;
3009 if (!use_artworkinfo_cache)
3012 if (optional_tokens_hash == NULL)
3016 // create hash from list of optional tokens (for quick access)
3017 optional_tokens_hash = newSetupFileHash();
3018 for (i = 0; optional_tokens[i] != NULL; i++)
3019 setHashEntry(optional_tokens_hash, optional_tokens[i], "");
3026 artwork_info = newTreeInfo();
3027 setTreeInfoToDefaults(artwork_info, type);
3029 // set all structure fields according to the token/value pairs
3030 ldi = *artwork_info;
3031 for (i = 0; artworkinfo_tokens[i].type != -1; i++)
3033 char *token_suffix = artworkinfo_tokens[i].text;
3034 char *token = getCacheToken(token_prefix, token_suffix);
3035 char *value = getHashEntry(artworkinfo_cache_old, token);
3037 (getHashEntry(optional_tokens_hash, token_suffix) != NULL);
3039 setSetupInfo(artworkinfo_tokens, i, value);
3041 // check if cache entry for this item is mandatory, but missing
3042 if (value == NULL && !optional)
3044 Warn("missing cache entry '%s'", token);
3050 *artwork_info = ldi;
3055 char *filename_levelinfo = getPath2(getLevelDirFromTreeInfo(level_node),
3056 LEVELINFO_FILENAME);
3057 char *filename_artworkinfo = getPath2(getSetupArtworkDir(artwork_info),
3058 ARTWORKINFO_FILENAME(type));
3060 // check if corresponding "levelinfo.conf" file has changed
3061 token_main = getCacheToken(token_prefix, "TIMESTAMP_LEVELINFO");
3062 cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
3064 if (modifiedFileTimestamp(filename_levelinfo, cache_entry))
3067 // check if corresponding "<artworkinfo>.conf" file has changed
3068 token_main = getCacheToken(token_prefix, "TIMESTAMP_ARTWORKINFO");
3069 cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
3071 if (modifiedFileTimestamp(filename_artworkinfo, cache_entry))
3074 checked_free(filename_levelinfo);
3075 checked_free(filename_artworkinfo);
3078 if (!cached && artwork_info != NULL)
3080 freeTreeInfo(artwork_info);
3085 return artwork_info;
3088 static void setArtworkInfoCacheEntry(TreeInfo *artwork_info,
3089 LevelDirTree *level_node, int type)
3091 char *identifier = level_node->subdir;
3092 char *type_string = ARTWORK_DIRECTORY(type);
3093 char *token_prefix = getCacheTokenPrefix(type_string, identifier);
3094 char *token_main = getCacheToken(token_prefix, "CACHED");
3095 boolean set_cache_timestamps = TRUE;
3098 setHashEntry(artworkinfo_cache_new, token_main, "true");
3100 if (set_cache_timestamps)
3102 char *filename_levelinfo = getPath2(getLevelDirFromTreeInfo(level_node),
3103 LEVELINFO_FILENAME);
3104 char *filename_artworkinfo = getPath2(getSetupArtworkDir(artwork_info),
3105 ARTWORKINFO_FILENAME(type));
3106 char *timestamp_levelinfo = getFileTimestampString(filename_levelinfo);
3107 char *timestamp_artworkinfo = getFileTimestampString(filename_artworkinfo);
3109 token_main = getCacheToken(token_prefix, "TIMESTAMP_LEVELINFO");
3110 setHashEntry(artworkinfo_cache_new, token_main, timestamp_levelinfo);
3112 token_main = getCacheToken(token_prefix, "TIMESTAMP_ARTWORKINFO");
3113 setHashEntry(artworkinfo_cache_new, token_main, timestamp_artworkinfo);
3115 checked_free(filename_levelinfo);
3116 checked_free(filename_artworkinfo);
3117 checked_free(timestamp_levelinfo);
3118 checked_free(timestamp_artworkinfo);
3121 ldi = *artwork_info;
3122 for (i = 0; artworkinfo_tokens[i].type != -1; i++)
3124 char *token = getCacheToken(token_prefix, artworkinfo_tokens[i].text);
3125 char *value = getSetupValue(artworkinfo_tokens[i].type,
3126 artworkinfo_tokens[i].value);
3128 setHashEntry(artworkinfo_cache_new, token, value);
3133 // ----------------------------------------------------------------------------
3134 // functions for loading level info and custom artwork info
3135 // ----------------------------------------------------------------------------
3137 int GetZipFileTreeType(char *zip_filename)
3139 static char *top_dir_path = NULL;
3140 static char *top_dir_conf_filename[NUM_BASE_TREE_TYPES] = { NULL };
3141 static char *conf_basename[NUM_BASE_TREE_TYPES] =
3143 GRAPHICSINFO_FILENAME,
3144 SOUNDSINFO_FILENAME,
3150 checked_free(top_dir_path);
3151 top_dir_path = NULL;
3153 for (j = 0; j < NUM_BASE_TREE_TYPES; j++)
3155 checked_free(top_dir_conf_filename[j]);
3156 top_dir_conf_filename[j] = NULL;
3159 char **zip_entries = zip_list(zip_filename);
3161 // check if zip file successfully opened
3162 if (zip_entries == NULL || zip_entries[0] == NULL)
3163 return TREE_TYPE_UNDEFINED;
3165 // first zip file entry is expected to be top level directory
3166 char *top_dir = zip_entries[0];
3168 // check if valid top level directory found in zip file
3169 if (!strSuffix(top_dir, "/"))
3170 return TREE_TYPE_UNDEFINED;
3172 // get filenames of valid configuration files in top level directory
3173 for (j = 0; j < NUM_BASE_TREE_TYPES; j++)
3174 top_dir_conf_filename[j] = getStringCat2(top_dir, conf_basename[j]);
3176 int tree_type = TREE_TYPE_UNDEFINED;
3179 while (zip_entries[e] != NULL)
3181 // check if every zip file entry is below top level directory
3182 if (!strPrefix(zip_entries[e], top_dir))
3183 return TREE_TYPE_UNDEFINED;
3185 // check if this zip file entry is a valid configuration filename
3186 for (j = 0; j < NUM_BASE_TREE_TYPES; j++)
3188 if (strEqual(zip_entries[e], top_dir_conf_filename[j]))
3190 // only exactly one valid configuration file allowed
3191 if (tree_type != TREE_TYPE_UNDEFINED)
3192 return TREE_TYPE_UNDEFINED;
3204 static boolean CheckZipFileForDirectory(char *zip_filename, char *directory,
3207 static char *top_dir_path = NULL;
3208 static char *top_dir_conf_filename = NULL;
3210 checked_free(top_dir_path);
3211 checked_free(top_dir_conf_filename);
3213 top_dir_path = NULL;
3214 top_dir_conf_filename = NULL;
3216 char *conf_basename = (tree_type == TREE_TYPE_LEVEL_DIR ? LEVELINFO_FILENAME :
3217 ARTWORKINFO_FILENAME(tree_type));
3219 // check if valid configuration filename determined
3220 if (conf_basename == NULL || strEqual(conf_basename, ""))
3223 char **zip_entries = zip_list(zip_filename);
3225 // check if zip file successfully opened
3226 if (zip_entries == NULL || zip_entries[0] == NULL)
3229 // first zip file entry is expected to be top level directory
3230 char *top_dir = zip_entries[0];
3232 // check if valid top level directory found in zip file
3233 if (!strSuffix(top_dir, "/"))
3236 // get path of extracted top level directory
3237 top_dir_path = getPath2(directory, top_dir);
3239 // remove trailing directory separator from top level directory path
3240 // (required to be able to check for file and directory in next step)
3241 top_dir_path[strlen(top_dir_path) - 1] = '\0';
3243 // check if zip file's top level directory already exists in target directory
3244 if (fileExists(top_dir_path)) // (checks for file and directory)
3247 // get filename of configuration file in top level directory
3248 top_dir_conf_filename = getStringCat2(top_dir, conf_basename);
3250 boolean found_top_dir_conf_filename = FALSE;
3253 while (zip_entries[i] != NULL)
3255 // check if every zip file entry is below top level directory
3256 if (!strPrefix(zip_entries[i], top_dir))
3259 // check if this zip file entry is the configuration filename
3260 if (strEqual(zip_entries[i], top_dir_conf_filename))
3261 found_top_dir_conf_filename = TRUE;
3266 // check if valid configuration filename was found in zip file
3267 if (!found_top_dir_conf_filename)
3273 char *ExtractZipFileIntoDirectory(char *zip_filename, char *directory,
3276 boolean zip_file_valid = CheckZipFileForDirectory(zip_filename, directory,
3279 if (!zip_file_valid)
3281 Warn("zip file '%s' rejected!", zip_filename);
3286 char **zip_entries = zip_extract(zip_filename, directory);
3288 if (zip_entries == NULL)
3290 Warn("zip file '%s' could not be extracted!", zip_filename);
3295 Info("zip file '%s' successfully extracted!", zip_filename);
3297 // first zip file entry contains top level directory
3298 char *top_dir = zip_entries[0];
3300 // remove trailing directory separator from top level directory
3301 top_dir[strlen(top_dir) - 1] = '\0';
3306 static void ProcessZipFilesInDirectory(char *directory, int tree_type)
3309 DirectoryEntry *dir_entry;
3311 if ((dir = openDirectory(directory)) == NULL)
3313 // display error if directory is main "options.graphics_directory" etc.
3314 if (tree_type == TREE_TYPE_LEVEL_DIR ||
3315 directory == OPTIONS_ARTWORK_DIRECTORY(tree_type))
3316 Warn("cannot read directory '%s'", directory);
3321 while ((dir_entry = readDirectory(dir)) != NULL) // loop all entries
3323 // skip non-zip files (and also directories with zip extension)
3324 if (!strSuffixLower(dir_entry->basename, ".zip") || dir_entry->is_directory)
3327 char *zip_filename = getPath2(directory, dir_entry->basename);
3328 char *zip_filename_extracted = getStringCat2(zip_filename, ".extracted");
3329 char *zip_filename_rejected = getStringCat2(zip_filename, ".rejected");
3331 // check if zip file hasn't already been extracted or rejected
3332 if (!fileExists(zip_filename_extracted) &&
3333 !fileExists(zip_filename_rejected))
3335 char *top_dir = ExtractZipFileIntoDirectory(zip_filename, directory,
3337 char *marker_filename = (top_dir != NULL ? zip_filename_extracted :
3338 zip_filename_rejected);
3341 // create empty file to mark zip file as extracted or rejected
3342 if ((marker_file = fopen(marker_filename, MODE_WRITE)))
3343 fclose(marker_file);
3346 free(zip_filename_extracted);
3347 free(zip_filename_rejected);
3351 closeDirectory(dir);
3354 // forward declaration for recursive call by "LoadLevelInfoFromLevelDir()"
3355 static void LoadLevelInfoFromLevelDir(TreeInfo **, TreeInfo *, char *);
3357 static boolean LoadLevelInfoFromLevelConf(TreeInfo **node_first,
3358 TreeInfo *node_parent,
3359 char *level_directory,
3360 char *directory_name)
3362 char *directory_path = getPath2(level_directory, directory_name);
3363 char *filename = getPath2(directory_path, LEVELINFO_FILENAME);
3364 SetupFileHash *setup_file_hash;
3365 LevelDirTree *leveldir_new = NULL;
3368 // unless debugging, silently ignore directories without "levelinfo.conf"
3369 if (!options.debug && !fileExists(filename))
3371 free(directory_path);
3377 setup_file_hash = loadSetupFileHash(filename);
3379 if (setup_file_hash == NULL)
3381 #if DEBUG_NO_CONFIG_FILE
3382 Debug("setup", "ignoring level directory '%s'", directory_path);
3385 free(directory_path);
3391 leveldir_new = newTreeInfo();
3394 setTreeInfoToDefaultsFromParent(leveldir_new, node_parent);
3396 setTreeInfoToDefaults(leveldir_new, TREE_TYPE_LEVEL_DIR);
3398 leveldir_new->subdir = getStringCopy(directory_name);
3400 // set all structure fields according to the token/value pairs
3401 ldi = *leveldir_new;
3402 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
3403 setSetupInfo(levelinfo_tokens, i,
3404 getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
3405 *leveldir_new = ldi;
3407 if (strEqual(leveldir_new->name, ANONYMOUS_NAME))
3408 setString(&leveldir_new->name, leveldir_new->subdir);
3410 if (leveldir_new->identifier == NULL)
3411 leveldir_new->identifier = getStringCopy(leveldir_new->subdir);
3413 if (leveldir_new->name_sorting == NULL)
3414 leveldir_new->name_sorting = getStringCopy(leveldir_new->name);
3416 if (node_parent == NULL) // top level group
3418 leveldir_new->basepath = getStringCopy(level_directory);
3419 leveldir_new->fullpath = getStringCopy(leveldir_new->subdir);
3421 else // sub level group
3423 leveldir_new->basepath = getStringCopy(node_parent->basepath);
3424 leveldir_new->fullpath = getPath2(node_parent->fullpath, directory_name);
3427 leveldir_new->last_level =
3428 leveldir_new->first_level + leveldir_new->levels - 1;
3430 leveldir_new->in_user_dir =
3431 (!strEqual(leveldir_new->basepath, options.level_directory));
3433 // adjust some settings if user's private level directory was detected
3434 if (leveldir_new->sort_priority == LEVELCLASS_UNDEFINED &&
3435 leveldir_new->in_user_dir &&
3436 (strEqual(leveldir_new->subdir, getLoginName()) ||
3437 strEqual(leveldir_new->name, getLoginName()) ||
3438 strEqual(leveldir_new->author, getRealName())))
3440 leveldir_new->sort_priority = LEVELCLASS_PRIVATE_START;
3441 leveldir_new->readonly = FALSE;
3444 leveldir_new->user_defined =
3445 (leveldir_new->in_user_dir && IS_LEVELCLASS_PRIVATE(leveldir_new));
3447 leveldir_new->color = LEVELCOLOR(leveldir_new);
3449 setString(&leveldir_new->class_desc, getLevelClassDescription(leveldir_new));
3451 leveldir_new->handicap_level = // set handicap to default value
3452 (leveldir_new->user_defined || !leveldir_new->handicap ?
3453 leveldir_new->last_level : leveldir_new->first_level);
3455 DrawInitText(leveldir_new->name, 150, FC_YELLOW);
3457 pushTreeInfo(node_first, leveldir_new);
3459 freeSetupFileHash(setup_file_hash);
3461 if (leveldir_new->level_group)
3463 // create node to link back to current level directory
3464 createParentTreeInfoNode(leveldir_new);
3466 // recursively step into sub-directory and look for more level series
3467 LoadLevelInfoFromLevelDir(&leveldir_new->node_group,
3468 leveldir_new, directory_path);
3471 free(directory_path);
3477 static void LoadLevelInfoFromLevelDir(TreeInfo **node_first,
3478 TreeInfo *node_parent,
3479 char *level_directory)
3481 // ---------- 1st stage: process any level set zip files ----------
3483 ProcessZipFilesInDirectory(level_directory, TREE_TYPE_LEVEL_DIR);
3485 // ---------- 2nd stage: check for level set directories ----------
3488 DirectoryEntry *dir_entry;
3489 boolean valid_entry_found = FALSE;
3491 if ((dir = openDirectory(level_directory)) == NULL)
3493 Warn("cannot read level directory '%s'", level_directory);
3498 while ((dir_entry = readDirectory(dir)) != NULL) // loop all entries
3500 char *directory_name = dir_entry->basename;
3501 char *directory_path = getPath2(level_directory, directory_name);
3503 // skip entries for current and parent directory
3504 if (strEqual(directory_name, ".") ||
3505 strEqual(directory_name, ".."))
3507 free(directory_path);
3512 // find out if directory entry is itself a directory
3513 if (!dir_entry->is_directory) // not a directory
3515 free(directory_path);
3520 free(directory_path);
3522 if (strEqual(directory_name, GRAPHICS_DIRECTORY) ||
3523 strEqual(directory_name, SOUNDS_DIRECTORY) ||
3524 strEqual(directory_name, MUSIC_DIRECTORY))
3527 valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
3532 closeDirectory(dir);
3534 // special case: top level directory may directly contain "levelinfo.conf"
3535 if (node_parent == NULL && !valid_entry_found)
3537 // check if this directory directly contains a file "levelinfo.conf"
3538 valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
3539 level_directory, ".");
3542 if (!valid_entry_found)
3543 Warn("cannot find any valid level series in directory '%s'",
3547 boolean AdjustGraphicsForEMC(void)
3549 boolean settings_changed = FALSE;
3551 settings_changed |= adjustTreeGraphicsForEMC(leveldir_first_all);
3552 settings_changed |= adjustTreeGraphicsForEMC(leveldir_first);
3554 return settings_changed;
3557 boolean AdjustSoundsForEMC(void)
3559 boolean settings_changed = FALSE;
3561 settings_changed |= adjustTreeSoundsForEMC(leveldir_first_all);
3562 settings_changed |= adjustTreeSoundsForEMC(leveldir_first);
3564 return settings_changed;
3567 void LoadLevelInfo(void)
3569 InitUserLevelDirectory(getLoginName());
3571 DrawInitText("Loading level series", 120, FC_GREEN);
3573 LoadLevelInfoFromLevelDir(&leveldir_first, NULL, options.level_directory);
3574 LoadLevelInfoFromLevelDir(&leveldir_first, NULL, getUserLevelDir(NULL));
3576 leveldir_first = createTopTreeInfoNode(leveldir_first);
3578 /* after loading all level set information, clone the level directory tree
3579 and remove all level sets without levels (these may still contain artwork
3580 to be offered in the setup menu as "custom artwork", and are therefore
3581 checked for existing artwork in the function "LoadLevelArtworkInfo()") */
3582 leveldir_first_all = leveldir_first;
3583 cloneTree(&leveldir_first, leveldir_first_all, TRUE);
3585 AdjustGraphicsForEMC();
3586 AdjustSoundsForEMC();
3588 // before sorting, the first entries will be from the user directory
3589 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
3591 if (leveldir_first == NULL)
3592 Fail("cannot find any valid level series in any directory");
3594 sortTreeInfo(&leveldir_first);
3596 #if ENABLE_UNUSED_CODE
3597 dumpTreeInfo(leveldir_first, 0);
3601 static boolean LoadArtworkInfoFromArtworkConf(TreeInfo **node_first,
3602 TreeInfo *node_parent,
3603 char *base_directory,
3604 char *directory_name, int type)
3606 char *directory_path = getPath2(base_directory, directory_name);
3607 char *filename = getPath2(directory_path, ARTWORKINFO_FILENAME(type));
3608 SetupFileHash *setup_file_hash = NULL;
3609 TreeInfo *artwork_new = NULL;
3612 if (fileExists(filename))
3613 setup_file_hash = loadSetupFileHash(filename);
3615 if (setup_file_hash == NULL) // no config file -- look for artwork files
3618 DirectoryEntry *dir_entry;
3619 boolean valid_file_found = FALSE;
3621 if ((dir = openDirectory(directory_path)) != NULL)
3623 while ((dir_entry = readDirectory(dir)) != NULL)
3625 if (FileIsArtworkType(dir_entry->filename, type))
3627 valid_file_found = TRUE;
3633 closeDirectory(dir);
3636 if (!valid_file_found)
3638 #if DEBUG_NO_CONFIG_FILE
3639 if (!strEqual(directory_name, "."))
3640 Debug("setup", "ignoring artwork directory '%s'", directory_path);
3643 free(directory_path);
3650 artwork_new = newTreeInfo();
3653 setTreeInfoToDefaultsFromParent(artwork_new, node_parent);
3655 setTreeInfoToDefaults(artwork_new, type);
3657 artwork_new->subdir = getStringCopy(directory_name);
3659 if (setup_file_hash) // (before defining ".color" and ".class_desc")
3661 // set all structure fields according to the token/value pairs
3663 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
3664 setSetupInfo(levelinfo_tokens, i,
3665 getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
3668 if (strEqual(artwork_new->name, ANONYMOUS_NAME))
3669 setString(&artwork_new->name, artwork_new->subdir);
3671 if (artwork_new->identifier == NULL)
3672 artwork_new->identifier = getStringCopy(artwork_new->subdir);
3674 if (artwork_new->name_sorting == NULL)
3675 artwork_new->name_sorting = getStringCopy(artwork_new->name);
3678 if (node_parent == NULL) // top level group
3680 artwork_new->basepath = getStringCopy(base_directory);
3681 artwork_new->fullpath = getStringCopy(artwork_new->subdir);
3683 else // sub level group
3685 artwork_new->basepath = getStringCopy(node_parent->basepath);
3686 artwork_new->fullpath = getPath2(node_parent->fullpath, directory_name);
3689 artwork_new->in_user_dir =
3690 (!strEqual(artwork_new->basepath, OPTIONS_ARTWORK_DIRECTORY(type)));
3692 // (may use ".sort_priority" from "setup_file_hash" above)
3693 artwork_new->color = ARTWORKCOLOR(artwork_new);
3695 setString(&artwork_new->class_desc, getLevelClassDescription(artwork_new));
3697 if (setup_file_hash == NULL) // (after determining ".user_defined")
3699 if (strEqual(artwork_new->subdir, "."))
3701 if (artwork_new->user_defined)
3703 setString(&artwork_new->identifier, "private");
3704 artwork_new->sort_priority = ARTWORKCLASS_PRIVATE;
3708 setString(&artwork_new->identifier, "classic");
3709 artwork_new->sort_priority = ARTWORKCLASS_CLASSICS;
3712 // set to new values after changing ".sort_priority"
3713 artwork_new->color = ARTWORKCOLOR(artwork_new);
3715 setString(&artwork_new->class_desc,
3716 getLevelClassDescription(artwork_new));
3720 setString(&artwork_new->identifier, artwork_new->subdir);
3723 setString(&artwork_new->name, artwork_new->identifier);
3724 setString(&artwork_new->name_sorting, artwork_new->name);
3727 pushTreeInfo(node_first, artwork_new);
3729 freeSetupFileHash(setup_file_hash);
3731 free(directory_path);
3737 static void LoadArtworkInfoFromArtworkDir(TreeInfo **node_first,
3738 TreeInfo *node_parent,
3739 char *base_directory, int type)
3741 // ---------- 1st stage: process any artwork set zip files ----------
3743 ProcessZipFilesInDirectory(base_directory, type);
3745 // ---------- 2nd stage: check for artwork set directories ----------
3748 DirectoryEntry *dir_entry;
3749 boolean valid_entry_found = FALSE;
3751 if ((dir = openDirectory(base_directory)) == NULL)
3753 // display error if directory is main "options.graphics_directory" etc.
3754 if (base_directory == OPTIONS_ARTWORK_DIRECTORY(type))
3755 Warn("cannot read directory '%s'", base_directory);
3760 while ((dir_entry = readDirectory(dir)) != NULL) // loop all entries
3762 char *directory_name = dir_entry->basename;
3763 char *directory_path = getPath2(base_directory, directory_name);
3765 // skip directory entries for current and parent directory
3766 if (strEqual(directory_name, ".") ||
3767 strEqual(directory_name, ".."))
3769 free(directory_path);
3774 // skip directory entries which are not a directory
3775 if (!dir_entry->is_directory) // not a directory
3777 free(directory_path);
3782 free(directory_path);
3784 // check if this directory contains artwork with or without config file
3785 valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
3787 directory_name, type);
3790 closeDirectory(dir);
3792 // check if this directory directly contains artwork itself
3793 valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
3794 base_directory, ".",
3796 if (!valid_entry_found)
3797 Warn("cannot find any valid artwork in directory '%s'", base_directory);
3800 static TreeInfo *getDummyArtworkInfo(int type)
3802 // this is only needed when there is completely no artwork available
3803 TreeInfo *artwork_new = newTreeInfo();
3805 setTreeInfoToDefaults(artwork_new, type);
3807 setString(&artwork_new->subdir, UNDEFINED_FILENAME);
3808 setString(&artwork_new->fullpath, UNDEFINED_FILENAME);
3809 setString(&artwork_new->basepath, UNDEFINED_FILENAME);
3811 setString(&artwork_new->identifier, UNDEFINED_FILENAME);
3812 setString(&artwork_new->name, UNDEFINED_FILENAME);
3813 setString(&artwork_new->name_sorting, UNDEFINED_FILENAME);
3818 void SetCurrentArtwork(int type)
3820 ArtworkDirTree **current_ptr = ARTWORK_CURRENT_PTR(artwork, type);
3821 ArtworkDirTree *first_node = ARTWORK_FIRST_NODE(artwork, type);
3822 char *setup_set = SETUP_ARTWORK_SET(setup, type);
3823 char *default_subdir = ARTWORK_DEFAULT_SUBDIR(type);
3825 // set current artwork to artwork configured in setup menu
3826 *current_ptr = getTreeInfoFromIdentifier(first_node, setup_set);
3828 // if not found, set current artwork to default artwork
3829 if (*current_ptr == NULL)
3830 *current_ptr = getTreeInfoFromIdentifier(first_node, default_subdir);
3832 // if not found, set current artwork to first artwork in tree
3833 if (*current_ptr == NULL)
3834 *current_ptr = getFirstValidTreeInfoEntry(first_node);
3837 void ChangeCurrentArtworkIfNeeded(int type)
3839 char *current_identifier = ARTWORK_CURRENT_IDENTIFIER(artwork, type);
3840 char *setup_set = SETUP_ARTWORK_SET(setup, type);
3842 if (!strEqual(current_identifier, setup_set))
3843 SetCurrentArtwork(type);
3846 void LoadArtworkInfo(void)
3848 LoadArtworkInfoCache();
3850 DrawInitText("Looking for custom artwork", 120, FC_GREEN);
3852 LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
3853 options.graphics_directory,
3854 TREE_TYPE_GRAPHICS_DIR);
3855 LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
3856 getUserGraphicsDir(),
3857 TREE_TYPE_GRAPHICS_DIR);
3859 LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
3860 options.sounds_directory,
3861 TREE_TYPE_SOUNDS_DIR);
3862 LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
3864 TREE_TYPE_SOUNDS_DIR);
3866 LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
3867 options.music_directory,
3868 TREE_TYPE_MUSIC_DIR);
3869 LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
3871 TREE_TYPE_MUSIC_DIR);
3873 if (artwork.gfx_first == NULL)
3874 artwork.gfx_first = getDummyArtworkInfo(TREE_TYPE_GRAPHICS_DIR);
3875 if (artwork.snd_first == NULL)
3876 artwork.snd_first = getDummyArtworkInfo(TREE_TYPE_SOUNDS_DIR);
3877 if (artwork.mus_first == NULL)
3878 artwork.mus_first = getDummyArtworkInfo(TREE_TYPE_MUSIC_DIR);
3880 // before sorting, the first entries will be from the user directory
3881 SetCurrentArtwork(ARTWORK_TYPE_GRAPHICS);
3882 SetCurrentArtwork(ARTWORK_TYPE_SOUNDS);
3883 SetCurrentArtwork(ARTWORK_TYPE_MUSIC);
3885 artwork.gfx_current_identifier = artwork.gfx_current->identifier;
3886 artwork.snd_current_identifier = artwork.snd_current->identifier;
3887 artwork.mus_current_identifier = artwork.mus_current->identifier;
3889 #if ENABLE_UNUSED_CODE
3890 Debug("setup:LoadArtworkInfo", "graphics set == %s",
3891 artwork.gfx_current_identifier);
3892 Debug("setup:LoadArtworkInfo", "sounds set == %s",
3893 artwork.snd_current_identifier);
3894 Debug("setup:LoadArtworkInfo", "music set == %s",
3895 artwork.mus_current_identifier);
3898 sortTreeInfo(&artwork.gfx_first);
3899 sortTreeInfo(&artwork.snd_first);
3900 sortTreeInfo(&artwork.mus_first);
3902 #if ENABLE_UNUSED_CODE
3903 dumpTreeInfo(artwork.gfx_first, 0);
3904 dumpTreeInfo(artwork.snd_first, 0);
3905 dumpTreeInfo(artwork.mus_first, 0);
3909 static void LoadArtworkInfoFromLevelInfo(ArtworkDirTree **artwork_node,
3910 LevelDirTree *level_node)
3912 int type = (*artwork_node)->type;
3914 // recursively check all level directories for artwork sub-directories
3918 // check all tree entries for artwork, but skip parent link entries
3919 if (!level_node->parent_link)
3921 TreeInfo *artwork_new = getArtworkInfoCacheEntry(level_node, type);
3922 boolean cached = (artwork_new != NULL);
3926 pushTreeInfo(artwork_node, artwork_new);
3930 TreeInfo *topnode_last = *artwork_node;
3931 char *path = getPath2(getLevelDirFromTreeInfo(level_node),
3932 ARTWORK_DIRECTORY(type));
3934 LoadArtworkInfoFromArtworkDir(artwork_node, NULL, path, type);
3936 if (topnode_last != *artwork_node) // check for newly added node
3938 artwork_new = *artwork_node;
3940 setString(&artwork_new->identifier, level_node->subdir);
3941 setString(&artwork_new->name, level_node->name);
3942 setString(&artwork_new->name_sorting, level_node->name_sorting);
3944 artwork_new->sort_priority = level_node->sort_priority;
3945 artwork_new->color = LEVELCOLOR(artwork_new);
3951 // insert artwork info (from old cache or filesystem) into new cache
3952 if (artwork_new != NULL)
3953 setArtworkInfoCacheEntry(artwork_new, level_node, type);
3956 DrawInitText(level_node->name, 150, FC_YELLOW);
3958 if (level_node->node_group != NULL)
3959 LoadArtworkInfoFromLevelInfo(artwork_node, level_node->node_group);
3961 level_node = level_node->next;
3965 void LoadLevelArtworkInfo(void)
3967 print_timestamp_init("LoadLevelArtworkInfo");
3969 DrawInitText("Looking for custom level artwork", 120, FC_GREEN);
3971 print_timestamp_time("DrawTimeText");
3973 LoadArtworkInfoFromLevelInfo(&artwork.gfx_first, leveldir_first_all);
3974 print_timestamp_time("LoadArtworkInfoFromLevelInfo (gfx)");
3975 LoadArtworkInfoFromLevelInfo(&artwork.snd_first, leveldir_first_all);
3976 print_timestamp_time("LoadArtworkInfoFromLevelInfo (snd)");
3977 LoadArtworkInfoFromLevelInfo(&artwork.mus_first, leveldir_first_all);
3978 print_timestamp_time("LoadArtworkInfoFromLevelInfo (mus)");
3980 SaveArtworkInfoCache();
3982 print_timestamp_time("SaveArtworkInfoCache");
3984 // needed for reloading level artwork not known at ealier stage
3985 ChangeCurrentArtworkIfNeeded(ARTWORK_TYPE_GRAPHICS);
3986 ChangeCurrentArtworkIfNeeded(ARTWORK_TYPE_SOUNDS);
3987 ChangeCurrentArtworkIfNeeded(ARTWORK_TYPE_MUSIC);
3989 print_timestamp_time("getTreeInfoFromIdentifier");
3991 sortTreeInfo(&artwork.gfx_first);
3992 sortTreeInfo(&artwork.snd_first);
3993 sortTreeInfo(&artwork.mus_first);
3995 print_timestamp_time("sortTreeInfo");
3997 #if ENABLE_UNUSED_CODE
3998 dumpTreeInfo(artwork.gfx_first, 0);
3999 dumpTreeInfo(artwork.snd_first, 0);
4000 dumpTreeInfo(artwork.mus_first, 0);
4003 print_timestamp_done("LoadLevelArtworkInfo");
4006 static boolean AddTreeSetToTreeInfoExt(TreeInfo *tree_node_old, char *tree_dir,
4007 char *tree_subdir_new, int type)
4009 if (tree_node_old == NULL)
4011 if (type == TREE_TYPE_LEVEL_DIR)
4013 // get level info tree node of personal user level set
4014 tree_node_old = getTreeInfoFromIdentifier(leveldir_first, getLoginName());
4016 // this may happen if "setup.internal.create_user_levelset" is FALSE
4017 // or if file "levelinfo.conf" is missing in personal user level set
4018 if (tree_node_old == NULL)
4019 tree_node_old = leveldir_first->node_group;
4023 // get artwork info tree node of first artwork set
4024 tree_node_old = ARTWORK_FIRST_NODE(artwork, type);
4028 if (tree_dir == NULL)
4029 tree_dir = TREE_USERDIR(type);
4031 if (tree_node_old == NULL ||
4033 tree_subdir_new == NULL) // should not happen
4036 int draw_deactivation_mask = GetDrawDeactivationMask();
4038 // override draw deactivation mask (temporarily disable drawing)
4039 SetDrawDeactivationMask(REDRAW_ALL);
4041 if (type == TREE_TYPE_LEVEL_DIR)
4043 // load new level set config and add it next to first user level set
4044 LoadLevelInfoFromLevelConf(&tree_node_old->next,
4045 tree_node_old->node_parent,
4046 tree_dir, tree_subdir_new);
4050 // load new artwork set config and add it next to first artwork set
4051 LoadArtworkInfoFromArtworkConf(&tree_node_old->next,
4052 tree_node_old->node_parent,
4053 tree_dir, tree_subdir_new, type);
4056 // set draw deactivation mask to previous value
4057 SetDrawDeactivationMask(draw_deactivation_mask);
4059 // get first node of level or artwork info tree
4060 TreeInfo **tree_node_first = TREE_FIRST_NODE_PTR(type);
4062 // get tree info node of newly added level or artwork set
4063 TreeInfo *tree_node_new = getTreeInfoFromIdentifier(*tree_node_first,
4066 if (tree_node_new == NULL) // should not happen
4069 // correct top link and parent node link of newly created tree node
4070 tree_node_new->node_top = tree_node_old->node_top;
4071 tree_node_new->node_parent = tree_node_old->node_parent;
4073 // sort tree info to adjust position of newly added tree set
4074 sortTreeInfo(tree_node_first);
4079 void AddTreeSetToTreeInfo(TreeInfo *tree_node, char *tree_dir,
4080 char *tree_subdir_new, int type)
4082 if (!AddTreeSetToTreeInfoExt(tree_node, tree_dir, tree_subdir_new, type))
4083 Fail("internal tree info structure corrupted -- aborting");
4086 void AddUserLevelSetToLevelInfo(char *level_subdir_new)
4088 AddTreeSetToTreeInfo(NULL, NULL, level_subdir_new, TREE_TYPE_LEVEL_DIR);
4091 char *getArtworkIdentifierForUserLevelSet(int type)
4093 char *classic_artwork_set = getClassicArtworkSet(type);
4095 // check for custom artwork configured in "levelinfo.conf"
4096 char *leveldir_artwork_set =
4097 *LEVELDIR_ARTWORK_SET_PTR(leveldir_current, type);
4098 boolean has_leveldir_artwork_set =
4099 (leveldir_artwork_set != NULL && !strEqual(leveldir_artwork_set,
4100 classic_artwork_set));
4102 // check for custom artwork in sub-directory "graphics" etc.
4103 TreeInfo *artwork_first_node = ARTWORK_FIRST_NODE(artwork, type);
4104 char *leveldir_identifier = leveldir_current->identifier;
4105 boolean has_artwork_subdir =
4106 (getTreeInfoFromIdentifier(artwork_first_node,
4107 leveldir_identifier) != NULL);
4109 return (has_leveldir_artwork_set ? leveldir_artwork_set :
4110 has_artwork_subdir ? leveldir_identifier :
4111 classic_artwork_set);
4114 TreeInfo *getArtworkTreeInfoForUserLevelSet(int type)
4116 char *artwork_set = getArtworkIdentifierForUserLevelSet(type);
4117 TreeInfo *artwork_first_node = ARTWORK_FIRST_NODE(artwork, type);
4118 TreeInfo *ti = getTreeInfoFromIdentifier(artwork_first_node, artwork_set);
4122 ti = getTreeInfoFromIdentifier(artwork_first_node,
4123 ARTWORK_DEFAULT_SUBDIR(type));
4125 Fail("cannot find default graphics -- should not happen");
4131 boolean checkIfCustomArtworkExistsForCurrentLevelSet(void)
4133 char *graphics_set =
4134 getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_GRAPHICS);
4136 getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_SOUNDS);
4138 getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_MUSIC);
4140 return (!strEqual(graphics_set, GFX_CLASSIC_SUBDIR) ||
4141 !strEqual(sounds_set, SND_CLASSIC_SUBDIR) ||
4142 !strEqual(music_set, MUS_CLASSIC_SUBDIR));
4145 boolean UpdateUserLevelSet(char *level_subdir, char *level_name,
4146 char *level_author, int num_levels)
4148 char *filename = getPath2(getUserLevelDir(level_subdir), LEVELINFO_FILENAME);
4149 char *filename_tmp = getStringCat2(filename, ".tmp");
4151 FILE *file_tmp = NULL;
4152 char line[MAX_LINE_LEN];
4153 boolean success = FALSE;
4154 LevelDirTree *leveldir = getTreeInfoFromIdentifier(leveldir_first,
4156 // update values in level directory tree
4158 if (level_name != NULL)
4159 setString(&leveldir->name, level_name);
4161 if (level_author != NULL)
4162 setString(&leveldir->author, level_author);
4164 if (num_levels != -1)
4165 leveldir->levels = num_levels;
4167 // update values that depend on other values
4169 setString(&leveldir->name_sorting, leveldir->name);
4171 leveldir->last_level = leveldir->first_level + leveldir->levels - 1;
4173 // sort order of level sets may have changed
4174 sortTreeInfo(&leveldir_first);
4176 if ((file = fopen(filename, MODE_READ)) &&
4177 (file_tmp = fopen(filename_tmp, MODE_WRITE)))
4179 while (fgets(line, MAX_LINE_LEN, file))
4181 if (strPrefix(line, "name:") && level_name != NULL)
4182 fprintf(file_tmp, "%-32s%s\n", "name:", level_name);
4183 else if (strPrefix(line, "author:") && level_author != NULL)
4184 fprintf(file_tmp, "%-32s%s\n", "author:", level_author);
4185 else if (strPrefix(line, "levels:") && num_levels != -1)
4186 fprintf(file_tmp, "%-32s%d\n", "levels:", num_levels);
4188 fputs(line, file_tmp);
4201 success = (rename(filename_tmp, filename) == 0);
4209 boolean CreateUserLevelSet(char *level_subdir, char *level_name,
4210 char *level_author, int num_levels,
4211 boolean use_artwork_set)
4213 LevelDirTree *level_info;
4218 // create user level sub-directory, if needed
4219 createDirectory(getUserLevelDir(level_subdir), "user level", PERMS_PRIVATE);
4221 filename = getPath2(getUserLevelDir(level_subdir), LEVELINFO_FILENAME);
4223 if (!(file = fopen(filename, MODE_WRITE)))
4225 Warn("cannot write level info file '%s'", filename);
4232 level_info = newTreeInfo();
4234 // always start with reliable default values
4235 setTreeInfoToDefaults(level_info, TREE_TYPE_LEVEL_DIR);
4237 setString(&level_info->name, level_name);
4238 setString(&level_info->author, level_author);
4239 level_info->levels = num_levels;
4240 level_info->first_level = 1;
4241 level_info->sort_priority = LEVELCLASS_PRIVATE_START;
4242 level_info->readonly = FALSE;
4244 if (use_artwork_set)
4246 level_info->graphics_set =
4247 getStringCopy(getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_GRAPHICS));
4248 level_info->sounds_set =
4249 getStringCopy(getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_SOUNDS));
4250 level_info->music_set =
4251 getStringCopy(getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_MUSIC));
4254 token_value_position = TOKEN_VALUE_POSITION_SHORT;
4256 fprintFileHeader(file, LEVELINFO_FILENAME);
4259 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
4261 if (i == LEVELINFO_TOKEN_NAME ||
4262 i == LEVELINFO_TOKEN_AUTHOR ||
4263 i == LEVELINFO_TOKEN_LEVELS ||
4264 i == LEVELINFO_TOKEN_FIRST_LEVEL ||
4265 i == LEVELINFO_TOKEN_SORT_PRIORITY ||
4266 i == LEVELINFO_TOKEN_READONLY ||
4267 (use_artwork_set && (i == LEVELINFO_TOKEN_GRAPHICS_SET ||
4268 i == LEVELINFO_TOKEN_SOUNDS_SET ||
4269 i == LEVELINFO_TOKEN_MUSIC_SET)))
4270 fprintf(file, "%s\n", getSetupLine(levelinfo_tokens, "", i));
4272 // just to make things nicer :)
4273 if (i == LEVELINFO_TOKEN_AUTHOR ||
4274 i == LEVELINFO_TOKEN_FIRST_LEVEL ||
4275 (use_artwork_set && i == LEVELINFO_TOKEN_READONLY))
4276 fprintf(file, "\n");
4279 token_value_position = TOKEN_VALUE_POSITION_DEFAULT;
4283 SetFilePermissions(filename, PERMS_PRIVATE);
4285 freeTreeInfo(level_info);
4291 static void SaveUserLevelInfo(void)
4293 CreateUserLevelSet(getLoginName(), getLoginName(), getRealName(), 100, FALSE);
4296 char *getSetupValue(int type, void *value)
4298 static char value_string[MAX_LINE_LEN];
4306 strcpy(value_string, (*(boolean *)value ? "true" : "false"));
4310 strcpy(value_string, (*(boolean *)value ? "on" : "off"));
4314 strcpy(value_string, (*(int *)value == AUTO ? "auto" :
4315 *(int *)value == FALSE ? "off" : "on"));
4319 strcpy(value_string, (*(boolean *)value ? "yes" : "no"));
4322 case TYPE_YES_NO_AUTO:
4323 strcpy(value_string, (*(int *)value == AUTO ? "auto" :
4324 *(int *)value == FALSE ? "no" : "yes"));
4328 strcpy(value_string, (*(boolean *)value ? "AGA" : "ECS"));
4332 strcpy(value_string, getKeyNameFromKey(*(Key *)value));
4336 strcpy(value_string, getX11KeyNameFromKey(*(Key *)value));
4340 sprintf(value_string, "%d", *(int *)value);
4344 if (*(char **)value == NULL)
4347 strcpy(value_string, *(char **)value);
4351 sprintf(value_string, "player_%d", *(int *)value + 1);
4355 value_string[0] = '\0';
4359 if (type & TYPE_GHOSTED)
4360 strcpy(value_string, "n/a");
4362 return value_string;
4365 char *getSetupLine(struct TokenInfo *token_info, char *prefix, int token_nr)
4369 static char token_string[MAX_LINE_LEN];
4370 int token_type = token_info[token_nr].type;
4371 void *setup_value = token_info[token_nr].value;
4372 char *token_text = token_info[token_nr].text;
4373 char *value_string = getSetupValue(token_type, setup_value);
4375 // build complete token string
4376 sprintf(token_string, "%s%s", prefix, token_text);
4378 // build setup entry line
4379 line = getFormattedSetupEntry(token_string, value_string);
4381 if (token_type == TYPE_KEY_X11)
4383 Key key = *(Key *)setup_value;
4384 char *keyname = getKeyNameFromKey(key);
4386 // add comment, if useful
4387 if (!strEqual(keyname, "(undefined)") &&
4388 !strEqual(keyname, "(unknown)"))
4390 // add at least one whitespace
4392 for (i = strlen(line); i < token_comment_position; i++)
4396 strcat(line, keyname);
4403 static void InitLastPlayedLevels_ParentNode(void)
4405 LevelDirTree **leveldir_top = &leveldir_first->node_group->next;
4406 LevelDirTree *leveldir_new = NULL;
4408 // check if parent node for last played levels already exists
4409 if (strEqual((*leveldir_top)->identifier, TOKEN_STR_LAST_LEVEL_SERIES))
4412 leveldir_new = newTreeInfo();
4414 setTreeInfoToDefaultsFromParent(leveldir_new, leveldir_first);
4416 leveldir_new->level_group = TRUE;
4418 setString(&leveldir_new->identifier, TOKEN_STR_LAST_LEVEL_SERIES);
4419 setString(&leveldir_new->name, "<< (last played level sets)");
4421 pushTreeInfo(leveldir_top, leveldir_new);
4423 // create node to link back to current level directory
4424 createParentTreeInfoNode(leveldir_new);
4427 void UpdateLastPlayedLevels_TreeInfo(void)
4429 char **last_level_series = setup.level_setup.last_level_series;
4430 boolean reset_leveldir_current = FALSE;
4431 LevelDirTree *leveldir_last;
4432 TreeInfo **node_new = NULL;
4435 if (last_level_series[0] == NULL)
4438 InitLastPlayedLevels_ParentNode();
4440 // check if current level set is from "last played" sub-tree to be rebuilt
4441 reset_leveldir_current = strEqual(leveldir_current->node_parent->identifier,
4442 TOKEN_STR_LAST_LEVEL_SERIES);
4444 leveldir_last = getTreeInfoFromIdentifierExt(leveldir_first,
4445 TOKEN_STR_LAST_LEVEL_SERIES,
4447 if (leveldir_last == NULL)
4450 node_new = &leveldir_last->node_group->next;
4452 freeTreeInfo(*node_new);
4454 for (i = 0; last_level_series[i] != NULL; i++)
4456 LevelDirTree *node_last = getTreeInfoFromIdentifier(leveldir_first,
4457 last_level_series[i]);
4459 *node_new = getTreeInfoCopy(node_last); // copy complete node
4461 (*node_new)->node_top = &leveldir_first; // correct top node link
4462 (*node_new)->node_parent = leveldir_last; // correct parent node link
4464 (*node_new)->node_group = NULL;
4465 (*node_new)->next = NULL;
4467 (*node_new)->cl_first = -1; // force setting tree cursor
4469 node_new = &((*node_new)->next);
4472 if (reset_leveldir_current)
4473 leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
4474 last_level_series[0]);
4477 static void UpdateLastPlayedLevels_List(void)
4479 char **last_level_series = setup.level_setup.last_level_series;
4480 int pos = MAX_LEVELDIR_HISTORY - 1;
4483 // search for potentially already existing entry in list of level sets
4484 for (i = 0; last_level_series[i] != NULL; i++)
4485 if (strEqual(last_level_series[i], leveldir_current->identifier))
4488 // move list of level sets one entry down (using potentially free entry)
4489 for (i = pos; i > 0; i--)
4490 setString(&last_level_series[i], last_level_series[i - 1]);
4492 // put last played level set at top position
4493 setString(&last_level_series[0], leveldir_current->identifier);
4496 void LoadLevelSetup_LastSeries(void)
4498 // --------------------------------------------------------------------------
4499 // ~/.<program>/levelsetup.conf
4500 // --------------------------------------------------------------------------
4502 char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
4503 SetupFileHash *level_setup_hash = NULL;
4507 // always start with reliable default values
4508 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
4510 // start with empty history of last played level sets
4511 setString(&setup.level_setup.last_level_series[0], NULL);
4513 if (!strEqual(DEFAULT_LEVELSET, UNDEFINED_LEVELSET))
4515 leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
4517 if (leveldir_current == NULL)
4518 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
4521 if ((level_setup_hash = loadSetupFileHash(filename)))
4523 char *last_level_series =
4524 getHashEntry(level_setup_hash, TOKEN_STR_LAST_LEVEL_SERIES);
4526 leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
4528 if (leveldir_current == NULL)
4529 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
4531 for (i = 0; i < MAX_LEVELDIR_HISTORY; i++)
4533 char token[strlen(TOKEN_STR_LAST_LEVEL_SERIES) + 10];
4534 LevelDirTree *leveldir_last;
4536 sprintf(token, "%s.%03d", TOKEN_STR_LAST_LEVEL_SERIES, i);
4538 last_level_series = getHashEntry(level_setup_hash, token);
4540 leveldir_last = getTreeInfoFromIdentifier(leveldir_first,
4542 if (leveldir_last != NULL)
4543 setString(&setup.level_setup.last_level_series[pos++],
4547 setString(&setup.level_setup.last_level_series[pos], NULL);
4549 freeSetupFileHash(level_setup_hash);
4553 Debug("setup", "using default setup values");
4559 static void SaveLevelSetup_LastSeries_Ext(boolean deactivate_last_level_series)
4561 // --------------------------------------------------------------------------
4562 // ~/.<program>/levelsetup.conf
4563 // --------------------------------------------------------------------------
4565 // check if the current level directory structure is available at this point
4566 if (leveldir_current == NULL)
4569 char **last_level_series = setup.level_setup.last_level_series;
4570 char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
4574 InitUserDataDirectory();
4576 UpdateLastPlayedLevels_List();
4578 if (!(file = fopen(filename, MODE_WRITE)))
4580 Warn("cannot write setup file '%s'", filename);
4587 fprintFileHeader(file, LEVELSETUP_FILENAME);
4589 if (deactivate_last_level_series)
4590 fprintf(file, "# %s\n# ", "the following level set may have caused a problem and was deactivated");
4592 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_LAST_LEVEL_SERIES,
4593 leveldir_current->identifier));
4595 for (i = 0; last_level_series[i] != NULL; i++)
4597 char token[strlen(TOKEN_STR_LAST_LEVEL_SERIES) + 10];
4599 sprintf(token, "%s.%03d", TOKEN_STR_LAST_LEVEL_SERIES, i);
4601 fprintf(file, "%s\n", getFormattedSetupEntry(token, last_level_series[i]));
4606 SetFilePermissions(filename, PERMS_PRIVATE);
4611 void SaveLevelSetup_LastSeries(void)
4613 SaveLevelSetup_LastSeries_Ext(FALSE);
4616 void SaveLevelSetup_LastSeries_Deactivate(void)
4618 SaveLevelSetup_LastSeries_Ext(TRUE);
4621 static void checkSeriesInfo(void)
4623 static char *level_directory = NULL;
4626 DirectoryEntry *dir_entry;
4629 checked_free(level_directory);
4631 // check for more levels besides the 'levels' field of 'levelinfo.conf'
4633 level_directory = getPath2((leveldir_current->in_user_dir ?
4634 getUserLevelDir(NULL) :
4635 options.level_directory),
4636 leveldir_current->fullpath);
4638 if ((dir = openDirectory(level_directory)) == NULL)
4640 Warn("cannot read level directory '%s'", level_directory);
4646 while ((dir_entry = readDirectory(dir)) != NULL) // loop all entries
4648 if (strlen(dir_entry->basename) > 4 &&
4649 dir_entry->basename[3] == '.' &&
4650 strEqual(&dir_entry->basename[4], LEVELFILE_EXTENSION))
4652 char levelnum_str[4];
4655 strncpy(levelnum_str, dir_entry->basename, 3);
4656 levelnum_str[3] = '\0';
4658 levelnum_value = atoi(levelnum_str);
4660 if (levelnum_value < leveldir_current->first_level)
4662 Warn("additional level %d found", levelnum_value);
4664 leveldir_current->first_level = levelnum_value;
4666 else if (levelnum_value > leveldir_current->last_level)
4668 Warn("additional level %d found", levelnum_value);
4670 leveldir_current->last_level = levelnum_value;
4676 closeDirectory(dir);
4679 void LoadLevelSetup_SeriesInfo(void)
4682 SetupFileHash *level_setup_hash = NULL;
4683 char *level_subdir = leveldir_current->subdir;
4686 // always start with reliable default values
4687 level_nr = leveldir_current->first_level;
4689 for (i = 0; i < MAX_LEVELS; i++)
4691 LevelStats_setPlayed(i, 0);
4692 LevelStats_setSolved(i, 0);
4697 // --------------------------------------------------------------------------
4698 // ~/.<program>/levelsetup/<level series>/levelsetup.conf
4699 // --------------------------------------------------------------------------
4701 level_subdir = leveldir_current->subdir;
4703 filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
4705 if ((level_setup_hash = loadSetupFileHash(filename)))
4709 // get last played level in this level set
4711 token_value = getHashEntry(level_setup_hash, TOKEN_STR_LAST_PLAYED_LEVEL);
4715 level_nr = atoi(token_value);
4717 if (level_nr < leveldir_current->first_level)
4718 level_nr = leveldir_current->first_level;
4719 if (level_nr > leveldir_current->last_level)
4720 level_nr = leveldir_current->last_level;
4723 // get handicap level in this level set
4725 token_value = getHashEntry(level_setup_hash, TOKEN_STR_HANDICAP_LEVEL);
4729 int level_nr = atoi(token_value);
4731 if (level_nr < leveldir_current->first_level)
4732 level_nr = leveldir_current->first_level;
4733 if (level_nr > leveldir_current->last_level + 1)
4734 level_nr = leveldir_current->last_level;
4736 if (leveldir_current->user_defined || !leveldir_current->handicap)
4737 level_nr = leveldir_current->last_level;
4739 leveldir_current->handicap_level = level_nr;
4742 // get number of played and solved levels in this level set
4744 BEGIN_HASH_ITERATION(level_setup_hash, itr)
4746 char *token = HASH_ITERATION_TOKEN(itr);
4747 char *value = HASH_ITERATION_VALUE(itr);
4749 if (strlen(token) == 3 &&
4750 token[0] >= '0' && token[0] <= '9' &&
4751 token[1] >= '0' && token[1] <= '9' &&
4752 token[2] >= '0' && token[2] <= '9')
4754 int level_nr = atoi(token);
4757 LevelStats_setPlayed(level_nr, atoi(value)); // read 1st column
4759 value = strchr(value, ' ');
4762 LevelStats_setSolved(level_nr, atoi(value)); // read 2nd column
4765 END_HASH_ITERATION(hash, itr)
4767 freeSetupFileHash(level_setup_hash);
4771 Debug("setup", "using default setup values");
4777 void SaveLevelSetup_SeriesInfo(void)
4780 char *level_subdir = leveldir_current->subdir;
4781 char *level_nr_str = int2str(level_nr, 0);
4782 char *handicap_level_str = int2str(leveldir_current->handicap_level, 0);
4786 // --------------------------------------------------------------------------
4787 // ~/.<program>/levelsetup/<level series>/levelsetup.conf
4788 // --------------------------------------------------------------------------
4790 InitLevelSetupDirectory(level_subdir);
4792 filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
4794 if (!(file = fopen(filename, MODE_WRITE)))
4796 Warn("cannot write setup file '%s'", filename);
4803 fprintFileHeader(file, LEVELSETUP_FILENAME);
4805 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_PLAYED_LEVEL,
4807 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_HANDICAP_LEVEL,
4808 handicap_level_str));
4810 for (i = leveldir_current->first_level; i <= leveldir_current->last_level;
4813 if (LevelStats_getPlayed(i) > 0 ||
4814 LevelStats_getSolved(i) > 0)
4819 sprintf(token, "%03d", i);
4820 sprintf(value, "%d %d", LevelStats_getPlayed(i), LevelStats_getSolved(i));
4822 fprintf(file, "%s\n", getFormattedSetupEntry(token, value));
4828 SetFilePermissions(filename, PERMS_PRIVATE);
4833 int LevelStats_getPlayed(int nr)
4835 return (nr >= 0 && nr < MAX_LEVELS ? level_stats[nr].played : 0);
4838 int LevelStats_getSolved(int nr)
4840 return (nr >= 0 && nr < MAX_LEVELS ? level_stats[nr].solved : 0);
4843 void LevelStats_setPlayed(int nr, int value)
4845 if (nr >= 0 && nr < MAX_LEVELS)
4846 level_stats[nr].played = value;
4849 void LevelStats_setSolved(int nr, int value)
4851 if (nr >= 0 && nr < MAX_LEVELS)
4852 level_stats[nr].solved = value;
4855 void LevelStats_incPlayed(int nr)
4857 if (nr >= 0 && nr < MAX_LEVELS)
4858 level_stats[nr].played++;
4861 void LevelStats_incSolved(int nr)
4863 if (nr >= 0 && nr < MAX_LEVELS)
4864 level_stats[nr].solved++;
4867 void LoadUserSetup(void)
4869 // --------------------------------------------------------------------------
4870 // ~/.<program>/usersetup.conf
4871 // --------------------------------------------------------------------------
4873 char *filename = getPath2(getMainUserGameDataDir(), USERSETUP_FILENAME);
4874 SetupFileHash *user_setup_hash = NULL;
4876 // always start with reliable default values
4879 if ((user_setup_hash = loadSetupFileHash(filename)))
4883 // get last selected user number
4884 token_value = getHashEntry(user_setup_hash, TOKEN_STR_LAST_USER);
4887 user.nr = MIN(MAX(0, atoi(token_value)), MAX_PLAYER_NAMES - 1);
4889 freeSetupFileHash(user_setup_hash);
4893 Debug("setup", "using default setup values");
4899 void SaveUserSetup(void)
4901 // --------------------------------------------------------------------------
4902 // ~/.<program>/usersetup.conf
4903 // --------------------------------------------------------------------------
4905 char *filename = getPath2(getMainUserGameDataDir(), USERSETUP_FILENAME);
4908 InitMainUserDataDirectory();
4910 if (!(file = fopen(filename, MODE_WRITE)))
4912 Warn("cannot write setup file '%s'", filename);
4919 fprintFileHeader(file, USERSETUP_FILENAME);
4921 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_USER,
4925 SetFilePermissions(filename, PERMS_PRIVATE);