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 posTreeInfo(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 TreeInfo *getTreeInfoFromIdentifier(TreeInfo *node, char *identifier)
1254 if (identifier == NULL)
1259 if (node->node_group)
1261 TreeInfo *node_group;
1263 node_group = getTreeInfoFromIdentifier(node->node_group, identifier);
1268 else if (!node->parent_link)
1270 if (strEqual(identifier, node->identifier))
1280 static TreeInfo *cloneTreeNode(TreeInfo **node_top, TreeInfo *node_parent,
1281 TreeInfo *node, boolean skip_sets_without_levels)
1288 if (!node->parent_link && !node->level_group &&
1289 skip_sets_without_levels && node->levels == 0)
1290 return cloneTreeNode(node_top, node_parent, node->next,
1291 skip_sets_without_levels);
1293 node_new = getTreeInfoCopy(node); // copy complete node
1295 node_new->node_top = node_top; // correct top node link
1296 node_new->node_parent = node_parent; // correct parent node link
1298 if (node->level_group)
1299 node_new->node_group = cloneTreeNode(node_top, node_new, node->node_group,
1300 skip_sets_without_levels);
1302 node_new->next = cloneTreeNode(node_top, node_parent, node->next,
1303 skip_sets_without_levels);
1308 static void cloneTree(TreeInfo **ti_new, TreeInfo *ti, boolean skip_empty_sets)
1310 TreeInfo *ti_cloned = cloneTreeNode(ti_new, NULL, ti, skip_empty_sets);
1312 *ti_new = ti_cloned;
1315 static boolean adjustTreeGraphicsForEMC(TreeInfo *node)
1317 boolean settings_changed = FALSE;
1321 boolean want_ecs = (setup.prefer_aga_graphics == FALSE);
1322 boolean want_aga = (setup.prefer_aga_graphics == TRUE);
1323 boolean has_only_ecs = (!node->graphics_set && !node->graphics_set_aga);
1324 boolean has_only_aga = (!node->graphics_set && !node->graphics_set_ecs);
1325 char *graphics_set = NULL;
1327 if (node->graphics_set_ecs && (want_ecs || has_only_ecs))
1328 graphics_set = node->graphics_set_ecs;
1330 if (node->graphics_set_aga && (want_aga || has_only_aga))
1331 graphics_set = node->graphics_set_aga;
1333 if (graphics_set && !strEqual(node->graphics_set, graphics_set))
1335 setString(&node->graphics_set, graphics_set);
1336 settings_changed = TRUE;
1339 if (node->node_group != NULL)
1340 settings_changed |= adjustTreeGraphicsForEMC(node->node_group);
1345 return settings_changed;
1348 static boolean adjustTreeSoundsForEMC(TreeInfo *node)
1350 boolean settings_changed = FALSE;
1354 boolean want_default = (setup.prefer_lowpass_sounds == FALSE);
1355 boolean want_lowpass = (setup.prefer_lowpass_sounds == TRUE);
1356 boolean has_only_default = (!node->sounds_set && !node->sounds_set_lowpass);
1357 boolean has_only_lowpass = (!node->sounds_set && !node->sounds_set_default);
1358 char *sounds_set = NULL;
1360 if (node->sounds_set_default && (want_default || has_only_default))
1361 sounds_set = node->sounds_set_default;
1363 if (node->sounds_set_lowpass && (want_lowpass || has_only_lowpass))
1364 sounds_set = node->sounds_set_lowpass;
1366 if (sounds_set && !strEqual(node->sounds_set, sounds_set))
1368 setString(&node->sounds_set, sounds_set);
1369 settings_changed = TRUE;
1372 if (node->node_group != NULL)
1373 settings_changed |= adjustTreeSoundsForEMC(node->node_group);
1378 return settings_changed;
1381 void dumpTreeInfo(TreeInfo *node, int depth)
1385 Debug("tree", "Dumping TreeInfo:");
1389 for (i = 0; i < (depth + 1) * 3; i++)
1390 DebugContinued("", " ");
1392 DebugContinued("tree", "'%s' / '%s'\n", node->identifier, node->name);
1395 // use for dumping artwork info tree
1396 Debug("tree", "subdir == '%s' ['%s', '%s'] [%d])",
1397 node->subdir, node->fullpath, node->basepath, node->in_user_dir);
1400 if (node->node_group != NULL)
1401 dumpTreeInfo(node->node_group, depth + 1);
1407 void sortTreeInfoBySortFunction(TreeInfo **node_first,
1408 int (*compare_function)(const void *,
1411 int num_nodes = numTreeInfo(*node_first);
1412 TreeInfo **sort_array;
1413 TreeInfo *node = *node_first;
1419 // allocate array for sorting structure pointers
1420 sort_array = checked_calloc(num_nodes * sizeof(TreeInfo *));
1422 // writing structure pointers to sorting array
1423 while (i < num_nodes && node) // double boundary check...
1425 sort_array[i] = node;
1431 // sorting the structure pointers in the sorting array
1432 qsort(sort_array, num_nodes, sizeof(TreeInfo *),
1435 // update the linkage of list elements with the sorted node array
1436 for (i = 0; i < num_nodes - 1; i++)
1437 sort_array[i]->next = sort_array[i + 1];
1438 sort_array[num_nodes - 1]->next = NULL;
1440 // update the linkage of the main list anchor pointer
1441 *node_first = sort_array[0];
1445 // now recursively sort the level group structures
1449 if (node->node_group != NULL)
1450 sortTreeInfoBySortFunction(&node->node_group, compare_function);
1456 void sortTreeInfo(TreeInfo **node_first)
1458 sortTreeInfoBySortFunction(node_first, compareTreeInfoEntries);
1462 // ============================================================================
1463 // some stuff from "files.c"
1464 // ============================================================================
1466 #if defined(PLATFORM_WIN32)
1468 #define S_IRGRP S_IRUSR
1471 #define S_IROTH S_IRUSR
1474 #define S_IWGRP S_IWUSR
1477 #define S_IWOTH S_IWUSR
1480 #define S_IXGRP S_IXUSR
1483 #define S_IXOTH S_IXUSR
1486 #define S_IRWXG (S_IRGRP | S_IWGRP | S_IXGRP)
1491 #endif // PLATFORM_WIN32
1493 // file permissions for newly written files
1494 #define MODE_R_ALL (S_IRUSR | S_IRGRP | S_IROTH)
1495 #define MODE_W_ALL (S_IWUSR | S_IWGRP | S_IWOTH)
1496 #define MODE_X_ALL (S_IXUSR | S_IXGRP | S_IXOTH)
1498 #define MODE_W_PRIVATE (S_IWUSR)
1499 #define MODE_W_PUBLIC_FILE (S_IWUSR | S_IWGRP)
1500 #define MODE_W_PUBLIC_DIR (S_IWUSR | S_IWGRP | S_ISGID)
1502 #define DIR_PERMS_PRIVATE (MODE_R_ALL | MODE_X_ALL | MODE_W_PRIVATE)
1503 #define DIR_PERMS_PUBLIC (MODE_R_ALL | MODE_X_ALL | MODE_W_PUBLIC_DIR)
1504 #define DIR_PERMS_PUBLIC_ALL (MODE_R_ALL | MODE_X_ALL | MODE_W_ALL)
1506 #define FILE_PERMS_PRIVATE (MODE_R_ALL | MODE_W_PRIVATE)
1507 #define FILE_PERMS_PUBLIC (MODE_R_ALL | MODE_W_PUBLIC_FILE)
1508 #define FILE_PERMS_PUBLIC_ALL (MODE_R_ALL | MODE_W_ALL)
1511 char *getHomeDir(void)
1513 static char *dir = NULL;
1515 #if defined(PLATFORM_WIN32)
1518 dir = checked_malloc(MAX_PATH + 1);
1520 if (!SUCCEEDED(SHGetFolderPath(NULL, CSIDL_PERSONAL, NULL, 0, dir)))
1523 #elif defined(PLATFORM_UNIX)
1526 if ((dir = getenv("HOME")) == NULL)
1528 dir = getUnixHomeDir();
1531 dir = getStringCopy(dir);
1543 char *getCommonDataDir(void)
1545 static char *common_data_dir = NULL;
1547 #if defined(PLATFORM_WIN32)
1548 if (common_data_dir == NULL)
1550 char *dir = checked_malloc(MAX_PATH + 1);
1552 if (SUCCEEDED(SHGetFolderPath(NULL, CSIDL_COMMON_DOCUMENTS, NULL, 0, dir))
1553 && !strEqual(dir, "")) // empty for Windows 95/98
1554 common_data_dir = getPath2(dir, program.userdata_subdir);
1556 common_data_dir = options.rw_base_directory;
1559 if (common_data_dir == NULL)
1560 common_data_dir = options.rw_base_directory;
1563 return common_data_dir;
1566 char *getPersonalDataDir(void)
1568 static char *personal_data_dir = NULL;
1570 #if defined(PLATFORM_MACOSX)
1571 if (personal_data_dir == NULL)
1572 personal_data_dir = getPath2(getHomeDir(), "Documents");
1574 if (personal_data_dir == NULL)
1575 personal_data_dir = getHomeDir();
1578 return personal_data_dir;
1581 char *getMainUserGameDataDir(void)
1583 static char *main_user_data_dir = NULL;
1585 #if defined(PLATFORM_ANDROID)
1586 if (main_user_data_dir == NULL)
1587 main_user_data_dir = (char *)(SDL_AndroidGetExternalStorageState() &
1588 SDL_ANDROID_EXTERNAL_STORAGE_WRITE ?
1589 SDL_AndroidGetExternalStoragePath() :
1590 SDL_AndroidGetInternalStoragePath());
1592 if (main_user_data_dir == NULL)
1593 main_user_data_dir = getPath2(getPersonalDataDir(),
1594 program.userdata_subdir);
1597 return main_user_data_dir;
1600 char *getUserGameDataDir(void)
1603 return getMainUserGameDataDir();
1605 return getUserDir(user.nr);
1608 char *getSetupDir(void)
1610 return getUserGameDataDir();
1613 static mode_t posix_umask(mode_t mask)
1615 #if defined(PLATFORM_UNIX)
1622 static int posix_mkdir(const char *pathname, mode_t mode)
1624 #if defined(PLATFORM_WIN32)
1625 return mkdir(pathname);
1627 return mkdir(pathname, mode);
1631 static boolean posix_process_running_setgid(void)
1633 #if defined(PLATFORM_UNIX)
1634 return (getgid() != getegid());
1640 void createDirectory(char *dir, char *text, int permission_class)
1642 if (directoryExists(dir))
1645 // leave "other" permissions in umask untouched, but ensure group parts
1646 // of USERDATA_DIR_MODE are not masked
1647 mode_t dir_mode = (permission_class == PERMS_PRIVATE ?
1648 DIR_PERMS_PRIVATE : DIR_PERMS_PUBLIC);
1649 mode_t last_umask = posix_umask(0);
1650 mode_t group_umask = ~(dir_mode & S_IRWXG);
1651 int running_setgid = posix_process_running_setgid();
1653 if (permission_class == PERMS_PUBLIC)
1655 // if we're setgid, protect files against "other"
1656 // else keep umask(0) to make the dir world-writable
1659 posix_umask(last_umask & group_umask);
1661 dir_mode = DIR_PERMS_PUBLIC_ALL;
1664 if (posix_mkdir(dir, dir_mode) != 0)
1665 Warn("cannot create %s directory '%s': %s", text, dir, strerror(errno));
1667 if (permission_class == PERMS_PUBLIC && !running_setgid)
1668 chmod(dir, dir_mode);
1670 posix_umask(last_umask); // restore previous umask
1673 void InitMainUserDataDirectory(void)
1675 createDirectory(getMainUserGameDataDir(), "main user data", PERMS_PRIVATE);
1678 void InitUserDataDirectory(void)
1680 createDirectory(getMainUserGameDataDir(), "main user data", PERMS_PRIVATE);
1684 createDirectory(getUserDir(-1), "users", PERMS_PRIVATE);
1685 createDirectory(getUserDir(user.nr), "user data", PERMS_PRIVATE);
1689 void SetFilePermissions(char *filename, int permission_class)
1691 int running_setgid = posix_process_running_setgid();
1692 int perms = (permission_class == PERMS_PRIVATE ?
1693 FILE_PERMS_PRIVATE : FILE_PERMS_PUBLIC);
1695 if (permission_class == PERMS_PUBLIC && !running_setgid)
1696 perms = FILE_PERMS_PUBLIC_ALL;
1698 chmod(filename, perms);
1701 char *getCookie(char *file_type)
1703 static char cookie[MAX_COOKIE_LEN + 1];
1705 if (strlen(program.cookie_prefix) + 1 +
1706 strlen(file_type) + strlen("_FILE_VERSION_x.x") > MAX_COOKIE_LEN)
1707 return "[COOKIE ERROR]"; // should never happen
1709 sprintf(cookie, "%s_%s_FILE_VERSION_%d.%d",
1710 program.cookie_prefix, file_type,
1711 program.version_super, program.version_major);
1716 void fprintFileHeader(FILE *file, char *basename)
1718 char *prefix = "# ";
1721 fprintf_line_with_prefix(file, prefix, sep1, 77);
1722 fprintf(file, "%s%s\n", prefix, basename);
1723 fprintf_line_with_prefix(file, prefix, sep1, 77);
1724 fprintf(file, "\n");
1727 int getFileVersionFromCookieString(const char *cookie)
1729 const char *ptr_cookie1, *ptr_cookie2;
1730 const char *pattern1 = "_FILE_VERSION_";
1731 const char *pattern2 = "?.?";
1732 const int len_cookie = strlen(cookie);
1733 const int len_pattern1 = strlen(pattern1);
1734 const int len_pattern2 = strlen(pattern2);
1735 const int len_pattern = len_pattern1 + len_pattern2;
1736 int version_super, version_major;
1738 if (len_cookie <= len_pattern)
1741 ptr_cookie1 = &cookie[len_cookie - len_pattern];
1742 ptr_cookie2 = &cookie[len_cookie - len_pattern2];
1744 if (strncmp(ptr_cookie1, pattern1, len_pattern1) != 0)
1747 if (ptr_cookie2[0] < '0' || ptr_cookie2[0] > '9' ||
1748 ptr_cookie2[1] != '.' ||
1749 ptr_cookie2[2] < '0' || ptr_cookie2[2] > '9')
1752 version_super = ptr_cookie2[0] - '0';
1753 version_major = ptr_cookie2[2] - '0';
1755 return VERSION_IDENT(version_super, version_major, 0, 0);
1758 boolean checkCookieString(const char *cookie, const char *template)
1760 const char *pattern = "_FILE_VERSION_?.?";
1761 const int len_cookie = strlen(cookie);
1762 const int len_template = strlen(template);
1763 const int len_pattern = strlen(pattern);
1765 if (len_cookie != len_template)
1768 if (strncmp(cookie, template, len_cookie - len_pattern) != 0)
1775 // ----------------------------------------------------------------------------
1776 // setup file list and hash handling functions
1777 // ----------------------------------------------------------------------------
1779 char *getFormattedSetupEntry(char *token, char *value)
1782 static char entry[MAX_LINE_LEN];
1784 // if value is an empty string, just return token without value
1788 // start with the token and some spaces to format output line
1789 sprintf(entry, "%s:", token);
1790 for (i = strlen(entry); i < token_value_position; i++)
1793 // continue with the token's value
1794 strcat(entry, value);
1799 SetupFileList *newSetupFileList(char *token, char *value)
1801 SetupFileList *new = checked_malloc(sizeof(SetupFileList));
1803 new->token = getStringCopy(token);
1804 new->value = getStringCopy(value);
1811 void freeSetupFileList(SetupFileList *list)
1816 checked_free(list->token);
1817 checked_free(list->value);
1820 freeSetupFileList(list->next);
1825 char *getListEntry(SetupFileList *list, char *token)
1830 if (strEqual(list->token, token))
1833 return getListEntry(list->next, token);
1836 SetupFileList *setListEntry(SetupFileList *list, char *token, char *value)
1841 if (strEqual(list->token, token))
1843 checked_free(list->value);
1845 list->value = getStringCopy(value);
1849 else if (list->next == NULL)
1850 return (list->next = newSetupFileList(token, value));
1852 return setListEntry(list->next, token, value);
1855 SetupFileList *addListEntry(SetupFileList *list, char *token, char *value)
1860 if (list->next == NULL)
1861 return (list->next = newSetupFileList(token, value));
1863 return addListEntry(list->next, token, value);
1866 #if ENABLE_UNUSED_CODE
1868 static void printSetupFileList(SetupFileList *list)
1873 Debug("setup:printSetupFileList", "token: '%s'", list->token);
1874 Debug("setup:printSetupFileList", "value: '%s'", list->value);
1876 printSetupFileList(list->next);
1882 DEFINE_HASHTABLE_INSERT(insert_hash_entry, char, char);
1883 DEFINE_HASHTABLE_SEARCH(search_hash_entry, char, char);
1884 DEFINE_HASHTABLE_CHANGE(change_hash_entry, char, char);
1885 DEFINE_HASHTABLE_REMOVE(remove_hash_entry, char, char);
1887 #define insert_hash_entry hashtable_insert
1888 #define search_hash_entry hashtable_search
1889 #define change_hash_entry hashtable_change
1890 #define remove_hash_entry hashtable_remove
1893 unsigned int get_hash_from_key(void *key)
1898 This algorithm (k=33) was first reported by Dan Bernstein many years ago in
1899 'comp.lang.c'. Another version of this algorithm (now favored by Bernstein)
1900 uses XOR: hash(i) = hash(i - 1) * 33 ^ str[i]; the magic of number 33 (why
1901 it works better than many other constants, prime or not) has never been
1902 adequately explained.
1904 If you just want to have a good hash function, and cannot wait, djb2
1905 is one of the best string hash functions i know. It has excellent
1906 distribution and speed on many different sets of keys and table sizes.
1907 You are not likely to do better with one of the "well known" functions
1908 such as PJW, K&R, etc.
1910 Ozan (oz) Yigit [http://www.cs.yorku.ca/~oz/hash.html]
1913 char *str = (char *)key;
1914 unsigned int hash = 5381;
1917 while ((c = *str++))
1918 hash = ((hash << 5) + hash) + c; // hash * 33 + c
1923 static int keys_are_equal(void *key1, void *key2)
1925 return (strEqual((char *)key1, (char *)key2));
1928 SetupFileHash *newSetupFileHash(void)
1930 SetupFileHash *new_hash =
1931 create_hashtable(16, 0.75, get_hash_from_key, keys_are_equal);
1933 if (new_hash == NULL)
1934 Fail("create_hashtable() failed -- out of memory");
1939 void freeSetupFileHash(SetupFileHash *hash)
1944 hashtable_destroy(hash, 1); // 1 == also free values stored in hash
1947 char *getHashEntry(SetupFileHash *hash, char *token)
1952 return search_hash_entry(hash, token);
1955 void setHashEntry(SetupFileHash *hash, char *token, char *value)
1962 value_copy = getStringCopy(value);
1964 // change value; if it does not exist, insert it as new
1965 if (!change_hash_entry(hash, token, value_copy))
1966 if (!insert_hash_entry(hash, getStringCopy(token), value_copy))
1967 Fail("cannot insert into hash -- aborting");
1970 char *removeHashEntry(SetupFileHash *hash, char *token)
1975 return remove_hash_entry(hash, token);
1978 #if ENABLE_UNUSED_CODE
1980 static void printSetupFileHash(SetupFileHash *hash)
1982 BEGIN_HASH_ITERATION(hash, itr)
1984 Debug("setup:printSetupFileHash", "token: '%s'", HASH_ITERATION_TOKEN(itr));
1985 Debug("setup:printSetupFileHash", "value: '%s'", HASH_ITERATION_VALUE(itr));
1987 END_HASH_ITERATION(hash, itr)
1992 #define ALLOW_TOKEN_VALUE_SEPARATOR_BEING_WHITESPACE 1
1993 #define CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING 0
1994 #define CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH 0
1996 static boolean token_value_separator_found = FALSE;
1997 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
1998 static boolean token_value_separator_warning = FALSE;
2000 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2001 static boolean token_already_exists_warning = FALSE;
2004 static boolean getTokenValueFromSetupLineExt(char *line,
2005 char **token_ptr, char **value_ptr,
2006 char *filename, char *line_raw,
2008 boolean separator_required)
2010 static char line_copy[MAX_LINE_LEN + 1], line_raw_copy[MAX_LINE_LEN + 1];
2011 char *token, *value, *line_ptr;
2013 // when externally invoked via ReadTokenValueFromLine(), copy line buffers
2014 if (line_raw == NULL)
2016 strncpy(line_copy, line, MAX_LINE_LEN);
2017 line_copy[MAX_LINE_LEN] = '\0';
2020 strcpy(line_raw_copy, line_copy);
2021 line_raw = line_raw_copy;
2024 // cut trailing comment from input line
2025 for (line_ptr = line; *line_ptr; line_ptr++)
2027 if (*line_ptr == '#')
2034 // cut trailing whitespaces from input line
2035 for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
2036 if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
2039 // ignore empty lines
2043 // cut leading whitespaces from token
2044 for (token = line; *token; token++)
2045 if (*token != ' ' && *token != '\t')
2048 // start with empty value as reliable default
2051 token_value_separator_found = FALSE;
2053 // find end of token to determine start of value
2054 for (line_ptr = token; *line_ptr; line_ptr++)
2056 // first look for an explicit token/value separator, like ':' or '='
2057 if (*line_ptr == ':' || *line_ptr == '=')
2059 *line_ptr = '\0'; // terminate token string
2060 value = line_ptr + 1; // set beginning of value
2062 token_value_separator_found = TRUE;
2068 #if ALLOW_TOKEN_VALUE_SEPARATOR_BEING_WHITESPACE
2069 // fallback: if no token/value separator found, also allow whitespaces
2070 if (!token_value_separator_found && !separator_required)
2072 for (line_ptr = token; *line_ptr; line_ptr++)
2074 if (*line_ptr == ' ' || *line_ptr == '\t')
2076 *line_ptr = '\0'; // terminate token string
2077 value = line_ptr + 1; // set beginning of value
2079 token_value_separator_found = TRUE;
2085 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2086 if (token_value_separator_found)
2088 if (!token_value_separator_warning)
2090 Debug("setup", "---");
2092 if (filename != NULL)
2094 Debug("setup", "missing token/value separator(s) in config file:");
2095 Debug("setup", "- config file: '%s'", filename);
2099 Debug("setup", "missing token/value separator(s):");
2102 token_value_separator_warning = TRUE;
2105 if (filename != NULL)
2106 Debug("setup", "- line %d: '%s'", line_nr, line_raw);
2108 Debug("setup", "- line: '%s'", line_raw);
2114 // cut trailing whitespaces from token
2115 for (line_ptr = &token[strlen(token)]; line_ptr >= token; line_ptr--)
2116 if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
2119 // cut leading whitespaces from value
2120 for (; *value; value++)
2121 if (*value != ' ' && *value != '\t')
2130 boolean getTokenValueFromSetupLine(char *line, char **token, char **value)
2132 // while the internal (old) interface does not require a token/value
2133 // separator (for downwards compatibility with existing files which
2134 // don't use them), it is mandatory for the external (new) interface
2136 return getTokenValueFromSetupLineExt(line, token, value, NULL, NULL, 0, TRUE);
2139 static boolean loadSetupFileData(void *setup_file_data, char *filename,
2140 boolean top_recursion_level, boolean is_hash)
2142 static SetupFileHash *include_filename_hash = NULL;
2143 char line[MAX_LINE_LEN], line_raw[MAX_LINE_LEN], previous_line[MAX_LINE_LEN];
2144 char *token, *value, *line_ptr;
2145 void *insert_ptr = NULL;
2146 boolean read_continued_line = FALSE;
2148 int line_nr = 0, token_count = 0, include_count = 0;
2150 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2151 token_value_separator_warning = FALSE;
2154 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2155 token_already_exists_warning = FALSE;
2158 if (!(file = openFile(filename, MODE_READ)))
2160 #if DEBUG_NO_CONFIG_FILE
2161 Debug("setup", "cannot open configuration file '%s'", filename);
2167 // use "insert pointer" to store list end for constant insertion complexity
2169 insert_ptr = setup_file_data;
2171 // on top invocation, create hash to mark included files (to prevent loops)
2172 if (top_recursion_level)
2173 include_filename_hash = newSetupFileHash();
2175 // mark this file as already included (to prevent including it again)
2176 setHashEntry(include_filename_hash, getBaseNamePtr(filename), "true");
2178 while (!checkEndOfFile(file))
2180 // read next line of input file
2181 if (!getStringFromFile(file, line, MAX_LINE_LEN))
2184 // check if line was completely read and is terminated by line break
2185 if (strlen(line) > 0 && line[strlen(line) - 1] == '\n')
2188 // cut trailing line break (this can be newline and/or carriage return)
2189 for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
2190 if ((*line_ptr == '\n' || *line_ptr == '\r') && *(line_ptr + 1) == '\0')
2193 // copy raw input line for later use (mainly debugging output)
2194 strcpy(line_raw, line);
2196 if (read_continued_line)
2198 // append new line to existing line, if there is enough space
2199 if (strlen(previous_line) + strlen(line_ptr) < MAX_LINE_LEN)
2200 strcat(previous_line, line_ptr);
2202 strcpy(line, previous_line); // copy storage buffer to line
2204 read_continued_line = FALSE;
2207 // if the last character is '\', continue at next line
2208 if (strlen(line) > 0 && line[strlen(line) - 1] == '\\')
2210 line[strlen(line) - 1] = '\0'; // cut off trailing backslash
2211 strcpy(previous_line, line); // copy line to storage buffer
2213 read_continued_line = TRUE;
2218 if (!getTokenValueFromSetupLineExt(line, &token, &value, filename,
2219 line_raw, line_nr, FALSE))
2224 if (strEqual(token, "include"))
2226 if (getHashEntry(include_filename_hash, value) == NULL)
2228 char *basepath = getBasePath(filename);
2229 char *basename = getBaseName(value);
2230 char *filename_include = getPath2(basepath, basename);
2232 loadSetupFileData(setup_file_data, filename_include, FALSE, is_hash);
2236 free(filename_include);
2242 Warn("ignoring already processed file '%s'", value);
2249 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2251 getHashEntry((SetupFileHash *)setup_file_data, token);
2253 if (old_value != NULL)
2255 if (!token_already_exists_warning)
2257 Debug("setup", "---");
2258 Debug("setup", "duplicate token(s) found in config file:");
2259 Debug("setup", "- config file: '%s'", filename);
2261 token_already_exists_warning = TRUE;
2264 Debug("setup", "- token: '%s' (in line %d)", token, line_nr);
2265 Debug("setup", " old value: '%s'", old_value);
2266 Debug("setup", " new value: '%s'", value);
2270 setHashEntry((SetupFileHash *)setup_file_data, token, value);
2274 insert_ptr = addListEntry((SetupFileList *)insert_ptr, token, value);
2284 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2285 if (token_value_separator_warning)
2286 Debug("setup", "---");
2289 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2290 if (token_already_exists_warning)
2291 Debug("setup", "---");
2294 if (token_count == 0 && include_count == 0)
2295 Warn("configuration file '%s' is empty", filename);
2297 if (top_recursion_level)
2298 freeSetupFileHash(include_filename_hash);
2303 static void saveSetupFileHash(SetupFileHash *hash, char *filename)
2307 if (!(file = fopen(filename, MODE_WRITE)))
2309 Warn("cannot write configuration file '%s'", filename);
2314 BEGIN_HASH_ITERATION(hash, itr)
2316 fprintf(file, "%s\n", getFormattedSetupEntry(HASH_ITERATION_TOKEN(itr),
2317 HASH_ITERATION_VALUE(itr)));
2319 END_HASH_ITERATION(hash, itr)
2324 SetupFileList *loadSetupFileList(char *filename)
2326 SetupFileList *setup_file_list = newSetupFileList("", "");
2327 SetupFileList *first_valid_list_entry;
2329 if (!loadSetupFileData(setup_file_list, filename, TRUE, FALSE))
2331 freeSetupFileList(setup_file_list);
2336 first_valid_list_entry = setup_file_list->next;
2338 // free empty list header
2339 setup_file_list->next = NULL;
2340 freeSetupFileList(setup_file_list);
2342 return first_valid_list_entry;
2345 SetupFileHash *loadSetupFileHash(char *filename)
2347 SetupFileHash *setup_file_hash = newSetupFileHash();
2349 if (!loadSetupFileData(setup_file_hash, filename, TRUE, TRUE))
2351 freeSetupFileHash(setup_file_hash);
2356 return setup_file_hash;
2360 // ============================================================================
2362 // ============================================================================
2364 #define TOKEN_STR_LAST_LEVEL_SERIES "last_level_series"
2365 #define TOKEN_STR_LAST_PLAYED_LEVEL "last_played_level"
2366 #define TOKEN_STR_HANDICAP_LEVEL "handicap_level"
2367 #define TOKEN_STR_LAST_USER "last_user"
2369 // level directory info
2370 #define LEVELINFO_TOKEN_IDENTIFIER 0
2371 #define LEVELINFO_TOKEN_NAME 1
2372 #define LEVELINFO_TOKEN_NAME_SORTING 2
2373 #define LEVELINFO_TOKEN_AUTHOR 3
2374 #define LEVELINFO_TOKEN_YEAR 4
2375 #define LEVELINFO_TOKEN_PROGRAM_TITLE 5
2376 #define LEVELINFO_TOKEN_PROGRAM_COPYRIGHT 6
2377 #define LEVELINFO_TOKEN_PROGRAM_COMPANY 7
2378 #define LEVELINFO_TOKEN_IMPORTED_FROM 8
2379 #define LEVELINFO_TOKEN_IMPORTED_BY 9
2380 #define LEVELINFO_TOKEN_TESTED_BY 10
2381 #define LEVELINFO_TOKEN_LEVELS 11
2382 #define LEVELINFO_TOKEN_FIRST_LEVEL 12
2383 #define LEVELINFO_TOKEN_SORT_PRIORITY 13
2384 #define LEVELINFO_TOKEN_LATEST_ENGINE 14
2385 #define LEVELINFO_TOKEN_LEVEL_GROUP 15
2386 #define LEVELINFO_TOKEN_READONLY 16
2387 #define LEVELINFO_TOKEN_GRAPHICS_SET_ECS 17
2388 #define LEVELINFO_TOKEN_GRAPHICS_SET_AGA 18
2389 #define LEVELINFO_TOKEN_GRAPHICS_SET 19
2390 #define LEVELINFO_TOKEN_SOUNDS_SET_DEFAULT 20
2391 #define LEVELINFO_TOKEN_SOUNDS_SET_LOWPASS 21
2392 #define LEVELINFO_TOKEN_SOUNDS_SET 22
2393 #define LEVELINFO_TOKEN_MUSIC_SET 23
2394 #define LEVELINFO_TOKEN_FILENAME 24
2395 #define LEVELINFO_TOKEN_FILETYPE 25
2396 #define LEVELINFO_TOKEN_SPECIAL_FLAGS 26
2397 #define LEVELINFO_TOKEN_HANDICAP 27
2398 #define LEVELINFO_TOKEN_SKIP_LEVELS 28
2399 #define LEVELINFO_TOKEN_USE_EMC_TILES 29
2401 #define NUM_LEVELINFO_TOKENS 30
2403 static LevelDirTree ldi;
2405 static struct TokenInfo levelinfo_tokens[] =
2407 // level directory info
2408 { TYPE_STRING, &ldi.identifier, "identifier" },
2409 { TYPE_STRING, &ldi.name, "name" },
2410 { TYPE_STRING, &ldi.name_sorting, "name_sorting" },
2411 { TYPE_STRING, &ldi.author, "author" },
2412 { TYPE_STRING, &ldi.year, "year" },
2413 { TYPE_STRING, &ldi.program_title, "program_title" },
2414 { TYPE_STRING, &ldi.program_copyright, "program_copyright" },
2415 { TYPE_STRING, &ldi.program_company, "program_company" },
2416 { TYPE_STRING, &ldi.imported_from, "imported_from" },
2417 { TYPE_STRING, &ldi.imported_by, "imported_by" },
2418 { TYPE_STRING, &ldi.tested_by, "tested_by" },
2419 { TYPE_INTEGER, &ldi.levels, "levels" },
2420 { TYPE_INTEGER, &ldi.first_level, "first_level" },
2421 { TYPE_INTEGER, &ldi.sort_priority, "sort_priority" },
2422 { TYPE_BOOLEAN, &ldi.latest_engine, "latest_engine" },
2423 { TYPE_BOOLEAN, &ldi.level_group, "level_group" },
2424 { TYPE_BOOLEAN, &ldi.readonly, "readonly" },
2425 { TYPE_STRING, &ldi.graphics_set_ecs, "graphics_set.ecs" },
2426 { TYPE_STRING, &ldi.graphics_set_aga, "graphics_set.aga" },
2427 { TYPE_STRING, &ldi.graphics_set, "graphics_set" },
2428 { TYPE_STRING, &ldi.sounds_set_default,"sounds_set.default" },
2429 { TYPE_STRING, &ldi.sounds_set_lowpass,"sounds_set.lowpass" },
2430 { TYPE_STRING, &ldi.sounds_set, "sounds_set" },
2431 { TYPE_STRING, &ldi.music_set, "music_set" },
2432 { TYPE_STRING, &ldi.level_filename, "filename" },
2433 { TYPE_STRING, &ldi.level_filetype, "filetype" },
2434 { TYPE_STRING, &ldi.special_flags, "special_flags" },
2435 { TYPE_BOOLEAN, &ldi.handicap, "handicap" },
2436 { TYPE_BOOLEAN, &ldi.skip_levels, "skip_levels" },
2437 { TYPE_BOOLEAN, &ldi.use_emc_tiles, "use_emc_tiles" }
2440 static struct TokenInfo artworkinfo_tokens[] =
2442 // artwork directory info
2443 { TYPE_STRING, &ldi.identifier, "identifier" },
2444 { TYPE_STRING, &ldi.subdir, "subdir" },
2445 { TYPE_STRING, &ldi.name, "name" },
2446 { TYPE_STRING, &ldi.name_sorting, "name_sorting" },
2447 { TYPE_STRING, &ldi.author, "author" },
2448 { TYPE_STRING, &ldi.program_title, "program_title" },
2449 { TYPE_STRING, &ldi.program_copyright, "program_copyright" },
2450 { TYPE_STRING, &ldi.program_company, "program_company" },
2451 { TYPE_INTEGER, &ldi.sort_priority, "sort_priority" },
2452 { TYPE_STRING, &ldi.basepath, "basepath" },
2453 { TYPE_STRING, &ldi.fullpath, "fullpath" },
2454 { TYPE_BOOLEAN, &ldi.in_user_dir, "in_user_dir" },
2455 { TYPE_INTEGER, &ldi.color, "color" },
2456 { TYPE_STRING, &ldi.class_desc, "class_desc" },
2461 static char *optional_tokens[] =
2464 "program_copyright",
2470 static void setTreeInfoToDefaults(TreeInfo *ti, int type)
2474 ti->node_top = (ti->type == TREE_TYPE_LEVEL_DIR ? &leveldir_first :
2475 ti->type == TREE_TYPE_GRAPHICS_DIR ? &artwork.gfx_first :
2476 ti->type == TREE_TYPE_SOUNDS_DIR ? &artwork.snd_first :
2477 ti->type == TREE_TYPE_MUSIC_DIR ? &artwork.mus_first :
2480 ti->node_parent = NULL;
2481 ti->node_group = NULL;
2488 ti->fullpath = NULL;
2489 ti->basepath = NULL;
2490 ti->identifier = NULL;
2491 ti->name = getStringCopy(ANONYMOUS_NAME);
2492 ti->name_sorting = NULL;
2493 ti->author = getStringCopy(ANONYMOUS_NAME);
2496 ti->program_title = NULL;
2497 ti->program_copyright = NULL;
2498 ti->program_company = NULL;
2500 ti->sort_priority = LEVELCLASS_UNDEFINED; // default: least priority
2501 ti->latest_engine = FALSE; // default: get from level
2502 ti->parent_link = FALSE;
2503 ti->in_user_dir = FALSE;
2504 ti->user_defined = FALSE;
2506 ti->class_desc = NULL;
2508 ti->infotext = getStringCopy(TREE_INFOTEXT(ti->type));
2510 if (ti->type == TREE_TYPE_LEVEL_DIR)
2512 ti->imported_from = NULL;
2513 ti->imported_by = NULL;
2514 ti->tested_by = NULL;
2516 ti->graphics_set_ecs = NULL;
2517 ti->graphics_set_aga = NULL;
2518 ti->graphics_set = NULL;
2519 ti->sounds_set_default = NULL;
2520 ti->sounds_set_lowpass = NULL;
2521 ti->sounds_set = NULL;
2522 ti->music_set = NULL;
2523 ti->graphics_path = getStringCopy(UNDEFINED_FILENAME);
2524 ti->sounds_path = getStringCopy(UNDEFINED_FILENAME);
2525 ti->music_path = getStringCopy(UNDEFINED_FILENAME);
2527 ti->level_filename = NULL;
2528 ti->level_filetype = NULL;
2530 ti->special_flags = NULL;
2533 ti->first_level = 0;
2535 ti->level_group = FALSE;
2536 ti->handicap_level = 0;
2537 ti->readonly = TRUE;
2538 ti->handicap = TRUE;
2539 ti->skip_levels = FALSE;
2541 ti->use_emc_tiles = FALSE;
2545 static void setTreeInfoToDefaultsFromParent(TreeInfo *ti, TreeInfo *parent)
2549 Warn("setTreeInfoToDefaultsFromParent(): parent == NULL");
2551 setTreeInfoToDefaults(ti, TREE_TYPE_UNDEFINED);
2556 // copy all values from the parent structure
2558 ti->type = parent->type;
2560 ti->node_top = parent->node_top;
2561 ti->node_parent = parent;
2562 ti->node_group = NULL;
2569 ti->fullpath = NULL;
2570 ti->basepath = NULL;
2571 ti->identifier = NULL;
2572 ti->name = getStringCopy(ANONYMOUS_NAME);
2573 ti->name_sorting = NULL;
2574 ti->author = getStringCopy(parent->author);
2575 ti->year = getStringCopy(parent->year);
2577 ti->program_title = getStringCopy(parent->program_title);
2578 ti->program_copyright = getStringCopy(parent->program_copyright);
2579 ti->program_company = getStringCopy(parent->program_company);
2581 ti->sort_priority = parent->sort_priority;
2582 ti->latest_engine = parent->latest_engine;
2583 ti->parent_link = FALSE;
2584 ti->in_user_dir = parent->in_user_dir;
2585 ti->user_defined = parent->user_defined;
2586 ti->color = parent->color;
2587 ti->class_desc = getStringCopy(parent->class_desc);
2589 ti->infotext = getStringCopy(parent->infotext);
2591 if (ti->type == TREE_TYPE_LEVEL_DIR)
2593 ti->imported_from = getStringCopy(parent->imported_from);
2594 ti->imported_by = getStringCopy(parent->imported_by);
2595 ti->tested_by = getStringCopy(parent->tested_by);
2597 ti->graphics_set_ecs = getStringCopy(parent->graphics_set_ecs);
2598 ti->graphics_set_aga = getStringCopy(parent->graphics_set_aga);
2599 ti->graphics_set = getStringCopy(parent->graphics_set);
2600 ti->sounds_set_default = getStringCopy(parent->sounds_set_default);
2601 ti->sounds_set_lowpass = getStringCopy(parent->sounds_set_lowpass);
2602 ti->sounds_set = getStringCopy(parent->sounds_set);
2603 ti->music_set = getStringCopy(parent->music_set);
2604 ti->graphics_path = getStringCopy(UNDEFINED_FILENAME);
2605 ti->sounds_path = getStringCopy(UNDEFINED_FILENAME);
2606 ti->music_path = getStringCopy(UNDEFINED_FILENAME);
2608 ti->level_filename = getStringCopy(parent->level_filename);
2609 ti->level_filetype = getStringCopy(parent->level_filetype);
2611 ti->special_flags = getStringCopy(parent->special_flags);
2613 ti->levels = parent->levels;
2614 ti->first_level = parent->first_level;
2615 ti->last_level = parent->last_level;
2616 ti->level_group = FALSE;
2617 ti->handicap_level = parent->handicap_level;
2618 ti->readonly = parent->readonly;
2619 ti->handicap = parent->handicap;
2620 ti->skip_levels = parent->skip_levels;
2622 ti->use_emc_tiles = parent->use_emc_tiles;
2626 static TreeInfo *getTreeInfoCopy(TreeInfo *ti)
2628 TreeInfo *ti_copy = newTreeInfo();
2630 // copy all values from the original structure
2632 ti_copy->type = ti->type;
2634 ti_copy->node_top = ti->node_top;
2635 ti_copy->node_parent = ti->node_parent;
2636 ti_copy->node_group = ti->node_group;
2637 ti_copy->next = ti->next;
2639 ti_copy->cl_first = ti->cl_first;
2640 ti_copy->cl_cursor = ti->cl_cursor;
2642 ti_copy->subdir = getStringCopy(ti->subdir);
2643 ti_copy->fullpath = getStringCopy(ti->fullpath);
2644 ti_copy->basepath = getStringCopy(ti->basepath);
2645 ti_copy->identifier = getStringCopy(ti->identifier);
2646 ti_copy->name = getStringCopy(ti->name);
2647 ti_copy->name_sorting = getStringCopy(ti->name_sorting);
2648 ti_copy->author = getStringCopy(ti->author);
2649 ti_copy->year = getStringCopy(ti->year);
2651 ti_copy->program_title = getStringCopy(ti->program_title);
2652 ti_copy->program_copyright = getStringCopy(ti->program_copyright);
2653 ti_copy->program_company = getStringCopy(ti->program_company);
2655 ti_copy->imported_from = getStringCopy(ti->imported_from);
2656 ti_copy->imported_by = getStringCopy(ti->imported_by);
2657 ti_copy->tested_by = getStringCopy(ti->tested_by);
2659 ti_copy->graphics_set_ecs = getStringCopy(ti->graphics_set_ecs);
2660 ti_copy->graphics_set_aga = getStringCopy(ti->graphics_set_aga);
2661 ti_copy->graphics_set = getStringCopy(ti->graphics_set);
2662 ti_copy->sounds_set_default = getStringCopy(ti->sounds_set_default);
2663 ti_copy->sounds_set_lowpass = getStringCopy(ti->sounds_set_lowpass);
2664 ti_copy->sounds_set = getStringCopy(ti->sounds_set);
2665 ti_copy->music_set = getStringCopy(ti->music_set);
2666 ti_copy->graphics_path = getStringCopy(ti->graphics_path);
2667 ti_copy->sounds_path = getStringCopy(ti->sounds_path);
2668 ti_copy->music_path = getStringCopy(ti->music_path);
2670 ti_copy->level_filename = getStringCopy(ti->level_filename);
2671 ti_copy->level_filetype = getStringCopy(ti->level_filetype);
2673 ti_copy->special_flags = getStringCopy(ti->special_flags);
2675 ti_copy->levels = ti->levels;
2676 ti_copy->first_level = ti->first_level;
2677 ti_copy->last_level = ti->last_level;
2678 ti_copy->sort_priority = ti->sort_priority;
2680 ti_copy->latest_engine = ti->latest_engine;
2682 ti_copy->level_group = ti->level_group;
2683 ti_copy->parent_link = ti->parent_link;
2684 ti_copy->in_user_dir = ti->in_user_dir;
2685 ti_copy->user_defined = ti->user_defined;
2686 ti_copy->readonly = ti->readonly;
2687 ti_copy->handicap = ti->handicap;
2688 ti_copy->skip_levels = ti->skip_levels;
2690 ti_copy->use_emc_tiles = ti->use_emc_tiles;
2692 ti_copy->color = ti->color;
2693 ti_copy->class_desc = getStringCopy(ti->class_desc);
2694 ti_copy->handicap_level = ti->handicap_level;
2696 ti_copy->infotext = getStringCopy(ti->infotext);
2701 void freeTreeInfo(TreeInfo *ti)
2706 checked_free(ti->subdir);
2707 checked_free(ti->fullpath);
2708 checked_free(ti->basepath);
2709 checked_free(ti->identifier);
2711 checked_free(ti->name);
2712 checked_free(ti->name_sorting);
2713 checked_free(ti->author);
2714 checked_free(ti->year);
2716 checked_free(ti->program_title);
2717 checked_free(ti->program_copyright);
2718 checked_free(ti->program_company);
2720 checked_free(ti->class_desc);
2722 checked_free(ti->infotext);
2724 if (ti->type == TREE_TYPE_LEVEL_DIR)
2726 checked_free(ti->imported_from);
2727 checked_free(ti->imported_by);
2728 checked_free(ti->tested_by);
2730 checked_free(ti->graphics_set_ecs);
2731 checked_free(ti->graphics_set_aga);
2732 checked_free(ti->graphics_set);
2733 checked_free(ti->sounds_set_default);
2734 checked_free(ti->sounds_set_lowpass);
2735 checked_free(ti->sounds_set);
2736 checked_free(ti->music_set);
2738 checked_free(ti->graphics_path);
2739 checked_free(ti->sounds_path);
2740 checked_free(ti->music_path);
2742 checked_free(ti->level_filename);
2743 checked_free(ti->level_filetype);
2745 checked_free(ti->special_flags);
2748 // recursively free child node
2750 freeTreeInfo(ti->node_group);
2752 // recursively free next node
2754 freeTreeInfo(ti->next);
2759 void setSetupInfo(struct TokenInfo *token_info,
2760 int token_nr, char *token_value)
2762 int token_type = token_info[token_nr].type;
2763 void *setup_value = token_info[token_nr].value;
2765 if (token_value == NULL)
2768 // set setup field to corresponding token value
2773 *(boolean *)setup_value = get_boolean_from_string(token_value);
2777 *(int *)setup_value = get_switch3_from_string(token_value);
2781 *(Key *)setup_value = getKeyFromKeyName(token_value);
2785 *(Key *)setup_value = getKeyFromX11KeyName(token_value);
2789 *(int *)setup_value = get_integer_from_string(token_value);
2793 checked_free(*(char **)setup_value);
2794 *(char **)setup_value = getStringCopy(token_value);
2798 *(int *)setup_value = get_player_nr_from_string(token_value);
2806 static int compareTreeInfoEntries(const void *object1, const void *object2)
2808 const TreeInfo *entry1 = *((TreeInfo **)object1);
2809 const TreeInfo *entry2 = *((TreeInfo **)object2);
2810 int class_sorting1 = 0, class_sorting2 = 0;
2813 if (entry1->type == TREE_TYPE_LEVEL_DIR)
2815 class_sorting1 = LEVELSORTING(entry1);
2816 class_sorting2 = LEVELSORTING(entry2);
2818 else if (entry1->type == TREE_TYPE_GRAPHICS_DIR ||
2819 entry1->type == TREE_TYPE_SOUNDS_DIR ||
2820 entry1->type == TREE_TYPE_MUSIC_DIR)
2822 class_sorting1 = ARTWORKSORTING(entry1);
2823 class_sorting2 = ARTWORKSORTING(entry2);
2826 if (entry1->parent_link || entry2->parent_link)
2827 compare_result = (entry1->parent_link ? -1 : +1);
2828 else if (entry1->sort_priority == entry2->sort_priority)
2830 char *name1 = getStringToLower(entry1->name_sorting);
2831 char *name2 = getStringToLower(entry2->name_sorting);
2833 compare_result = strcmp(name1, name2);
2838 else if (class_sorting1 == class_sorting2)
2839 compare_result = entry1->sort_priority - entry2->sort_priority;
2841 compare_result = class_sorting1 - class_sorting2;
2843 return compare_result;
2846 static TreeInfo *createParentTreeInfoNode(TreeInfo *node_parent)
2850 if (node_parent == NULL)
2853 ti_new = newTreeInfo();
2854 setTreeInfoToDefaults(ti_new, node_parent->type);
2856 ti_new->node_parent = node_parent;
2857 ti_new->parent_link = TRUE;
2859 setString(&ti_new->identifier, node_parent->identifier);
2860 setString(&ti_new->name, ".. (parent directory)");
2861 setString(&ti_new->name_sorting, ti_new->name);
2863 setString(&ti_new->subdir, STRING_PARENT_DIRECTORY);
2864 setString(&ti_new->fullpath, node_parent->fullpath);
2866 ti_new->sort_priority = node_parent->sort_priority;
2867 ti_new->latest_engine = node_parent->latest_engine;
2869 setString(&ti_new->class_desc, getLevelClassDescription(ti_new));
2871 pushTreeInfo(&node_parent->node_group, ti_new);
2876 static TreeInfo *createTopTreeInfoNode(TreeInfo *node_first)
2878 TreeInfo *ti_new, *ti_new2;
2880 if (node_first == NULL)
2883 ti_new = newTreeInfo();
2884 setTreeInfoToDefaults(ti_new, TREE_TYPE_LEVEL_DIR);
2886 ti_new->node_parent = NULL;
2887 ti_new->parent_link = FALSE;
2889 setString(&ti_new->identifier, node_first->identifier);
2890 setString(&ti_new->name, "level sets");
2891 setString(&ti_new->name_sorting, ti_new->name);
2893 setString(&ti_new->subdir, STRING_TOP_DIRECTORY);
2894 setString(&ti_new->fullpath, ".");
2896 ti_new->sort_priority = node_first->sort_priority;;
2897 ti_new->latest_engine = node_first->latest_engine;
2899 setString(&ti_new->class_desc, "level sets");
2901 ti_new->node_group = node_first;
2902 ti_new->level_group = TRUE;
2904 ti_new2 = createParentTreeInfoNode(ti_new);
2906 setString(&ti_new2->name, ".. (main menu)");
2907 setString(&ti_new2->name_sorting, ti_new2->name);
2913 // ----------------------------------------------------------------------------
2914 // functions for handling level and custom artwork info cache
2915 // ----------------------------------------------------------------------------
2917 static void LoadArtworkInfoCache(void)
2919 InitCacheDirectory();
2921 if (artworkinfo_cache_old == NULL)
2923 char *filename = getPath2(getCacheDir(), ARTWORKINFO_CACHE_FILE);
2925 // try to load artwork info hash from already existing cache file
2926 artworkinfo_cache_old = loadSetupFileHash(filename);
2928 // if no artwork info cache file was found, start with empty hash
2929 if (artworkinfo_cache_old == NULL)
2930 artworkinfo_cache_old = newSetupFileHash();
2935 if (artworkinfo_cache_new == NULL)
2936 artworkinfo_cache_new = newSetupFileHash();
2939 static void SaveArtworkInfoCache(void)
2941 char *filename = getPath2(getCacheDir(), ARTWORKINFO_CACHE_FILE);
2943 InitCacheDirectory();
2945 saveSetupFileHash(artworkinfo_cache_new, filename);
2950 static char *getCacheTokenPrefix(char *prefix1, char *prefix2)
2952 static char *prefix = NULL;
2954 checked_free(prefix);
2956 prefix = getStringCat2WithSeparator(prefix1, prefix2, ".");
2961 // (identical to above function, but separate string buffer needed -- nasty)
2962 static char *getCacheToken(char *prefix, char *suffix)
2964 static char *token = NULL;
2966 checked_free(token);
2968 token = getStringCat2WithSeparator(prefix, suffix, ".");
2973 static char *getFileTimestampString(char *filename)
2975 return getStringCopy(i_to_a(getFileTimestampEpochSeconds(filename)));
2978 static boolean modifiedFileTimestamp(char *filename, char *timestamp_string)
2980 struct stat file_status;
2982 if (timestamp_string == NULL)
2985 if (stat(filename, &file_status) != 0) // cannot stat file
2988 return (file_status.st_mtime != atoi(timestamp_string));
2991 static TreeInfo *getArtworkInfoCacheEntry(LevelDirTree *level_node, int type)
2993 char *identifier = level_node->subdir;
2994 char *type_string = ARTWORK_DIRECTORY(type);
2995 char *token_prefix = getCacheTokenPrefix(type_string, identifier);
2996 char *token_main = getCacheToken(token_prefix, "CACHED");
2997 char *cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
2998 boolean cached = (cache_entry != NULL && strEqual(cache_entry, "true"));
2999 TreeInfo *artwork_info = NULL;
3001 if (!use_artworkinfo_cache)
3004 if (optional_tokens_hash == NULL)
3008 // create hash from list of optional tokens (for quick access)
3009 optional_tokens_hash = newSetupFileHash();
3010 for (i = 0; optional_tokens[i] != NULL; i++)
3011 setHashEntry(optional_tokens_hash, optional_tokens[i], "");
3018 artwork_info = newTreeInfo();
3019 setTreeInfoToDefaults(artwork_info, type);
3021 // set all structure fields according to the token/value pairs
3022 ldi = *artwork_info;
3023 for (i = 0; artworkinfo_tokens[i].type != -1; i++)
3025 char *token_suffix = artworkinfo_tokens[i].text;
3026 char *token = getCacheToken(token_prefix, token_suffix);
3027 char *value = getHashEntry(artworkinfo_cache_old, token);
3029 (getHashEntry(optional_tokens_hash, token_suffix) != NULL);
3031 setSetupInfo(artworkinfo_tokens, i, value);
3033 // check if cache entry for this item is mandatory, but missing
3034 if (value == NULL && !optional)
3036 Warn("missing cache entry '%s'", token);
3042 *artwork_info = ldi;
3047 char *filename_levelinfo = getPath2(getLevelDirFromTreeInfo(level_node),
3048 LEVELINFO_FILENAME);
3049 char *filename_artworkinfo = getPath2(getSetupArtworkDir(artwork_info),
3050 ARTWORKINFO_FILENAME(type));
3052 // check if corresponding "levelinfo.conf" file has changed
3053 token_main = getCacheToken(token_prefix, "TIMESTAMP_LEVELINFO");
3054 cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
3056 if (modifiedFileTimestamp(filename_levelinfo, cache_entry))
3059 // check if corresponding "<artworkinfo>.conf" file has changed
3060 token_main = getCacheToken(token_prefix, "TIMESTAMP_ARTWORKINFO");
3061 cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
3063 if (modifiedFileTimestamp(filename_artworkinfo, cache_entry))
3066 checked_free(filename_levelinfo);
3067 checked_free(filename_artworkinfo);
3070 if (!cached && artwork_info != NULL)
3072 freeTreeInfo(artwork_info);
3077 return artwork_info;
3080 static void setArtworkInfoCacheEntry(TreeInfo *artwork_info,
3081 LevelDirTree *level_node, int type)
3083 char *identifier = level_node->subdir;
3084 char *type_string = ARTWORK_DIRECTORY(type);
3085 char *token_prefix = getCacheTokenPrefix(type_string, identifier);
3086 char *token_main = getCacheToken(token_prefix, "CACHED");
3087 boolean set_cache_timestamps = TRUE;
3090 setHashEntry(artworkinfo_cache_new, token_main, "true");
3092 if (set_cache_timestamps)
3094 char *filename_levelinfo = getPath2(getLevelDirFromTreeInfo(level_node),
3095 LEVELINFO_FILENAME);
3096 char *filename_artworkinfo = getPath2(getSetupArtworkDir(artwork_info),
3097 ARTWORKINFO_FILENAME(type));
3098 char *timestamp_levelinfo = getFileTimestampString(filename_levelinfo);
3099 char *timestamp_artworkinfo = getFileTimestampString(filename_artworkinfo);
3101 token_main = getCacheToken(token_prefix, "TIMESTAMP_LEVELINFO");
3102 setHashEntry(artworkinfo_cache_new, token_main, timestamp_levelinfo);
3104 token_main = getCacheToken(token_prefix, "TIMESTAMP_ARTWORKINFO");
3105 setHashEntry(artworkinfo_cache_new, token_main, timestamp_artworkinfo);
3107 checked_free(filename_levelinfo);
3108 checked_free(filename_artworkinfo);
3109 checked_free(timestamp_levelinfo);
3110 checked_free(timestamp_artworkinfo);
3113 ldi = *artwork_info;
3114 for (i = 0; artworkinfo_tokens[i].type != -1; i++)
3116 char *token = getCacheToken(token_prefix, artworkinfo_tokens[i].text);
3117 char *value = getSetupValue(artworkinfo_tokens[i].type,
3118 artworkinfo_tokens[i].value);
3120 setHashEntry(artworkinfo_cache_new, token, value);
3125 // ----------------------------------------------------------------------------
3126 // functions for loading level info and custom artwork info
3127 // ----------------------------------------------------------------------------
3129 int GetZipFileTreeType(char *zip_filename)
3131 static char *top_dir_path = NULL;
3132 static char *top_dir_conf_filename[NUM_BASE_TREE_TYPES] = { NULL };
3133 static char *conf_basename[NUM_BASE_TREE_TYPES] =
3135 GRAPHICSINFO_FILENAME,
3136 SOUNDSINFO_FILENAME,
3142 checked_free(top_dir_path);
3143 top_dir_path = NULL;
3145 for (j = 0; j < NUM_BASE_TREE_TYPES; j++)
3147 checked_free(top_dir_conf_filename[j]);
3148 top_dir_conf_filename[j] = NULL;
3151 char **zip_entries = zip_list(zip_filename);
3153 // check if zip file successfully opened
3154 if (zip_entries == NULL || zip_entries[0] == NULL)
3155 return TREE_TYPE_UNDEFINED;
3157 // first zip file entry is expected to be top level directory
3158 char *top_dir = zip_entries[0];
3160 // check if valid top level directory found in zip file
3161 if (!strSuffix(top_dir, "/"))
3162 return TREE_TYPE_UNDEFINED;
3164 // get filenames of valid configuration files in top level directory
3165 for (j = 0; j < NUM_BASE_TREE_TYPES; j++)
3166 top_dir_conf_filename[j] = getStringCat2(top_dir, conf_basename[j]);
3168 int tree_type = TREE_TYPE_UNDEFINED;
3171 while (zip_entries[e] != NULL)
3173 // check if every zip file entry is below top level directory
3174 if (!strPrefix(zip_entries[e], top_dir))
3175 return TREE_TYPE_UNDEFINED;
3177 // check if this zip file entry is a valid configuration filename
3178 for (j = 0; j < NUM_BASE_TREE_TYPES; j++)
3180 if (strEqual(zip_entries[e], top_dir_conf_filename[j]))
3182 // only exactly one valid configuration file allowed
3183 if (tree_type != TREE_TYPE_UNDEFINED)
3184 return TREE_TYPE_UNDEFINED;
3196 static boolean CheckZipFileForDirectory(char *zip_filename, char *directory,
3199 static char *top_dir_path = NULL;
3200 static char *top_dir_conf_filename = NULL;
3202 checked_free(top_dir_path);
3203 checked_free(top_dir_conf_filename);
3205 top_dir_path = NULL;
3206 top_dir_conf_filename = NULL;
3208 char *conf_basename = (tree_type == TREE_TYPE_LEVEL_DIR ? LEVELINFO_FILENAME :
3209 ARTWORKINFO_FILENAME(tree_type));
3211 // check if valid configuration filename determined
3212 if (conf_basename == NULL || strEqual(conf_basename, ""))
3215 char **zip_entries = zip_list(zip_filename);
3217 // check if zip file successfully opened
3218 if (zip_entries == NULL || zip_entries[0] == NULL)
3221 // first zip file entry is expected to be top level directory
3222 char *top_dir = zip_entries[0];
3224 // check if valid top level directory found in zip file
3225 if (!strSuffix(top_dir, "/"))
3228 // get path of extracted top level directory
3229 top_dir_path = getPath2(directory, top_dir);
3231 // remove trailing directory separator from top level directory path
3232 // (required to be able to check for file and directory in next step)
3233 top_dir_path[strlen(top_dir_path) - 1] = '\0';
3235 // check if zip file's top level directory already exists in target directory
3236 if (fileExists(top_dir_path)) // (checks for file and directory)
3239 // get filename of configuration file in top level directory
3240 top_dir_conf_filename = getStringCat2(top_dir, conf_basename);
3242 boolean found_top_dir_conf_filename = FALSE;
3245 while (zip_entries[i] != NULL)
3247 // check if every zip file entry is below top level directory
3248 if (!strPrefix(zip_entries[i], top_dir))
3251 // check if this zip file entry is the configuration filename
3252 if (strEqual(zip_entries[i], top_dir_conf_filename))
3253 found_top_dir_conf_filename = TRUE;
3258 // check if valid configuration filename was found in zip file
3259 if (!found_top_dir_conf_filename)
3265 char *ExtractZipFileIntoDirectory(char *zip_filename, char *directory,
3268 boolean zip_file_valid = CheckZipFileForDirectory(zip_filename, directory,
3271 if (!zip_file_valid)
3273 Warn("zip file '%s' rejected!", zip_filename);
3278 char **zip_entries = zip_extract(zip_filename, directory);
3280 if (zip_entries == NULL)
3282 Warn("zip file '%s' could not be extracted!", zip_filename);
3287 Info("zip file '%s' successfully extracted!", zip_filename);
3289 // first zip file entry contains top level directory
3290 char *top_dir = zip_entries[0];
3292 // remove trailing directory separator from top level directory
3293 top_dir[strlen(top_dir) - 1] = '\0';
3298 static void ProcessZipFilesInDirectory(char *directory, int tree_type)
3301 DirectoryEntry *dir_entry;
3303 if ((dir = openDirectory(directory)) == NULL)
3305 // display error if directory is main "options.graphics_directory" etc.
3306 if (tree_type == TREE_TYPE_LEVEL_DIR ||
3307 directory == OPTIONS_ARTWORK_DIRECTORY(tree_type))
3308 Warn("cannot read directory '%s'", directory);
3313 while ((dir_entry = readDirectory(dir)) != NULL) // loop all entries
3315 // skip non-zip files (and also directories with zip extension)
3316 if (!strSuffixLower(dir_entry->basename, ".zip") || dir_entry->is_directory)
3319 char *zip_filename = getPath2(directory, dir_entry->basename);
3320 char *zip_filename_extracted = getStringCat2(zip_filename, ".extracted");
3321 char *zip_filename_rejected = getStringCat2(zip_filename, ".rejected");
3323 // check if zip file hasn't already been extracted or rejected
3324 if (!fileExists(zip_filename_extracted) &&
3325 !fileExists(zip_filename_rejected))
3327 char *top_dir = ExtractZipFileIntoDirectory(zip_filename, directory,
3329 char *marker_filename = (top_dir != NULL ? zip_filename_extracted :
3330 zip_filename_rejected);
3333 // create empty file to mark zip file as extracted or rejected
3334 if ((marker_file = fopen(marker_filename, MODE_WRITE)))
3335 fclose(marker_file);
3338 free(zip_filename_extracted);
3339 free(zip_filename_rejected);
3343 closeDirectory(dir);
3346 // forward declaration for recursive call by "LoadLevelInfoFromLevelDir()"
3347 static void LoadLevelInfoFromLevelDir(TreeInfo **, TreeInfo *, char *);
3349 static boolean LoadLevelInfoFromLevelConf(TreeInfo **node_first,
3350 TreeInfo *node_parent,
3351 char *level_directory,
3352 char *directory_name)
3354 char *directory_path = getPath2(level_directory, directory_name);
3355 char *filename = getPath2(directory_path, LEVELINFO_FILENAME);
3356 SetupFileHash *setup_file_hash;
3357 LevelDirTree *leveldir_new = NULL;
3360 // unless debugging, silently ignore directories without "levelinfo.conf"
3361 if (!options.debug && !fileExists(filename))
3363 free(directory_path);
3369 setup_file_hash = loadSetupFileHash(filename);
3371 if (setup_file_hash == NULL)
3373 #if DEBUG_NO_CONFIG_FILE
3374 Debug("setup", "ignoring level directory '%s'", directory_path);
3377 free(directory_path);
3383 leveldir_new = newTreeInfo();
3386 setTreeInfoToDefaultsFromParent(leveldir_new, node_parent);
3388 setTreeInfoToDefaults(leveldir_new, TREE_TYPE_LEVEL_DIR);
3390 leveldir_new->subdir = getStringCopy(directory_name);
3392 // set all structure fields according to the token/value pairs
3393 ldi = *leveldir_new;
3394 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
3395 setSetupInfo(levelinfo_tokens, i,
3396 getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
3397 *leveldir_new = ldi;
3399 if (strEqual(leveldir_new->name, ANONYMOUS_NAME))
3400 setString(&leveldir_new->name, leveldir_new->subdir);
3402 if (leveldir_new->identifier == NULL)
3403 leveldir_new->identifier = getStringCopy(leveldir_new->subdir);
3405 if (leveldir_new->name_sorting == NULL)
3406 leveldir_new->name_sorting = getStringCopy(leveldir_new->name);
3408 if (node_parent == NULL) // top level group
3410 leveldir_new->basepath = getStringCopy(level_directory);
3411 leveldir_new->fullpath = getStringCopy(leveldir_new->subdir);
3413 else // sub level group
3415 leveldir_new->basepath = getStringCopy(node_parent->basepath);
3416 leveldir_new->fullpath = getPath2(node_parent->fullpath, directory_name);
3419 leveldir_new->last_level =
3420 leveldir_new->first_level + leveldir_new->levels - 1;
3422 leveldir_new->in_user_dir =
3423 (!strEqual(leveldir_new->basepath, options.level_directory));
3425 // adjust some settings if user's private level directory was detected
3426 if (leveldir_new->sort_priority == LEVELCLASS_UNDEFINED &&
3427 leveldir_new->in_user_dir &&
3428 (strEqual(leveldir_new->subdir, getLoginName()) ||
3429 strEqual(leveldir_new->name, getLoginName()) ||
3430 strEqual(leveldir_new->author, getRealName())))
3432 leveldir_new->sort_priority = LEVELCLASS_PRIVATE_START;
3433 leveldir_new->readonly = FALSE;
3436 leveldir_new->user_defined =
3437 (leveldir_new->in_user_dir && IS_LEVELCLASS_PRIVATE(leveldir_new));
3439 leveldir_new->color = LEVELCOLOR(leveldir_new);
3441 setString(&leveldir_new->class_desc, getLevelClassDescription(leveldir_new));
3443 leveldir_new->handicap_level = // set handicap to default value
3444 (leveldir_new->user_defined || !leveldir_new->handicap ?
3445 leveldir_new->last_level : leveldir_new->first_level);
3447 DrawInitText(leveldir_new->name, 150, FC_YELLOW);
3449 pushTreeInfo(node_first, leveldir_new);
3451 freeSetupFileHash(setup_file_hash);
3453 if (leveldir_new->level_group)
3455 // create node to link back to current level directory
3456 createParentTreeInfoNode(leveldir_new);
3458 // recursively step into sub-directory and look for more level series
3459 LoadLevelInfoFromLevelDir(&leveldir_new->node_group,
3460 leveldir_new, directory_path);
3463 free(directory_path);
3469 static void LoadLevelInfoFromLevelDir(TreeInfo **node_first,
3470 TreeInfo *node_parent,
3471 char *level_directory)
3473 // ---------- 1st stage: process any level set zip files ----------
3475 ProcessZipFilesInDirectory(level_directory, TREE_TYPE_LEVEL_DIR);
3477 // ---------- 2nd stage: check for level set directories ----------
3480 DirectoryEntry *dir_entry;
3481 boolean valid_entry_found = FALSE;
3483 if ((dir = openDirectory(level_directory)) == NULL)
3485 Warn("cannot read level directory '%s'", level_directory);
3490 while ((dir_entry = readDirectory(dir)) != NULL) // loop all entries
3492 char *directory_name = dir_entry->basename;
3493 char *directory_path = getPath2(level_directory, directory_name);
3495 // skip entries for current and parent directory
3496 if (strEqual(directory_name, ".") ||
3497 strEqual(directory_name, ".."))
3499 free(directory_path);
3504 // find out if directory entry is itself a directory
3505 if (!dir_entry->is_directory) // not a directory
3507 free(directory_path);
3512 free(directory_path);
3514 if (strEqual(directory_name, GRAPHICS_DIRECTORY) ||
3515 strEqual(directory_name, SOUNDS_DIRECTORY) ||
3516 strEqual(directory_name, MUSIC_DIRECTORY))
3519 valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
3524 closeDirectory(dir);
3526 // special case: top level directory may directly contain "levelinfo.conf"
3527 if (node_parent == NULL && !valid_entry_found)
3529 // check if this directory directly contains a file "levelinfo.conf"
3530 valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
3531 level_directory, ".");
3534 if (!valid_entry_found)
3535 Warn("cannot find any valid level series in directory '%s'",
3539 boolean AdjustGraphicsForEMC(void)
3541 boolean settings_changed = FALSE;
3543 settings_changed |= adjustTreeGraphicsForEMC(leveldir_first_all);
3544 settings_changed |= adjustTreeGraphicsForEMC(leveldir_first);
3546 return settings_changed;
3549 boolean AdjustSoundsForEMC(void)
3551 boolean settings_changed = FALSE;
3553 settings_changed |= adjustTreeSoundsForEMC(leveldir_first_all);
3554 settings_changed |= adjustTreeSoundsForEMC(leveldir_first);
3556 return settings_changed;
3559 void LoadLevelInfo(void)
3561 InitUserLevelDirectory(getLoginName());
3563 DrawInitText("Loading level series", 120, FC_GREEN);
3565 LoadLevelInfoFromLevelDir(&leveldir_first, NULL, options.level_directory);
3566 LoadLevelInfoFromLevelDir(&leveldir_first, NULL, getUserLevelDir(NULL));
3568 leveldir_first = createTopTreeInfoNode(leveldir_first);
3570 /* after loading all level set information, clone the level directory tree
3571 and remove all level sets without levels (these may still contain artwork
3572 to be offered in the setup menu as "custom artwork", and are therefore
3573 checked for existing artwork in the function "LoadLevelArtworkInfo()") */
3574 leveldir_first_all = leveldir_first;
3575 cloneTree(&leveldir_first, leveldir_first_all, TRUE);
3577 AdjustGraphicsForEMC();
3578 AdjustSoundsForEMC();
3580 // before sorting, the first entries will be from the user directory
3581 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
3583 if (leveldir_first == NULL)
3584 Fail("cannot find any valid level series in any directory");
3586 sortTreeInfo(&leveldir_first);
3588 #if ENABLE_UNUSED_CODE
3589 dumpTreeInfo(leveldir_first, 0);
3593 static boolean LoadArtworkInfoFromArtworkConf(TreeInfo **node_first,
3594 TreeInfo *node_parent,
3595 char *base_directory,
3596 char *directory_name, int type)
3598 char *directory_path = getPath2(base_directory, directory_name);
3599 char *filename = getPath2(directory_path, ARTWORKINFO_FILENAME(type));
3600 SetupFileHash *setup_file_hash = NULL;
3601 TreeInfo *artwork_new = NULL;
3604 if (fileExists(filename))
3605 setup_file_hash = loadSetupFileHash(filename);
3607 if (setup_file_hash == NULL) // no config file -- look for artwork files
3610 DirectoryEntry *dir_entry;
3611 boolean valid_file_found = FALSE;
3613 if ((dir = openDirectory(directory_path)) != NULL)
3615 while ((dir_entry = readDirectory(dir)) != NULL)
3617 if (FileIsArtworkType(dir_entry->filename, type))
3619 valid_file_found = TRUE;
3625 closeDirectory(dir);
3628 if (!valid_file_found)
3630 #if DEBUG_NO_CONFIG_FILE
3631 if (!strEqual(directory_name, "."))
3632 Debug("setup", "ignoring artwork directory '%s'", directory_path);
3635 free(directory_path);
3642 artwork_new = newTreeInfo();
3645 setTreeInfoToDefaultsFromParent(artwork_new, node_parent);
3647 setTreeInfoToDefaults(artwork_new, type);
3649 artwork_new->subdir = getStringCopy(directory_name);
3651 if (setup_file_hash) // (before defining ".color" and ".class_desc")
3653 // set all structure fields according to the token/value pairs
3655 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
3656 setSetupInfo(levelinfo_tokens, i,
3657 getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
3660 if (strEqual(artwork_new->name, ANONYMOUS_NAME))
3661 setString(&artwork_new->name, artwork_new->subdir);
3663 if (artwork_new->identifier == NULL)
3664 artwork_new->identifier = getStringCopy(artwork_new->subdir);
3666 if (artwork_new->name_sorting == NULL)
3667 artwork_new->name_sorting = getStringCopy(artwork_new->name);
3670 if (node_parent == NULL) // top level group
3672 artwork_new->basepath = getStringCopy(base_directory);
3673 artwork_new->fullpath = getStringCopy(artwork_new->subdir);
3675 else // sub level group
3677 artwork_new->basepath = getStringCopy(node_parent->basepath);
3678 artwork_new->fullpath = getPath2(node_parent->fullpath, directory_name);
3681 artwork_new->in_user_dir =
3682 (!strEqual(artwork_new->basepath, OPTIONS_ARTWORK_DIRECTORY(type)));
3684 // (may use ".sort_priority" from "setup_file_hash" above)
3685 artwork_new->color = ARTWORKCOLOR(artwork_new);
3687 setString(&artwork_new->class_desc, getLevelClassDescription(artwork_new));
3689 if (setup_file_hash == NULL) // (after determining ".user_defined")
3691 if (strEqual(artwork_new->subdir, "."))
3693 if (artwork_new->user_defined)
3695 setString(&artwork_new->identifier, "private");
3696 artwork_new->sort_priority = ARTWORKCLASS_PRIVATE;
3700 setString(&artwork_new->identifier, "classic");
3701 artwork_new->sort_priority = ARTWORKCLASS_CLASSICS;
3704 // set to new values after changing ".sort_priority"
3705 artwork_new->color = ARTWORKCOLOR(artwork_new);
3707 setString(&artwork_new->class_desc,
3708 getLevelClassDescription(artwork_new));
3712 setString(&artwork_new->identifier, artwork_new->subdir);
3715 setString(&artwork_new->name, artwork_new->identifier);
3716 setString(&artwork_new->name_sorting, artwork_new->name);
3719 pushTreeInfo(node_first, artwork_new);
3721 freeSetupFileHash(setup_file_hash);
3723 free(directory_path);
3729 static void LoadArtworkInfoFromArtworkDir(TreeInfo **node_first,
3730 TreeInfo *node_parent,
3731 char *base_directory, int type)
3733 // ---------- 1st stage: process any artwork set zip files ----------
3735 ProcessZipFilesInDirectory(base_directory, type);
3737 // ---------- 2nd stage: check for artwork set directories ----------
3740 DirectoryEntry *dir_entry;
3741 boolean valid_entry_found = FALSE;
3743 if ((dir = openDirectory(base_directory)) == NULL)
3745 // display error if directory is main "options.graphics_directory" etc.
3746 if (base_directory == OPTIONS_ARTWORK_DIRECTORY(type))
3747 Warn("cannot read directory '%s'", base_directory);
3752 while ((dir_entry = readDirectory(dir)) != NULL) // loop all entries
3754 char *directory_name = dir_entry->basename;
3755 char *directory_path = getPath2(base_directory, directory_name);
3757 // skip directory entries for current and parent directory
3758 if (strEqual(directory_name, ".") ||
3759 strEqual(directory_name, ".."))
3761 free(directory_path);
3766 // skip directory entries which are not a directory
3767 if (!dir_entry->is_directory) // not a directory
3769 free(directory_path);
3774 free(directory_path);
3776 // check if this directory contains artwork with or without config file
3777 valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
3779 directory_name, type);
3782 closeDirectory(dir);
3784 // check if this directory directly contains artwork itself
3785 valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
3786 base_directory, ".",
3788 if (!valid_entry_found)
3789 Warn("cannot find any valid artwork in directory '%s'", base_directory);
3792 static TreeInfo *getDummyArtworkInfo(int type)
3794 // this is only needed when there is completely no artwork available
3795 TreeInfo *artwork_new = newTreeInfo();
3797 setTreeInfoToDefaults(artwork_new, type);
3799 setString(&artwork_new->subdir, UNDEFINED_FILENAME);
3800 setString(&artwork_new->fullpath, UNDEFINED_FILENAME);
3801 setString(&artwork_new->basepath, UNDEFINED_FILENAME);
3803 setString(&artwork_new->identifier, UNDEFINED_FILENAME);
3804 setString(&artwork_new->name, UNDEFINED_FILENAME);
3805 setString(&artwork_new->name_sorting, UNDEFINED_FILENAME);
3810 void SetCurrentArtwork(int type)
3812 ArtworkDirTree **current_ptr = ARTWORK_CURRENT_PTR(artwork, type);
3813 ArtworkDirTree *first_node = ARTWORK_FIRST_NODE(artwork, type);
3814 char *setup_set = SETUP_ARTWORK_SET(setup, type);
3815 char *default_subdir = ARTWORK_DEFAULT_SUBDIR(type);
3817 // set current artwork to artwork configured in setup menu
3818 *current_ptr = getTreeInfoFromIdentifier(first_node, setup_set);
3820 // if not found, set current artwork to default artwork
3821 if (*current_ptr == NULL)
3822 *current_ptr = getTreeInfoFromIdentifier(first_node, default_subdir);
3824 // if not found, set current artwork to first artwork in tree
3825 if (*current_ptr == NULL)
3826 *current_ptr = getFirstValidTreeInfoEntry(first_node);
3829 void ChangeCurrentArtworkIfNeeded(int type)
3831 char *current_identifier = ARTWORK_CURRENT_IDENTIFIER(artwork, type);
3832 char *setup_set = SETUP_ARTWORK_SET(setup, type);
3834 if (!strEqual(current_identifier, setup_set))
3835 SetCurrentArtwork(type);
3838 void LoadArtworkInfo(void)
3840 LoadArtworkInfoCache();
3842 DrawInitText("Looking for custom artwork", 120, FC_GREEN);
3844 LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
3845 options.graphics_directory,
3846 TREE_TYPE_GRAPHICS_DIR);
3847 LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
3848 getUserGraphicsDir(),
3849 TREE_TYPE_GRAPHICS_DIR);
3851 LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
3852 options.sounds_directory,
3853 TREE_TYPE_SOUNDS_DIR);
3854 LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
3856 TREE_TYPE_SOUNDS_DIR);
3858 LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
3859 options.music_directory,
3860 TREE_TYPE_MUSIC_DIR);
3861 LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
3863 TREE_TYPE_MUSIC_DIR);
3865 if (artwork.gfx_first == NULL)
3866 artwork.gfx_first = getDummyArtworkInfo(TREE_TYPE_GRAPHICS_DIR);
3867 if (artwork.snd_first == NULL)
3868 artwork.snd_first = getDummyArtworkInfo(TREE_TYPE_SOUNDS_DIR);
3869 if (artwork.mus_first == NULL)
3870 artwork.mus_first = getDummyArtworkInfo(TREE_TYPE_MUSIC_DIR);
3872 // before sorting, the first entries will be from the user directory
3873 SetCurrentArtwork(ARTWORK_TYPE_GRAPHICS);
3874 SetCurrentArtwork(ARTWORK_TYPE_SOUNDS);
3875 SetCurrentArtwork(ARTWORK_TYPE_MUSIC);
3877 artwork.gfx_current_identifier = artwork.gfx_current->identifier;
3878 artwork.snd_current_identifier = artwork.snd_current->identifier;
3879 artwork.mus_current_identifier = artwork.mus_current->identifier;
3881 #if ENABLE_UNUSED_CODE
3882 Debug("setup:LoadArtworkInfo", "graphics set == %s",
3883 artwork.gfx_current_identifier);
3884 Debug("setup:LoadArtworkInfo", "sounds set == %s",
3885 artwork.snd_current_identifier);
3886 Debug("setup:LoadArtworkInfo", "music set == %s",
3887 artwork.mus_current_identifier);
3890 sortTreeInfo(&artwork.gfx_first);
3891 sortTreeInfo(&artwork.snd_first);
3892 sortTreeInfo(&artwork.mus_first);
3894 #if ENABLE_UNUSED_CODE
3895 dumpTreeInfo(artwork.gfx_first, 0);
3896 dumpTreeInfo(artwork.snd_first, 0);
3897 dumpTreeInfo(artwork.mus_first, 0);
3901 static void LoadArtworkInfoFromLevelInfo(ArtworkDirTree **artwork_node,
3902 LevelDirTree *level_node)
3904 int type = (*artwork_node)->type;
3906 // recursively check all level directories for artwork sub-directories
3910 // check all tree entries for artwork, but skip parent link entries
3911 if (!level_node->parent_link)
3913 TreeInfo *artwork_new = getArtworkInfoCacheEntry(level_node, type);
3914 boolean cached = (artwork_new != NULL);
3918 pushTreeInfo(artwork_node, artwork_new);
3922 TreeInfo *topnode_last = *artwork_node;
3923 char *path = getPath2(getLevelDirFromTreeInfo(level_node),
3924 ARTWORK_DIRECTORY(type));
3926 LoadArtworkInfoFromArtworkDir(artwork_node, NULL, path, type);
3928 if (topnode_last != *artwork_node) // check for newly added node
3930 artwork_new = *artwork_node;
3932 setString(&artwork_new->identifier, level_node->subdir);
3933 setString(&artwork_new->name, level_node->name);
3934 setString(&artwork_new->name_sorting, level_node->name_sorting);
3936 artwork_new->sort_priority = level_node->sort_priority;
3937 artwork_new->color = LEVELCOLOR(artwork_new);
3943 // insert artwork info (from old cache or filesystem) into new cache
3944 if (artwork_new != NULL)
3945 setArtworkInfoCacheEntry(artwork_new, level_node, type);
3948 DrawInitText(level_node->name, 150, FC_YELLOW);
3950 if (level_node->node_group != NULL)
3951 LoadArtworkInfoFromLevelInfo(artwork_node, level_node->node_group);
3953 level_node = level_node->next;
3957 void LoadLevelArtworkInfo(void)
3959 print_timestamp_init("LoadLevelArtworkInfo");
3961 DrawInitText("Looking for custom level artwork", 120, FC_GREEN);
3963 print_timestamp_time("DrawTimeText");
3965 LoadArtworkInfoFromLevelInfo(&artwork.gfx_first, leveldir_first_all);
3966 print_timestamp_time("LoadArtworkInfoFromLevelInfo (gfx)");
3967 LoadArtworkInfoFromLevelInfo(&artwork.snd_first, leveldir_first_all);
3968 print_timestamp_time("LoadArtworkInfoFromLevelInfo (snd)");
3969 LoadArtworkInfoFromLevelInfo(&artwork.mus_first, leveldir_first_all);
3970 print_timestamp_time("LoadArtworkInfoFromLevelInfo (mus)");
3972 SaveArtworkInfoCache();
3974 print_timestamp_time("SaveArtworkInfoCache");
3976 // needed for reloading level artwork not known at ealier stage
3977 ChangeCurrentArtworkIfNeeded(ARTWORK_TYPE_GRAPHICS);
3978 ChangeCurrentArtworkIfNeeded(ARTWORK_TYPE_SOUNDS);
3979 ChangeCurrentArtworkIfNeeded(ARTWORK_TYPE_MUSIC);
3981 print_timestamp_time("getTreeInfoFromIdentifier");
3983 sortTreeInfo(&artwork.gfx_first);
3984 sortTreeInfo(&artwork.snd_first);
3985 sortTreeInfo(&artwork.mus_first);
3987 print_timestamp_time("sortTreeInfo");
3989 #if ENABLE_UNUSED_CODE
3990 dumpTreeInfo(artwork.gfx_first, 0);
3991 dumpTreeInfo(artwork.snd_first, 0);
3992 dumpTreeInfo(artwork.mus_first, 0);
3995 print_timestamp_done("LoadLevelArtworkInfo");
3998 static boolean AddTreeSetToTreeInfoExt(TreeInfo *tree_node_old, char *tree_dir,
3999 char *tree_subdir_new, int type)
4001 if (tree_node_old == NULL)
4003 if (type == TREE_TYPE_LEVEL_DIR)
4005 // get level info tree node of personal user level set
4006 tree_node_old = getTreeInfoFromIdentifier(leveldir_first, getLoginName());
4008 // this may happen if "setup.internal.create_user_levelset" is FALSE
4009 // or if file "levelinfo.conf" is missing in personal user level set
4010 if (tree_node_old == NULL)
4011 tree_node_old = leveldir_first->node_group;
4015 // get artwork info tree node of first artwork set
4016 tree_node_old = ARTWORK_FIRST_NODE(artwork, type);
4020 if (tree_dir == NULL)
4021 tree_dir = TREE_USERDIR(type);
4023 if (tree_node_old == NULL ||
4025 tree_subdir_new == NULL) // should not happen
4028 int draw_deactivation_mask = GetDrawDeactivationMask();
4030 // override draw deactivation mask (temporarily disable drawing)
4031 SetDrawDeactivationMask(REDRAW_ALL);
4033 if (type == TREE_TYPE_LEVEL_DIR)
4035 // load new level set config and add it next to first user level set
4036 LoadLevelInfoFromLevelConf(&tree_node_old->next,
4037 tree_node_old->node_parent,
4038 tree_dir, tree_subdir_new);
4042 // load new artwork set config and add it next to first artwork set
4043 LoadArtworkInfoFromArtworkConf(&tree_node_old->next,
4044 tree_node_old->node_parent,
4045 tree_dir, tree_subdir_new, type);
4048 // set draw deactivation mask to previous value
4049 SetDrawDeactivationMask(draw_deactivation_mask);
4051 // get first node of level or artwork info tree
4052 TreeInfo **tree_node_first = TREE_FIRST_NODE_PTR(type);
4054 // get tree info node of newly added level or artwork set
4055 TreeInfo *tree_node_new = getTreeInfoFromIdentifier(*tree_node_first,
4058 if (tree_node_new == NULL) // should not happen
4061 // correct top link and parent node link of newly created tree node
4062 tree_node_new->node_top = tree_node_old->node_top;
4063 tree_node_new->node_parent = tree_node_old->node_parent;
4065 // sort tree info to adjust position of newly added tree set
4066 sortTreeInfo(tree_node_first);
4071 void AddTreeSetToTreeInfo(TreeInfo *tree_node, char *tree_dir,
4072 char *tree_subdir_new, int type)
4074 if (!AddTreeSetToTreeInfoExt(tree_node, tree_dir, tree_subdir_new, type))
4075 Fail("internal tree info structure corrupted -- aborting");
4078 void AddUserLevelSetToLevelInfo(char *level_subdir_new)
4080 AddTreeSetToTreeInfo(NULL, NULL, level_subdir_new, TREE_TYPE_LEVEL_DIR);
4083 char *getArtworkIdentifierForUserLevelSet(int type)
4085 char *classic_artwork_set = getClassicArtworkSet(type);
4087 // check for custom artwork configured in "levelinfo.conf"
4088 char *leveldir_artwork_set =
4089 *LEVELDIR_ARTWORK_SET_PTR(leveldir_current, type);
4090 boolean has_leveldir_artwork_set =
4091 (leveldir_artwork_set != NULL && !strEqual(leveldir_artwork_set,
4092 classic_artwork_set));
4094 // check for custom artwork in sub-directory "graphics" etc.
4095 TreeInfo *artwork_first_node = ARTWORK_FIRST_NODE(artwork, type);
4096 char *leveldir_identifier = leveldir_current->identifier;
4097 boolean has_artwork_subdir =
4098 (getTreeInfoFromIdentifier(artwork_first_node,
4099 leveldir_identifier) != NULL);
4101 return (has_leveldir_artwork_set ? leveldir_artwork_set :
4102 has_artwork_subdir ? leveldir_identifier :
4103 classic_artwork_set);
4106 TreeInfo *getArtworkTreeInfoForUserLevelSet(int type)
4108 char *artwork_set = getArtworkIdentifierForUserLevelSet(type);
4109 TreeInfo *artwork_first_node = ARTWORK_FIRST_NODE(artwork, type);
4110 TreeInfo *ti = getTreeInfoFromIdentifier(artwork_first_node, artwork_set);
4114 ti = getTreeInfoFromIdentifier(artwork_first_node,
4115 ARTWORK_DEFAULT_SUBDIR(type));
4117 Fail("cannot find default graphics -- should not happen");
4123 boolean checkIfCustomArtworkExistsForCurrentLevelSet(void)
4125 char *graphics_set =
4126 getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_GRAPHICS);
4128 getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_SOUNDS);
4130 getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_MUSIC);
4132 return (!strEqual(graphics_set, GFX_CLASSIC_SUBDIR) ||
4133 !strEqual(sounds_set, SND_CLASSIC_SUBDIR) ||
4134 !strEqual(music_set, MUS_CLASSIC_SUBDIR));
4137 boolean UpdateUserLevelSet(char *level_subdir, char *level_name,
4138 char *level_author, int num_levels)
4140 char *filename = getPath2(getUserLevelDir(level_subdir), LEVELINFO_FILENAME);
4141 char *filename_tmp = getStringCat2(filename, ".tmp");
4143 FILE *file_tmp = NULL;
4144 char line[MAX_LINE_LEN];
4145 boolean success = FALSE;
4146 LevelDirTree *leveldir = getTreeInfoFromIdentifier(leveldir_first,
4148 // update values in level directory tree
4150 if (level_name != NULL)
4151 setString(&leveldir->name, level_name);
4153 if (level_author != NULL)
4154 setString(&leveldir->author, level_author);
4156 if (num_levels != -1)
4157 leveldir->levels = num_levels;
4159 // update values that depend on other values
4161 setString(&leveldir->name_sorting, leveldir->name);
4163 leveldir->last_level = leveldir->first_level + leveldir->levels - 1;
4165 // sort order of level sets may have changed
4166 sortTreeInfo(&leveldir_first);
4168 if ((file = fopen(filename, MODE_READ)) &&
4169 (file_tmp = fopen(filename_tmp, MODE_WRITE)))
4171 while (fgets(line, MAX_LINE_LEN, file))
4173 if (strPrefix(line, "name:") && level_name != NULL)
4174 fprintf(file_tmp, "%-32s%s\n", "name:", level_name);
4175 else if (strPrefix(line, "author:") && level_author != NULL)
4176 fprintf(file_tmp, "%-32s%s\n", "author:", level_author);
4177 else if (strPrefix(line, "levels:") && num_levels != -1)
4178 fprintf(file_tmp, "%-32s%d\n", "levels:", num_levels);
4180 fputs(line, file_tmp);
4193 success = (rename(filename_tmp, filename) == 0);
4201 boolean CreateUserLevelSet(char *level_subdir, char *level_name,
4202 char *level_author, int num_levels,
4203 boolean use_artwork_set)
4205 LevelDirTree *level_info;
4210 // create user level sub-directory, if needed
4211 createDirectory(getUserLevelDir(level_subdir), "user level", PERMS_PRIVATE);
4213 filename = getPath2(getUserLevelDir(level_subdir), LEVELINFO_FILENAME);
4215 if (!(file = fopen(filename, MODE_WRITE)))
4217 Warn("cannot write level info file '%s'", filename);
4224 level_info = newTreeInfo();
4226 // always start with reliable default values
4227 setTreeInfoToDefaults(level_info, TREE_TYPE_LEVEL_DIR);
4229 setString(&level_info->name, level_name);
4230 setString(&level_info->author, level_author);
4231 level_info->levels = num_levels;
4232 level_info->first_level = 1;
4233 level_info->sort_priority = LEVELCLASS_PRIVATE_START;
4234 level_info->readonly = FALSE;
4236 if (use_artwork_set)
4238 level_info->graphics_set =
4239 getStringCopy(getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_GRAPHICS));
4240 level_info->sounds_set =
4241 getStringCopy(getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_SOUNDS));
4242 level_info->music_set =
4243 getStringCopy(getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_MUSIC));
4246 token_value_position = TOKEN_VALUE_POSITION_SHORT;
4248 fprintFileHeader(file, LEVELINFO_FILENAME);
4251 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
4253 if (i == LEVELINFO_TOKEN_NAME ||
4254 i == LEVELINFO_TOKEN_AUTHOR ||
4255 i == LEVELINFO_TOKEN_LEVELS ||
4256 i == LEVELINFO_TOKEN_FIRST_LEVEL ||
4257 i == LEVELINFO_TOKEN_SORT_PRIORITY ||
4258 i == LEVELINFO_TOKEN_READONLY ||
4259 (use_artwork_set && (i == LEVELINFO_TOKEN_GRAPHICS_SET ||
4260 i == LEVELINFO_TOKEN_SOUNDS_SET ||
4261 i == LEVELINFO_TOKEN_MUSIC_SET)))
4262 fprintf(file, "%s\n", getSetupLine(levelinfo_tokens, "", i));
4264 // just to make things nicer :)
4265 if (i == LEVELINFO_TOKEN_AUTHOR ||
4266 i == LEVELINFO_TOKEN_FIRST_LEVEL ||
4267 (use_artwork_set && i == LEVELINFO_TOKEN_READONLY))
4268 fprintf(file, "\n");
4271 token_value_position = TOKEN_VALUE_POSITION_DEFAULT;
4275 SetFilePermissions(filename, PERMS_PRIVATE);
4277 freeTreeInfo(level_info);
4283 static void SaveUserLevelInfo(void)
4285 CreateUserLevelSet(getLoginName(), getLoginName(), getRealName(), 100, FALSE);
4288 char *getSetupValue(int type, void *value)
4290 static char value_string[MAX_LINE_LEN];
4298 strcpy(value_string, (*(boolean *)value ? "true" : "false"));
4302 strcpy(value_string, (*(boolean *)value ? "on" : "off"));
4306 strcpy(value_string, (*(int *)value == AUTO ? "auto" :
4307 *(int *)value == FALSE ? "off" : "on"));
4311 strcpy(value_string, (*(boolean *)value ? "yes" : "no"));
4314 case TYPE_YES_NO_AUTO:
4315 strcpy(value_string, (*(int *)value == AUTO ? "auto" :
4316 *(int *)value == FALSE ? "no" : "yes"));
4320 strcpy(value_string, (*(boolean *)value ? "AGA" : "ECS"));
4324 strcpy(value_string, getKeyNameFromKey(*(Key *)value));
4328 strcpy(value_string, getX11KeyNameFromKey(*(Key *)value));
4332 sprintf(value_string, "%d", *(int *)value);
4336 if (*(char **)value == NULL)
4339 strcpy(value_string, *(char **)value);
4343 sprintf(value_string, "player_%d", *(int *)value + 1);
4347 value_string[0] = '\0';
4351 if (type & TYPE_GHOSTED)
4352 strcpy(value_string, "n/a");
4354 return value_string;
4357 char *getSetupLine(struct TokenInfo *token_info, char *prefix, int token_nr)
4361 static char token_string[MAX_LINE_LEN];
4362 int token_type = token_info[token_nr].type;
4363 void *setup_value = token_info[token_nr].value;
4364 char *token_text = token_info[token_nr].text;
4365 char *value_string = getSetupValue(token_type, setup_value);
4367 // build complete token string
4368 sprintf(token_string, "%s%s", prefix, token_text);
4370 // build setup entry line
4371 line = getFormattedSetupEntry(token_string, value_string);
4373 if (token_type == TYPE_KEY_X11)
4375 Key key = *(Key *)setup_value;
4376 char *keyname = getKeyNameFromKey(key);
4378 // add comment, if useful
4379 if (!strEqual(keyname, "(undefined)") &&
4380 !strEqual(keyname, "(unknown)"))
4382 // add at least one whitespace
4384 for (i = strlen(line); i < token_comment_position; i++)
4388 strcat(line, keyname);
4395 void LoadLevelSetup_LastSeries(void)
4397 // --------------------------------------------------------------------------
4398 // ~/.<program>/levelsetup.conf
4399 // --------------------------------------------------------------------------
4401 char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
4402 SetupFileHash *level_setup_hash = NULL;
4404 // always start with reliable default values
4405 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
4407 if (!strEqual(DEFAULT_LEVELSET, UNDEFINED_LEVELSET))
4409 leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
4411 if (leveldir_current == NULL)
4412 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
4415 if ((level_setup_hash = loadSetupFileHash(filename)))
4417 char *last_level_series =
4418 getHashEntry(level_setup_hash, TOKEN_STR_LAST_LEVEL_SERIES);
4420 leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
4422 if (leveldir_current == NULL)
4423 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
4425 freeSetupFileHash(level_setup_hash);
4429 Debug("setup", "using default setup values");
4435 static void SaveLevelSetup_LastSeries_Ext(boolean deactivate_last_level_series)
4437 // --------------------------------------------------------------------------
4438 // ~/.<program>/levelsetup.conf
4439 // --------------------------------------------------------------------------
4441 // check if the current level directory structure is available at this point
4442 if (leveldir_current == NULL)
4445 char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
4446 char *level_subdir = leveldir_current->subdir;
4449 InitUserDataDirectory();
4451 if (!(file = fopen(filename, MODE_WRITE)))
4453 Warn("cannot write setup file '%s'", filename);
4460 fprintFileHeader(file, LEVELSETUP_FILENAME);
4462 if (deactivate_last_level_series)
4463 fprintf(file, "# %s\n# ", "the following level set may have caused a problem and was deactivated");
4465 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_LEVEL_SERIES,
4470 SetFilePermissions(filename, PERMS_PRIVATE);
4475 void SaveLevelSetup_LastSeries(void)
4477 SaveLevelSetup_LastSeries_Ext(FALSE);
4480 void SaveLevelSetup_LastSeries_Deactivate(void)
4482 SaveLevelSetup_LastSeries_Ext(TRUE);
4485 static void checkSeriesInfo(void)
4487 static char *level_directory = NULL;
4490 DirectoryEntry *dir_entry;
4493 checked_free(level_directory);
4495 // check for more levels besides the 'levels' field of 'levelinfo.conf'
4497 level_directory = getPath2((leveldir_current->in_user_dir ?
4498 getUserLevelDir(NULL) :
4499 options.level_directory),
4500 leveldir_current->fullpath);
4502 if ((dir = openDirectory(level_directory)) == NULL)
4504 Warn("cannot read level directory '%s'", level_directory);
4510 while ((dir_entry = readDirectory(dir)) != NULL) // loop all entries
4512 if (strlen(dir_entry->basename) > 4 &&
4513 dir_entry->basename[3] == '.' &&
4514 strEqual(&dir_entry->basename[4], LEVELFILE_EXTENSION))
4516 char levelnum_str[4];
4519 strncpy(levelnum_str, dir_entry->basename, 3);
4520 levelnum_str[3] = '\0';
4522 levelnum_value = atoi(levelnum_str);
4524 if (levelnum_value < leveldir_current->first_level)
4526 Warn("additional level %d found", levelnum_value);
4528 leveldir_current->first_level = levelnum_value;
4530 else if (levelnum_value > leveldir_current->last_level)
4532 Warn("additional level %d found", levelnum_value);
4534 leveldir_current->last_level = levelnum_value;
4540 closeDirectory(dir);
4543 void LoadLevelSetup_SeriesInfo(void)
4546 SetupFileHash *level_setup_hash = NULL;
4547 char *level_subdir = leveldir_current->subdir;
4550 // always start with reliable default values
4551 level_nr = leveldir_current->first_level;
4553 for (i = 0; i < MAX_LEVELS; i++)
4555 LevelStats_setPlayed(i, 0);
4556 LevelStats_setSolved(i, 0);
4561 // --------------------------------------------------------------------------
4562 // ~/.<program>/levelsetup/<level series>/levelsetup.conf
4563 // --------------------------------------------------------------------------
4565 level_subdir = leveldir_current->subdir;
4567 filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
4569 if ((level_setup_hash = loadSetupFileHash(filename)))
4573 // get last played level in this level set
4575 token_value = getHashEntry(level_setup_hash, TOKEN_STR_LAST_PLAYED_LEVEL);
4579 level_nr = atoi(token_value);
4581 if (level_nr < leveldir_current->first_level)
4582 level_nr = leveldir_current->first_level;
4583 if (level_nr > leveldir_current->last_level)
4584 level_nr = leveldir_current->last_level;
4587 // get handicap level in this level set
4589 token_value = getHashEntry(level_setup_hash, TOKEN_STR_HANDICAP_LEVEL);
4593 int level_nr = atoi(token_value);
4595 if (level_nr < leveldir_current->first_level)
4596 level_nr = leveldir_current->first_level;
4597 if (level_nr > leveldir_current->last_level + 1)
4598 level_nr = leveldir_current->last_level;
4600 if (leveldir_current->user_defined || !leveldir_current->handicap)
4601 level_nr = leveldir_current->last_level;
4603 leveldir_current->handicap_level = level_nr;
4606 // get number of played and solved levels in this level set
4608 BEGIN_HASH_ITERATION(level_setup_hash, itr)
4610 char *token = HASH_ITERATION_TOKEN(itr);
4611 char *value = HASH_ITERATION_VALUE(itr);
4613 if (strlen(token) == 3 &&
4614 token[0] >= '0' && token[0] <= '9' &&
4615 token[1] >= '0' && token[1] <= '9' &&
4616 token[2] >= '0' && token[2] <= '9')
4618 int level_nr = atoi(token);
4621 LevelStats_setPlayed(level_nr, atoi(value)); // read 1st column
4623 value = strchr(value, ' ');
4626 LevelStats_setSolved(level_nr, atoi(value)); // read 2nd column
4629 END_HASH_ITERATION(hash, itr)
4631 freeSetupFileHash(level_setup_hash);
4635 Debug("setup", "using default setup values");
4641 void SaveLevelSetup_SeriesInfo(void)
4644 char *level_subdir = leveldir_current->subdir;
4645 char *level_nr_str = int2str(level_nr, 0);
4646 char *handicap_level_str = int2str(leveldir_current->handicap_level, 0);
4650 // --------------------------------------------------------------------------
4651 // ~/.<program>/levelsetup/<level series>/levelsetup.conf
4652 // --------------------------------------------------------------------------
4654 InitLevelSetupDirectory(level_subdir);
4656 filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
4658 if (!(file = fopen(filename, MODE_WRITE)))
4660 Warn("cannot write setup file '%s'", filename);
4667 fprintFileHeader(file, LEVELSETUP_FILENAME);
4669 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_PLAYED_LEVEL,
4671 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_HANDICAP_LEVEL,
4672 handicap_level_str));
4674 for (i = leveldir_current->first_level; i <= leveldir_current->last_level;
4677 if (LevelStats_getPlayed(i) > 0 ||
4678 LevelStats_getSolved(i) > 0)
4683 sprintf(token, "%03d", i);
4684 sprintf(value, "%d %d", LevelStats_getPlayed(i), LevelStats_getSolved(i));
4686 fprintf(file, "%s\n", getFormattedSetupEntry(token, value));
4692 SetFilePermissions(filename, PERMS_PRIVATE);
4697 int LevelStats_getPlayed(int nr)
4699 return (nr >= 0 && nr < MAX_LEVELS ? level_stats[nr].played : 0);
4702 int LevelStats_getSolved(int nr)
4704 return (nr >= 0 && nr < MAX_LEVELS ? level_stats[nr].solved : 0);
4707 void LevelStats_setPlayed(int nr, int value)
4709 if (nr >= 0 && nr < MAX_LEVELS)
4710 level_stats[nr].played = value;
4713 void LevelStats_setSolved(int nr, int value)
4715 if (nr >= 0 && nr < MAX_LEVELS)
4716 level_stats[nr].solved = value;
4719 void LevelStats_incPlayed(int nr)
4721 if (nr >= 0 && nr < MAX_LEVELS)
4722 level_stats[nr].played++;
4725 void LevelStats_incSolved(int nr)
4727 if (nr >= 0 && nr < MAX_LEVELS)
4728 level_stats[nr].solved++;
4731 void LoadUserSetup(void)
4733 // --------------------------------------------------------------------------
4734 // ~/.<program>/usersetup.conf
4735 // --------------------------------------------------------------------------
4737 char *filename = getPath2(getMainUserGameDataDir(), USERSETUP_FILENAME);
4738 SetupFileHash *user_setup_hash = NULL;
4740 // always start with reliable default values
4743 if ((user_setup_hash = loadSetupFileHash(filename)))
4747 // get last selected user number
4748 token_value = getHashEntry(user_setup_hash, TOKEN_STR_LAST_USER);
4751 user.nr = MIN(MAX(0, atoi(token_value)), MAX_PLAYER_NAMES - 1);
4753 freeSetupFileHash(user_setup_hash);
4757 Debug("setup", "using default setup values");
4763 void SaveUserSetup(void)
4765 // --------------------------------------------------------------------------
4766 // ~/.<program>/usersetup.conf
4767 // --------------------------------------------------------------------------
4769 char *filename = getPath2(getMainUserGameDataDir(), USERSETUP_FILENAME);
4772 InitMainUserDataDirectory();
4774 if (!(file = fopen(filename, MODE_WRITE)))
4776 Warn("cannot write setup file '%s'", filename);
4783 fprintFileHeader(file, USERSETUP_FILENAME);
4785 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_USER,
4789 SetFilePermissions(filename, PERMS_PRIVATE);