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>
21 #if !defined(PLATFORM_WIN32)
23 #include <sys/param.h>
31 #include "zip/miniunz.h"
34 #define ENABLE_UNUSED_CODE FALSE // for currently unused functions
35 #define DEBUG_NO_CONFIG_FILE FALSE // for extra-verbose debug output
37 #define NUM_LEVELCLASS_DESC 8
39 static char *levelclass_desc[NUM_LEVELCLASS_DESC] =
52 #define LEVELCOLOR(n) (IS_LEVELCLASS_TUTORIAL(n) ? FC_BLUE : \
53 IS_LEVELCLASS_CLASSICS(n) ? FC_RED : \
54 IS_LEVELCLASS_BD(n) ? FC_YELLOW : \
55 IS_LEVELCLASS_EM(n) ? FC_YELLOW : \
56 IS_LEVELCLASS_SP(n) ? FC_YELLOW : \
57 IS_LEVELCLASS_DX(n) ? FC_YELLOW : \
58 IS_LEVELCLASS_SB(n) ? FC_YELLOW : \
59 IS_LEVELCLASS_CONTRIB(n) ? FC_GREEN : \
60 IS_LEVELCLASS_PRIVATE(n) ? FC_RED : \
63 #define LEVELSORTING(n) (IS_LEVELCLASS_TUTORIAL(n) ? 0 : \
64 IS_LEVELCLASS_CLASSICS(n) ? 1 : \
65 IS_LEVELCLASS_BD(n) ? 2 : \
66 IS_LEVELCLASS_EM(n) ? 3 : \
67 IS_LEVELCLASS_SP(n) ? 4 : \
68 IS_LEVELCLASS_DX(n) ? 5 : \
69 IS_LEVELCLASS_SB(n) ? 6 : \
70 IS_LEVELCLASS_CONTRIB(n) ? 7 : \
71 IS_LEVELCLASS_PRIVATE(n) ? 8 : \
74 #define ARTWORKCOLOR(n) (IS_ARTWORKCLASS_CLASSICS(n) ? FC_RED : \
75 IS_ARTWORKCLASS_CONTRIB(n) ? FC_GREEN : \
76 IS_ARTWORKCLASS_PRIVATE(n) ? FC_RED : \
77 IS_ARTWORKCLASS_LEVEL(n) ? FC_YELLOW : \
80 #define ARTWORKSORTING(n) (IS_ARTWORKCLASS_CLASSICS(n) ? 0 : \
81 IS_ARTWORKCLASS_LEVEL(n) ? 1 : \
82 IS_ARTWORKCLASS_CONTRIB(n) ? 2 : \
83 IS_ARTWORKCLASS_PRIVATE(n) ? 3 : \
86 #define TOKEN_VALUE_POSITION_SHORT 32
87 #define TOKEN_VALUE_POSITION_DEFAULT 40
88 #define TOKEN_COMMENT_POSITION_DEFAULT 60
90 #define MAX_COOKIE_LEN 256
93 static void setTreeInfoToDefaults(TreeInfo *, int);
94 static TreeInfo *getTreeInfoCopy(TreeInfo *ti);
95 static int compareTreeInfoEntries(const void *, const void *);
97 static int token_value_position = TOKEN_VALUE_POSITION_DEFAULT;
98 static int token_comment_position = TOKEN_COMMENT_POSITION_DEFAULT;
100 static SetupFileHash *artworkinfo_cache_old = NULL;
101 static SetupFileHash *artworkinfo_cache_new = NULL;
102 static SetupFileHash *optional_tokens_hash = NULL;
103 static boolean use_artworkinfo_cache = TRUE;
106 // ----------------------------------------------------------------------------
108 // ----------------------------------------------------------------------------
110 static char *getLevelClassDescription(TreeInfo *ti)
112 int position = ti->sort_priority / 100;
114 if (position >= 0 && position < NUM_LEVELCLASS_DESC)
115 return levelclass_desc[position];
117 return "Unknown Level Class";
120 static char *getScoreDir(char *level_subdir)
122 static char *score_dir = NULL;
123 static char *score_level_dir = NULL;
124 char *score_subdir = SCORES_DIRECTORY;
126 if (score_dir == NULL)
128 if (program.global_scores)
129 score_dir = getPath2(getCommonDataDir(), score_subdir);
131 score_dir = getPath2(getUserGameDataDir(), score_subdir);
134 if (level_subdir != NULL)
136 checked_free(score_level_dir);
138 score_level_dir = getPath2(score_dir, level_subdir);
140 return score_level_dir;
146 static char *getLevelSetupDir(char *level_subdir)
148 static char *levelsetup_dir = NULL;
149 char *data_dir = getUserGameDataDir();
150 char *levelsetup_subdir = LEVELSETUP_DIRECTORY;
152 checked_free(levelsetup_dir);
154 if (level_subdir != NULL)
155 levelsetup_dir = getPath3(data_dir, levelsetup_subdir, level_subdir);
157 levelsetup_dir = getPath2(data_dir, levelsetup_subdir);
159 return levelsetup_dir;
162 static char *getCacheDir(void)
164 static char *cache_dir = NULL;
166 if (cache_dir == NULL)
167 cache_dir = getPath2(getUserGameDataDir(), CACHE_DIRECTORY);
172 static char *getNetworkDir(void)
174 static char *network_dir = NULL;
176 if (network_dir == NULL)
177 network_dir = getPath2(getUserGameDataDir(), NETWORK_DIRECTORY);
182 char *getLevelDirFromTreeInfo(TreeInfo *node)
184 static char *level_dir = NULL;
187 return options.level_directory;
189 checked_free(level_dir);
191 level_dir = getPath2((node->in_user_dir ? getUserLevelDir(NULL) :
192 options.level_directory), node->fullpath);
197 char *getUserLevelDir(char *level_subdir)
199 static char *userlevel_dir = NULL;
200 char *data_dir = getUserGameDataDir();
201 char *userlevel_subdir = LEVELS_DIRECTORY;
203 checked_free(userlevel_dir);
205 if (level_subdir != NULL)
206 userlevel_dir = getPath3(data_dir, userlevel_subdir, level_subdir);
208 userlevel_dir = getPath2(data_dir, userlevel_subdir);
210 return userlevel_dir;
213 char *getNetworkLevelDir(char *level_subdir)
215 static char *network_level_dir = NULL;
216 char *data_dir = getNetworkDir();
217 char *networklevel_subdir = LEVELS_DIRECTORY;
219 checked_free(network_level_dir);
221 if (level_subdir != NULL)
222 network_level_dir = getPath3(data_dir, networklevel_subdir, level_subdir);
224 network_level_dir = getPath2(data_dir, networklevel_subdir);
226 return network_level_dir;
229 char *getCurrentLevelDir(void)
231 return getLevelDirFromTreeInfo(leveldir_current);
234 char *getNewUserLevelSubdir(void)
236 static char *new_level_subdir = NULL;
237 char *subdir_prefix = getLoginName();
238 char subdir_suffix[10];
239 int max_suffix_number = 1000;
242 while (++i < max_suffix_number)
244 sprintf(subdir_suffix, "_%d", i);
246 checked_free(new_level_subdir);
247 new_level_subdir = getStringCat2(subdir_prefix, subdir_suffix);
249 if (!directoryExists(getUserLevelDir(new_level_subdir)))
253 return new_level_subdir;
256 static char *getTapeDir(char *level_subdir)
258 static char *tape_dir = NULL;
259 char *data_dir = getUserGameDataDir();
260 char *tape_subdir = TAPES_DIRECTORY;
262 checked_free(tape_dir);
264 if (level_subdir != NULL)
265 tape_dir = getPath3(data_dir, tape_subdir, level_subdir);
267 tape_dir = getPath2(data_dir, tape_subdir);
272 static char *getSolutionTapeDir(void)
274 static char *tape_dir = NULL;
275 char *data_dir = getCurrentLevelDir();
276 char *tape_subdir = TAPES_DIRECTORY;
278 checked_free(tape_dir);
280 tape_dir = getPath2(data_dir, tape_subdir);
285 static char *getDefaultGraphicsDir(char *graphics_subdir)
287 static char *graphics_dir = NULL;
289 if (graphics_subdir == NULL)
290 return options.graphics_directory;
292 checked_free(graphics_dir);
294 graphics_dir = getPath2(options.graphics_directory, graphics_subdir);
299 static char *getDefaultSoundsDir(char *sounds_subdir)
301 static char *sounds_dir = NULL;
303 if (sounds_subdir == NULL)
304 return options.sounds_directory;
306 checked_free(sounds_dir);
308 sounds_dir = getPath2(options.sounds_directory, sounds_subdir);
313 static char *getDefaultMusicDir(char *music_subdir)
315 static char *music_dir = NULL;
317 if (music_subdir == NULL)
318 return options.music_directory;
320 checked_free(music_dir);
322 music_dir = getPath2(options.music_directory, music_subdir);
327 static char *getClassicArtworkSet(int type)
329 return (type == TREE_TYPE_GRAPHICS_DIR ? GFX_CLASSIC_SUBDIR :
330 type == TREE_TYPE_SOUNDS_DIR ? SND_CLASSIC_SUBDIR :
331 type == TREE_TYPE_MUSIC_DIR ? MUS_CLASSIC_SUBDIR : "");
334 static char *getClassicArtworkDir(int type)
336 return (type == TREE_TYPE_GRAPHICS_DIR ?
337 getDefaultGraphicsDir(GFX_CLASSIC_SUBDIR) :
338 type == TREE_TYPE_SOUNDS_DIR ?
339 getDefaultSoundsDir(SND_CLASSIC_SUBDIR) :
340 type == TREE_TYPE_MUSIC_DIR ?
341 getDefaultMusicDir(MUS_CLASSIC_SUBDIR) : "");
344 char *getUserGraphicsDir(void)
346 static char *usergraphics_dir = NULL;
348 if (usergraphics_dir == NULL)
349 usergraphics_dir = getPath2(getUserGameDataDir(), GRAPHICS_DIRECTORY);
351 return usergraphics_dir;
354 char *getUserSoundsDir(void)
356 static char *usersounds_dir = NULL;
358 if (usersounds_dir == NULL)
359 usersounds_dir = getPath2(getUserGameDataDir(), SOUNDS_DIRECTORY);
361 return usersounds_dir;
364 char *getUserMusicDir(void)
366 static char *usermusic_dir = NULL;
368 if (usermusic_dir == NULL)
369 usermusic_dir = getPath2(getUserGameDataDir(), MUSIC_DIRECTORY);
371 return usermusic_dir;
374 static char *getSetupArtworkDir(TreeInfo *ti)
376 static char *artwork_dir = NULL;
381 checked_free(artwork_dir);
383 artwork_dir = getPath2(ti->basepath, ti->fullpath);
388 char *setLevelArtworkDir(TreeInfo *ti)
390 char **artwork_path_ptr, **artwork_set_ptr;
391 TreeInfo *level_artwork;
393 if (ti == NULL || leveldir_current == NULL)
396 artwork_path_ptr = LEVELDIR_ARTWORK_PATH_PTR(leveldir_current, ti->type);
397 artwork_set_ptr = LEVELDIR_ARTWORK_SET_PTR( leveldir_current, ti->type);
399 checked_free(*artwork_path_ptr);
401 if ((level_artwork = getTreeInfoFromIdentifier(ti, *artwork_set_ptr)))
403 *artwork_path_ptr = getStringCopy(getSetupArtworkDir(level_artwork));
408 No (or non-existing) artwork configured in "levelinfo.conf". This would
409 normally result in using the artwork configured in the setup menu. But
410 if an artwork subdirectory exists (which might contain custom artwork
411 or an artwork configuration file), this level artwork must be treated
412 as relative to the default "classic" artwork, not to the artwork that
413 is currently configured in the setup menu.
415 Update: For "special" versions of R'n'D (like "R'n'D jue"), do not use
416 the "default" artwork (which would be "jue0" for "R'n'D jue"), but use
417 the real "classic" artwork from the original R'n'D (like "gfx_classic").
420 char *dir = getPath2(getCurrentLevelDir(), ARTWORK_DIRECTORY(ti->type));
422 checked_free(*artwork_set_ptr);
424 if (directoryExists(dir))
426 *artwork_path_ptr = getStringCopy(getClassicArtworkDir(ti->type));
427 *artwork_set_ptr = getStringCopy(getClassicArtworkSet(ti->type));
431 *artwork_path_ptr = getStringCopy(UNDEFINED_FILENAME);
432 *artwork_set_ptr = NULL;
438 return *artwork_set_ptr;
441 static char *getLevelArtworkSet(int type)
443 if (leveldir_current == NULL)
446 return LEVELDIR_ARTWORK_SET(leveldir_current, type);
449 static char *getLevelArtworkDir(int type)
451 if (leveldir_current == NULL)
452 return UNDEFINED_FILENAME;
454 return LEVELDIR_ARTWORK_PATH(leveldir_current, type);
457 char *getProgramMainDataPath(char *command_filename, char *base_path)
459 // check if the program's main data base directory is configured
460 if (!strEqual(base_path, "."))
461 return getStringCopy(base_path);
463 /* if the program is configured to start from current directory (default),
464 determine program package directory from program binary (some versions
465 of KDE/Konqueror and Mac OS X (especially "Mavericks") apparently do not
466 set the current working directory to the program package directory) */
467 char *main_data_path = getBasePath(command_filename);
469 #if defined(PLATFORM_MACOSX)
470 if (strSuffix(main_data_path, MAC_APP_BINARY_SUBDIR))
472 char *main_data_path_old = main_data_path;
474 // cut relative path to Mac OS X application binary directory from path
475 main_data_path[strlen(main_data_path) -
476 strlen(MAC_APP_BINARY_SUBDIR)] = '\0';
478 // cut trailing path separator from path (but not if path is root directory)
479 if (strSuffix(main_data_path, "/") && !strEqual(main_data_path, "/"))
480 main_data_path[strlen(main_data_path) - 1] = '\0';
482 // replace empty path with current directory
483 if (strEqual(main_data_path, ""))
484 main_data_path = ".";
486 // add relative path to Mac OS X application resources directory to path
487 main_data_path = getPath2(main_data_path, MAC_APP_FILES_SUBDIR);
489 free(main_data_path_old);
493 return main_data_path;
496 char *getProgramConfigFilename(char *command_filename)
498 static char *config_filename_1 = NULL;
499 static char *config_filename_2 = NULL;
500 static char *config_filename_3 = NULL;
501 static boolean initialized = FALSE;
505 char *command_filename_1 = getStringCopy(command_filename);
507 // strip trailing executable suffix from command filename
508 if (strSuffix(command_filename_1, ".exe"))
509 command_filename_1[strlen(command_filename_1) - 4] = '\0';
511 char *ro_base_path = getProgramMainDataPath(command_filename, RO_BASE_PATH);
512 char *conf_directory = getPath2(ro_base_path, CONF_DIRECTORY);
514 char *command_basepath = getBasePath(command_filename);
515 char *command_basename = getBaseNameNoSuffix(command_filename);
516 char *command_filename_2 = getPath2(command_basepath, command_basename);
518 config_filename_1 = getStringCat2(command_filename_1, ".conf");
519 config_filename_2 = getStringCat2(command_filename_2, ".conf");
520 config_filename_3 = getPath2(conf_directory, SETUP_FILENAME);
522 checked_free(ro_base_path);
523 checked_free(conf_directory);
525 checked_free(command_basepath);
526 checked_free(command_basename);
528 checked_free(command_filename_1);
529 checked_free(command_filename_2);
534 // 1st try: look for config file that exactly matches the binary filename
535 if (fileExists(config_filename_1))
536 return config_filename_1;
538 // 2nd try: look for config file that matches binary filename without suffix
539 if (fileExists(config_filename_2))
540 return config_filename_2;
542 // 3rd try: return setup config filename in global program config directory
543 return config_filename_3;
546 char *getTapeFilename(int nr)
548 static char *filename = NULL;
549 char basename[MAX_FILENAME_LEN];
551 checked_free(filename);
553 sprintf(basename, "%03d.%s", nr, TAPEFILE_EXTENSION);
554 filename = getPath2(getTapeDir(leveldir_current->subdir), basename);
559 char *getSolutionTapeFilename(int nr)
561 static char *filename = NULL;
562 char basename[MAX_FILENAME_LEN];
564 checked_free(filename);
566 sprintf(basename, "%03d.%s", nr, TAPEFILE_EXTENSION);
567 filename = getPath2(getSolutionTapeDir(), basename);
569 if (!fileExists(filename))
571 static char *filename_sln = NULL;
573 checked_free(filename_sln);
575 sprintf(basename, "%03d.sln", nr);
576 filename_sln = getPath2(getSolutionTapeDir(), basename);
578 if (fileExists(filename_sln))
585 char *getScoreFilename(int nr)
587 static char *filename = NULL;
588 char basename[MAX_FILENAME_LEN];
590 checked_free(filename);
592 sprintf(basename, "%03d.%s", nr, SCOREFILE_EXTENSION);
594 // used instead of "leveldir_current->subdir" (for network games)
595 filename = getPath2(getScoreDir(levelset.identifier), basename);
600 char *getSetupFilename(void)
602 static char *filename = NULL;
604 checked_free(filename);
606 filename = getPath2(getSetupDir(), SETUP_FILENAME);
611 char *getDefaultSetupFilename(void)
613 return program.config_filename;
616 char *getEditorSetupFilename(void)
618 static char *filename = NULL;
620 checked_free(filename);
621 filename = getPath2(getCurrentLevelDir(), EDITORSETUP_FILENAME);
623 if (fileExists(filename))
626 checked_free(filename);
627 filename = getPath2(getSetupDir(), EDITORSETUP_FILENAME);
632 char *getHelpAnimFilename(void)
634 static char *filename = NULL;
636 checked_free(filename);
638 filename = getPath2(getCurrentLevelDir(), HELPANIM_FILENAME);
643 char *getHelpTextFilename(void)
645 static char *filename = NULL;
647 checked_free(filename);
649 filename = getPath2(getCurrentLevelDir(), HELPTEXT_FILENAME);
654 char *getLevelSetInfoFilename(void)
656 static char *filename = NULL;
671 for (i = 0; basenames[i] != NULL; i++)
673 checked_free(filename);
674 filename = getPath2(getCurrentLevelDir(), basenames[i]);
676 if (fileExists(filename))
683 static char *getLevelSetTitleMessageBasename(int nr, boolean initial)
685 static char basename[32];
687 sprintf(basename, "%s_%d.txt",
688 (initial ? "titlemessage_initial" : "titlemessage"), nr + 1);
693 char *getLevelSetTitleMessageFilename(int nr, boolean initial)
695 static char *filename = NULL;
697 boolean skip_setup_artwork = FALSE;
699 checked_free(filename);
701 basename = getLevelSetTitleMessageBasename(nr, initial);
703 if (!gfx.override_level_graphics)
705 // 1st try: look for special artwork in current level series directory
706 filename = getPath3(getCurrentLevelDir(), GRAPHICS_DIRECTORY, basename);
707 if (fileExists(filename))
712 // 2nd try: look for message file in current level set directory
713 filename = getPath2(getCurrentLevelDir(), basename);
714 if (fileExists(filename))
719 // check if there is special artwork configured in level series config
720 if (getLevelArtworkSet(ARTWORK_TYPE_GRAPHICS) != NULL)
722 // 3rd try: look for special artwork configured in level series config
723 filename = getPath2(getLevelArtworkDir(ARTWORK_TYPE_GRAPHICS), basename);
724 if (fileExists(filename))
729 // take missing artwork configured in level set config from default
730 skip_setup_artwork = TRUE;
734 if (!skip_setup_artwork)
736 // 4th try: look for special artwork in configured artwork directory
737 filename = getPath2(getSetupArtworkDir(artwork.gfx_current), basename);
738 if (fileExists(filename))
744 // 5th try: look for default artwork in new default artwork directory
745 filename = getPath2(getDefaultGraphicsDir(GFX_DEFAULT_SUBDIR), basename);
746 if (fileExists(filename))
751 // 6th try: look for default artwork in old default artwork directory
752 filename = getPath2(options.graphics_directory, basename);
753 if (fileExists(filename))
756 return NULL; // cannot find specified artwork file anywhere
759 static char *getCorrectedArtworkBasename(char *basename)
764 char *getCustomImageFilename(char *basename)
766 static char *filename = NULL;
767 boolean skip_setup_artwork = FALSE;
769 checked_free(filename);
771 basename = getCorrectedArtworkBasename(basename);
773 if (!gfx.override_level_graphics)
775 // 1st try: look for special artwork in current level series directory
776 filename = getImg3(getCurrentLevelDir(), GRAPHICS_DIRECTORY, basename);
777 if (fileExists(filename))
782 // check if there is special artwork configured in level series config
783 if (getLevelArtworkSet(ARTWORK_TYPE_GRAPHICS) != NULL)
785 // 2nd try: look for special artwork configured in level series config
786 filename = getImg2(getLevelArtworkDir(ARTWORK_TYPE_GRAPHICS), basename);
787 if (fileExists(filename))
792 // take missing artwork configured in level set config from default
793 skip_setup_artwork = TRUE;
797 if (!skip_setup_artwork)
799 // 3rd try: look for special artwork in configured artwork directory
800 filename = getImg2(getSetupArtworkDir(artwork.gfx_current), basename);
801 if (fileExists(filename))
807 // 4th try: look for default artwork in new default artwork directory
808 filename = getImg2(getDefaultGraphicsDir(GFX_DEFAULT_SUBDIR), basename);
809 if (fileExists(filename))
814 // 5th try: look for default artwork in old default artwork directory
815 filename = getImg2(options.graphics_directory, basename);
816 if (fileExists(filename))
819 if (!strEqual(GFX_FALLBACK_FILENAME, UNDEFINED_FILENAME))
823 Warn("cannot find artwork file '%s' (using fallback)", basename);
825 // 6th try: look for fallback artwork in old default artwork directory
826 // (needed to prevent errors when trying to access unused artwork files)
827 filename = getImg2(options.graphics_directory, GFX_FALLBACK_FILENAME);
828 if (fileExists(filename))
832 return NULL; // cannot find specified artwork file anywhere
835 char *getCustomSoundFilename(char *basename)
837 static char *filename = NULL;
838 boolean skip_setup_artwork = FALSE;
840 checked_free(filename);
842 basename = getCorrectedArtworkBasename(basename);
844 if (!gfx.override_level_sounds)
846 // 1st try: look for special artwork in current level series directory
847 filename = getPath3(getCurrentLevelDir(), SOUNDS_DIRECTORY, basename);
848 if (fileExists(filename))
853 // check if there is special artwork configured in level series config
854 if (getLevelArtworkSet(ARTWORK_TYPE_SOUNDS) != NULL)
856 // 2nd try: look for special artwork configured in level series config
857 filename = getPath2(getLevelArtworkDir(TREE_TYPE_SOUNDS_DIR), basename);
858 if (fileExists(filename))
863 // take missing artwork configured in level set config from default
864 skip_setup_artwork = TRUE;
868 if (!skip_setup_artwork)
870 // 3rd try: look for special artwork in configured artwork directory
871 filename = getPath2(getSetupArtworkDir(artwork.snd_current), basename);
872 if (fileExists(filename))
878 // 4th try: look for default artwork in new default artwork directory
879 filename = getPath2(getDefaultSoundsDir(SND_DEFAULT_SUBDIR), basename);
880 if (fileExists(filename))
885 // 5th try: look for default artwork in old default artwork directory
886 filename = getPath2(options.sounds_directory, basename);
887 if (fileExists(filename))
890 if (!strEqual(SND_FALLBACK_FILENAME, UNDEFINED_FILENAME))
894 Warn("cannot find artwork file '%s' (using fallback)", basename);
896 // 6th try: look for fallback artwork in old default artwork directory
897 // (needed to prevent errors when trying to access unused artwork files)
898 filename = getPath2(options.sounds_directory, SND_FALLBACK_FILENAME);
899 if (fileExists(filename))
903 return NULL; // cannot find specified artwork file anywhere
906 char *getCustomMusicFilename(char *basename)
908 static char *filename = NULL;
909 boolean skip_setup_artwork = FALSE;
911 checked_free(filename);
913 basename = getCorrectedArtworkBasename(basename);
915 if (!gfx.override_level_music)
917 // 1st try: look for special artwork in current level series directory
918 filename = getPath3(getCurrentLevelDir(), MUSIC_DIRECTORY, basename);
919 if (fileExists(filename))
924 // check if there is special artwork configured in level series config
925 if (getLevelArtworkSet(ARTWORK_TYPE_MUSIC) != NULL)
927 // 2nd try: look for special artwork configured in level series config
928 filename = getPath2(getLevelArtworkDir(TREE_TYPE_MUSIC_DIR), basename);
929 if (fileExists(filename))
934 // take missing artwork configured in level set config from default
935 skip_setup_artwork = TRUE;
939 if (!skip_setup_artwork)
941 // 3rd try: look for special artwork in configured artwork directory
942 filename = getPath2(getSetupArtworkDir(artwork.mus_current), basename);
943 if (fileExists(filename))
949 // 4th try: look for default artwork in new default artwork directory
950 filename = getPath2(getDefaultMusicDir(MUS_DEFAULT_SUBDIR), basename);
951 if (fileExists(filename))
956 // 5th try: look for default artwork in old default artwork directory
957 filename = getPath2(options.music_directory, basename);
958 if (fileExists(filename))
961 if (!strEqual(MUS_FALLBACK_FILENAME, UNDEFINED_FILENAME))
965 Warn("cannot find artwork file '%s' (using fallback)", basename);
967 // 6th try: look for fallback artwork in old default artwork directory
968 // (needed to prevent errors when trying to access unused artwork files)
969 filename = getPath2(options.music_directory, MUS_FALLBACK_FILENAME);
970 if (fileExists(filename))
974 return NULL; // cannot find specified artwork file anywhere
977 char *getCustomArtworkFilename(char *basename, int type)
979 if (type == ARTWORK_TYPE_GRAPHICS)
980 return getCustomImageFilename(basename);
981 else if (type == ARTWORK_TYPE_SOUNDS)
982 return getCustomSoundFilename(basename);
983 else if (type == ARTWORK_TYPE_MUSIC)
984 return getCustomMusicFilename(basename);
986 return UNDEFINED_FILENAME;
989 char *getCustomArtworkConfigFilename(int type)
991 return getCustomArtworkFilename(ARTWORKINFO_FILENAME(type), type);
994 char *getCustomArtworkLevelConfigFilename(int type)
996 static char *filename = NULL;
998 checked_free(filename);
1000 filename = getPath2(getLevelArtworkDir(type), ARTWORKINFO_FILENAME(type));
1005 char *getCustomMusicDirectory(void)
1007 static char *directory = NULL;
1008 boolean skip_setup_artwork = FALSE;
1010 checked_free(directory);
1012 if (!gfx.override_level_music)
1014 // 1st try: look for special artwork in current level series directory
1015 directory = getPath2(getCurrentLevelDir(), MUSIC_DIRECTORY);
1016 if (directoryExists(directory))
1021 // check if there is special artwork configured in level series config
1022 if (getLevelArtworkSet(ARTWORK_TYPE_MUSIC) != NULL)
1024 // 2nd try: look for special artwork configured in level series config
1025 directory = getStringCopy(getLevelArtworkDir(TREE_TYPE_MUSIC_DIR));
1026 if (directoryExists(directory))
1031 // take missing artwork configured in level set config from default
1032 skip_setup_artwork = TRUE;
1036 if (!skip_setup_artwork)
1038 // 3rd try: look for special artwork in configured artwork directory
1039 directory = getStringCopy(getSetupArtworkDir(artwork.mus_current));
1040 if (directoryExists(directory))
1046 // 4th try: look for default artwork in new default artwork directory
1047 directory = getStringCopy(getDefaultMusicDir(MUS_DEFAULT_SUBDIR));
1048 if (directoryExists(directory))
1053 // 5th try: look for default artwork in old default artwork directory
1054 directory = getStringCopy(options.music_directory);
1055 if (directoryExists(directory))
1058 return NULL; // cannot find specified artwork file anywhere
1061 void InitTapeDirectory(char *level_subdir)
1063 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
1064 createDirectory(getTapeDir(NULL), "main tape", PERMS_PRIVATE);
1065 createDirectory(getTapeDir(level_subdir), "level tape", PERMS_PRIVATE);
1068 void InitScoreDirectory(char *level_subdir)
1070 int permissions = (program.global_scores ? PERMS_PUBLIC : PERMS_PRIVATE);
1072 if (program.global_scores)
1073 createDirectory(getCommonDataDir(), "common data", permissions);
1075 createDirectory(getUserGameDataDir(), "user data", permissions);
1077 createDirectory(getScoreDir(NULL), "main score", permissions);
1078 createDirectory(getScoreDir(level_subdir), "level score", permissions);
1081 static void SaveUserLevelInfo(void);
1083 void InitUserLevelDirectory(char *level_subdir)
1085 if (!directoryExists(getUserLevelDir(level_subdir)))
1087 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
1088 createDirectory(getUserLevelDir(NULL), "main user level", PERMS_PRIVATE);
1089 createDirectory(getUserLevelDir(level_subdir), "user level", PERMS_PRIVATE);
1091 if (setup.internal.create_user_levelset)
1092 SaveUserLevelInfo();
1096 void InitNetworkLevelDirectory(char *level_subdir)
1098 if (!directoryExists(getNetworkLevelDir(level_subdir)))
1100 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
1101 createDirectory(getNetworkDir(), "network data", PERMS_PRIVATE);
1102 createDirectory(getNetworkLevelDir(NULL), "main network level", PERMS_PRIVATE);
1103 createDirectory(getNetworkLevelDir(level_subdir), "network level", PERMS_PRIVATE);
1107 void InitLevelSetupDirectory(char *level_subdir)
1109 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
1110 createDirectory(getLevelSetupDir(NULL), "main level setup", PERMS_PRIVATE);
1111 createDirectory(getLevelSetupDir(level_subdir), "level setup", PERMS_PRIVATE);
1114 static void InitCacheDirectory(void)
1116 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
1117 createDirectory(getCacheDir(), "cache data", PERMS_PRIVATE);
1121 // ----------------------------------------------------------------------------
1122 // some functions to handle lists of level and artwork directories
1123 // ----------------------------------------------------------------------------
1125 TreeInfo *newTreeInfo(void)
1127 return checked_calloc(sizeof(TreeInfo));
1130 TreeInfo *newTreeInfo_setDefaults(int type)
1132 TreeInfo *ti = newTreeInfo();
1134 setTreeInfoToDefaults(ti, type);
1139 void pushTreeInfo(TreeInfo **node_first, TreeInfo *node_new)
1141 node_new->next = *node_first;
1142 *node_first = node_new;
1145 int numTreeInfo(TreeInfo *node)
1158 boolean validLevelSeries(TreeInfo *node)
1160 return (node != NULL && !node->node_group && !node->parent_link);
1163 TreeInfo *getFirstValidTreeInfoEntry(TreeInfo *node)
1168 if (node->node_group) // enter level group (step down into tree)
1169 return getFirstValidTreeInfoEntry(node->node_group);
1170 else if (node->parent_link) // skip start entry of level group
1172 if (node->next) // get first real level series entry
1173 return getFirstValidTreeInfoEntry(node->next);
1174 else // leave empty level group and go on
1175 return getFirstValidTreeInfoEntry(node->node_parent->next);
1177 else // this seems to be a regular level series
1181 TreeInfo *getTreeInfoFirstGroupEntry(TreeInfo *node)
1186 if (node->node_parent == NULL) // top level group
1187 return *node->node_top;
1188 else // sub level group
1189 return node->node_parent->node_group;
1192 int numTreeInfoInGroup(TreeInfo *node)
1194 return numTreeInfo(getTreeInfoFirstGroupEntry(node));
1197 int posTreeInfo(TreeInfo *node)
1199 TreeInfo *node_cmp = getTreeInfoFirstGroupEntry(node);
1204 if (node_cmp == node)
1208 node_cmp = node_cmp->next;
1214 TreeInfo *getTreeInfoFromPos(TreeInfo *node, int pos)
1216 TreeInfo *node_default = node;
1228 return node_default;
1231 TreeInfo *getTreeInfoFromIdentifier(TreeInfo *node, char *identifier)
1233 if (identifier == NULL)
1238 if (node->node_group)
1240 TreeInfo *node_group;
1242 node_group = getTreeInfoFromIdentifier(node->node_group, identifier);
1247 else if (!node->parent_link)
1249 if (strEqual(identifier, node->identifier))
1259 static TreeInfo *cloneTreeNode(TreeInfo **node_top, TreeInfo *node_parent,
1260 TreeInfo *node, boolean skip_sets_without_levels)
1267 if (!node->parent_link && !node->level_group &&
1268 skip_sets_without_levels && node->levels == 0)
1269 return cloneTreeNode(node_top, node_parent, node->next,
1270 skip_sets_without_levels);
1272 node_new = getTreeInfoCopy(node); // copy complete node
1274 node_new->node_top = node_top; // correct top node link
1275 node_new->node_parent = node_parent; // correct parent node link
1277 if (node->level_group)
1278 node_new->node_group = cloneTreeNode(node_top, node_new, node->node_group,
1279 skip_sets_without_levels);
1281 node_new->next = cloneTreeNode(node_top, node_parent, node->next,
1282 skip_sets_without_levels);
1287 static void cloneTree(TreeInfo **ti_new, TreeInfo *ti, boolean skip_empty_sets)
1289 TreeInfo *ti_cloned = cloneTreeNode(ti_new, NULL, ti, skip_empty_sets);
1291 *ti_new = ti_cloned;
1294 static boolean adjustTreeGraphicsForEMC(TreeInfo *node)
1296 boolean settings_changed = FALSE;
1300 boolean want_ecs = (setup.prefer_aga_graphics == FALSE);
1301 boolean want_aga = (setup.prefer_aga_graphics == TRUE);
1302 boolean has_only_ecs = (!node->graphics_set && !node->graphics_set_aga);
1303 boolean has_only_aga = (!node->graphics_set && !node->graphics_set_ecs);
1304 char *graphics_set = NULL;
1306 if (node->graphics_set_ecs && (want_ecs || has_only_ecs))
1307 graphics_set = node->graphics_set_ecs;
1309 if (node->graphics_set_aga && (want_aga || has_only_aga))
1310 graphics_set = node->graphics_set_aga;
1312 if (graphics_set && !strEqual(node->graphics_set, graphics_set))
1314 setString(&node->graphics_set, graphics_set);
1315 settings_changed = TRUE;
1318 if (node->node_group != NULL)
1319 settings_changed |= adjustTreeGraphicsForEMC(node->node_group);
1324 return settings_changed;
1327 static boolean adjustTreeSoundsForEMC(TreeInfo *node)
1329 boolean settings_changed = FALSE;
1333 boolean want_default = (setup.prefer_lowpass_sounds == FALSE);
1334 boolean want_lowpass = (setup.prefer_lowpass_sounds == TRUE);
1335 boolean has_only_default = (!node->sounds_set && !node->sounds_set_lowpass);
1336 boolean has_only_lowpass = (!node->sounds_set && !node->sounds_set_default);
1337 char *sounds_set = NULL;
1339 if (node->sounds_set_default && (want_default || has_only_default))
1340 sounds_set = node->sounds_set_default;
1342 if (node->sounds_set_lowpass && (want_lowpass || has_only_lowpass))
1343 sounds_set = node->sounds_set_lowpass;
1345 if (sounds_set && !strEqual(node->sounds_set, sounds_set))
1347 setString(&node->sounds_set, sounds_set);
1348 settings_changed = TRUE;
1351 if (node->node_group != NULL)
1352 settings_changed |= adjustTreeSoundsForEMC(node->node_group);
1357 return settings_changed;
1360 void dumpTreeInfo(TreeInfo *node, int depth)
1364 Debug("tree", "Dumping TreeInfo:");
1368 for (i = 0; i < (depth + 1) * 3; i++)
1369 DebugContinued("", " ");
1371 DebugContinued("tree", "'%s' / '%s'\n", node->identifier, node->name);
1374 // use for dumping artwork info tree
1375 Debug("tree", "subdir == '%s' ['%s', '%s'] [%d])",
1376 node->subdir, node->fullpath, node->basepath, node->in_user_dir);
1379 if (node->node_group != NULL)
1380 dumpTreeInfo(node->node_group, depth + 1);
1386 void sortTreeInfoBySortFunction(TreeInfo **node_first,
1387 int (*compare_function)(const void *,
1390 int num_nodes = numTreeInfo(*node_first);
1391 TreeInfo **sort_array;
1392 TreeInfo *node = *node_first;
1398 // allocate array for sorting structure pointers
1399 sort_array = checked_calloc(num_nodes * sizeof(TreeInfo *));
1401 // writing structure pointers to sorting array
1402 while (i < num_nodes && node) // double boundary check...
1404 sort_array[i] = node;
1410 // sorting the structure pointers in the sorting array
1411 qsort(sort_array, num_nodes, sizeof(TreeInfo *),
1414 // update the linkage of list elements with the sorted node array
1415 for (i = 0; i < num_nodes - 1; i++)
1416 sort_array[i]->next = sort_array[i + 1];
1417 sort_array[num_nodes - 1]->next = NULL;
1419 // update the linkage of the main list anchor pointer
1420 *node_first = sort_array[0];
1424 // now recursively sort the level group structures
1428 if (node->node_group != NULL)
1429 sortTreeInfoBySortFunction(&node->node_group, compare_function);
1435 void sortTreeInfo(TreeInfo **node_first)
1437 sortTreeInfoBySortFunction(node_first, compareTreeInfoEntries);
1441 // ============================================================================
1442 // some stuff from "files.c"
1443 // ============================================================================
1445 #if defined(PLATFORM_WIN32)
1447 #define S_IRGRP S_IRUSR
1450 #define S_IROTH S_IRUSR
1453 #define S_IWGRP S_IWUSR
1456 #define S_IWOTH S_IWUSR
1459 #define S_IXGRP S_IXUSR
1462 #define S_IXOTH S_IXUSR
1465 #define S_IRWXG (S_IRGRP | S_IWGRP | S_IXGRP)
1470 #endif // PLATFORM_WIN32
1472 // file permissions for newly written files
1473 #define MODE_R_ALL (S_IRUSR | S_IRGRP | S_IROTH)
1474 #define MODE_W_ALL (S_IWUSR | S_IWGRP | S_IWOTH)
1475 #define MODE_X_ALL (S_IXUSR | S_IXGRP | S_IXOTH)
1477 #define MODE_W_PRIVATE (S_IWUSR)
1478 #define MODE_W_PUBLIC_FILE (S_IWUSR | S_IWGRP)
1479 #define MODE_W_PUBLIC_DIR (S_IWUSR | S_IWGRP | S_ISGID)
1481 #define DIR_PERMS_PRIVATE (MODE_R_ALL | MODE_X_ALL | MODE_W_PRIVATE)
1482 #define DIR_PERMS_PUBLIC (MODE_R_ALL | MODE_X_ALL | MODE_W_PUBLIC_DIR)
1483 #define DIR_PERMS_PUBLIC_ALL (MODE_R_ALL | MODE_X_ALL | MODE_W_ALL)
1485 #define FILE_PERMS_PRIVATE (MODE_R_ALL | MODE_W_PRIVATE)
1486 #define FILE_PERMS_PUBLIC (MODE_R_ALL | MODE_W_PUBLIC_FILE)
1487 #define FILE_PERMS_PUBLIC_ALL (MODE_R_ALL | MODE_W_ALL)
1490 char *getHomeDir(void)
1492 static char *dir = NULL;
1494 #if defined(PLATFORM_WIN32)
1497 dir = checked_malloc(MAX_PATH + 1);
1499 if (!SUCCEEDED(SHGetFolderPath(NULL, CSIDL_PERSONAL, NULL, 0, dir)))
1502 #elif defined(PLATFORM_UNIX)
1505 if ((dir = getenv("HOME")) == NULL)
1509 if ((pwd = getpwuid(getuid())) != NULL)
1510 dir = getStringCopy(pwd->pw_dir);
1522 char *getCommonDataDir(void)
1524 static char *common_data_dir = NULL;
1526 #if defined(PLATFORM_WIN32)
1527 if (common_data_dir == NULL)
1529 char *dir = checked_malloc(MAX_PATH + 1);
1531 if (SUCCEEDED(SHGetFolderPath(NULL, CSIDL_COMMON_DOCUMENTS, NULL, 0, dir))
1532 && !strEqual(dir, "")) // empty for Windows 95/98
1533 common_data_dir = getPath2(dir, program.userdata_subdir);
1535 common_data_dir = options.rw_base_directory;
1538 if (common_data_dir == NULL)
1539 common_data_dir = options.rw_base_directory;
1542 return common_data_dir;
1545 char *getPersonalDataDir(void)
1547 static char *personal_data_dir = NULL;
1549 #if defined(PLATFORM_MACOSX)
1550 if (personal_data_dir == NULL)
1551 personal_data_dir = getPath2(getHomeDir(), "Documents");
1553 if (personal_data_dir == NULL)
1554 personal_data_dir = getHomeDir();
1557 return personal_data_dir;
1560 char *getUserGameDataDir(void)
1562 static char *user_game_data_dir = NULL;
1564 #if defined(PLATFORM_ANDROID)
1565 if (user_game_data_dir == NULL)
1566 user_game_data_dir = (char *)(SDL_AndroidGetExternalStorageState() &
1567 SDL_ANDROID_EXTERNAL_STORAGE_WRITE ?
1568 SDL_AndroidGetExternalStoragePath() :
1569 SDL_AndroidGetInternalStoragePath());
1571 if (user_game_data_dir == NULL)
1572 user_game_data_dir = getPath2(getPersonalDataDir(),
1573 program.userdata_subdir);
1576 return user_game_data_dir;
1579 char *getSetupDir(void)
1581 return getUserGameDataDir();
1584 static mode_t posix_umask(mode_t mask)
1586 #if defined(PLATFORM_UNIX)
1593 static int posix_mkdir(const char *pathname, mode_t mode)
1595 #if defined(PLATFORM_WIN32)
1596 return mkdir(pathname);
1598 return mkdir(pathname, mode);
1602 static boolean posix_process_running_setgid(void)
1604 #if defined(PLATFORM_UNIX)
1605 return (getgid() != getegid());
1611 void createDirectory(char *dir, char *text, int permission_class)
1613 if (directoryExists(dir))
1616 // leave "other" permissions in umask untouched, but ensure group parts
1617 // of USERDATA_DIR_MODE are not masked
1618 mode_t dir_mode = (permission_class == PERMS_PRIVATE ?
1619 DIR_PERMS_PRIVATE : DIR_PERMS_PUBLIC);
1620 mode_t last_umask = posix_umask(0);
1621 mode_t group_umask = ~(dir_mode & S_IRWXG);
1622 int running_setgid = posix_process_running_setgid();
1624 if (permission_class == PERMS_PUBLIC)
1626 // if we're setgid, protect files against "other"
1627 // else keep umask(0) to make the dir world-writable
1630 posix_umask(last_umask & group_umask);
1632 dir_mode = DIR_PERMS_PUBLIC_ALL;
1635 if (posix_mkdir(dir, dir_mode) != 0)
1636 Warn("cannot create %s directory '%s': %s", text, dir, strerror(errno));
1638 if (permission_class == PERMS_PUBLIC && !running_setgid)
1639 chmod(dir, dir_mode);
1641 posix_umask(last_umask); // restore previous umask
1644 void InitUserDataDirectory(void)
1646 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
1649 void SetFilePermissions(char *filename, int permission_class)
1651 int running_setgid = posix_process_running_setgid();
1652 int perms = (permission_class == PERMS_PRIVATE ?
1653 FILE_PERMS_PRIVATE : FILE_PERMS_PUBLIC);
1655 if (permission_class == PERMS_PUBLIC && !running_setgid)
1656 perms = FILE_PERMS_PUBLIC_ALL;
1658 chmod(filename, perms);
1661 char *getCookie(char *file_type)
1663 static char cookie[MAX_COOKIE_LEN + 1];
1665 if (strlen(program.cookie_prefix) + 1 +
1666 strlen(file_type) + strlen("_FILE_VERSION_x.x") > MAX_COOKIE_LEN)
1667 return "[COOKIE ERROR]"; // should never happen
1669 sprintf(cookie, "%s_%s_FILE_VERSION_%d.%d",
1670 program.cookie_prefix, file_type,
1671 program.version_super, program.version_major);
1676 void fprintFileHeader(FILE *file, char *basename)
1678 char *prefix = "# ";
1681 fprintf_line_with_prefix(file, prefix, sep1, 77);
1682 fprintf(file, "%s%s\n", prefix, basename);
1683 fprintf_line_with_prefix(file, prefix, sep1, 77);
1684 fprintf(file, "\n");
1687 int getFileVersionFromCookieString(const char *cookie)
1689 const char *ptr_cookie1, *ptr_cookie2;
1690 const char *pattern1 = "_FILE_VERSION_";
1691 const char *pattern2 = "?.?";
1692 const int len_cookie = strlen(cookie);
1693 const int len_pattern1 = strlen(pattern1);
1694 const int len_pattern2 = strlen(pattern2);
1695 const int len_pattern = len_pattern1 + len_pattern2;
1696 int version_super, version_major;
1698 if (len_cookie <= len_pattern)
1701 ptr_cookie1 = &cookie[len_cookie - len_pattern];
1702 ptr_cookie2 = &cookie[len_cookie - len_pattern2];
1704 if (strncmp(ptr_cookie1, pattern1, len_pattern1) != 0)
1707 if (ptr_cookie2[0] < '0' || ptr_cookie2[0] > '9' ||
1708 ptr_cookie2[1] != '.' ||
1709 ptr_cookie2[2] < '0' || ptr_cookie2[2] > '9')
1712 version_super = ptr_cookie2[0] - '0';
1713 version_major = ptr_cookie2[2] - '0';
1715 return VERSION_IDENT(version_super, version_major, 0, 0);
1718 boolean checkCookieString(const char *cookie, const char *template)
1720 const char *pattern = "_FILE_VERSION_?.?";
1721 const int len_cookie = strlen(cookie);
1722 const int len_template = strlen(template);
1723 const int len_pattern = strlen(pattern);
1725 if (len_cookie != len_template)
1728 if (strncmp(cookie, template, len_cookie - len_pattern) != 0)
1735 // ----------------------------------------------------------------------------
1736 // setup file list and hash handling functions
1737 // ----------------------------------------------------------------------------
1739 char *getFormattedSetupEntry(char *token, char *value)
1742 static char entry[MAX_LINE_LEN];
1744 // if value is an empty string, just return token without value
1748 // start with the token and some spaces to format output line
1749 sprintf(entry, "%s:", token);
1750 for (i = strlen(entry); i < token_value_position; i++)
1753 // continue with the token's value
1754 strcat(entry, value);
1759 SetupFileList *newSetupFileList(char *token, char *value)
1761 SetupFileList *new = checked_malloc(sizeof(SetupFileList));
1763 new->token = getStringCopy(token);
1764 new->value = getStringCopy(value);
1771 void freeSetupFileList(SetupFileList *list)
1776 checked_free(list->token);
1777 checked_free(list->value);
1780 freeSetupFileList(list->next);
1785 char *getListEntry(SetupFileList *list, char *token)
1790 if (strEqual(list->token, token))
1793 return getListEntry(list->next, token);
1796 SetupFileList *setListEntry(SetupFileList *list, char *token, char *value)
1801 if (strEqual(list->token, token))
1803 checked_free(list->value);
1805 list->value = getStringCopy(value);
1809 else if (list->next == NULL)
1810 return (list->next = newSetupFileList(token, value));
1812 return setListEntry(list->next, token, value);
1815 SetupFileList *addListEntry(SetupFileList *list, char *token, char *value)
1820 if (list->next == NULL)
1821 return (list->next = newSetupFileList(token, value));
1823 return addListEntry(list->next, token, value);
1826 #if ENABLE_UNUSED_CODE
1828 static void printSetupFileList(SetupFileList *list)
1833 Debug("setup:printSetupFileList", "token: '%s'", list->token);
1834 Debug("setup:printSetupFileList", "value: '%s'", list->value);
1836 printSetupFileList(list->next);
1842 DEFINE_HASHTABLE_INSERT(insert_hash_entry, char, char);
1843 DEFINE_HASHTABLE_SEARCH(search_hash_entry, char, char);
1844 DEFINE_HASHTABLE_CHANGE(change_hash_entry, char, char);
1845 DEFINE_HASHTABLE_REMOVE(remove_hash_entry, char, char);
1847 #define insert_hash_entry hashtable_insert
1848 #define search_hash_entry hashtable_search
1849 #define change_hash_entry hashtable_change
1850 #define remove_hash_entry hashtable_remove
1853 unsigned int get_hash_from_key(void *key)
1858 This algorithm (k=33) was first reported by Dan Bernstein many years ago in
1859 'comp.lang.c'. Another version of this algorithm (now favored by Bernstein)
1860 uses XOR: hash(i) = hash(i - 1) * 33 ^ str[i]; the magic of number 33 (why
1861 it works better than many other constants, prime or not) has never been
1862 adequately explained.
1864 If you just want to have a good hash function, and cannot wait, djb2
1865 is one of the best string hash functions i know. It has excellent
1866 distribution and speed on many different sets of keys and table sizes.
1867 You are not likely to do better with one of the "well known" functions
1868 such as PJW, K&R, etc.
1870 Ozan (oz) Yigit [http://www.cs.yorku.ca/~oz/hash.html]
1873 char *str = (char *)key;
1874 unsigned int hash = 5381;
1877 while ((c = *str++))
1878 hash = ((hash << 5) + hash) + c; // hash * 33 + c
1883 static int keys_are_equal(void *key1, void *key2)
1885 return (strEqual((char *)key1, (char *)key2));
1888 SetupFileHash *newSetupFileHash(void)
1890 SetupFileHash *new_hash =
1891 create_hashtable(16, 0.75, get_hash_from_key, keys_are_equal);
1893 if (new_hash == NULL)
1894 Fail("create_hashtable() failed -- out of memory");
1899 void freeSetupFileHash(SetupFileHash *hash)
1904 hashtable_destroy(hash, 1); // 1 == also free values stored in hash
1907 char *getHashEntry(SetupFileHash *hash, char *token)
1912 return search_hash_entry(hash, token);
1915 void setHashEntry(SetupFileHash *hash, char *token, char *value)
1922 value_copy = getStringCopy(value);
1924 // change value; if it does not exist, insert it as new
1925 if (!change_hash_entry(hash, token, value_copy))
1926 if (!insert_hash_entry(hash, getStringCopy(token), value_copy))
1927 Fail("cannot insert into hash -- aborting");
1930 char *removeHashEntry(SetupFileHash *hash, char *token)
1935 return remove_hash_entry(hash, token);
1938 #if ENABLE_UNUSED_CODE
1940 static void printSetupFileHash(SetupFileHash *hash)
1942 BEGIN_HASH_ITERATION(hash, itr)
1944 Debug("setup:printSetupFileHash", "token: '%s'", HASH_ITERATION_TOKEN(itr));
1945 Debug("setup:printSetupFileHash", "value: '%s'", HASH_ITERATION_VALUE(itr));
1947 END_HASH_ITERATION(hash, itr)
1952 #define ALLOW_TOKEN_VALUE_SEPARATOR_BEING_WHITESPACE 1
1953 #define CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING 0
1954 #define CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH 0
1956 static boolean token_value_separator_found = FALSE;
1957 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
1958 static boolean token_value_separator_warning = FALSE;
1960 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
1961 static boolean token_already_exists_warning = FALSE;
1964 static boolean getTokenValueFromSetupLineExt(char *line,
1965 char **token_ptr, char **value_ptr,
1966 char *filename, char *line_raw,
1968 boolean separator_required)
1970 static char line_copy[MAX_LINE_LEN + 1], line_raw_copy[MAX_LINE_LEN + 1];
1971 char *token, *value, *line_ptr;
1973 // when externally invoked via ReadTokenValueFromLine(), copy line buffers
1974 if (line_raw == NULL)
1976 strncpy(line_copy, line, MAX_LINE_LEN);
1977 line_copy[MAX_LINE_LEN] = '\0';
1980 strcpy(line_raw_copy, line_copy);
1981 line_raw = line_raw_copy;
1984 // cut trailing comment from input line
1985 for (line_ptr = line; *line_ptr; line_ptr++)
1987 if (*line_ptr == '#')
1994 // cut trailing whitespaces from input line
1995 for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
1996 if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
1999 // ignore empty lines
2003 // cut leading whitespaces from token
2004 for (token = line; *token; token++)
2005 if (*token != ' ' && *token != '\t')
2008 // start with empty value as reliable default
2011 token_value_separator_found = FALSE;
2013 // find end of token to determine start of value
2014 for (line_ptr = token; *line_ptr; line_ptr++)
2016 // first look for an explicit token/value separator, like ':' or '='
2017 if (*line_ptr == ':' || *line_ptr == '=')
2019 *line_ptr = '\0'; // terminate token string
2020 value = line_ptr + 1; // set beginning of value
2022 token_value_separator_found = TRUE;
2028 #if ALLOW_TOKEN_VALUE_SEPARATOR_BEING_WHITESPACE
2029 // fallback: if no token/value separator found, also allow whitespaces
2030 if (!token_value_separator_found && !separator_required)
2032 for (line_ptr = token; *line_ptr; line_ptr++)
2034 if (*line_ptr == ' ' || *line_ptr == '\t')
2036 *line_ptr = '\0'; // terminate token string
2037 value = line_ptr + 1; // set beginning of value
2039 token_value_separator_found = TRUE;
2045 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2046 if (token_value_separator_found)
2048 if (!token_value_separator_warning)
2050 Debug("setup", "---");
2052 if (filename != NULL)
2054 Debug("setup", "missing token/value separator(s) in config file:");
2055 Debug("setup", "- config file: '%s'", filename);
2059 Debug("setup", "missing token/value separator(s):");
2062 token_value_separator_warning = TRUE;
2065 if (filename != NULL)
2066 Debug("setup", "- line %d: '%s'", line_nr, line_raw);
2068 Debug("setup", "- line: '%s'", line_raw);
2074 // cut trailing whitespaces from token
2075 for (line_ptr = &token[strlen(token)]; line_ptr >= token; line_ptr--)
2076 if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
2079 // cut leading whitespaces from value
2080 for (; *value; value++)
2081 if (*value != ' ' && *value != '\t')
2090 boolean getTokenValueFromSetupLine(char *line, char **token, char **value)
2092 // while the internal (old) interface does not require a token/value
2093 // separator (for downwards compatibility with existing files which
2094 // don't use them), it is mandatory for the external (new) interface
2096 return getTokenValueFromSetupLineExt(line, token, value, NULL, NULL, 0, TRUE);
2099 static boolean loadSetupFileData(void *setup_file_data, char *filename,
2100 boolean top_recursion_level, boolean is_hash)
2102 static SetupFileHash *include_filename_hash = NULL;
2103 char line[MAX_LINE_LEN], line_raw[MAX_LINE_LEN], previous_line[MAX_LINE_LEN];
2104 char *token, *value, *line_ptr;
2105 void *insert_ptr = NULL;
2106 boolean read_continued_line = FALSE;
2108 int line_nr = 0, token_count = 0, include_count = 0;
2110 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2111 token_value_separator_warning = FALSE;
2114 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2115 token_already_exists_warning = FALSE;
2118 if (!(file = openFile(filename, MODE_READ)))
2120 #if DEBUG_NO_CONFIG_FILE
2121 Debug("setup", "cannot open configuration file '%s'", filename);
2127 // use "insert pointer" to store list end for constant insertion complexity
2129 insert_ptr = setup_file_data;
2131 // on top invocation, create hash to mark included files (to prevent loops)
2132 if (top_recursion_level)
2133 include_filename_hash = newSetupFileHash();
2135 // mark this file as already included (to prevent including it again)
2136 setHashEntry(include_filename_hash, getBaseNamePtr(filename), "true");
2138 while (!checkEndOfFile(file))
2140 // read next line of input file
2141 if (!getStringFromFile(file, line, MAX_LINE_LEN))
2144 // check if line was completely read and is terminated by line break
2145 if (strlen(line) > 0 && line[strlen(line) - 1] == '\n')
2148 // cut trailing line break (this can be newline and/or carriage return)
2149 for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
2150 if ((*line_ptr == '\n' || *line_ptr == '\r') && *(line_ptr + 1) == '\0')
2153 // copy raw input line for later use (mainly debugging output)
2154 strcpy(line_raw, line);
2156 if (read_continued_line)
2158 // append new line to existing line, if there is enough space
2159 if (strlen(previous_line) + strlen(line_ptr) < MAX_LINE_LEN)
2160 strcat(previous_line, line_ptr);
2162 strcpy(line, previous_line); // copy storage buffer to line
2164 read_continued_line = FALSE;
2167 // if the last character is '\', continue at next line
2168 if (strlen(line) > 0 && line[strlen(line) - 1] == '\\')
2170 line[strlen(line) - 1] = '\0'; // cut off trailing backslash
2171 strcpy(previous_line, line); // copy line to storage buffer
2173 read_continued_line = TRUE;
2178 if (!getTokenValueFromSetupLineExt(line, &token, &value, filename,
2179 line_raw, line_nr, FALSE))
2184 if (strEqual(token, "include"))
2186 if (getHashEntry(include_filename_hash, value) == NULL)
2188 char *basepath = getBasePath(filename);
2189 char *basename = getBaseName(value);
2190 char *filename_include = getPath2(basepath, basename);
2192 loadSetupFileData(setup_file_data, filename_include, FALSE, is_hash);
2196 free(filename_include);
2202 Warn("ignoring already processed file '%s'", value);
2209 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2211 getHashEntry((SetupFileHash *)setup_file_data, token);
2213 if (old_value != NULL)
2215 if (!token_already_exists_warning)
2217 Debug("setup", "---");
2218 Debug("setup", "duplicate token(s) found in config file:");
2219 Debug("setup", "- config file: '%s'", filename);
2221 token_already_exists_warning = TRUE;
2224 Debug("setup", "- token: '%s' (in line %d)", token, line_nr);
2225 Debug("setup", " old value: '%s'", old_value);
2226 Debug("setup", " new value: '%s'", value);
2230 setHashEntry((SetupFileHash *)setup_file_data, token, value);
2234 insert_ptr = addListEntry((SetupFileList *)insert_ptr, token, value);
2244 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2245 if (token_value_separator_warning)
2246 Debug("setup", "---");
2249 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2250 if (token_already_exists_warning)
2251 Debug("setup", "---");
2254 if (token_count == 0 && include_count == 0)
2255 Warn("configuration file '%s' is empty", filename);
2257 if (top_recursion_level)
2258 freeSetupFileHash(include_filename_hash);
2263 static void saveSetupFileHash(SetupFileHash *hash, char *filename)
2267 if (!(file = fopen(filename, MODE_WRITE)))
2269 Warn("cannot write configuration file '%s'", filename);
2274 BEGIN_HASH_ITERATION(hash, itr)
2276 fprintf(file, "%s\n", getFormattedSetupEntry(HASH_ITERATION_TOKEN(itr),
2277 HASH_ITERATION_VALUE(itr)));
2279 END_HASH_ITERATION(hash, itr)
2284 SetupFileList *loadSetupFileList(char *filename)
2286 SetupFileList *setup_file_list = newSetupFileList("", "");
2287 SetupFileList *first_valid_list_entry;
2289 if (!loadSetupFileData(setup_file_list, filename, TRUE, FALSE))
2291 freeSetupFileList(setup_file_list);
2296 first_valid_list_entry = setup_file_list->next;
2298 // free empty list header
2299 setup_file_list->next = NULL;
2300 freeSetupFileList(setup_file_list);
2302 return first_valid_list_entry;
2305 SetupFileHash *loadSetupFileHash(char *filename)
2307 SetupFileHash *setup_file_hash = newSetupFileHash();
2309 if (!loadSetupFileData(setup_file_hash, filename, TRUE, TRUE))
2311 freeSetupFileHash(setup_file_hash);
2316 return setup_file_hash;
2320 // ============================================================================
2322 // ============================================================================
2324 #define TOKEN_STR_LAST_LEVEL_SERIES "last_level_series"
2325 #define TOKEN_STR_LAST_PLAYED_LEVEL "last_played_level"
2326 #define TOKEN_STR_HANDICAP_LEVEL "handicap_level"
2328 // level directory info
2329 #define LEVELINFO_TOKEN_IDENTIFIER 0
2330 #define LEVELINFO_TOKEN_NAME 1
2331 #define LEVELINFO_TOKEN_NAME_SORTING 2
2332 #define LEVELINFO_TOKEN_AUTHOR 3
2333 #define LEVELINFO_TOKEN_YEAR 4
2334 #define LEVELINFO_TOKEN_PROGRAM_TITLE 5
2335 #define LEVELINFO_TOKEN_PROGRAM_COPYRIGHT 6
2336 #define LEVELINFO_TOKEN_PROGRAM_COMPANY 7
2337 #define LEVELINFO_TOKEN_IMPORTED_FROM 8
2338 #define LEVELINFO_TOKEN_IMPORTED_BY 9
2339 #define LEVELINFO_TOKEN_TESTED_BY 10
2340 #define LEVELINFO_TOKEN_LEVELS 11
2341 #define LEVELINFO_TOKEN_FIRST_LEVEL 12
2342 #define LEVELINFO_TOKEN_SORT_PRIORITY 13
2343 #define LEVELINFO_TOKEN_LATEST_ENGINE 14
2344 #define LEVELINFO_TOKEN_LEVEL_GROUP 15
2345 #define LEVELINFO_TOKEN_READONLY 16
2346 #define LEVELINFO_TOKEN_GRAPHICS_SET_ECS 17
2347 #define LEVELINFO_TOKEN_GRAPHICS_SET_AGA 18
2348 #define LEVELINFO_TOKEN_GRAPHICS_SET 19
2349 #define LEVELINFO_TOKEN_SOUNDS_SET_DEFAULT 20
2350 #define LEVELINFO_TOKEN_SOUNDS_SET_LOWPASS 21
2351 #define LEVELINFO_TOKEN_SOUNDS_SET 22
2352 #define LEVELINFO_TOKEN_MUSIC_SET 23
2353 #define LEVELINFO_TOKEN_FILENAME 24
2354 #define LEVELINFO_TOKEN_FILETYPE 25
2355 #define LEVELINFO_TOKEN_SPECIAL_FLAGS 26
2356 #define LEVELINFO_TOKEN_HANDICAP 27
2357 #define LEVELINFO_TOKEN_SKIP_LEVELS 28
2358 #define LEVELINFO_TOKEN_USE_EMC_TILES 29
2360 #define NUM_LEVELINFO_TOKENS 30
2362 static LevelDirTree ldi;
2364 static struct TokenInfo levelinfo_tokens[] =
2366 // level directory info
2367 { TYPE_STRING, &ldi.identifier, "identifier" },
2368 { TYPE_STRING, &ldi.name, "name" },
2369 { TYPE_STRING, &ldi.name_sorting, "name_sorting" },
2370 { TYPE_STRING, &ldi.author, "author" },
2371 { TYPE_STRING, &ldi.year, "year" },
2372 { TYPE_STRING, &ldi.program_title, "program_title" },
2373 { TYPE_STRING, &ldi.program_copyright, "program_copyright" },
2374 { TYPE_STRING, &ldi.program_company, "program_company" },
2375 { TYPE_STRING, &ldi.imported_from, "imported_from" },
2376 { TYPE_STRING, &ldi.imported_by, "imported_by" },
2377 { TYPE_STRING, &ldi.tested_by, "tested_by" },
2378 { TYPE_INTEGER, &ldi.levels, "levels" },
2379 { TYPE_INTEGER, &ldi.first_level, "first_level" },
2380 { TYPE_INTEGER, &ldi.sort_priority, "sort_priority" },
2381 { TYPE_BOOLEAN, &ldi.latest_engine, "latest_engine" },
2382 { TYPE_BOOLEAN, &ldi.level_group, "level_group" },
2383 { TYPE_BOOLEAN, &ldi.readonly, "readonly" },
2384 { TYPE_STRING, &ldi.graphics_set_ecs, "graphics_set.ecs" },
2385 { TYPE_STRING, &ldi.graphics_set_aga, "graphics_set.aga" },
2386 { TYPE_STRING, &ldi.graphics_set, "graphics_set" },
2387 { TYPE_STRING, &ldi.sounds_set_default,"sounds_set.default" },
2388 { TYPE_STRING, &ldi.sounds_set_lowpass,"sounds_set.lowpass" },
2389 { TYPE_STRING, &ldi.sounds_set, "sounds_set" },
2390 { TYPE_STRING, &ldi.music_set, "music_set" },
2391 { TYPE_STRING, &ldi.level_filename, "filename" },
2392 { TYPE_STRING, &ldi.level_filetype, "filetype" },
2393 { TYPE_STRING, &ldi.special_flags, "special_flags" },
2394 { TYPE_BOOLEAN, &ldi.handicap, "handicap" },
2395 { TYPE_BOOLEAN, &ldi.skip_levels, "skip_levels" },
2396 { TYPE_BOOLEAN, &ldi.use_emc_tiles, "use_emc_tiles" }
2399 static struct TokenInfo artworkinfo_tokens[] =
2401 // artwork directory info
2402 { TYPE_STRING, &ldi.identifier, "identifier" },
2403 { TYPE_STRING, &ldi.subdir, "subdir" },
2404 { TYPE_STRING, &ldi.name, "name" },
2405 { TYPE_STRING, &ldi.name_sorting, "name_sorting" },
2406 { TYPE_STRING, &ldi.author, "author" },
2407 { TYPE_STRING, &ldi.program_title, "program_title" },
2408 { TYPE_STRING, &ldi.program_copyright, "program_copyright" },
2409 { TYPE_STRING, &ldi.program_company, "program_company" },
2410 { TYPE_INTEGER, &ldi.sort_priority, "sort_priority" },
2411 { TYPE_STRING, &ldi.basepath, "basepath" },
2412 { TYPE_STRING, &ldi.fullpath, "fullpath" },
2413 { TYPE_BOOLEAN, &ldi.in_user_dir, "in_user_dir" },
2414 { TYPE_INTEGER, &ldi.color, "color" },
2415 { TYPE_STRING, &ldi.class_desc, "class_desc" },
2420 static char *optional_tokens[] =
2423 "program_copyright",
2429 static void setTreeInfoToDefaults(TreeInfo *ti, int type)
2433 ti->node_top = (ti->type == TREE_TYPE_LEVEL_DIR ? &leveldir_first :
2434 ti->type == TREE_TYPE_GRAPHICS_DIR ? &artwork.gfx_first :
2435 ti->type == TREE_TYPE_SOUNDS_DIR ? &artwork.snd_first :
2436 ti->type == TREE_TYPE_MUSIC_DIR ? &artwork.mus_first :
2439 ti->node_parent = NULL;
2440 ti->node_group = NULL;
2447 ti->fullpath = NULL;
2448 ti->basepath = NULL;
2449 ti->identifier = NULL;
2450 ti->name = getStringCopy(ANONYMOUS_NAME);
2451 ti->name_sorting = NULL;
2452 ti->author = getStringCopy(ANONYMOUS_NAME);
2455 ti->program_title = NULL;
2456 ti->program_copyright = NULL;
2457 ti->program_company = NULL;
2459 ti->sort_priority = LEVELCLASS_UNDEFINED; // default: least priority
2460 ti->latest_engine = FALSE; // default: get from level
2461 ti->parent_link = FALSE;
2462 ti->in_user_dir = FALSE;
2463 ti->user_defined = FALSE;
2465 ti->class_desc = NULL;
2467 ti->infotext = getStringCopy(TREE_INFOTEXT(ti->type));
2469 if (ti->type == TREE_TYPE_LEVEL_DIR)
2471 ti->imported_from = NULL;
2472 ti->imported_by = NULL;
2473 ti->tested_by = NULL;
2475 ti->graphics_set_ecs = NULL;
2476 ti->graphics_set_aga = NULL;
2477 ti->graphics_set = NULL;
2478 ti->sounds_set_default = NULL;
2479 ti->sounds_set_lowpass = NULL;
2480 ti->sounds_set = NULL;
2481 ti->music_set = NULL;
2482 ti->graphics_path = getStringCopy(UNDEFINED_FILENAME);
2483 ti->sounds_path = getStringCopy(UNDEFINED_FILENAME);
2484 ti->music_path = getStringCopy(UNDEFINED_FILENAME);
2486 ti->level_filename = NULL;
2487 ti->level_filetype = NULL;
2489 ti->special_flags = NULL;
2492 ti->first_level = 0;
2494 ti->level_group = FALSE;
2495 ti->handicap_level = 0;
2496 ti->readonly = TRUE;
2497 ti->handicap = TRUE;
2498 ti->skip_levels = FALSE;
2500 ti->use_emc_tiles = FALSE;
2504 static void setTreeInfoToDefaultsFromParent(TreeInfo *ti, TreeInfo *parent)
2508 Warn("setTreeInfoToDefaultsFromParent(): parent == NULL");
2510 setTreeInfoToDefaults(ti, TREE_TYPE_UNDEFINED);
2515 // copy all values from the parent structure
2517 ti->type = parent->type;
2519 ti->node_top = parent->node_top;
2520 ti->node_parent = parent;
2521 ti->node_group = NULL;
2528 ti->fullpath = NULL;
2529 ti->basepath = NULL;
2530 ti->identifier = NULL;
2531 ti->name = getStringCopy(ANONYMOUS_NAME);
2532 ti->name_sorting = NULL;
2533 ti->author = getStringCopy(parent->author);
2534 ti->year = getStringCopy(parent->year);
2536 ti->program_title = getStringCopy(parent->program_title);
2537 ti->program_copyright = getStringCopy(parent->program_copyright);
2538 ti->program_company = getStringCopy(parent->program_company);
2540 ti->sort_priority = parent->sort_priority;
2541 ti->latest_engine = parent->latest_engine;
2542 ti->parent_link = FALSE;
2543 ti->in_user_dir = parent->in_user_dir;
2544 ti->user_defined = parent->user_defined;
2545 ti->color = parent->color;
2546 ti->class_desc = getStringCopy(parent->class_desc);
2548 ti->infotext = getStringCopy(parent->infotext);
2550 if (ti->type == TREE_TYPE_LEVEL_DIR)
2552 ti->imported_from = getStringCopy(parent->imported_from);
2553 ti->imported_by = getStringCopy(parent->imported_by);
2554 ti->tested_by = getStringCopy(parent->tested_by);
2556 ti->graphics_set_ecs = getStringCopy(parent->graphics_set_ecs);
2557 ti->graphics_set_aga = getStringCopy(parent->graphics_set_aga);
2558 ti->graphics_set = getStringCopy(parent->graphics_set);
2559 ti->sounds_set_default = getStringCopy(parent->sounds_set_default);
2560 ti->sounds_set_lowpass = getStringCopy(parent->sounds_set_lowpass);
2561 ti->sounds_set = getStringCopy(parent->sounds_set);
2562 ti->music_set = getStringCopy(parent->music_set);
2563 ti->graphics_path = getStringCopy(UNDEFINED_FILENAME);
2564 ti->sounds_path = getStringCopy(UNDEFINED_FILENAME);
2565 ti->music_path = getStringCopy(UNDEFINED_FILENAME);
2567 ti->level_filename = getStringCopy(parent->level_filename);
2568 ti->level_filetype = getStringCopy(parent->level_filetype);
2570 ti->special_flags = getStringCopy(parent->special_flags);
2572 ti->levels = parent->levels;
2573 ti->first_level = parent->first_level;
2574 ti->last_level = parent->last_level;
2575 ti->level_group = FALSE;
2576 ti->handicap_level = parent->handicap_level;
2577 ti->readonly = parent->readonly;
2578 ti->handicap = parent->handicap;
2579 ti->skip_levels = parent->skip_levels;
2581 ti->use_emc_tiles = parent->use_emc_tiles;
2585 static TreeInfo *getTreeInfoCopy(TreeInfo *ti)
2587 TreeInfo *ti_copy = newTreeInfo();
2589 // copy all values from the original structure
2591 ti_copy->type = ti->type;
2593 ti_copy->node_top = ti->node_top;
2594 ti_copy->node_parent = ti->node_parent;
2595 ti_copy->node_group = ti->node_group;
2596 ti_copy->next = ti->next;
2598 ti_copy->cl_first = ti->cl_first;
2599 ti_copy->cl_cursor = ti->cl_cursor;
2601 ti_copy->subdir = getStringCopy(ti->subdir);
2602 ti_copy->fullpath = getStringCopy(ti->fullpath);
2603 ti_copy->basepath = getStringCopy(ti->basepath);
2604 ti_copy->identifier = getStringCopy(ti->identifier);
2605 ti_copy->name = getStringCopy(ti->name);
2606 ti_copy->name_sorting = getStringCopy(ti->name_sorting);
2607 ti_copy->author = getStringCopy(ti->author);
2608 ti_copy->year = getStringCopy(ti->year);
2610 ti_copy->program_title = getStringCopy(ti->program_title);
2611 ti_copy->program_copyright = getStringCopy(ti->program_copyright);
2612 ti_copy->program_company = getStringCopy(ti->program_company);
2614 ti_copy->imported_from = getStringCopy(ti->imported_from);
2615 ti_copy->imported_by = getStringCopy(ti->imported_by);
2616 ti_copy->tested_by = getStringCopy(ti->tested_by);
2618 ti_copy->graphics_set_ecs = getStringCopy(ti->graphics_set_ecs);
2619 ti_copy->graphics_set_aga = getStringCopy(ti->graphics_set_aga);
2620 ti_copy->graphics_set = getStringCopy(ti->graphics_set);
2621 ti_copy->sounds_set_default = getStringCopy(ti->sounds_set_default);
2622 ti_copy->sounds_set_lowpass = getStringCopy(ti->sounds_set_lowpass);
2623 ti_copy->sounds_set = getStringCopy(ti->sounds_set);
2624 ti_copy->music_set = getStringCopy(ti->music_set);
2625 ti_copy->graphics_path = getStringCopy(ti->graphics_path);
2626 ti_copy->sounds_path = getStringCopy(ti->sounds_path);
2627 ti_copy->music_path = getStringCopy(ti->music_path);
2629 ti_copy->level_filename = getStringCopy(ti->level_filename);
2630 ti_copy->level_filetype = getStringCopy(ti->level_filetype);
2632 ti_copy->special_flags = getStringCopy(ti->special_flags);
2634 ti_copy->levels = ti->levels;
2635 ti_copy->first_level = ti->first_level;
2636 ti_copy->last_level = ti->last_level;
2637 ti_copy->sort_priority = ti->sort_priority;
2639 ti_copy->latest_engine = ti->latest_engine;
2641 ti_copy->level_group = ti->level_group;
2642 ti_copy->parent_link = ti->parent_link;
2643 ti_copy->in_user_dir = ti->in_user_dir;
2644 ti_copy->user_defined = ti->user_defined;
2645 ti_copy->readonly = ti->readonly;
2646 ti_copy->handicap = ti->handicap;
2647 ti_copy->skip_levels = ti->skip_levels;
2649 ti_copy->use_emc_tiles = ti->use_emc_tiles;
2651 ti_copy->color = ti->color;
2652 ti_copy->class_desc = getStringCopy(ti->class_desc);
2653 ti_copy->handicap_level = ti->handicap_level;
2655 ti_copy->infotext = getStringCopy(ti->infotext);
2660 void freeTreeInfo(TreeInfo *ti)
2665 checked_free(ti->subdir);
2666 checked_free(ti->fullpath);
2667 checked_free(ti->basepath);
2668 checked_free(ti->identifier);
2670 checked_free(ti->name);
2671 checked_free(ti->name_sorting);
2672 checked_free(ti->author);
2673 checked_free(ti->year);
2675 checked_free(ti->program_title);
2676 checked_free(ti->program_copyright);
2677 checked_free(ti->program_company);
2679 checked_free(ti->class_desc);
2681 checked_free(ti->infotext);
2683 if (ti->type == TREE_TYPE_LEVEL_DIR)
2685 checked_free(ti->imported_from);
2686 checked_free(ti->imported_by);
2687 checked_free(ti->tested_by);
2689 checked_free(ti->graphics_set_ecs);
2690 checked_free(ti->graphics_set_aga);
2691 checked_free(ti->graphics_set);
2692 checked_free(ti->sounds_set_default);
2693 checked_free(ti->sounds_set_lowpass);
2694 checked_free(ti->sounds_set);
2695 checked_free(ti->music_set);
2697 checked_free(ti->graphics_path);
2698 checked_free(ti->sounds_path);
2699 checked_free(ti->music_path);
2701 checked_free(ti->level_filename);
2702 checked_free(ti->level_filetype);
2704 checked_free(ti->special_flags);
2707 // recursively free child node
2709 freeTreeInfo(ti->node_group);
2711 // recursively free next node
2713 freeTreeInfo(ti->next);
2718 void setSetupInfo(struct TokenInfo *token_info,
2719 int token_nr, char *token_value)
2721 int token_type = token_info[token_nr].type;
2722 void *setup_value = token_info[token_nr].value;
2724 if (token_value == NULL)
2727 // set setup field to corresponding token value
2732 *(boolean *)setup_value = get_boolean_from_string(token_value);
2736 *(int *)setup_value = get_switch3_from_string(token_value);
2740 *(Key *)setup_value = getKeyFromKeyName(token_value);
2744 *(Key *)setup_value = getKeyFromX11KeyName(token_value);
2748 *(int *)setup_value = get_integer_from_string(token_value);
2752 checked_free(*(char **)setup_value);
2753 *(char **)setup_value = getStringCopy(token_value);
2757 *(int *)setup_value = get_player_nr_from_string(token_value);
2765 static int compareTreeInfoEntries(const void *object1, const void *object2)
2767 const TreeInfo *entry1 = *((TreeInfo **)object1);
2768 const TreeInfo *entry2 = *((TreeInfo **)object2);
2769 int class_sorting1 = 0, class_sorting2 = 0;
2772 if (entry1->type == TREE_TYPE_LEVEL_DIR)
2774 class_sorting1 = LEVELSORTING(entry1);
2775 class_sorting2 = LEVELSORTING(entry2);
2777 else if (entry1->type == TREE_TYPE_GRAPHICS_DIR ||
2778 entry1->type == TREE_TYPE_SOUNDS_DIR ||
2779 entry1->type == TREE_TYPE_MUSIC_DIR)
2781 class_sorting1 = ARTWORKSORTING(entry1);
2782 class_sorting2 = ARTWORKSORTING(entry2);
2785 if (entry1->parent_link || entry2->parent_link)
2786 compare_result = (entry1->parent_link ? -1 : +1);
2787 else if (entry1->sort_priority == entry2->sort_priority)
2789 char *name1 = getStringToLower(entry1->name_sorting);
2790 char *name2 = getStringToLower(entry2->name_sorting);
2792 compare_result = strcmp(name1, name2);
2797 else if (class_sorting1 == class_sorting2)
2798 compare_result = entry1->sort_priority - entry2->sort_priority;
2800 compare_result = class_sorting1 - class_sorting2;
2802 return compare_result;
2805 static TreeInfo *createParentTreeInfoNode(TreeInfo *node_parent)
2809 if (node_parent == NULL)
2812 ti_new = newTreeInfo();
2813 setTreeInfoToDefaults(ti_new, node_parent->type);
2815 ti_new->node_parent = node_parent;
2816 ti_new->parent_link = TRUE;
2818 setString(&ti_new->identifier, node_parent->identifier);
2819 setString(&ti_new->name, ".. (parent directory)");
2820 setString(&ti_new->name_sorting, ti_new->name);
2822 setString(&ti_new->subdir, STRING_PARENT_DIRECTORY);
2823 setString(&ti_new->fullpath, node_parent->fullpath);
2825 ti_new->sort_priority = node_parent->sort_priority;
2826 ti_new->latest_engine = node_parent->latest_engine;
2828 setString(&ti_new->class_desc, getLevelClassDescription(ti_new));
2830 pushTreeInfo(&node_parent->node_group, ti_new);
2835 static TreeInfo *createTopTreeInfoNode(TreeInfo *node_first)
2837 TreeInfo *ti_new, *ti_new2;
2839 if (node_first == NULL)
2842 ti_new = newTreeInfo();
2843 setTreeInfoToDefaults(ti_new, TREE_TYPE_LEVEL_DIR);
2845 ti_new->node_parent = NULL;
2846 ti_new->parent_link = FALSE;
2848 setString(&ti_new->identifier, node_first->identifier);
2849 setString(&ti_new->name, "level sets");
2850 setString(&ti_new->name_sorting, ti_new->name);
2852 setString(&ti_new->subdir, STRING_TOP_DIRECTORY);
2853 setString(&ti_new->fullpath, ".");
2855 ti_new->sort_priority = node_first->sort_priority;;
2856 ti_new->latest_engine = node_first->latest_engine;
2858 setString(&ti_new->class_desc, "level sets");
2860 ti_new->node_group = node_first;
2861 ti_new->level_group = TRUE;
2863 ti_new2 = createParentTreeInfoNode(ti_new);
2865 setString(&ti_new2->name, ".. (main menu)");
2866 setString(&ti_new2->name_sorting, ti_new2->name);
2872 // ----------------------------------------------------------------------------
2873 // functions for handling level and custom artwork info cache
2874 // ----------------------------------------------------------------------------
2876 static void LoadArtworkInfoCache(void)
2878 InitCacheDirectory();
2880 if (artworkinfo_cache_old == NULL)
2882 char *filename = getPath2(getCacheDir(), ARTWORKINFO_CACHE_FILE);
2884 // try to load artwork info hash from already existing cache file
2885 artworkinfo_cache_old = loadSetupFileHash(filename);
2887 // if no artwork info cache file was found, start with empty hash
2888 if (artworkinfo_cache_old == NULL)
2889 artworkinfo_cache_old = newSetupFileHash();
2894 if (artworkinfo_cache_new == NULL)
2895 artworkinfo_cache_new = newSetupFileHash();
2898 static void SaveArtworkInfoCache(void)
2900 char *filename = getPath2(getCacheDir(), ARTWORKINFO_CACHE_FILE);
2902 InitCacheDirectory();
2904 saveSetupFileHash(artworkinfo_cache_new, filename);
2909 static char *getCacheTokenPrefix(char *prefix1, char *prefix2)
2911 static char *prefix = NULL;
2913 checked_free(prefix);
2915 prefix = getStringCat2WithSeparator(prefix1, prefix2, ".");
2920 // (identical to above function, but separate string buffer needed -- nasty)
2921 static char *getCacheToken(char *prefix, char *suffix)
2923 static char *token = NULL;
2925 checked_free(token);
2927 token = getStringCat2WithSeparator(prefix, suffix, ".");
2932 static char *getFileTimestampString(char *filename)
2934 return getStringCopy(i_to_a(getFileTimestampEpochSeconds(filename)));
2937 static boolean modifiedFileTimestamp(char *filename, char *timestamp_string)
2939 struct stat file_status;
2941 if (timestamp_string == NULL)
2944 if (stat(filename, &file_status) != 0) // cannot stat file
2947 return (file_status.st_mtime != atoi(timestamp_string));
2950 static TreeInfo *getArtworkInfoCacheEntry(LevelDirTree *level_node, int type)
2952 char *identifier = level_node->subdir;
2953 char *type_string = ARTWORK_DIRECTORY(type);
2954 char *token_prefix = getCacheTokenPrefix(type_string, identifier);
2955 char *token_main = getCacheToken(token_prefix, "CACHED");
2956 char *cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
2957 boolean cached = (cache_entry != NULL && strEqual(cache_entry, "true"));
2958 TreeInfo *artwork_info = NULL;
2960 if (!use_artworkinfo_cache)
2963 if (optional_tokens_hash == NULL)
2967 // create hash from list of optional tokens (for quick access)
2968 optional_tokens_hash = newSetupFileHash();
2969 for (i = 0; optional_tokens[i] != NULL; i++)
2970 setHashEntry(optional_tokens_hash, optional_tokens[i], "");
2977 artwork_info = newTreeInfo();
2978 setTreeInfoToDefaults(artwork_info, type);
2980 // set all structure fields according to the token/value pairs
2981 ldi = *artwork_info;
2982 for (i = 0; artworkinfo_tokens[i].type != -1; i++)
2984 char *token_suffix = artworkinfo_tokens[i].text;
2985 char *token = getCacheToken(token_prefix, token_suffix);
2986 char *value = getHashEntry(artworkinfo_cache_old, token);
2988 (getHashEntry(optional_tokens_hash, token_suffix) != NULL);
2990 setSetupInfo(artworkinfo_tokens, i, value);
2992 // check if cache entry for this item is mandatory, but missing
2993 if (value == NULL && !optional)
2995 Warn("missing cache entry '%s'", token);
3001 *artwork_info = ldi;
3006 char *filename_levelinfo = getPath2(getLevelDirFromTreeInfo(level_node),
3007 LEVELINFO_FILENAME);
3008 char *filename_artworkinfo = getPath2(getSetupArtworkDir(artwork_info),
3009 ARTWORKINFO_FILENAME(type));
3011 // check if corresponding "levelinfo.conf" file has changed
3012 token_main = getCacheToken(token_prefix, "TIMESTAMP_LEVELINFO");
3013 cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
3015 if (modifiedFileTimestamp(filename_levelinfo, cache_entry))
3018 // check if corresponding "<artworkinfo>.conf" file has changed
3019 token_main = getCacheToken(token_prefix, "TIMESTAMP_ARTWORKINFO");
3020 cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
3022 if (modifiedFileTimestamp(filename_artworkinfo, cache_entry))
3025 checked_free(filename_levelinfo);
3026 checked_free(filename_artworkinfo);
3029 if (!cached && artwork_info != NULL)
3031 freeTreeInfo(artwork_info);
3036 return artwork_info;
3039 static void setArtworkInfoCacheEntry(TreeInfo *artwork_info,
3040 LevelDirTree *level_node, int type)
3042 char *identifier = level_node->subdir;
3043 char *type_string = ARTWORK_DIRECTORY(type);
3044 char *token_prefix = getCacheTokenPrefix(type_string, identifier);
3045 char *token_main = getCacheToken(token_prefix, "CACHED");
3046 boolean set_cache_timestamps = TRUE;
3049 setHashEntry(artworkinfo_cache_new, token_main, "true");
3051 if (set_cache_timestamps)
3053 char *filename_levelinfo = getPath2(getLevelDirFromTreeInfo(level_node),
3054 LEVELINFO_FILENAME);
3055 char *filename_artworkinfo = getPath2(getSetupArtworkDir(artwork_info),
3056 ARTWORKINFO_FILENAME(type));
3057 char *timestamp_levelinfo = getFileTimestampString(filename_levelinfo);
3058 char *timestamp_artworkinfo = getFileTimestampString(filename_artworkinfo);
3060 token_main = getCacheToken(token_prefix, "TIMESTAMP_LEVELINFO");
3061 setHashEntry(artworkinfo_cache_new, token_main, timestamp_levelinfo);
3063 token_main = getCacheToken(token_prefix, "TIMESTAMP_ARTWORKINFO");
3064 setHashEntry(artworkinfo_cache_new, token_main, timestamp_artworkinfo);
3066 checked_free(filename_levelinfo);
3067 checked_free(filename_artworkinfo);
3068 checked_free(timestamp_levelinfo);
3069 checked_free(timestamp_artworkinfo);
3072 ldi = *artwork_info;
3073 for (i = 0; artworkinfo_tokens[i].type != -1; i++)
3075 char *token = getCacheToken(token_prefix, artworkinfo_tokens[i].text);
3076 char *value = getSetupValue(artworkinfo_tokens[i].type,
3077 artworkinfo_tokens[i].value);
3079 setHashEntry(artworkinfo_cache_new, token, value);
3084 // ----------------------------------------------------------------------------
3085 // functions for loading level info and custom artwork info
3086 // ----------------------------------------------------------------------------
3088 int GetZipFileTreeType(char *zip_filename)
3090 static char *top_dir_path = NULL;
3091 static char *top_dir_conf_filename[NUM_BASE_TREE_TYPES] = { NULL };
3092 static char *conf_basename[NUM_BASE_TREE_TYPES] =
3094 GRAPHICSINFO_FILENAME,
3095 SOUNDSINFO_FILENAME,
3101 checked_free(top_dir_path);
3102 top_dir_path = NULL;
3104 for (j = 0; j < NUM_BASE_TREE_TYPES; j++)
3106 checked_free(top_dir_conf_filename[j]);
3107 top_dir_conf_filename[j] = NULL;
3110 char **zip_entries = zip_list(zip_filename);
3112 // check if zip file successfully opened
3113 if (zip_entries == NULL || zip_entries[0] == NULL)
3114 return TREE_TYPE_UNDEFINED;
3116 // first zip file entry is expected to be top level directory
3117 char *top_dir = zip_entries[0];
3119 // check if valid top level directory found in zip file
3120 if (!strSuffix(top_dir, "/"))
3121 return TREE_TYPE_UNDEFINED;
3123 // get filenames of valid configuration files in top level directory
3124 for (j = 0; j < NUM_BASE_TREE_TYPES; j++)
3125 top_dir_conf_filename[j] = getStringCat2(top_dir, conf_basename[j]);
3127 int tree_type = TREE_TYPE_UNDEFINED;
3130 while (zip_entries[e] != NULL)
3132 // check if every zip file entry is below top level directory
3133 if (!strPrefix(zip_entries[e], top_dir))
3134 return TREE_TYPE_UNDEFINED;
3136 // check if this zip file entry is a valid configuration filename
3137 for (j = 0; j < NUM_BASE_TREE_TYPES; j++)
3139 if (strEqual(zip_entries[e], top_dir_conf_filename[j]))
3141 // only exactly one valid configuration file allowed
3142 if (tree_type != TREE_TYPE_UNDEFINED)
3143 return TREE_TYPE_UNDEFINED;
3155 static boolean CheckZipFileForDirectory(char *zip_filename, char *directory,
3158 static char *top_dir_path = NULL;
3159 static char *top_dir_conf_filename = NULL;
3161 checked_free(top_dir_path);
3162 checked_free(top_dir_conf_filename);
3164 top_dir_path = NULL;
3165 top_dir_conf_filename = NULL;
3167 char *conf_basename = (tree_type == TREE_TYPE_LEVEL_DIR ? LEVELINFO_FILENAME :
3168 ARTWORKINFO_FILENAME(tree_type));
3170 // check if valid configuration filename determined
3171 if (conf_basename == NULL || strEqual(conf_basename, ""))
3174 char **zip_entries = zip_list(zip_filename);
3176 // check if zip file successfully opened
3177 if (zip_entries == NULL || zip_entries[0] == NULL)
3180 // first zip file entry is expected to be top level directory
3181 char *top_dir = zip_entries[0];
3183 // check if valid top level directory found in zip file
3184 if (!strSuffix(top_dir, "/"))
3187 // get path of extracted top level directory
3188 top_dir_path = getPath2(directory, top_dir);
3190 // remove trailing directory separator from top level directory path
3191 // (required to be able to check for file and directory in next step)
3192 top_dir_path[strlen(top_dir_path) - 1] = '\0';
3194 // check if zip file's top level directory already exists in target directory
3195 if (fileExists(top_dir_path)) // (checks for file and directory)
3198 // get filename of configuration file in top level directory
3199 top_dir_conf_filename = getStringCat2(top_dir, conf_basename);
3201 boolean found_top_dir_conf_filename = FALSE;
3204 while (zip_entries[i] != NULL)
3206 // check if every zip file entry is below top level directory
3207 if (!strPrefix(zip_entries[i], top_dir))
3210 // check if this zip file entry is the configuration filename
3211 if (strEqual(zip_entries[i], top_dir_conf_filename))
3212 found_top_dir_conf_filename = TRUE;
3217 // check if valid configuration filename was found in zip file
3218 if (!found_top_dir_conf_filename)
3224 char *ExtractZipFileIntoDirectory(char *zip_filename, char *directory,
3227 boolean zip_file_valid = CheckZipFileForDirectory(zip_filename, directory,
3230 if (!zip_file_valid)
3232 Warn("zip file '%s' rejected!", zip_filename);
3237 char **zip_entries = zip_extract(zip_filename, directory);
3239 if (zip_entries == NULL)
3241 Warn("zip file '%s' could not be extracted!", zip_filename);
3246 Info("zip file '%s' successfully extracted!", zip_filename);
3248 // first zip file entry contains top level directory
3249 char *top_dir = zip_entries[0];
3251 // remove trailing directory separator from top level directory
3252 top_dir[strlen(top_dir) - 1] = '\0';
3257 static void ProcessZipFilesInDirectory(char *directory, int tree_type)
3260 DirectoryEntry *dir_entry;
3262 if ((dir = openDirectory(directory)) == NULL)
3264 // display error if directory is main "options.graphics_directory" etc.
3265 if (tree_type == TREE_TYPE_LEVEL_DIR ||
3266 directory == OPTIONS_ARTWORK_DIRECTORY(tree_type))
3267 Warn("cannot read directory '%s'", directory);
3272 while ((dir_entry = readDirectory(dir)) != NULL) // loop all entries
3274 // skip non-zip files (and also directories with zip extension)
3275 if (!strSuffixLower(dir_entry->basename, ".zip") || dir_entry->is_directory)
3278 char *zip_filename = getPath2(directory, dir_entry->basename);
3279 char *zip_filename_extracted = getStringCat2(zip_filename, ".extracted");
3280 char *zip_filename_rejected = getStringCat2(zip_filename, ".rejected");
3282 // check if zip file hasn't already been extracted or rejected
3283 if (!fileExists(zip_filename_extracted) &&
3284 !fileExists(zip_filename_rejected))
3286 char *top_dir = ExtractZipFileIntoDirectory(zip_filename, directory,
3288 char *marker_filename = (top_dir != NULL ? zip_filename_extracted :
3289 zip_filename_rejected);
3292 // create empty file to mark zip file as extracted or rejected
3293 if ((marker_file = fopen(marker_filename, MODE_WRITE)))
3294 fclose(marker_file);
3297 free(zip_filename_extracted);
3298 free(zip_filename_rejected);
3302 closeDirectory(dir);
3305 // forward declaration for recursive call by "LoadLevelInfoFromLevelDir()"
3306 static void LoadLevelInfoFromLevelDir(TreeInfo **, TreeInfo *, char *);
3308 static boolean LoadLevelInfoFromLevelConf(TreeInfo **node_first,
3309 TreeInfo *node_parent,
3310 char *level_directory,
3311 char *directory_name)
3313 char *directory_path = getPath2(level_directory, directory_name);
3314 char *filename = getPath2(directory_path, LEVELINFO_FILENAME);
3315 SetupFileHash *setup_file_hash;
3316 LevelDirTree *leveldir_new = NULL;
3319 // unless debugging, silently ignore directories without "levelinfo.conf"
3320 if (!options.debug && !fileExists(filename))
3322 free(directory_path);
3328 setup_file_hash = loadSetupFileHash(filename);
3330 if (setup_file_hash == NULL)
3332 #if DEBUG_NO_CONFIG_FILE
3333 Debug("setup", "ignoring level directory '%s'", directory_path);
3336 free(directory_path);
3342 leveldir_new = newTreeInfo();
3345 setTreeInfoToDefaultsFromParent(leveldir_new, node_parent);
3347 setTreeInfoToDefaults(leveldir_new, TREE_TYPE_LEVEL_DIR);
3349 leveldir_new->subdir = getStringCopy(directory_name);
3351 // set all structure fields according to the token/value pairs
3352 ldi = *leveldir_new;
3353 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
3354 setSetupInfo(levelinfo_tokens, i,
3355 getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
3356 *leveldir_new = ldi;
3358 if (strEqual(leveldir_new->name, ANONYMOUS_NAME))
3359 setString(&leveldir_new->name, leveldir_new->subdir);
3361 if (leveldir_new->identifier == NULL)
3362 leveldir_new->identifier = getStringCopy(leveldir_new->subdir);
3364 if (leveldir_new->name_sorting == NULL)
3365 leveldir_new->name_sorting = getStringCopy(leveldir_new->name);
3367 if (node_parent == NULL) // top level group
3369 leveldir_new->basepath = getStringCopy(level_directory);
3370 leveldir_new->fullpath = getStringCopy(leveldir_new->subdir);
3372 else // sub level group
3374 leveldir_new->basepath = getStringCopy(node_parent->basepath);
3375 leveldir_new->fullpath = getPath2(node_parent->fullpath, directory_name);
3378 leveldir_new->last_level =
3379 leveldir_new->first_level + leveldir_new->levels - 1;
3381 leveldir_new->in_user_dir =
3382 (!strEqual(leveldir_new->basepath, options.level_directory));
3384 // adjust some settings if user's private level directory was detected
3385 if (leveldir_new->sort_priority == LEVELCLASS_UNDEFINED &&
3386 leveldir_new->in_user_dir &&
3387 (strEqual(leveldir_new->subdir, getLoginName()) ||
3388 strEqual(leveldir_new->name, getLoginName()) ||
3389 strEqual(leveldir_new->author, getRealName())))
3391 leveldir_new->sort_priority = LEVELCLASS_PRIVATE_START;
3392 leveldir_new->readonly = FALSE;
3395 leveldir_new->user_defined =
3396 (leveldir_new->in_user_dir && IS_LEVELCLASS_PRIVATE(leveldir_new));
3398 leveldir_new->color = LEVELCOLOR(leveldir_new);
3400 setString(&leveldir_new->class_desc, getLevelClassDescription(leveldir_new));
3402 leveldir_new->handicap_level = // set handicap to default value
3403 (leveldir_new->user_defined || !leveldir_new->handicap ?
3404 leveldir_new->last_level : leveldir_new->first_level);
3406 DrawInitText(leveldir_new->name, 150, FC_YELLOW);
3408 pushTreeInfo(node_first, leveldir_new);
3410 freeSetupFileHash(setup_file_hash);
3412 if (leveldir_new->level_group)
3414 // create node to link back to current level directory
3415 createParentTreeInfoNode(leveldir_new);
3417 // recursively step into sub-directory and look for more level series
3418 LoadLevelInfoFromLevelDir(&leveldir_new->node_group,
3419 leveldir_new, directory_path);
3422 free(directory_path);
3428 static void LoadLevelInfoFromLevelDir(TreeInfo **node_first,
3429 TreeInfo *node_parent,
3430 char *level_directory)
3432 // ---------- 1st stage: process any level set zip files ----------
3434 ProcessZipFilesInDirectory(level_directory, TREE_TYPE_LEVEL_DIR);
3436 // ---------- 2nd stage: check for level set directories ----------
3439 DirectoryEntry *dir_entry;
3440 boolean valid_entry_found = FALSE;
3442 if ((dir = openDirectory(level_directory)) == NULL)
3444 Warn("cannot read level directory '%s'", level_directory);
3449 while ((dir_entry = readDirectory(dir)) != NULL) // loop all entries
3451 char *directory_name = dir_entry->basename;
3452 char *directory_path = getPath2(level_directory, directory_name);
3454 // skip entries for current and parent directory
3455 if (strEqual(directory_name, ".") ||
3456 strEqual(directory_name, ".."))
3458 free(directory_path);
3463 // find out if directory entry is itself a directory
3464 if (!dir_entry->is_directory) // not a directory
3466 free(directory_path);
3471 free(directory_path);
3473 if (strEqual(directory_name, GRAPHICS_DIRECTORY) ||
3474 strEqual(directory_name, SOUNDS_DIRECTORY) ||
3475 strEqual(directory_name, MUSIC_DIRECTORY))
3478 valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
3483 closeDirectory(dir);
3485 // special case: top level directory may directly contain "levelinfo.conf"
3486 if (node_parent == NULL && !valid_entry_found)
3488 // check if this directory directly contains a file "levelinfo.conf"
3489 valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
3490 level_directory, ".");
3493 if (!valid_entry_found)
3494 Warn("cannot find any valid level series in directory '%s'",
3498 boolean AdjustGraphicsForEMC(void)
3500 boolean settings_changed = FALSE;
3502 settings_changed |= adjustTreeGraphicsForEMC(leveldir_first_all);
3503 settings_changed |= adjustTreeGraphicsForEMC(leveldir_first);
3505 return settings_changed;
3508 boolean AdjustSoundsForEMC(void)
3510 boolean settings_changed = FALSE;
3512 settings_changed |= adjustTreeSoundsForEMC(leveldir_first_all);
3513 settings_changed |= adjustTreeSoundsForEMC(leveldir_first);
3515 return settings_changed;
3518 void LoadLevelInfo(void)
3520 InitUserLevelDirectory(getLoginName());
3522 DrawInitText("Loading level series", 120, FC_GREEN);
3524 LoadLevelInfoFromLevelDir(&leveldir_first, NULL, options.level_directory);
3525 LoadLevelInfoFromLevelDir(&leveldir_first, NULL, getUserLevelDir(NULL));
3527 leveldir_first = createTopTreeInfoNode(leveldir_first);
3529 /* after loading all level set information, clone the level directory tree
3530 and remove all level sets without levels (these may still contain artwork
3531 to be offered in the setup menu as "custom artwork", and are therefore
3532 checked for existing artwork in the function "LoadLevelArtworkInfo()") */
3533 leveldir_first_all = leveldir_first;
3534 cloneTree(&leveldir_first, leveldir_first_all, TRUE);
3536 AdjustGraphicsForEMC();
3537 AdjustSoundsForEMC();
3539 // before sorting, the first entries will be from the user directory
3540 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
3542 if (leveldir_first == NULL)
3543 Fail("cannot find any valid level series in any directory");
3545 sortTreeInfo(&leveldir_first);
3547 #if ENABLE_UNUSED_CODE
3548 dumpTreeInfo(leveldir_first, 0);
3552 static boolean LoadArtworkInfoFromArtworkConf(TreeInfo **node_first,
3553 TreeInfo *node_parent,
3554 char *base_directory,
3555 char *directory_name, int type)
3557 char *directory_path = getPath2(base_directory, directory_name);
3558 char *filename = getPath2(directory_path, ARTWORKINFO_FILENAME(type));
3559 SetupFileHash *setup_file_hash = NULL;
3560 TreeInfo *artwork_new = NULL;
3563 if (fileExists(filename))
3564 setup_file_hash = loadSetupFileHash(filename);
3566 if (setup_file_hash == NULL) // no config file -- look for artwork files
3569 DirectoryEntry *dir_entry;
3570 boolean valid_file_found = FALSE;
3572 if ((dir = openDirectory(directory_path)) != NULL)
3574 while ((dir_entry = readDirectory(dir)) != NULL)
3576 if (FileIsArtworkType(dir_entry->filename, type))
3578 valid_file_found = TRUE;
3584 closeDirectory(dir);
3587 if (!valid_file_found)
3589 #if DEBUG_NO_CONFIG_FILE
3590 if (!strEqual(directory_name, "."))
3591 Debug("setup", "ignoring artwork directory '%s'", directory_path);
3594 free(directory_path);
3601 artwork_new = newTreeInfo();
3604 setTreeInfoToDefaultsFromParent(artwork_new, node_parent);
3606 setTreeInfoToDefaults(artwork_new, type);
3608 artwork_new->subdir = getStringCopy(directory_name);
3610 if (setup_file_hash) // (before defining ".color" and ".class_desc")
3612 // set all structure fields according to the token/value pairs
3614 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
3615 setSetupInfo(levelinfo_tokens, i,
3616 getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
3619 if (strEqual(artwork_new->name, ANONYMOUS_NAME))
3620 setString(&artwork_new->name, artwork_new->subdir);
3622 if (artwork_new->identifier == NULL)
3623 artwork_new->identifier = getStringCopy(artwork_new->subdir);
3625 if (artwork_new->name_sorting == NULL)
3626 artwork_new->name_sorting = getStringCopy(artwork_new->name);
3629 if (node_parent == NULL) // top level group
3631 artwork_new->basepath = getStringCopy(base_directory);
3632 artwork_new->fullpath = getStringCopy(artwork_new->subdir);
3634 else // sub level group
3636 artwork_new->basepath = getStringCopy(node_parent->basepath);
3637 artwork_new->fullpath = getPath2(node_parent->fullpath, directory_name);
3640 artwork_new->in_user_dir =
3641 (!strEqual(artwork_new->basepath, OPTIONS_ARTWORK_DIRECTORY(type)));
3643 // (may use ".sort_priority" from "setup_file_hash" above)
3644 artwork_new->color = ARTWORKCOLOR(artwork_new);
3646 setString(&artwork_new->class_desc, getLevelClassDescription(artwork_new));
3648 if (setup_file_hash == NULL) // (after determining ".user_defined")
3650 if (strEqual(artwork_new->subdir, "."))
3652 if (artwork_new->user_defined)
3654 setString(&artwork_new->identifier, "private");
3655 artwork_new->sort_priority = ARTWORKCLASS_PRIVATE;
3659 setString(&artwork_new->identifier, "classic");
3660 artwork_new->sort_priority = ARTWORKCLASS_CLASSICS;
3663 // set to new values after changing ".sort_priority"
3664 artwork_new->color = ARTWORKCOLOR(artwork_new);
3666 setString(&artwork_new->class_desc,
3667 getLevelClassDescription(artwork_new));
3671 setString(&artwork_new->identifier, artwork_new->subdir);
3674 setString(&artwork_new->name, artwork_new->identifier);
3675 setString(&artwork_new->name_sorting, artwork_new->name);
3678 pushTreeInfo(node_first, artwork_new);
3680 freeSetupFileHash(setup_file_hash);
3682 free(directory_path);
3688 static void LoadArtworkInfoFromArtworkDir(TreeInfo **node_first,
3689 TreeInfo *node_parent,
3690 char *base_directory, int type)
3692 // ---------- 1st stage: process any artwork set zip files ----------
3694 ProcessZipFilesInDirectory(base_directory, type);
3696 // ---------- 2nd stage: check for artwork set directories ----------
3699 DirectoryEntry *dir_entry;
3700 boolean valid_entry_found = FALSE;
3702 if ((dir = openDirectory(base_directory)) == NULL)
3704 // display error if directory is main "options.graphics_directory" etc.
3705 if (base_directory == OPTIONS_ARTWORK_DIRECTORY(type))
3706 Warn("cannot read directory '%s'", base_directory);
3711 while ((dir_entry = readDirectory(dir)) != NULL) // loop all entries
3713 char *directory_name = dir_entry->basename;
3714 char *directory_path = getPath2(base_directory, directory_name);
3716 // skip directory entries for current and parent directory
3717 if (strEqual(directory_name, ".") ||
3718 strEqual(directory_name, ".."))
3720 free(directory_path);
3725 // skip directory entries which are not a directory
3726 if (!dir_entry->is_directory) // not a directory
3728 free(directory_path);
3733 free(directory_path);
3735 // check if this directory contains artwork with or without config file
3736 valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
3738 directory_name, type);
3741 closeDirectory(dir);
3743 // check if this directory directly contains artwork itself
3744 valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
3745 base_directory, ".",
3747 if (!valid_entry_found)
3748 Warn("cannot find any valid artwork in directory '%s'", base_directory);
3751 static TreeInfo *getDummyArtworkInfo(int type)
3753 // this is only needed when there is completely no artwork available
3754 TreeInfo *artwork_new = newTreeInfo();
3756 setTreeInfoToDefaults(artwork_new, type);
3758 setString(&artwork_new->subdir, UNDEFINED_FILENAME);
3759 setString(&artwork_new->fullpath, UNDEFINED_FILENAME);
3760 setString(&artwork_new->basepath, UNDEFINED_FILENAME);
3762 setString(&artwork_new->identifier, UNDEFINED_FILENAME);
3763 setString(&artwork_new->name, UNDEFINED_FILENAME);
3764 setString(&artwork_new->name_sorting, UNDEFINED_FILENAME);
3769 void LoadArtworkInfo(void)
3771 LoadArtworkInfoCache();
3773 DrawInitText("Looking for custom artwork", 120, FC_GREEN);
3775 LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
3776 options.graphics_directory,
3777 TREE_TYPE_GRAPHICS_DIR);
3778 LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
3779 getUserGraphicsDir(),
3780 TREE_TYPE_GRAPHICS_DIR);
3782 LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
3783 options.sounds_directory,
3784 TREE_TYPE_SOUNDS_DIR);
3785 LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
3787 TREE_TYPE_SOUNDS_DIR);
3789 LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
3790 options.music_directory,
3791 TREE_TYPE_MUSIC_DIR);
3792 LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
3794 TREE_TYPE_MUSIC_DIR);
3796 if (artwork.gfx_first == NULL)
3797 artwork.gfx_first = getDummyArtworkInfo(TREE_TYPE_GRAPHICS_DIR);
3798 if (artwork.snd_first == NULL)
3799 artwork.snd_first = getDummyArtworkInfo(TREE_TYPE_SOUNDS_DIR);
3800 if (artwork.mus_first == NULL)
3801 artwork.mus_first = getDummyArtworkInfo(TREE_TYPE_MUSIC_DIR);
3803 // before sorting, the first entries will be from the user directory
3804 artwork.gfx_current =
3805 getTreeInfoFromIdentifier(artwork.gfx_first, setup.graphics_set);
3806 if (artwork.gfx_current == NULL)
3807 artwork.gfx_current =
3808 getTreeInfoFromIdentifier(artwork.gfx_first, GFX_DEFAULT_SUBDIR);
3809 if (artwork.gfx_current == NULL)
3810 artwork.gfx_current = getFirstValidTreeInfoEntry(artwork.gfx_first);
3812 artwork.snd_current =
3813 getTreeInfoFromIdentifier(artwork.snd_first, setup.sounds_set);
3814 if (artwork.snd_current == NULL)
3815 artwork.snd_current =
3816 getTreeInfoFromIdentifier(artwork.snd_first, SND_DEFAULT_SUBDIR);
3817 if (artwork.snd_current == NULL)
3818 artwork.snd_current = getFirstValidTreeInfoEntry(artwork.snd_first);
3820 artwork.mus_current =
3821 getTreeInfoFromIdentifier(artwork.mus_first, setup.music_set);
3822 if (artwork.mus_current == NULL)
3823 artwork.mus_current =
3824 getTreeInfoFromIdentifier(artwork.mus_first, MUS_DEFAULT_SUBDIR);
3825 if (artwork.mus_current == NULL)
3826 artwork.mus_current = getFirstValidTreeInfoEntry(artwork.mus_first);
3828 artwork.gfx_current_identifier = artwork.gfx_current->identifier;
3829 artwork.snd_current_identifier = artwork.snd_current->identifier;
3830 artwork.mus_current_identifier = artwork.mus_current->identifier;
3832 #if ENABLE_UNUSED_CODE
3833 Debug("setup:LoadArtworkInfo", "graphics set == %s",
3834 artwork.gfx_current_identifier);
3835 Debug("setup:LoadArtworkInfo", "sounds set == %s",
3836 artwork.snd_current_identifier);
3837 Debug("setup:LoadArtworkInfo", "music set == %s",
3838 artwork.mus_current_identifier);
3841 sortTreeInfo(&artwork.gfx_first);
3842 sortTreeInfo(&artwork.snd_first);
3843 sortTreeInfo(&artwork.mus_first);
3845 #if ENABLE_UNUSED_CODE
3846 dumpTreeInfo(artwork.gfx_first, 0);
3847 dumpTreeInfo(artwork.snd_first, 0);
3848 dumpTreeInfo(artwork.mus_first, 0);
3852 static void LoadArtworkInfoFromLevelInfo(ArtworkDirTree **artwork_node,
3853 LevelDirTree *level_node)
3855 int type = (*artwork_node)->type;
3857 // recursively check all level directories for artwork sub-directories
3861 // check all tree entries for artwork, but skip parent link entries
3862 if (!level_node->parent_link)
3864 TreeInfo *artwork_new = getArtworkInfoCacheEntry(level_node, type);
3865 boolean cached = (artwork_new != NULL);
3869 pushTreeInfo(artwork_node, artwork_new);
3873 TreeInfo *topnode_last = *artwork_node;
3874 char *path = getPath2(getLevelDirFromTreeInfo(level_node),
3875 ARTWORK_DIRECTORY(type));
3877 LoadArtworkInfoFromArtworkDir(artwork_node, NULL, path, type);
3879 if (topnode_last != *artwork_node) // check for newly added node
3881 artwork_new = *artwork_node;
3883 setString(&artwork_new->identifier, level_node->subdir);
3884 setString(&artwork_new->name, level_node->name);
3885 setString(&artwork_new->name_sorting, level_node->name_sorting);
3887 artwork_new->sort_priority = level_node->sort_priority;
3888 artwork_new->color = LEVELCOLOR(artwork_new);
3894 // insert artwork info (from old cache or filesystem) into new cache
3895 if (artwork_new != NULL)
3896 setArtworkInfoCacheEntry(artwork_new, level_node, type);
3899 DrawInitText(level_node->name, 150, FC_YELLOW);
3901 if (level_node->node_group != NULL)
3902 LoadArtworkInfoFromLevelInfo(artwork_node, level_node->node_group);
3904 level_node = level_node->next;
3908 void LoadLevelArtworkInfo(void)
3910 print_timestamp_init("LoadLevelArtworkInfo");
3912 DrawInitText("Looking for custom level artwork", 120, FC_GREEN);
3914 print_timestamp_time("DrawTimeText");
3916 LoadArtworkInfoFromLevelInfo(&artwork.gfx_first, leveldir_first_all);
3917 print_timestamp_time("LoadArtworkInfoFromLevelInfo (gfx)");
3918 LoadArtworkInfoFromLevelInfo(&artwork.snd_first, leveldir_first_all);
3919 print_timestamp_time("LoadArtworkInfoFromLevelInfo (snd)");
3920 LoadArtworkInfoFromLevelInfo(&artwork.mus_first, leveldir_first_all);
3921 print_timestamp_time("LoadArtworkInfoFromLevelInfo (mus)");
3923 SaveArtworkInfoCache();
3925 print_timestamp_time("SaveArtworkInfoCache");
3927 // needed for reloading level artwork not known at ealier stage
3929 if (!strEqual(artwork.gfx_current_identifier, setup.graphics_set))
3931 artwork.gfx_current =
3932 getTreeInfoFromIdentifier(artwork.gfx_first, setup.graphics_set);
3933 if (artwork.gfx_current == NULL)
3934 artwork.gfx_current =
3935 getTreeInfoFromIdentifier(artwork.gfx_first, GFX_DEFAULT_SUBDIR);
3936 if (artwork.gfx_current == NULL)
3937 artwork.gfx_current = getFirstValidTreeInfoEntry(artwork.gfx_first);
3940 if (!strEqual(artwork.snd_current_identifier, setup.sounds_set))
3942 artwork.snd_current =
3943 getTreeInfoFromIdentifier(artwork.snd_first, setup.sounds_set);
3944 if (artwork.snd_current == NULL)
3945 artwork.snd_current =
3946 getTreeInfoFromIdentifier(artwork.snd_first, SND_DEFAULT_SUBDIR);
3947 if (artwork.snd_current == NULL)
3948 artwork.snd_current = getFirstValidTreeInfoEntry(artwork.snd_first);
3951 if (!strEqual(artwork.mus_current_identifier, setup.music_set))
3953 artwork.mus_current =
3954 getTreeInfoFromIdentifier(artwork.mus_first, setup.music_set);
3955 if (artwork.mus_current == NULL)
3956 artwork.mus_current =
3957 getTreeInfoFromIdentifier(artwork.mus_first, MUS_DEFAULT_SUBDIR);
3958 if (artwork.mus_current == NULL)
3959 artwork.mus_current = getFirstValidTreeInfoEntry(artwork.mus_first);
3962 print_timestamp_time("getTreeInfoFromIdentifier");
3964 sortTreeInfo(&artwork.gfx_first);
3965 sortTreeInfo(&artwork.snd_first);
3966 sortTreeInfo(&artwork.mus_first);
3968 print_timestamp_time("sortTreeInfo");
3970 #if ENABLE_UNUSED_CODE
3971 dumpTreeInfo(artwork.gfx_first, 0);
3972 dumpTreeInfo(artwork.snd_first, 0);
3973 dumpTreeInfo(artwork.mus_first, 0);
3976 print_timestamp_done("LoadLevelArtworkInfo");
3979 static boolean AddTreeSetToTreeInfoExt(TreeInfo *tree_node_old, char *tree_dir,
3980 char *tree_subdir_new, int type)
3982 if (tree_node_old == NULL)
3984 if (type == TREE_TYPE_LEVEL_DIR)
3986 // get level info tree node of personal user level set
3987 tree_node_old = getTreeInfoFromIdentifier(leveldir_first, getLoginName());
3989 // this may happen if "setup.internal.create_user_levelset" is FALSE
3990 // or if file "levelinfo.conf" is missing in personal user level set
3991 if (tree_node_old == NULL)
3992 tree_node_old = leveldir_first->node_group;
3996 // get artwork info tree node of first artwork set
3997 tree_node_old = ARTWORK_FIRST_NODE(artwork, type);
4001 if (tree_dir == NULL)
4002 tree_dir = TREE_USERDIR(type);
4004 if (tree_node_old == NULL ||
4006 tree_subdir_new == NULL) // should not happen
4009 int draw_deactivation_mask = GetDrawDeactivationMask();
4011 // override draw deactivation mask (temporarily disable drawing)
4012 SetDrawDeactivationMask(REDRAW_ALL);
4014 if (type == TREE_TYPE_LEVEL_DIR)
4016 // load new level set config and add it next to first user level set
4017 LoadLevelInfoFromLevelConf(&tree_node_old->next,
4018 tree_node_old->node_parent,
4019 tree_dir, tree_subdir_new);
4023 // load new artwork set config and add it next to first artwork set
4024 LoadArtworkInfoFromArtworkConf(&tree_node_old->next,
4025 tree_node_old->node_parent,
4026 tree_dir, tree_subdir_new, type);
4029 // set draw deactivation mask to previous value
4030 SetDrawDeactivationMask(draw_deactivation_mask);
4032 // get first node of level or artwork info tree
4033 TreeInfo **tree_node_first = TREE_FIRST_NODE_PTR(type);
4035 // get tree info node of newly added level or artwork set
4036 TreeInfo *tree_node_new = getTreeInfoFromIdentifier(*tree_node_first,
4039 if (tree_node_new == NULL) // should not happen
4042 // correct top link and parent node link of newly created tree node
4043 tree_node_new->node_top = tree_node_old->node_top;
4044 tree_node_new->node_parent = tree_node_old->node_parent;
4046 // sort tree info to adjust position of newly added tree set
4047 sortTreeInfo(tree_node_first);
4052 void AddTreeSetToTreeInfo(TreeInfo *tree_node, char *tree_dir,
4053 char *tree_subdir_new, int type)
4055 if (!AddTreeSetToTreeInfoExt(tree_node, tree_dir, tree_subdir_new, type))
4056 Fail("internal tree info structure corrupted -- aborting");
4059 void AddUserLevelSetToLevelInfo(char *level_subdir_new)
4061 AddTreeSetToTreeInfo(NULL, NULL, level_subdir_new, TREE_TYPE_LEVEL_DIR);
4064 char *getArtworkIdentifierForUserLevelSet(int type)
4066 char *classic_artwork_set = getClassicArtworkSet(type);
4068 // check for custom artwork configured in "levelinfo.conf"
4069 char *leveldir_artwork_set =
4070 *LEVELDIR_ARTWORK_SET_PTR(leveldir_current, type);
4071 boolean has_leveldir_artwork_set =
4072 (leveldir_artwork_set != NULL && !strEqual(leveldir_artwork_set,
4073 classic_artwork_set));
4075 // check for custom artwork in sub-directory "graphics" etc.
4076 TreeInfo *artwork_first_node = ARTWORK_FIRST_NODE(artwork, type);
4077 char *leveldir_identifier = leveldir_current->identifier;
4078 boolean has_artwork_subdir =
4079 (getTreeInfoFromIdentifier(artwork_first_node,
4080 leveldir_identifier) != NULL);
4082 return (has_leveldir_artwork_set ? leveldir_artwork_set :
4083 has_artwork_subdir ? leveldir_identifier :
4084 classic_artwork_set);
4087 TreeInfo *getArtworkTreeInfoForUserLevelSet(int type)
4089 char *artwork_set = getArtworkIdentifierForUserLevelSet(type);
4090 TreeInfo *artwork_first_node = ARTWORK_FIRST_NODE(artwork, type);
4091 TreeInfo *ti = getTreeInfoFromIdentifier(artwork_first_node, artwork_set);
4095 ti = getTreeInfoFromIdentifier(artwork_first_node,
4096 ARTWORK_DEFAULT_SUBDIR(type));
4098 Fail("cannot find default graphics -- should not happen");
4104 boolean checkIfCustomArtworkExistsForCurrentLevelSet(void)
4106 char *graphics_set =
4107 getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_GRAPHICS);
4109 getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_SOUNDS);
4111 getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_MUSIC);
4113 return (!strEqual(graphics_set, GFX_CLASSIC_SUBDIR) ||
4114 !strEqual(sounds_set, SND_CLASSIC_SUBDIR) ||
4115 !strEqual(music_set, MUS_CLASSIC_SUBDIR));
4118 boolean UpdateUserLevelSet(char *level_subdir, char *level_name,
4119 char *level_author, int num_levels)
4121 char *filename = getPath2(getUserLevelDir(level_subdir), LEVELINFO_FILENAME);
4122 char *filename_tmp = getStringCat2(filename, ".tmp");
4124 FILE *file_tmp = NULL;
4125 char line[MAX_LINE_LEN];
4126 boolean success = FALSE;
4127 LevelDirTree *leveldir = getTreeInfoFromIdentifier(leveldir_first,
4129 // update values in level directory tree
4131 if (level_name != NULL)
4132 setString(&leveldir->name, level_name);
4134 if (level_author != NULL)
4135 setString(&leveldir->author, level_author);
4137 if (num_levels != -1)
4138 leveldir->levels = num_levels;
4140 // update values that depend on other values
4142 setString(&leveldir->name_sorting, leveldir->name);
4144 leveldir->last_level = leveldir->first_level + leveldir->levels - 1;
4146 // sort order of level sets may have changed
4147 sortTreeInfo(&leveldir_first);
4149 if ((file = fopen(filename, MODE_READ)) &&
4150 (file_tmp = fopen(filename_tmp, MODE_WRITE)))
4152 while (fgets(line, MAX_LINE_LEN, file))
4154 if (strPrefix(line, "name:") && level_name != NULL)
4155 fprintf(file_tmp, "%-32s%s\n", "name:", level_name);
4156 else if (strPrefix(line, "author:") && level_author != NULL)
4157 fprintf(file_tmp, "%-32s%s\n", "author:", level_author);
4158 else if (strPrefix(line, "levels:") && num_levels != -1)
4159 fprintf(file_tmp, "%-32s%d\n", "levels:", num_levels);
4161 fputs(line, file_tmp);
4174 success = (rename(filename_tmp, filename) == 0);
4182 boolean CreateUserLevelSet(char *level_subdir, char *level_name,
4183 char *level_author, int num_levels,
4184 boolean use_artwork_set)
4186 LevelDirTree *level_info;
4191 // create user level sub-directory, if needed
4192 createDirectory(getUserLevelDir(level_subdir), "user level", PERMS_PRIVATE);
4194 filename = getPath2(getUserLevelDir(level_subdir), LEVELINFO_FILENAME);
4196 if (!(file = fopen(filename, MODE_WRITE)))
4198 Warn("cannot write level info file '%s'", filename);
4205 level_info = newTreeInfo();
4207 // always start with reliable default values
4208 setTreeInfoToDefaults(level_info, TREE_TYPE_LEVEL_DIR);
4210 setString(&level_info->name, level_name);
4211 setString(&level_info->author, level_author);
4212 level_info->levels = num_levels;
4213 level_info->first_level = 1;
4214 level_info->sort_priority = LEVELCLASS_PRIVATE_START;
4215 level_info->readonly = FALSE;
4217 if (use_artwork_set)
4219 level_info->graphics_set =
4220 getStringCopy(getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_GRAPHICS));
4221 level_info->sounds_set =
4222 getStringCopy(getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_SOUNDS));
4223 level_info->music_set =
4224 getStringCopy(getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_MUSIC));
4227 token_value_position = TOKEN_VALUE_POSITION_SHORT;
4229 fprintFileHeader(file, LEVELINFO_FILENAME);
4232 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
4234 if (i == LEVELINFO_TOKEN_NAME ||
4235 i == LEVELINFO_TOKEN_AUTHOR ||
4236 i == LEVELINFO_TOKEN_LEVELS ||
4237 i == LEVELINFO_TOKEN_FIRST_LEVEL ||
4238 i == LEVELINFO_TOKEN_SORT_PRIORITY ||
4239 i == LEVELINFO_TOKEN_READONLY ||
4240 (use_artwork_set && (i == LEVELINFO_TOKEN_GRAPHICS_SET ||
4241 i == LEVELINFO_TOKEN_SOUNDS_SET ||
4242 i == LEVELINFO_TOKEN_MUSIC_SET)))
4243 fprintf(file, "%s\n", getSetupLine(levelinfo_tokens, "", i));
4245 // just to make things nicer :)
4246 if (i == LEVELINFO_TOKEN_AUTHOR ||
4247 i == LEVELINFO_TOKEN_FIRST_LEVEL ||
4248 (use_artwork_set && i == LEVELINFO_TOKEN_READONLY))
4249 fprintf(file, "\n");
4252 token_value_position = TOKEN_VALUE_POSITION_DEFAULT;
4256 SetFilePermissions(filename, PERMS_PRIVATE);
4258 freeTreeInfo(level_info);
4264 static void SaveUserLevelInfo(void)
4266 CreateUserLevelSet(getLoginName(), getLoginName(), getRealName(), 100, FALSE);
4269 char *getSetupValue(int type, void *value)
4271 static char value_string[MAX_LINE_LEN];
4279 strcpy(value_string, (*(boolean *)value ? "true" : "false"));
4283 strcpy(value_string, (*(boolean *)value ? "on" : "off"));
4287 strcpy(value_string, (*(int *)value == AUTO ? "auto" :
4288 *(int *)value == FALSE ? "off" : "on"));
4292 strcpy(value_string, (*(boolean *)value ? "yes" : "no"));
4295 case TYPE_YES_NO_AUTO:
4296 strcpy(value_string, (*(int *)value == AUTO ? "auto" :
4297 *(int *)value == FALSE ? "no" : "yes"));
4301 strcpy(value_string, (*(boolean *)value ? "AGA" : "ECS"));
4305 strcpy(value_string, getKeyNameFromKey(*(Key *)value));
4309 strcpy(value_string, getX11KeyNameFromKey(*(Key *)value));
4313 sprintf(value_string, "%d", *(int *)value);
4317 if (*(char **)value == NULL)
4320 strcpy(value_string, *(char **)value);
4324 sprintf(value_string, "player_%d", *(int *)value + 1);
4328 value_string[0] = '\0';
4332 if (type & TYPE_GHOSTED)
4333 strcpy(value_string, "n/a");
4335 return value_string;
4338 char *getSetupLine(struct TokenInfo *token_info, char *prefix, int token_nr)
4342 static char token_string[MAX_LINE_LEN];
4343 int token_type = token_info[token_nr].type;
4344 void *setup_value = token_info[token_nr].value;
4345 char *token_text = token_info[token_nr].text;
4346 char *value_string = getSetupValue(token_type, setup_value);
4348 // build complete token string
4349 sprintf(token_string, "%s%s", prefix, token_text);
4351 // build setup entry line
4352 line = getFormattedSetupEntry(token_string, value_string);
4354 if (token_type == TYPE_KEY_X11)
4356 Key key = *(Key *)setup_value;
4357 char *keyname = getKeyNameFromKey(key);
4359 // add comment, if useful
4360 if (!strEqual(keyname, "(undefined)") &&
4361 !strEqual(keyname, "(unknown)"))
4363 // add at least one whitespace
4365 for (i = strlen(line); i < token_comment_position; i++)
4369 strcat(line, keyname);
4376 void LoadLevelSetup_LastSeries(void)
4378 // --------------------------------------------------------------------------
4379 // ~/.<program>/levelsetup.conf
4380 // --------------------------------------------------------------------------
4382 char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
4383 SetupFileHash *level_setup_hash = NULL;
4385 // always start with reliable default values
4386 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
4388 if (!strEqual(DEFAULT_LEVELSET, UNDEFINED_LEVELSET))
4390 leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
4392 if (leveldir_current == NULL)
4393 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
4396 if ((level_setup_hash = loadSetupFileHash(filename)))
4398 char *last_level_series =
4399 getHashEntry(level_setup_hash, TOKEN_STR_LAST_LEVEL_SERIES);
4401 leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
4403 if (leveldir_current == NULL)
4404 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
4406 freeSetupFileHash(level_setup_hash);
4410 Debug("setup", "using default setup values");
4416 static void SaveLevelSetup_LastSeries_Ext(boolean deactivate_last_level_series)
4418 // --------------------------------------------------------------------------
4419 // ~/.<program>/levelsetup.conf
4420 // --------------------------------------------------------------------------
4422 // check if the current level directory structure is available at this point
4423 if (leveldir_current == NULL)
4426 char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
4427 char *level_subdir = leveldir_current->subdir;
4430 InitUserDataDirectory();
4432 if (!(file = fopen(filename, MODE_WRITE)))
4434 Warn("cannot write setup file '%s'", filename);
4441 fprintFileHeader(file, LEVELSETUP_FILENAME);
4443 if (deactivate_last_level_series)
4444 fprintf(file, "# %s\n# ", "the following level set may have caused a problem and was deactivated");
4446 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_LEVEL_SERIES,
4451 SetFilePermissions(filename, PERMS_PRIVATE);
4456 void SaveLevelSetup_LastSeries(void)
4458 SaveLevelSetup_LastSeries_Ext(FALSE);
4461 void SaveLevelSetup_LastSeries_Deactivate(void)
4463 SaveLevelSetup_LastSeries_Ext(TRUE);
4466 static void checkSeriesInfo(void)
4468 static char *level_directory = NULL;
4471 DirectoryEntry *dir_entry;
4474 checked_free(level_directory);
4476 // check for more levels besides the 'levels' field of 'levelinfo.conf'
4478 level_directory = getPath2((leveldir_current->in_user_dir ?
4479 getUserLevelDir(NULL) :
4480 options.level_directory),
4481 leveldir_current->fullpath);
4483 if ((dir = openDirectory(level_directory)) == NULL)
4485 Warn("cannot read level directory '%s'", level_directory);
4491 while ((dir_entry = readDirectory(dir)) != NULL) // loop all entries
4493 if (strlen(dir_entry->basename) > 4 &&
4494 dir_entry->basename[3] == '.' &&
4495 strEqual(&dir_entry->basename[4], LEVELFILE_EXTENSION))
4497 char levelnum_str[4];
4500 strncpy(levelnum_str, dir_entry->basename, 3);
4501 levelnum_str[3] = '\0';
4503 levelnum_value = atoi(levelnum_str);
4505 if (levelnum_value < leveldir_current->first_level)
4507 Warn("additional level %d found", levelnum_value);
4509 leveldir_current->first_level = levelnum_value;
4511 else if (levelnum_value > leveldir_current->last_level)
4513 Warn("additional level %d found", levelnum_value);
4515 leveldir_current->last_level = levelnum_value;
4521 closeDirectory(dir);
4524 void LoadLevelSetup_SeriesInfo(void)
4527 SetupFileHash *level_setup_hash = NULL;
4528 char *level_subdir = leveldir_current->subdir;
4531 // always start with reliable default values
4532 level_nr = leveldir_current->first_level;
4534 for (i = 0; i < MAX_LEVELS; i++)
4536 LevelStats_setPlayed(i, 0);
4537 LevelStats_setSolved(i, 0);
4542 // --------------------------------------------------------------------------
4543 // ~/.<program>/levelsetup/<level series>/levelsetup.conf
4544 // --------------------------------------------------------------------------
4546 level_subdir = leveldir_current->subdir;
4548 filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
4550 if ((level_setup_hash = loadSetupFileHash(filename)))
4554 // get last played level in this level set
4556 token_value = getHashEntry(level_setup_hash, TOKEN_STR_LAST_PLAYED_LEVEL);
4560 level_nr = atoi(token_value);
4562 if (level_nr < leveldir_current->first_level)
4563 level_nr = leveldir_current->first_level;
4564 if (level_nr > leveldir_current->last_level)
4565 level_nr = leveldir_current->last_level;
4568 // get handicap level in this level set
4570 token_value = getHashEntry(level_setup_hash, TOKEN_STR_HANDICAP_LEVEL);
4574 int level_nr = atoi(token_value);
4576 if (level_nr < leveldir_current->first_level)
4577 level_nr = leveldir_current->first_level;
4578 if (level_nr > leveldir_current->last_level + 1)
4579 level_nr = leveldir_current->last_level;
4581 if (leveldir_current->user_defined || !leveldir_current->handicap)
4582 level_nr = leveldir_current->last_level;
4584 leveldir_current->handicap_level = level_nr;
4587 // get number of played and solved levels in this level set
4589 BEGIN_HASH_ITERATION(level_setup_hash, itr)
4591 char *token = HASH_ITERATION_TOKEN(itr);
4592 char *value = HASH_ITERATION_VALUE(itr);
4594 if (strlen(token) == 3 &&
4595 token[0] >= '0' && token[0] <= '9' &&
4596 token[1] >= '0' && token[1] <= '9' &&
4597 token[2] >= '0' && token[2] <= '9')
4599 int level_nr = atoi(token);
4602 LevelStats_setPlayed(level_nr, atoi(value)); // read 1st column
4604 value = strchr(value, ' ');
4607 LevelStats_setSolved(level_nr, atoi(value)); // read 2nd column
4610 END_HASH_ITERATION(hash, itr)
4612 freeSetupFileHash(level_setup_hash);
4616 Debug("setup", "using default setup values");
4622 void SaveLevelSetup_SeriesInfo(void)
4625 char *level_subdir = leveldir_current->subdir;
4626 char *level_nr_str = int2str(level_nr, 0);
4627 char *handicap_level_str = int2str(leveldir_current->handicap_level, 0);
4631 // --------------------------------------------------------------------------
4632 // ~/.<program>/levelsetup/<level series>/levelsetup.conf
4633 // --------------------------------------------------------------------------
4635 InitLevelSetupDirectory(level_subdir);
4637 filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
4639 if (!(file = fopen(filename, MODE_WRITE)))
4641 Warn("cannot write setup file '%s'", filename);
4648 fprintFileHeader(file, LEVELSETUP_FILENAME);
4650 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_PLAYED_LEVEL,
4652 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_HANDICAP_LEVEL,
4653 handicap_level_str));
4655 for (i = leveldir_current->first_level; i <= leveldir_current->last_level;
4658 if (LevelStats_getPlayed(i) > 0 ||
4659 LevelStats_getSolved(i) > 0)
4664 sprintf(token, "%03d", i);
4665 sprintf(value, "%d %d", LevelStats_getPlayed(i), LevelStats_getSolved(i));
4667 fprintf(file, "%s\n", getFormattedSetupEntry(token, value));
4673 SetFilePermissions(filename, PERMS_PRIVATE);
4678 int LevelStats_getPlayed(int nr)
4680 return (nr >= 0 && nr < MAX_LEVELS ? level_stats[nr].played : 0);
4683 int LevelStats_getSolved(int nr)
4685 return (nr >= 0 && nr < MAX_LEVELS ? level_stats[nr].solved : 0);
4688 void LevelStats_setPlayed(int nr, int value)
4690 if (nr >= 0 && nr < MAX_LEVELS)
4691 level_stats[nr].played = value;
4694 void LevelStats_setSolved(int nr, int value)
4696 if (nr >= 0 && nr < MAX_LEVELS)
4697 level_stats[nr].solved = value;
4700 void LevelStats_incPlayed(int nr)
4702 if (nr >= 0 && nr < MAX_LEVELS)
4703 level_stats[nr].played++;
4706 void LevelStats_incSolved(int nr)
4708 if (nr >= 0 && nr < MAX_LEVELS)
4709 level_stats[nr].solved++;