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))
824 Error(ERR_WARN, "cannot find artwork file '%s' (using fallback)",
827 // 6th try: look for fallback artwork in old default artwork directory
828 // (needed to prevent errors when trying to access unused artwork files)
829 filename = getImg2(options.graphics_directory, GFX_FALLBACK_FILENAME);
830 if (fileExists(filename))
834 return NULL; // cannot find specified artwork file anywhere
837 char *getCustomSoundFilename(char *basename)
839 static char *filename = NULL;
840 boolean skip_setup_artwork = FALSE;
842 checked_free(filename);
844 basename = getCorrectedArtworkBasename(basename);
846 if (!gfx.override_level_sounds)
848 // 1st try: look for special artwork in current level series directory
849 filename = getPath3(getCurrentLevelDir(), SOUNDS_DIRECTORY, basename);
850 if (fileExists(filename))
855 // check if there is special artwork configured in level series config
856 if (getLevelArtworkSet(ARTWORK_TYPE_SOUNDS) != NULL)
858 // 2nd try: look for special artwork configured in level series config
859 filename = getPath2(getLevelArtworkDir(TREE_TYPE_SOUNDS_DIR), basename);
860 if (fileExists(filename))
865 // take missing artwork configured in level set config from default
866 skip_setup_artwork = TRUE;
870 if (!skip_setup_artwork)
872 // 3rd try: look for special artwork in configured artwork directory
873 filename = getPath2(getSetupArtworkDir(artwork.snd_current), basename);
874 if (fileExists(filename))
880 // 4th try: look for default artwork in new default artwork directory
881 filename = getPath2(getDefaultSoundsDir(SND_DEFAULT_SUBDIR), basename);
882 if (fileExists(filename))
887 // 5th try: look for default artwork in old default artwork directory
888 filename = getPath2(options.sounds_directory, basename);
889 if (fileExists(filename))
892 if (!strEqual(SND_FALLBACK_FILENAME, UNDEFINED_FILENAME))
897 Error(ERR_WARN, "cannot find artwork file '%s' (using fallback)",
900 // 6th try: look for fallback artwork in old default artwork directory
901 // (needed to prevent errors when trying to access unused artwork files)
902 filename = getPath2(options.sounds_directory, SND_FALLBACK_FILENAME);
903 if (fileExists(filename))
907 return NULL; // cannot find specified artwork file anywhere
910 char *getCustomMusicFilename(char *basename)
912 static char *filename = NULL;
913 boolean skip_setup_artwork = FALSE;
915 checked_free(filename);
917 basename = getCorrectedArtworkBasename(basename);
919 if (!gfx.override_level_music)
921 // 1st try: look for special artwork in current level series directory
922 filename = getPath3(getCurrentLevelDir(), MUSIC_DIRECTORY, basename);
923 if (fileExists(filename))
928 // check if there is special artwork configured in level series config
929 if (getLevelArtworkSet(ARTWORK_TYPE_MUSIC) != NULL)
931 // 2nd try: look for special artwork configured in level series config
932 filename = getPath2(getLevelArtworkDir(TREE_TYPE_MUSIC_DIR), basename);
933 if (fileExists(filename))
938 // take missing artwork configured in level set config from default
939 skip_setup_artwork = TRUE;
943 if (!skip_setup_artwork)
945 // 3rd try: look for special artwork in configured artwork directory
946 filename = getPath2(getSetupArtworkDir(artwork.mus_current), basename);
947 if (fileExists(filename))
953 // 4th try: look for default artwork in new default artwork directory
954 filename = getPath2(getDefaultMusicDir(MUS_DEFAULT_SUBDIR), basename);
955 if (fileExists(filename))
960 // 5th try: look for default artwork in old default artwork directory
961 filename = getPath2(options.music_directory, basename);
962 if (fileExists(filename))
965 if (!strEqual(MUS_FALLBACK_FILENAME, UNDEFINED_FILENAME))
970 Error(ERR_WARN, "cannot find artwork file '%s' (using fallback)",
973 // 6th try: look for fallback artwork in old default artwork directory
974 // (needed to prevent errors when trying to access unused artwork files)
975 filename = getPath2(options.music_directory, MUS_FALLBACK_FILENAME);
976 if (fileExists(filename))
980 return NULL; // cannot find specified artwork file anywhere
983 char *getCustomArtworkFilename(char *basename, int type)
985 if (type == ARTWORK_TYPE_GRAPHICS)
986 return getCustomImageFilename(basename);
987 else if (type == ARTWORK_TYPE_SOUNDS)
988 return getCustomSoundFilename(basename);
989 else if (type == ARTWORK_TYPE_MUSIC)
990 return getCustomMusicFilename(basename);
992 return UNDEFINED_FILENAME;
995 char *getCustomArtworkConfigFilename(int type)
997 return getCustomArtworkFilename(ARTWORKINFO_FILENAME(type), type);
1000 char *getCustomArtworkLevelConfigFilename(int type)
1002 static char *filename = NULL;
1004 checked_free(filename);
1006 filename = getPath2(getLevelArtworkDir(type), ARTWORKINFO_FILENAME(type));
1011 char *getCustomMusicDirectory(void)
1013 static char *directory = NULL;
1014 boolean skip_setup_artwork = FALSE;
1016 checked_free(directory);
1018 if (!gfx.override_level_music)
1020 // 1st try: look for special artwork in current level series directory
1021 directory = getPath2(getCurrentLevelDir(), MUSIC_DIRECTORY);
1022 if (directoryExists(directory))
1027 // check if there is special artwork configured in level series config
1028 if (getLevelArtworkSet(ARTWORK_TYPE_MUSIC) != NULL)
1030 // 2nd try: look for special artwork configured in level series config
1031 directory = getStringCopy(getLevelArtworkDir(TREE_TYPE_MUSIC_DIR));
1032 if (directoryExists(directory))
1037 // take missing artwork configured in level set config from default
1038 skip_setup_artwork = TRUE;
1042 if (!skip_setup_artwork)
1044 // 3rd try: look for special artwork in configured artwork directory
1045 directory = getStringCopy(getSetupArtworkDir(artwork.mus_current));
1046 if (directoryExists(directory))
1052 // 4th try: look for default artwork in new default artwork directory
1053 directory = getStringCopy(getDefaultMusicDir(MUS_DEFAULT_SUBDIR));
1054 if (directoryExists(directory))
1059 // 5th try: look for default artwork in old default artwork directory
1060 directory = getStringCopy(options.music_directory);
1061 if (directoryExists(directory))
1064 return NULL; // cannot find specified artwork file anywhere
1067 void InitTapeDirectory(char *level_subdir)
1069 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
1070 createDirectory(getTapeDir(NULL), "main tape", PERMS_PRIVATE);
1071 createDirectory(getTapeDir(level_subdir), "level tape", PERMS_PRIVATE);
1074 void InitScoreDirectory(char *level_subdir)
1076 int permissions = (program.global_scores ? PERMS_PUBLIC : PERMS_PRIVATE);
1078 if (program.global_scores)
1079 createDirectory(getCommonDataDir(), "common data", permissions);
1081 createDirectory(getUserGameDataDir(), "user data", permissions);
1083 createDirectory(getScoreDir(NULL), "main score", permissions);
1084 createDirectory(getScoreDir(level_subdir), "level score", permissions);
1087 static void SaveUserLevelInfo(void);
1089 void InitUserLevelDirectory(char *level_subdir)
1091 if (!directoryExists(getUserLevelDir(level_subdir)))
1093 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
1094 createDirectory(getUserLevelDir(NULL), "main user level", PERMS_PRIVATE);
1095 createDirectory(getUserLevelDir(level_subdir), "user level", PERMS_PRIVATE);
1097 if (setup.internal.create_user_levelset)
1098 SaveUserLevelInfo();
1102 void InitNetworkLevelDirectory(char *level_subdir)
1104 if (!directoryExists(getNetworkLevelDir(level_subdir)))
1106 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
1107 createDirectory(getNetworkDir(), "network data", PERMS_PRIVATE);
1108 createDirectory(getNetworkLevelDir(NULL), "main network level", PERMS_PRIVATE);
1109 createDirectory(getNetworkLevelDir(level_subdir), "network level", PERMS_PRIVATE);
1113 void InitLevelSetupDirectory(char *level_subdir)
1115 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
1116 createDirectory(getLevelSetupDir(NULL), "main level setup", PERMS_PRIVATE);
1117 createDirectory(getLevelSetupDir(level_subdir), "level setup", PERMS_PRIVATE);
1120 static void InitCacheDirectory(void)
1122 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
1123 createDirectory(getCacheDir(), "cache data", PERMS_PRIVATE);
1127 // ----------------------------------------------------------------------------
1128 // some functions to handle lists of level and artwork directories
1129 // ----------------------------------------------------------------------------
1131 TreeInfo *newTreeInfo(void)
1133 return checked_calloc(sizeof(TreeInfo));
1136 TreeInfo *newTreeInfo_setDefaults(int type)
1138 TreeInfo *ti = newTreeInfo();
1140 setTreeInfoToDefaults(ti, type);
1145 void pushTreeInfo(TreeInfo **node_first, TreeInfo *node_new)
1147 node_new->next = *node_first;
1148 *node_first = node_new;
1151 int numTreeInfo(TreeInfo *node)
1164 boolean validLevelSeries(TreeInfo *node)
1166 return (node != NULL && !node->node_group && !node->parent_link);
1169 TreeInfo *getFirstValidTreeInfoEntry(TreeInfo *node)
1174 if (node->node_group) // enter level group (step down into tree)
1175 return getFirstValidTreeInfoEntry(node->node_group);
1176 else if (node->parent_link) // skip start entry of level group
1178 if (node->next) // get first real level series entry
1179 return getFirstValidTreeInfoEntry(node->next);
1180 else // leave empty level group and go on
1181 return getFirstValidTreeInfoEntry(node->node_parent->next);
1183 else // this seems to be a regular level series
1187 TreeInfo *getTreeInfoFirstGroupEntry(TreeInfo *node)
1192 if (node->node_parent == NULL) // top level group
1193 return *node->node_top;
1194 else // sub level group
1195 return node->node_parent->node_group;
1198 int numTreeInfoInGroup(TreeInfo *node)
1200 return numTreeInfo(getTreeInfoFirstGroupEntry(node));
1203 int posTreeInfo(TreeInfo *node)
1205 TreeInfo *node_cmp = getTreeInfoFirstGroupEntry(node);
1210 if (node_cmp == node)
1214 node_cmp = node_cmp->next;
1220 TreeInfo *getTreeInfoFromPos(TreeInfo *node, int pos)
1222 TreeInfo *node_default = node;
1234 return node_default;
1237 TreeInfo *getTreeInfoFromIdentifier(TreeInfo *node, char *identifier)
1239 if (identifier == NULL)
1244 if (node->node_group)
1246 TreeInfo *node_group;
1248 node_group = getTreeInfoFromIdentifier(node->node_group, identifier);
1253 else if (!node->parent_link)
1255 if (strEqual(identifier, node->identifier))
1265 static TreeInfo *cloneTreeNode(TreeInfo **node_top, TreeInfo *node_parent,
1266 TreeInfo *node, boolean skip_sets_without_levels)
1273 if (!node->parent_link && !node->level_group &&
1274 skip_sets_without_levels && node->levels == 0)
1275 return cloneTreeNode(node_top, node_parent, node->next,
1276 skip_sets_without_levels);
1278 node_new = getTreeInfoCopy(node); // copy complete node
1280 node_new->node_top = node_top; // correct top node link
1281 node_new->node_parent = node_parent; // correct parent node link
1283 if (node->level_group)
1284 node_new->node_group = cloneTreeNode(node_top, node_new, node->node_group,
1285 skip_sets_without_levels);
1287 node_new->next = cloneTreeNode(node_top, node_parent, node->next,
1288 skip_sets_without_levels);
1293 static void cloneTree(TreeInfo **ti_new, TreeInfo *ti, boolean skip_empty_sets)
1295 TreeInfo *ti_cloned = cloneTreeNode(ti_new, NULL, ti, skip_empty_sets);
1297 *ti_new = ti_cloned;
1300 static boolean adjustTreeGraphicsForEMC(TreeInfo *node)
1302 boolean settings_changed = FALSE;
1306 boolean want_ecs = (setup.prefer_aga_graphics == FALSE);
1307 boolean want_aga = (setup.prefer_aga_graphics == TRUE);
1308 boolean has_only_ecs = (!node->graphics_set && !node->graphics_set_aga);
1309 boolean has_only_aga = (!node->graphics_set && !node->graphics_set_ecs);
1310 char *graphics_set = NULL;
1312 if (node->graphics_set_ecs && (want_ecs || has_only_ecs))
1313 graphics_set = node->graphics_set_ecs;
1315 if (node->graphics_set_aga && (want_aga || has_only_aga))
1316 graphics_set = node->graphics_set_aga;
1318 if (graphics_set && !strEqual(node->graphics_set, graphics_set))
1320 setString(&node->graphics_set, graphics_set);
1321 settings_changed = TRUE;
1324 if (node->node_group != NULL)
1325 settings_changed |= adjustTreeGraphicsForEMC(node->node_group);
1330 return settings_changed;
1333 static boolean adjustTreeSoundsForEMC(TreeInfo *node)
1335 boolean settings_changed = FALSE;
1339 boolean want_default = (setup.prefer_lowpass_sounds == FALSE);
1340 boolean want_lowpass = (setup.prefer_lowpass_sounds == TRUE);
1341 boolean has_only_default = (!node->sounds_set && !node->sounds_set_lowpass);
1342 boolean has_only_lowpass = (!node->sounds_set && !node->sounds_set_default);
1343 char *sounds_set = NULL;
1345 if (node->sounds_set_default && (want_default || has_only_default))
1346 sounds_set = node->sounds_set_default;
1348 if (node->sounds_set_lowpass && (want_lowpass || has_only_lowpass))
1349 sounds_set = node->sounds_set_lowpass;
1351 if (sounds_set && !strEqual(node->sounds_set, sounds_set))
1353 setString(&node->sounds_set, sounds_set);
1354 settings_changed = TRUE;
1357 if (node->node_group != NULL)
1358 settings_changed |= adjustTreeSoundsForEMC(node->node_group);
1363 return settings_changed;
1366 void dumpTreeInfo(TreeInfo *node, int depth)
1370 printf("Dumping TreeInfo:\n");
1374 for (i = 0; i < (depth + 1) * 3; i++)
1377 printf("'%s' / '%s'\n", node->identifier, node->name);
1380 // use for dumping artwork info tree
1381 printf("subdir == '%s' ['%s', '%s'] [%d])\n",
1382 node->subdir, node->fullpath, node->basepath, node->in_user_dir);
1385 if (node->node_group != NULL)
1386 dumpTreeInfo(node->node_group, depth + 1);
1392 void sortTreeInfoBySortFunction(TreeInfo **node_first,
1393 int (*compare_function)(const void *,
1396 int num_nodes = numTreeInfo(*node_first);
1397 TreeInfo **sort_array;
1398 TreeInfo *node = *node_first;
1404 // allocate array for sorting structure pointers
1405 sort_array = checked_calloc(num_nodes * sizeof(TreeInfo *));
1407 // writing structure pointers to sorting array
1408 while (i < num_nodes && node) // double boundary check...
1410 sort_array[i] = node;
1416 // sorting the structure pointers in the sorting array
1417 qsort(sort_array, num_nodes, sizeof(TreeInfo *),
1420 // update the linkage of list elements with the sorted node array
1421 for (i = 0; i < num_nodes - 1; i++)
1422 sort_array[i]->next = sort_array[i + 1];
1423 sort_array[num_nodes - 1]->next = NULL;
1425 // update the linkage of the main list anchor pointer
1426 *node_first = sort_array[0];
1430 // now recursively sort the level group structures
1434 if (node->node_group != NULL)
1435 sortTreeInfoBySortFunction(&node->node_group, compare_function);
1441 void sortTreeInfo(TreeInfo **node_first)
1443 sortTreeInfoBySortFunction(node_first, compareTreeInfoEntries);
1447 // ============================================================================
1448 // some stuff from "files.c"
1449 // ============================================================================
1451 #if defined(PLATFORM_WIN32)
1453 #define S_IRGRP S_IRUSR
1456 #define S_IROTH S_IRUSR
1459 #define S_IWGRP S_IWUSR
1462 #define S_IWOTH S_IWUSR
1465 #define S_IXGRP S_IXUSR
1468 #define S_IXOTH S_IXUSR
1471 #define S_IRWXG (S_IRGRP | S_IWGRP | S_IXGRP)
1476 #endif // PLATFORM_WIN32
1478 // file permissions for newly written files
1479 #define MODE_R_ALL (S_IRUSR | S_IRGRP | S_IROTH)
1480 #define MODE_W_ALL (S_IWUSR | S_IWGRP | S_IWOTH)
1481 #define MODE_X_ALL (S_IXUSR | S_IXGRP | S_IXOTH)
1483 #define MODE_W_PRIVATE (S_IWUSR)
1484 #define MODE_W_PUBLIC_FILE (S_IWUSR | S_IWGRP)
1485 #define MODE_W_PUBLIC_DIR (S_IWUSR | S_IWGRP | S_ISGID)
1487 #define DIR_PERMS_PRIVATE (MODE_R_ALL | MODE_X_ALL | MODE_W_PRIVATE)
1488 #define DIR_PERMS_PUBLIC (MODE_R_ALL | MODE_X_ALL | MODE_W_PUBLIC_DIR)
1489 #define DIR_PERMS_PUBLIC_ALL (MODE_R_ALL | MODE_X_ALL | MODE_W_ALL)
1491 #define FILE_PERMS_PRIVATE (MODE_R_ALL | MODE_W_PRIVATE)
1492 #define FILE_PERMS_PUBLIC (MODE_R_ALL | MODE_W_PUBLIC_FILE)
1493 #define FILE_PERMS_PUBLIC_ALL (MODE_R_ALL | MODE_W_ALL)
1496 char *getHomeDir(void)
1498 static char *dir = NULL;
1500 #if defined(PLATFORM_WIN32)
1503 dir = checked_malloc(MAX_PATH + 1);
1505 if (!SUCCEEDED(SHGetFolderPath(NULL, CSIDL_PERSONAL, NULL, 0, dir)))
1508 #elif defined(PLATFORM_UNIX)
1511 if ((dir = getenv("HOME")) == NULL)
1515 if ((pwd = getpwuid(getuid())) != NULL)
1516 dir = getStringCopy(pwd->pw_dir);
1528 char *getCommonDataDir(void)
1530 static char *common_data_dir = NULL;
1532 #if defined(PLATFORM_WIN32)
1533 if (common_data_dir == NULL)
1535 char *dir = checked_malloc(MAX_PATH + 1);
1537 if (SUCCEEDED(SHGetFolderPath(NULL, CSIDL_COMMON_DOCUMENTS, NULL, 0, dir))
1538 && !strEqual(dir, "")) // empty for Windows 95/98
1539 common_data_dir = getPath2(dir, program.userdata_subdir);
1541 common_data_dir = options.rw_base_directory;
1544 if (common_data_dir == NULL)
1545 common_data_dir = options.rw_base_directory;
1548 return common_data_dir;
1551 char *getPersonalDataDir(void)
1553 static char *personal_data_dir = NULL;
1555 #if defined(PLATFORM_MACOSX)
1556 if (personal_data_dir == NULL)
1557 personal_data_dir = getPath2(getHomeDir(), "Documents");
1559 if (personal_data_dir == NULL)
1560 personal_data_dir = getHomeDir();
1563 return personal_data_dir;
1566 char *getUserGameDataDir(void)
1568 static char *user_game_data_dir = NULL;
1570 #if defined(PLATFORM_ANDROID)
1571 if (user_game_data_dir == NULL)
1572 user_game_data_dir = (char *)(SDL_AndroidGetExternalStorageState() &
1573 SDL_ANDROID_EXTERNAL_STORAGE_WRITE ?
1574 SDL_AndroidGetExternalStoragePath() :
1575 SDL_AndroidGetInternalStoragePath());
1577 if (user_game_data_dir == NULL)
1578 user_game_data_dir = getPath2(getPersonalDataDir(),
1579 program.userdata_subdir);
1582 return user_game_data_dir;
1585 char *getSetupDir(void)
1587 return getUserGameDataDir();
1590 static mode_t posix_umask(mode_t mask)
1592 #if defined(PLATFORM_UNIX)
1599 static int posix_mkdir(const char *pathname, mode_t mode)
1601 #if defined(PLATFORM_WIN32)
1602 return mkdir(pathname);
1604 return mkdir(pathname, mode);
1608 static boolean posix_process_running_setgid(void)
1610 #if defined(PLATFORM_UNIX)
1611 return (getgid() != getegid());
1617 void createDirectory(char *dir, char *text, int permission_class)
1619 if (directoryExists(dir))
1622 // leave "other" permissions in umask untouched, but ensure group parts
1623 // of USERDATA_DIR_MODE are not masked
1624 mode_t dir_mode = (permission_class == PERMS_PRIVATE ?
1625 DIR_PERMS_PRIVATE : DIR_PERMS_PUBLIC);
1626 mode_t last_umask = posix_umask(0);
1627 mode_t group_umask = ~(dir_mode & S_IRWXG);
1628 int running_setgid = posix_process_running_setgid();
1630 if (permission_class == PERMS_PUBLIC)
1632 // if we're setgid, protect files against "other"
1633 // else keep umask(0) to make the dir world-writable
1636 posix_umask(last_umask & group_umask);
1638 dir_mode = DIR_PERMS_PUBLIC_ALL;
1641 if (posix_mkdir(dir, dir_mode) != 0)
1642 Error(ERR_WARN, "cannot create %s directory '%s': %s",
1643 text, dir, strerror(errno));
1645 if (permission_class == PERMS_PUBLIC && !running_setgid)
1646 chmod(dir, dir_mode);
1648 posix_umask(last_umask); // restore previous umask
1651 void InitUserDataDirectory(void)
1653 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
1656 void SetFilePermissions(char *filename, int permission_class)
1658 int running_setgid = posix_process_running_setgid();
1659 int perms = (permission_class == PERMS_PRIVATE ?
1660 FILE_PERMS_PRIVATE : FILE_PERMS_PUBLIC);
1662 if (permission_class == PERMS_PUBLIC && !running_setgid)
1663 perms = FILE_PERMS_PUBLIC_ALL;
1665 chmod(filename, perms);
1668 char *getCookie(char *file_type)
1670 static char cookie[MAX_COOKIE_LEN + 1];
1672 if (strlen(program.cookie_prefix) + 1 +
1673 strlen(file_type) + strlen("_FILE_VERSION_x.x") > MAX_COOKIE_LEN)
1674 return "[COOKIE ERROR]"; // should never happen
1676 sprintf(cookie, "%s_%s_FILE_VERSION_%d.%d",
1677 program.cookie_prefix, file_type,
1678 program.version_super, program.version_major);
1683 void fprintFileHeader(FILE *file, char *basename)
1685 char *prefix = "# ";
1688 fprintf_line_with_prefix(file, prefix, sep1, 77);
1689 fprintf(file, "%s%s\n", prefix, basename);
1690 fprintf_line_with_prefix(file, prefix, sep1, 77);
1691 fprintf(file, "\n");
1694 int getFileVersionFromCookieString(const char *cookie)
1696 const char *ptr_cookie1, *ptr_cookie2;
1697 const char *pattern1 = "_FILE_VERSION_";
1698 const char *pattern2 = "?.?";
1699 const int len_cookie = strlen(cookie);
1700 const int len_pattern1 = strlen(pattern1);
1701 const int len_pattern2 = strlen(pattern2);
1702 const int len_pattern = len_pattern1 + len_pattern2;
1703 int version_super, version_major;
1705 if (len_cookie <= len_pattern)
1708 ptr_cookie1 = &cookie[len_cookie - len_pattern];
1709 ptr_cookie2 = &cookie[len_cookie - len_pattern2];
1711 if (strncmp(ptr_cookie1, pattern1, len_pattern1) != 0)
1714 if (ptr_cookie2[0] < '0' || ptr_cookie2[0] > '9' ||
1715 ptr_cookie2[1] != '.' ||
1716 ptr_cookie2[2] < '0' || ptr_cookie2[2] > '9')
1719 version_super = ptr_cookie2[0] - '0';
1720 version_major = ptr_cookie2[2] - '0';
1722 return VERSION_IDENT(version_super, version_major, 0, 0);
1725 boolean checkCookieString(const char *cookie, const char *template)
1727 const char *pattern = "_FILE_VERSION_?.?";
1728 const int len_cookie = strlen(cookie);
1729 const int len_template = strlen(template);
1730 const int len_pattern = strlen(pattern);
1732 if (len_cookie != len_template)
1735 if (strncmp(cookie, template, len_cookie - len_pattern) != 0)
1742 // ----------------------------------------------------------------------------
1743 // setup file list and hash handling functions
1744 // ----------------------------------------------------------------------------
1746 char *getFormattedSetupEntry(char *token, char *value)
1749 static char entry[MAX_LINE_LEN];
1751 // if value is an empty string, just return token without value
1755 // start with the token and some spaces to format output line
1756 sprintf(entry, "%s:", token);
1757 for (i = strlen(entry); i < token_value_position; i++)
1760 // continue with the token's value
1761 strcat(entry, value);
1766 SetupFileList *newSetupFileList(char *token, char *value)
1768 SetupFileList *new = checked_malloc(sizeof(SetupFileList));
1770 new->token = getStringCopy(token);
1771 new->value = getStringCopy(value);
1778 void freeSetupFileList(SetupFileList *list)
1783 checked_free(list->token);
1784 checked_free(list->value);
1787 freeSetupFileList(list->next);
1792 char *getListEntry(SetupFileList *list, char *token)
1797 if (strEqual(list->token, token))
1800 return getListEntry(list->next, token);
1803 SetupFileList *setListEntry(SetupFileList *list, char *token, char *value)
1808 if (strEqual(list->token, token))
1810 checked_free(list->value);
1812 list->value = getStringCopy(value);
1816 else if (list->next == NULL)
1817 return (list->next = newSetupFileList(token, value));
1819 return setListEntry(list->next, token, value);
1822 SetupFileList *addListEntry(SetupFileList *list, char *token, char *value)
1827 if (list->next == NULL)
1828 return (list->next = newSetupFileList(token, value));
1830 return addListEntry(list->next, token, value);
1833 #if ENABLE_UNUSED_CODE
1835 static void printSetupFileList(SetupFileList *list)
1840 printf("token: '%s'\n", list->token);
1841 printf("value: '%s'\n", list->value);
1843 printSetupFileList(list->next);
1849 DEFINE_HASHTABLE_INSERT(insert_hash_entry, char, char);
1850 DEFINE_HASHTABLE_SEARCH(search_hash_entry, char, char);
1851 DEFINE_HASHTABLE_CHANGE(change_hash_entry, char, char);
1852 DEFINE_HASHTABLE_REMOVE(remove_hash_entry, char, char);
1854 #define insert_hash_entry hashtable_insert
1855 #define search_hash_entry hashtable_search
1856 #define change_hash_entry hashtable_change
1857 #define remove_hash_entry hashtable_remove
1860 unsigned int get_hash_from_key(void *key)
1865 This algorithm (k=33) was first reported by Dan Bernstein many years ago in
1866 'comp.lang.c'. Another version of this algorithm (now favored by Bernstein)
1867 uses XOR: hash(i) = hash(i - 1) * 33 ^ str[i]; the magic of number 33 (why
1868 it works better than many other constants, prime or not) has never been
1869 adequately explained.
1871 If you just want to have a good hash function, and cannot wait, djb2
1872 is one of the best string hash functions i know. It has excellent
1873 distribution and speed on many different sets of keys and table sizes.
1874 You are not likely to do better with one of the "well known" functions
1875 such as PJW, K&R, etc.
1877 Ozan (oz) Yigit [http://www.cs.yorku.ca/~oz/hash.html]
1880 char *str = (char *)key;
1881 unsigned int hash = 5381;
1884 while ((c = *str++))
1885 hash = ((hash << 5) + hash) + c; // hash * 33 + c
1890 static int keys_are_equal(void *key1, void *key2)
1892 return (strEqual((char *)key1, (char *)key2));
1895 SetupFileHash *newSetupFileHash(void)
1897 SetupFileHash *new_hash =
1898 create_hashtable(16, 0.75, get_hash_from_key, keys_are_equal);
1900 if (new_hash == NULL)
1901 Error(ERR_EXIT, "create_hashtable() failed -- out of memory");
1906 void freeSetupFileHash(SetupFileHash *hash)
1911 hashtable_destroy(hash, 1); // 1 == also free values stored in hash
1914 char *getHashEntry(SetupFileHash *hash, char *token)
1919 return search_hash_entry(hash, token);
1922 void setHashEntry(SetupFileHash *hash, char *token, char *value)
1929 value_copy = getStringCopy(value);
1931 // change value; if it does not exist, insert it as new
1932 if (!change_hash_entry(hash, token, value_copy))
1933 if (!insert_hash_entry(hash, getStringCopy(token), value_copy))
1934 Error(ERR_EXIT, "cannot insert into hash -- aborting");
1937 char *removeHashEntry(SetupFileHash *hash, char *token)
1942 return remove_hash_entry(hash, token);
1945 #if ENABLE_UNUSED_CODE
1947 static void printSetupFileHash(SetupFileHash *hash)
1949 BEGIN_HASH_ITERATION(hash, itr)
1951 printf("token: '%s'\n", HASH_ITERATION_TOKEN(itr));
1952 printf("value: '%s'\n", HASH_ITERATION_VALUE(itr));
1954 END_HASH_ITERATION(hash, itr)
1959 #define ALLOW_TOKEN_VALUE_SEPARATOR_BEING_WHITESPACE 1
1960 #define CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING 0
1961 #define CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH 0
1963 static boolean token_value_separator_found = FALSE;
1964 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
1965 static boolean token_value_separator_warning = FALSE;
1967 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
1968 static boolean token_already_exists_warning = FALSE;
1971 static boolean getTokenValueFromSetupLineExt(char *line,
1972 char **token_ptr, char **value_ptr,
1973 char *filename, char *line_raw,
1975 boolean separator_required)
1977 static char line_copy[MAX_LINE_LEN + 1], line_raw_copy[MAX_LINE_LEN + 1];
1978 char *token, *value, *line_ptr;
1980 // when externally invoked via ReadTokenValueFromLine(), copy line buffers
1981 if (line_raw == NULL)
1983 strncpy(line_copy, line, MAX_LINE_LEN);
1984 line_copy[MAX_LINE_LEN] = '\0';
1987 strcpy(line_raw_copy, line_copy);
1988 line_raw = line_raw_copy;
1991 // cut trailing comment from input line
1992 for (line_ptr = line; *line_ptr; line_ptr++)
1994 if (*line_ptr == '#')
2001 // cut trailing whitespaces from input line
2002 for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
2003 if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
2006 // ignore empty lines
2010 // cut leading whitespaces from token
2011 for (token = line; *token; token++)
2012 if (*token != ' ' && *token != '\t')
2015 // start with empty value as reliable default
2018 token_value_separator_found = FALSE;
2020 // find end of token to determine start of value
2021 for (line_ptr = token; *line_ptr; line_ptr++)
2023 // first look for an explicit token/value separator, like ':' or '='
2024 if (*line_ptr == ':' || *line_ptr == '=')
2026 *line_ptr = '\0'; // terminate token string
2027 value = line_ptr + 1; // set beginning of value
2029 token_value_separator_found = TRUE;
2035 #if ALLOW_TOKEN_VALUE_SEPARATOR_BEING_WHITESPACE
2036 // fallback: if no token/value separator found, also allow whitespaces
2037 if (!token_value_separator_found && !separator_required)
2039 for (line_ptr = token; *line_ptr; line_ptr++)
2041 if (*line_ptr == ' ' || *line_ptr == '\t')
2043 *line_ptr = '\0'; // terminate token string
2044 value = line_ptr + 1; // set beginning of value
2046 token_value_separator_found = TRUE;
2052 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2053 if (token_value_separator_found)
2055 if (!token_value_separator_warning)
2057 Error(ERR_INFO_LINE, "-");
2059 if (filename != NULL)
2061 Error(ERR_WARN, "missing token/value separator(s) in config file:");
2062 Error(ERR_INFO, "- config file: '%s'", filename);
2066 Error(ERR_WARN, "missing token/value separator(s):");
2069 token_value_separator_warning = TRUE;
2072 if (filename != NULL)
2073 Error(ERR_INFO, "- line %d: '%s'", line_nr, line_raw);
2075 Error(ERR_INFO, "- line: '%s'", line_raw);
2081 // cut trailing whitespaces from token
2082 for (line_ptr = &token[strlen(token)]; line_ptr >= token; line_ptr--)
2083 if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
2086 // cut leading whitespaces from value
2087 for (; *value; value++)
2088 if (*value != ' ' && *value != '\t')
2097 boolean getTokenValueFromSetupLine(char *line, char **token, char **value)
2099 // while the internal (old) interface does not require a token/value
2100 // separator (for downwards compatibility with existing files which
2101 // don't use them), it is mandatory for the external (new) interface
2103 return getTokenValueFromSetupLineExt(line, token, value, NULL, NULL, 0, TRUE);
2106 static boolean loadSetupFileData(void *setup_file_data, char *filename,
2107 boolean top_recursion_level, boolean is_hash)
2109 static SetupFileHash *include_filename_hash = NULL;
2110 char line[MAX_LINE_LEN], line_raw[MAX_LINE_LEN], previous_line[MAX_LINE_LEN];
2111 char *token, *value, *line_ptr;
2112 void *insert_ptr = NULL;
2113 boolean read_continued_line = FALSE;
2115 int line_nr = 0, token_count = 0, include_count = 0;
2117 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2118 token_value_separator_warning = FALSE;
2121 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2122 token_already_exists_warning = FALSE;
2125 if (!(file = openFile(filename, MODE_READ)))
2127 #if DEBUG_NO_CONFIG_FILE
2128 Error(ERR_DEBUG, "cannot open configuration file '%s'", filename);
2134 // use "insert pointer" to store list end for constant insertion complexity
2136 insert_ptr = setup_file_data;
2138 // on top invocation, create hash to mark included files (to prevent loops)
2139 if (top_recursion_level)
2140 include_filename_hash = newSetupFileHash();
2142 // mark this file as already included (to prevent including it again)
2143 setHashEntry(include_filename_hash, getBaseNamePtr(filename), "true");
2145 while (!checkEndOfFile(file))
2147 // read next line of input file
2148 if (!getStringFromFile(file, line, MAX_LINE_LEN))
2151 // check if line was completely read and is terminated by line break
2152 if (strlen(line) > 0 && line[strlen(line) - 1] == '\n')
2155 // cut trailing line break (this can be newline and/or carriage return)
2156 for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
2157 if ((*line_ptr == '\n' || *line_ptr == '\r') && *(line_ptr + 1) == '\0')
2160 // copy raw input line for later use (mainly debugging output)
2161 strcpy(line_raw, line);
2163 if (read_continued_line)
2165 // append new line to existing line, if there is enough space
2166 if (strlen(previous_line) + strlen(line_ptr) < MAX_LINE_LEN)
2167 strcat(previous_line, line_ptr);
2169 strcpy(line, previous_line); // copy storage buffer to line
2171 read_continued_line = FALSE;
2174 // if the last character is '\', continue at next line
2175 if (strlen(line) > 0 && line[strlen(line) - 1] == '\\')
2177 line[strlen(line) - 1] = '\0'; // cut off trailing backslash
2178 strcpy(previous_line, line); // copy line to storage buffer
2180 read_continued_line = TRUE;
2185 if (!getTokenValueFromSetupLineExt(line, &token, &value, filename,
2186 line_raw, line_nr, FALSE))
2191 if (strEqual(token, "include"))
2193 if (getHashEntry(include_filename_hash, value) == NULL)
2195 char *basepath = getBasePath(filename);
2196 char *basename = getBaseName(value);
2197 char *filename_include = getPath2(basepath, basename);
2199 loadSetupFileData(setup_file_data, filename_include, FALSE, is_hash);
2203 free(filename_include);
2209 Error(ERR_WARN, "ignoring already processed file '%s'", value);
2216 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2218 getHashEntry((SetupFileHash *)setup_file_data, token);
2220 if (old_value != NULL)
2222 if (!token_already_exists_warning)
2224 Error(ERR_INFO_LINE, "-");
2225 Error(ERR_WARN, "duplicate token(s) found in config file:");
2226 Error(ERR_INFO, "- config file: '%s'", filename);
2228 token_already_exists_warning = TRUE;
2231 Error(ERR_INFO, "- token: '%s' (in line %d)", token, line_nr);
2232 Error(ERR_INFO, " old value: '%s'", old_value);
2233 Error(ERR_INFO, " new value: '%s'", value);
2237 setHashEntry((SetupFileHash *)setup_file_data, token, value);
2241 insert_ptr = addListEntry((SetupFileList *)insert_ptr, token, value);
2251 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2252 if (token_value_separator_warning)
2253 Error(ERR_INFO_LINE, "-");
2256 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2257 if (token_already_exists_warning)
2258 Error(ERR_INFO_LINE, "-");
2261 if (token_count == 0 && include_count == 0)
2262 Error(ERR_WARN, "configuration file '%s' is empty", filename);
2264 if (top_recursion_level)
2265 freeSetupFileHash(include_filename_hash);
2270 static void saveSetupFileHash(SetupFileHash *hash, char *filename)
2274 if (!(file = fopen(filename, MODE_WRITE)))
2276 Error(ERR_WARN, "cannot write configuration file '%s'", filename);
2281 BEGIN_HASH_ITERATION(hash, itr)
2283 fprintf(file, "%s\n", getFormattedSetupEntry(HASH_ITERATION_TOKEN(itr),
2284 HASH_ITERATION_VALUE(itr)));
2286 END_HASH_ITERATION(hash, itr)
2291 SetupFileList *loadSetupFileList(char *filename)
2293 SetupFileList *setup_file_list = newSetupFileList("", "");
2294 SetupFileList *first_valid_list_entry;
2296 if (!loadSetupFileData(setup_file_list, filename, TRUE, FALSE))
2298 freeSetupFileList(setup_file_list);
2303 first_valid_list_entry = setup_file_list->next;
2305 // free empty list header
2306 setup_file_list->next = NULL;
2307 freeSetupFileList(setup_file_list);
2309 return first_valid_list_entry;
2312 SetupFileHash *loadSetupFileHash(char *filename)
2314 SetupFileHash *setup_file_hash = newSetupFileHash();
2316 if (!loadSetupFileData(setup_file_hash, filename, TRUE, TRUE))
2318 freeSetupFileHash(setup_file_hash);
2323 return setup_file_hash;
2327 // ============================================================================
2329 // ============================================================================
2331 #define TOKEN_STR_LAST_LEVEL_SERIES "last_level_series"
2332 #define TOKEN_STR_LAST_PLAYED_LEVEL "last_played_level"
2333 #define TOKEN_STR_HANDICAP_LEVEL "handicap_level"
2335 // level directory info
2336 #define LEVELINFO_TOKEN_IDENTIFIER 0
2337 #define LEVELINFO_TOKEN_NAME 1
2338 #define LEVELINFO_TOKEN_NAME_SORTING 2
2339 #define LEVELINFO_TOKEN_AUTHOR 3
2340 #define LEVELINFO_TOKEN_YEAR 4
2341 #define LEVELINFO_TOKEN_PROGRAM_TITLE 5
2342 #define LEVELINFO_TOKEN_PROGRAM_COPYRIGHT 6
2343 #define LEVELINFO_TOKEN_PROGRAM_COMPANY 7
2344 #define LEVELINFO_TOKEN_IMPORTED_FROM 8
2345 #define LEVELINFO_TOKEN_IMPORTED_BY 9
2346 #define LEVELINFO_TOKEN_TESTED_BY 10
2347 #define LEVELINFO_TOKEN_LEVELS 11
2348 #define LEVELINFO_TOKEN_FIRST_LEVEL 12
2349 #define LEVELINFO_TOKEN_SORT_PRIORITY 13
2350 #define LEVELINFO_TOKEN_LATEST_ENGINE 14
2351 #define LEVELINFO_TOKEN_LEVEL_GROUP 15
2352 #define LEVELINFO_TOKEN_READONLY 16
2353 #define LEVELINFO_TOKEN_GRAPHICS_SET_ECS 17
2354 #define LEVELINFO_TOKEN_GRAPHICS_SET_AGA 18
2355 #define LEVELINFO_TOKEN_GRAPHICS_SET 19
2356 #define LEVELINFO_TOKEN_SOUNDS_SET_DEFAULT 20
2357 #define LEVELINFO_TOKEN_SOUNDS_SET_LOWPASS 21
2358 #define LEVELINFO_TOKEN_SOUNDS_SET 22
2359 #define LEVELINFO_TOKEN_MUSIC_SET 23
2360 #define LEVELINFO_TOKEN_FILENAME 24
2361 #define LEVELINFO_TOKEN_FILETYPE 25
2362 #define LEVELINFO_TOKEN_SPECIAL_FLAGS 26
2363 #define LEVELINFO_TOKEN_HANDICAP 27
2364 #define LEVELINFO_TOKEN_SKIP_LEVELS 28
2365 #define LEVELINFO_TOKEN_USE_EMC_TILES 29
2367 #define NUM_LEVELINFO_TOKENS 30
2369 static LevelDirTree ldi;
2371 static struct TokenInfo levelinfo_tokens[] =
2373 // level directory info
2374 { TYPE_STRING, &ldi.identifier, "identifier" },
2375 { TYPE_STRING, &ldi.name, "name" },
2376 { TYPE_STRING, &ldi.name_sorting, "name_sorting" },
2377 { TYPE_STRING, &ldi.author, "author" },
2378 { TYPE_STRING, &ldi.year, "year" },
2379 { TYPE_STRING, &ldi.program_title, "program_title" },
2380 { TYPE_STRING, &ldi.program_copyright, "program_copyright" },
2381 { TYPE_STRING, &ldi.program_company, "program_company" },
2382 { TYPE_STRING, &ldi.imported_from, "imported_from" },
2383 { TYPE_STRING, &ldi.imported_by, "imported_by" },
2384 { TYPE_STRING, &ldi.tested_by, "tested_by" },
2385 { TYPE_INTEGER, &ldi.levels, "levels" },
2386 { TYPE_INTEGER, &ldi.first_level, "first_level" },
2387 { TYPE_INTEGER, &ldi.sort_priority, "sort_priority" },
2388 { TYPE_BOOLEAN, &ldi.latest_engine, "latest_engine" },
2389 { TYPE_BOOLEAN, &ldi.level_group, "level_group" },
2390 { TYPE_BOOLEAN, &ldi.readonly, "readonly" },
2391 { TYPE_STRING, &ldi.graphics_set_ecs, "graphics_set.ecs" },
2392 { TYPE_STRING, &ldi.graphics_set_aga, "graphics_set.aga" },
2393 { TYPE_STRING, &ldi.graphics_set, "graphics_set" },
2394 { TYPE_STRING, &ldi.sounds_set_default,"sounds_set.default" },
2395 { TYPE_STRING, &ldi.sounds_set_lowpass,"sounds_set.lowpass" },
2396 { TYPE_STRING, &ldi.sounds_set, "sounds_set" },
2397 { TYPE_STRING, &ldi.music_set, "music_set" },
2398 { TYPE_STRING, &ldi.level_filename, "filename" },
2399 { TYPE_STRING, &ldi.level_filetype, "filetype" },
2400 { TYPE_STRING, &ldi.special_flags, "special_flags" },
2401 { TYPE_BOOLEAN, &ldi.handicap, "handicap" },
2402 { TYPE_BOOLEAN, &ldi.skip_levels, "skip_levels" },
2403 { TYPE_BOOLEAN, &ldi.use_emc_tiles, "use_emc_tiles" }
2406 static struct TokenInfo artworkinfo_tokens[] =
2408 // artwork directory info
2409 { TYPE_STRING, &ldi.identifier, "identifier" },
2410 { TYPE_STRING, &ldi.subdir, "subdir" },
2411 { TYPE_STRING, &ldi.name, "name" },
2412 { TYPE_STRING, &ldi.name_sorting, "name_sorting" },
2413 { TYPE_STRING, &ldi.author, "author" },
2414 { TYPE_STRING, &ldi.program_title, "program_title" },
2415 { TYPE_STRING, &ldi.program_copyright, "program_copyright" },
2416 { TYPE_STRING, &ldi.program_company, "program_company" },
2417 { TYPE_INTEGER, &ldi.sort_priority, "sort_priority" },
2418 { TYPE_STRING, &ldi.basepath, "basepath" },
2419 { TYPE_STRING, &ldi.fullpath, "fullpath" },
2420 { TYPE_BOOLEAN, &ldi.in_user_dir, "in_user_dir" },
2421 { TYPE_INTEGER, &ldi.color, "color" },
2422 { TYPE_STRING, &ldi.class_desc, "class_desc" },
2427 static char *optional_tokens[] =
2430 "program_copyright",
2436 static void setTreeInfoToDefaults(TreeInfo *ti, int type)
2440 ti->node_top = (ti->type == TREE_TYPE_LEVEL_DIR ? &leveldir_first :
2441 ti->type == TREE_TYPE_GRAPHICS_DIR ? &artwork.gfx_first :
2442 ti->type == TREE_TYPE_SOUNDS_DIR ? &artwork.snd_first :
2443 ti->type == TREE_TYPE_MUSIC_DIR ? &artwork.mus_first :
2446 ti->node_parent = NULL;
2447 ti->node_group = NULL;
2454 ti->fullpath = NULL;
2455 ti->basepath = NULL;
2456 ti->identifier = NULL;
2457 ti->name = getStringCopy(ANONYMOUS_NAME);
2458 ti->name_sorting = NULL;
2459 ti->author = getStringCopy(ANONYMOUS_NAME);
2462 ti->program_title = NULL;
2463 ti->program_copyright = NULL;
2464 ti->program_company = NULL;
2466 ti->sort_priority = LEVELCLASS_UNDEFINED; // default: least priority
2467 ti->latest_engine = FALSE; // default: get from level
2468 ti->parent_link = FALSE;
2469 ti->in_user_dir = FALSE;
2470 ti->user_defined = FALSE;
2472 ti->class_desc = NULL;
2474 ti->infotext = getStringCopy(TREE_INFOTEXT(ti->type));
2476 if (ti->type == TREE_TYPE_LEVEL_DIR)
2478 ti->imported_from = NULL;
2479 ti->imported_by = NULL;
2480 ti->tested_by = NULL;
2482 ti->graphics_set_ecs = NULL;
2483 ti->graphics_set_aga = NULL;
2484 ti->graphics_set = NULL;
2485 ti->sounds_set_default = NULL;
2486 ti->sounds_set_lowpass = NULL;
2487 ti->sounds_set = NULL;
2488 ti->music_set = NULL;
2489 ti->graphics_path = getStringCopy(UNDEFINED_FILENAME);
2490 ti->sounds_path = getStringCopy(UNDEFINED_FILENAME);
2491 ti->music_path = getStringCopy(UNDEFINED_FILENAME);
2493 ti->level_filename = NULL;
2494 ti->level_filetype = NULL;
2496 ti->special_flags = NULL;
2499 ti->first_level = 0;
2501 ti->level_group = FALSE;
2502 ti->handicap_level = 0;
2503 ti->readonly = TRUE;
2504 ti->handicap = TRUE;
2505 ti->skip_levels = FALSE;
2507 ti->use_emc_tiles = FALSE;
2511 static void setTreeInfoToDefaultsFromParent(TreeInfo *ti, TreeInfo *parent)
2515 Error(ERR_WARN, "setTreeInfoToDefaultsFromParent(): parent == NULL");
2517 setTreeInfoToDefaults(ti, TREE_TYPE_UNDEFINED);
2522 // copy all values from the parent structure
2524 ti->type = parent->type;
2526 ti->node_top = parent->node_top;
2527 ti->node_parent = parent;
2528 ti->node_group = NULL;
2535 ti->fullpath = NULL;
2536 ti->basepath = NULL;
2537 ti->identifier = NULL;
2538 ti->name = getStringCopy(ANONYMOUS_NAME);
2539 ti->name_sorting = NULL;
2540 ti->author = getStringCopy(parent->author);
2541 ti->year = getStringCopy(parent->year);
2543 ti->program_title = getStringCopy(parent->program_title);
2544 ti->program_copyright = getStringCopy(parent->program_copyright);
2545 ti->program_company = getStringCopy(parent->program_company);
2547 ti->sort_priority = parent->sort_priority;
2548 ti->latest_engine = parent->latest_engine;
2549 ti->parent_link = FALSE;
2550 ti->in_user_dir = parent->in_user_dir;
2551 ti->user_defined = parent->user_defined;
2552 ti->color = parent->color;
2553 ti->class_desc = getStringCopy(parent->class_desc);
2555 ti->infotext = getStringCopy(parent->infotext);
2557 if (ti->type == TREE_TYPE_LEVEL_DIR)
2559 ti->imported_from = getStringCopy(parent->imported_from);
2560 ti->imported_by = getStringCopy(parent->imported_by);
2561 ti->tested_by = getStringCopy(parent->tested_by);
2563 ti->graphics_set_ecs = getStringCopy(parent->graphics_set_ecs);
2564 ti->graphics_set_aga = getStringCopy(parent->graphics_set_aga);
2565 ti->graphics_set = getStringCopy(parent->graphics_set);
2566 ti->sounds_set_default = getStringCopy(parent->sounds_set_default);
2567 ti->sounds_set_lowpass = getStringCopy(parent->sounds_set_lowpass);
2568 ti->sounds_set = getStringCopy(parent->sounds_set);
2569 ti->music_set = getStringCopy(parent->music_set);
2570 ti->graphics_path = getStringCopy(UNDEFINED_FILENAME);
2571 ti->sounds_path = getStringCopy(UNDEFINED_FILENAME);
2572 ti->music_path = getStringCopy(UNDEFINED_FILENAME);
2574 ti->level_filename = getStringCopy(parent->level_filename);
2575 ti->level_filetype = getStringCopy(parent->level_filetype);
2577 ti->special_flags = getStringCopy(parent->special_flags);
2579 ti->levels = parent->levels;
2580 ti->first_level = parent->first_level;
2581 ti->last_level = parent->last_level;
2582 ti->level_group = FALSE;
2583 ti->handicap_level = parent->handicap_level;
2584 ti->readonly = parent->readonly;
2585 ti->handicap = parent->handicap;
2586 ti->skip_levels = parent->skip_levels;
2588 ti->use_emc_tiles = parent->use_emc_tiles;
2592 static TreeInfo *getTreeInfoCopy(TreeInfo *ti)
2594 TreeInfo *ti_copy = newTreeInfo();
2596 // copy all values from the original structure
2598 ti_copy->type = ti->type;
2600 ti_copy->node_top = ti->node_top;
2601 ti_copy->node_parent = ti->node_parent;
2602 ti_copy->node_group = ti->node_group;
2603 ti_copy->next = ti->next;
2605 ti_copy->cl_first = ti->cl_first;
2606 ti_copy->cl_cursor = ti->cl_cursor;
2608 ti_copy->subdir = getStringCopy(ti->subdir);
2609 ti_copy->fullpath = getStringCopy(ti->fullpath);
2610 ti_copy->basepath = getStringCopy(ti->basepath);
2611 ti_copy->identifier = getStringCopy(ti->identifier);
2612 ti_copy->name = getStringCopy(ti->name);
2613 ti_copy->name_sorting = getStringCopy(ti->name_sorting);
2614 ti_copy->author = getStringCopy(ti->author);
2615 ti_copy->year = getStringCopy(ti->year);
2617 ti_copy->program_title = getStringCopy(ti->program_title);
2618 ti_copy->program_copyright = getStringCopy(ti->program_copyright);
2619 ti_copy->program_company = getStringCopy(ti->program_company);
2621 ti_copy->imported_from = getStringCopy(ti->imported_from);
2622 ti_copy->imported_by = getStringCopy(ti->imported_by);
2623 ti_copy->tested_by = getStringCopy(ti->tested_by);
2625 ti_copy->graphics_set_ecs = getStringCopy(ti->graphics_set_ecs);
2626 ti_copy->graphics_set_aga = getStringCopy(ti->graphics_set_aga);
2627 ti_copy->graphics_set = getStringCopy(ti->graphics_set);
2628 ti_copy->sounds_set_default = getStringCopy(ti->sounds_set_default);
2629 ti_copy->sounds_set_lowpass = getStringCopy(ti->sounds_set_lowpass);
2630 ti_copy->sounds_set = getStringCopy(ti->sounds_set);
2631 ti_copy->music_set = getStringCopy(ti->music_set);
2632 ti_copy->graphics_path = getStringCopy(ti->graphics_path);
2633 ti_copy->sounds_path = getStringCopy(ti->sounds_path);
2634 ti_copy->music_path = getStringCopy(ti->music_path);
2636 ti_copy->level_filename = getStringCopy(ti->level_filename);
2637 ti_copy->level_filetype = getStringCopy(ti->level_filetype);
2639 ti_copy->special_flags = getStringCopy(ti->special_flags);
2641 ti_copy->levels = ti->levels;
2642 ti_copy->first_level = ti->first_level;
2643 ti_copy->last_level = ti->last_level;
2644 ti_copy->sort_priority = ti->sort_priority;
2646 ti_copy->latest_engine = ti->latest_engine;
2648 ti_copy->level_group = ti->level_group;
2649 ti_copy->parent_link = ti->parent_link;
2650 ti_copy->in_user_dir = ti->in_user_dir;
2651 ti_copy->user_defined = ti->user_defined;
2652 ti_copy->readonly = ti->readonly;
2653 ti_copy->handicap = ti->handicap;
2654 ti_copy->skip_levels = ti->skip_levels;
2656 ti_copy->use_emc_tiles = ti->use_emc_tiles;
2658 ti_copy->color = ti->color;
2659 ti_copy->class_desc = getStringCopy(ti->class_desc);
2660 ti_copy->handicap_level = ti->handicap_level;
2662 ti_copy->infotext = getStringCopy(ti->infotext);
2667 void freeTreeInfo(TreeInfo *ti)
2672 checked_free(ti->subdir);
2673 checked_free(ti->fullpath);
2674 checked_free(ti->basepath);
2675 checked_free(ti->identifier);
2677 checked_free(ti->name);
2678 checked_free(ti->name_sorting);
2679 checked_free(ti->author);
2680 checked_free(ti->year);
2682 checked_free(ti->program_title);
2683 checked_free(ti->program_copyright);
2684 checked_free(ti->program_company);
2686 checked_free(ti->class_desc);
2688 checked_free(ti->infotext);
2690 if (ti->type == TREE_TYPE_LEVEL_DIR)
2692 checked_free(ti->imported_from);
2693 checked_free(ti->imported_by);
2694 checked_free(ti->tested_by);
2696 checked_free(ti->graphics_set_ecs);
2697 checked_free(ti->graphics_set_aga);
2698 checked_free(ti->graphics_set);
2699 checked_free(ti->sounds_set_default);
2700 checked_free(ti->sounds_set_lowpass);
2701 checked_free(ti->sounds_set);
2702 checked_free(ti->music_set);
2704 checked_free(ti->graphics_path);
2705 checked_free(ti->sounds_path);
2706 checked_free(ti->music_path);
2708 checked_free(ti->level_filename);
2709 checked_free(ti->level_filetype);
2711 checked_free(ti->special_flags);
2714 // recursively free child node
2716 freeTreeInfo(ti->node_group);
2718 // recursively free next node
2720 freeTreeInfo(ti->next);
2725 void setSetupInfo(struct TokenInfo *token_info,
2726 int token_nr, char *token_value)
2728 int token_type = token_info[token_nr].type;
2729 void *setup_value = token_info[token_nr].value;
2731 if (token_value == NULL)
2734 // set setup field to corresponding token value
2739 *(boolean *)setup_value = get_boolean_from_string(token_value);
2743 *(int *)setup_value = get_switch3_from_string(token_value);
2747 *(Key *)setup_value = getKeyFromKeyName(token_value);
2751 *(Key *)setup_value = getKeyFromX11KeyName(token_value);
2755 *(int *)setup_value = get_integer_from_string(token_value);
2759 checked_free(*(char **)setup_value);
2760 *(char **)setup_value = getStringCopy(token_value);
2764 *(int *)setup_value = get_player_nr_from_string(token_value);
2772 static int compareTreeInfoEntries(const void *object1, const void *object2)
2774 const TreeInfo *entry1 = *((TreeInfo **)object1);
2775 const TreeInfo *entry2 = *((TreeInfo **)object2);
2776 int class_sorting1 = 0, class_sorting2 = 0;
2779 if (entry1->type == TREE_TYPE_LEVEL_DIR)
2781 class_sorting1 = LEVELSORTING(entry1);
2782 class_sorting2 = LEVELSORTING(entry2);
2784 else if (entry1->type == TREE_TYPE_GRAPHICS_DIR ||
2785 entry1->type == TREE_TYPE_SOUNDS_DIR ||
2786 entry1->type == TREE_TYPE_MUSIC_DIR)
2788 class_sorting1 = ARTWORKSORTING(entry1);
2789 class_sorting2 = ARTWORKSORTING(entry2);
2792 if (entry1->parent_link || entry2->parent_link)
2793 compare_result = (entry1->parent_link ? -1 : +1);
2794 else if (entry1->sort_priority == entry2->sort_priority)
2796 char *name1 = getStringToLower(entry1->name_sorting);
2797 char *name2 = getStringToLower(entry2->name_sorting);
2799 compare_result = strcmp(name1, name2);
2804 else if (class_sorting1 == class_sorting2)
2805 compare_result = entry1->sort_priority - entry2->sort_priority;
2807 compare_result = class_sorting1 - class_sorting2;
2809 return compare_result;
2812 static TreeInfo *createParentTreeInfoNode(TreeInfo *node_parent)
2816 if (node_parent == NULL)
2819 ti_new = newTreeInfo();
2820 setTreeInfoToDefaults(ti_new, node_parent->type);
2822 ti_new->node_parent = node_parent;
2823 ti_new->parent_link = TRUE;
2825 setString(&ti_new->identifier, node_parent->identifier);
2826 setString(&ti_new->name, ".. (parent directory)");
2827 setString(&ti_new->name_sorting, ti_new->name);
2829 setString(&ti_new->subdir, STRING_PARENT_DIRECTORY);
2830 setString(&ti_new->fullpath, node_parent->fullpath);
2832 ti_new->sort_priority = node_parent->sort_priority;
2833 ti_new->latest_engine = node_parent->latest_engine;
2835 setString(&ti_new->class_desc, getLevelClassDescription(ti_new));
2837 pushTreeInfo(&node_parent->node_group, ti_new);
2842 static TreeInfo *createTopTreeInfoNode(TreeInfo *node_first)
2844 TreeInfo *ti_new, *ti_new2;
2846 if (node_first == NULL)
2849 ti_new = newTreeInfo();
2850 setTreeInfoToDefaults(ti_new, TREE_TYPE_LEVEL_DIR);
2852 ti_new->node_parent = NULL;
2853 ti_new->parent_link = FALSE;
2855 setString(&ti_new->identifier, node_first->identifier);
2856 setString(&ti_new->name, "level sets");
2857 setString(&ti_new->name_sorting, ti_new->name);
2859 setString(&ti_new->subdir, STRING_TOP_DIRECTORY);
2860 setString(&ti_new->fullpath, ".");
2862 ti_new->sort_priority = node_first->sort_priority;;
2863 ti_new->latest_engine = node_first->latest_engine;
2865 setString(&ti_new->class_desc, "level sets");
2867 ti_new->node_group = node_first;
2868 ti_new->level_group = TRUE;
2870 ti_new2 = createParentTreeInfoNode(ti_new);
2872 setString(&ti_new2->name, ".. (main menu)");
2873 setString(&ti_new2->name_sorting, ti_new2->name);
2879 // ----------------------------------------------------------------------------
2880 // functions for handling level and custom artwork info cache
2881 // ----------------------------------------------------------------------------
2883 static void LoadArtworkInfoCache(void)
2885 InitCacheDirectory();
2887 if (artworkinfo_cache_old == NULL)
2889 char *filename = getPath2(getCacheDir(), ARTWORKINFO_CACHE_FILE);
2891 // try to load artwork info hash from already existing cache file
2892 artworkinfo_cache_old = loadSetupFileHash(filename);
2894 // if no artwork info cache file was found, start with empty hash
2895 if (artworkinfo_cache_old == NULL)
2896 artworkinfo_cache_old = newSetupFileHash();
2901 if (artworkinfo_cache_new == NULL)
2902 artworkinfo_cache_new = newSetupFileHash();
2905 static void SaveArtworkInfoCache(void)
2907 char *filename = getPath2(getCacheDir(), ARTWORKINFO_CACHE_FILE);
2909 InitCacheDirectory();
2911 saveSetupFileHash(artworkinfo_cache_new, filename);
2916 static char *getCacheTokenPrefix(char *prefix1, char *prefix2)
2918 static char *prefix = NULL;
2920 checked_free(prefix);
2922 prefix = getStringCat2WithSeparator(prefix1, prefix2, ".");
2927 // (identical to above function, but separate string buffer needed -- nasty)
2928 static char *getCacheToken(char *prefix, char *suffix)
2930 static char *token = NULL;
2932 checked_free(token);
2934 token = getStringCat2WithSeparator(prefix, suffix, ".");
2939 static char *getFileTimestampString(char *filename)
2941 return getStringCopy(i_to_a(getFileTimestampEpochSeconds(filename)));
2944 static boolean modifiedFileTimestamp(char *filename, char *timestamp_string)
2946 struct stat file_status;
2948 if (timestamp_string == NULL)
2951 if (stat(filename, &file_status) != 0) // cannot stat file
2954 return (file_status.st_mtime != atoi(timestamp_string));
2957 static TreeInfo *getArtworkInfoCacheEntry(LevelDirTree *level_node, int type)
2959 char *identifier = level_node->subdir;
2960 char *type_string = ARTWORK_DIRECTORY(type);
2961 char *token_prefix = getCacheTokenPrefix(type_string, identifier);
2962 char *token_main = getCacheToken(token_prefix, "CACHED");
2963 char *cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
2964 boolean cached = (cache_entry != NULL && strEqual(cache_entry, "true"));
2965 TreeInfo *artwork_info = NULL;
2967 if (!use_artworkinfo_cache)
2970 if (optional_tokens_hash == NULL)
2974 // create hash from list of optional tokens (for quick access)
2975 optional_tokens_hash = newSetupFileHash();
2976 for (i = 0; optional_tokens[i] != NULL; i++)
2977 setHashEntry(optional_tokens_hash, optional_tokens[i], "");
2984 artwork_info = newTreeInfo();
2985 setTreeInfoToDefaults(artwork_info, type);
2987 // set all structure fields according to the token/value pairs
2988 ldi = *artwork_info;
2989 for (i = 0; artworkinfo_tokens[i].type != -1; i++)
2991 char *token_suffix = artworkinfo_tokens[i].text;
2992 char *token = getCacheToken(token_prefix, token_suffix);
2993 char *value = getHashEntry(artworkinfo_cache_old, token);
2995 (getHashEntry(optional_tokens_hash, token_suffix) != NULL);
2997 setSetupInfo(artworkinfo_tokens, i, value);
2999 // check if cache entry for this item is mandatory, but missing
3000 if (value == NULL && !optional)
3002 Error(ERR_WARN, "missing cache entry '%s'", token);
3008 *artwork_info = ldi;
3013 char *filename_levelinfo = getPath2(getLevelDirFromTreeInfo(level_node),
3014 LEVELINFO_FILENAME);
3015 char *filename_artworkinfo = getPath2(getSetupArtworkDir(artwork_info),
3016 ARTWORKINFO_FILENAME(type));
3018 // check if corresponding "levelinfo.conf" file has changed
3019 token_main = getCacheToken(token_prefix, "TIMESTAMP_LEVELINFO");
3020 cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
3022 if (modifiedFileTimestamp(filename_levelinfo, cache_entry))
3025 // check if corresponding "<artworkinfo>.conf" file has changed
3026 token_main = getCacheToken(token_prefix, "TIMESTAMP_ARTWORKINFO");
3027 cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
3029 if (modifiedFileTimestamp(filename_artworkinfo, cache_entry))
3032 checked_free(filename_levelinfo);
3033 checked_free(filename_artworkinfo);
3036 if (!cached && artwork_info != NULL)
3038 freeTreeInfo(artwork_info);
3043 return artwork_info;
3046 static void setArtworkInfoCacheEntry(TreeInfo *artwork_info,
3047 LevelDirTree *level_node, int type)
3049 char *identifier = level_node->subdir;
3050 char *type_string = ARTWORK_DIRECTORY(type);
3051 char *token_prefix = getCacheTokenPrefix(type_string, identifier);
3052 char *token_main = getCacheToken(token_prefix, "CACHED");
3053 boolean set_cache_timestamps = TRUE;
3056 setHashEntry(artworkinfo_cache_new, token_main, "true");
3058 if (set_cache_timestamps)
3060 char *filename_levelinfo = getPath2(getLevelDirFromTreeInfo(level_node),
3061 LEVELINFO_FILENAME);
3062 char *filename_artworkinfo = getPath2(getSetupArtworkDir(artwork_info),
3063 ARTWORKINFO_FILENAME(type));
3064 char *timestamp_levelinfo = getFileTimestampString(filename_levelinfo);
3065 char *timestamp_artworkinfo = getFileTimestampString(filename_artworkinfo);
3067 token_main = getCacheToken(token_prefix, "TIMESTAMP_LEVELINFO");
3068 setHashEntry(artworkinfo_cache_new, token_main, timestamp_levelinfo);
3070 token_main = getCacheToken(token_prefix, "TIMESTAMP_ARTWORKINFO");
3071 setHashEntry(artworkinfo_cache_new, token_main, timestamp_artworkinfo);
3073 checked_free(filename_levelinfo);
3074 checked_free(filename_artworkinfo);
3075 checked_free(timestamp_levelinfo);
3076 checked_free(timestamp_artworkinfo);
3079 ldi = *artwork_info;
3080 for (i = 0; artworkinfo_tokens[i].type != -1; i++)
3082 char *token = getCacheToken(token_prefix, artworkinfo_tokens[i].text);
3083 char *value = getSetupValue(artworkinfo_tokens[i].type,
3084 artworkinfo_tokens[i].value);
3086 setHashEntry(artworkinfo_cache_new, token, value);
3091 // ----------------------------------------------------------------------------
3092 // functions for loading level info and custom artwork info
3093 // ----------------------------------------------------------------------------
3095 int GetZipFileTreeType(char *zip_filename)
3097 static char *top_dir_path = NULL;
3098 static char *top_dir_conf_filename[NUM_BASE_TREE_TYPES] = { NULL };
3099 static char *conf_basename[NUM_BASE_TREE_TYPES] =
3101 GRAPHICSINFO_FILENAME,
3102 SOUNDSINFO_FILENAME,
3108 checked_free(top_dir_path);
3109 top_dir_path = NULL;
3111 for (j = 0; j < NUM_BASE_TREE_TYPES; j++)
3113 checked_free(top_dir_conf_filename[j]);
3114 top_dir_conf_filename[j] = NULL;
3117 char **zip_entries = zip_list(zip_filename);
3119 // check if zip file successfully opened
3120 if (zip_entries == NULL || zip_entries[0] == NULL)
3121 return TREE_TYPE_UNDEFINED;
3123 // first zip file entry is expected to be top level directory
3124 char *top_dir = zip_entries[0];
3126 // check if valid top level directory found in zip file
3127 if (!strSuffix(top_dir, "/"))
3128 return TREE_TYPE_UNDEFINED;
3130 // get filenames of valid configuration files in top level directory
3131 for (j = 0; j < NUM_BASE_TREE_TYPES; j++)
3132 top_dir_conf_filename[j] = getStringCat2(top_dir, conf_basename[j]);
3134 int tree_type = TREE_TYPE_UNDEFINED;
3137 while (zip_entries[e] != NULL)
3139 // check if every zip file entry is below top level directory
3140 if (!strPrefix(zip_entries[e], top_dir))
3141 return TREE_TYPE_UNDEFINED;
3143 // check if this zip file entry is a valid configuration filename
3144 for (j = 0; j < NUM_BASE_TREE_TYPES; j++)
3146 if (strEqual(zip_entries[e], top_dir_conf_filename[j]))
3148 // only exactly one valid configuration file allowed
3149 if (tree_type != TREE_TYPE_UNDEFINED)
3150 return TREE_TYPE_UNDEFINED;
3162 static boolean CheckZipFileForDirectory(char *zip_filename, char *directory,
3165 static char *top_dir_path = NULL;
3166 static char *top_dir_conf_filename = NULL;
3168 checked_free(top_dir_path);
3169 checked_free(top_dir_conf_filename);
3171 top_dir_path = NULL;
3172 top_dir_conf_filename = NULL;
3174 char *conf_basename = (tree_type == TREE_TYPE_LEVEL_DIR ? LEVELINFO_FILENAME :
3175 ARTWORKINFO_FILENAME(tree_type));
3177 // check if valid configuration filename determined
3178 if (conf_basename == NULL || strEqual(conf_basename, ""))
3181 char **zip_entries = zip_list(zip_filename);
3183 // check if zip file successfully opened
3184 if (zip_entries == NULL || zip_entries[0] == NULL)
3187 // first zip file entry is expected to be top level directory
3188 char *top_dir = zip_entries[0];
3190 // check if valid top level directory found in zip file
3191 if (!strSuffix(top_dir, "/"))
3194 // get path of extracted top level directory
3195 top_dir_path = getPath2(directory, top_dir);
3197 // remove trailing directory separator from top level directory path
3198 // (required to be able to check for file and directory in next step)
3199 top_dir_path[strlen(top_dir_path) - 1] = '\0';
3201 // check if zip file's top level directory already exists in target directory
3202 if (fileExists(top_dir_path)) // (checks for file and directory)
3205 // get filename of configuration file in top level directory
3206 top_dir_conf_filename = getStringCat2(top_dir, conf_basename);
3208 boolean found_top_dir_conf_filename = FALSE;
3211 while (zip_entries[i] != NULL)
3213 // check if every zip file entry is below top level directory
3214 if (!strPrefix(zip_entries[i], top_dir))
3217 // check if this zip file entry is the configuration filename
3218 if (strEqual(zip_entries[i], top_dir_conf_filename))
3219 found_top_dir_conf_filename = TRUE;
3224 // check if valid configuration filename was found in zip file
3225 if (!found_top_dir_conf_filename)
3231 char *ExtractZipFileIntoDirectory(char *zip_filename, char *directory,
3234 boolean zip_file_valid = CheckZipFileForDirectory(zip_filename, directory,
3237 if (!zip_file_valid)
3239 Error(ERR_WARN, "zip file '%s' rejected!", zip_filename);
3244 char **zip_entries = zip_extract(zip_filename, directory);
3246 if (zip_entries == NULL)
3248 Error(ERR_WARN, "zip file '%s' could not be extracted!", zip_filename);
3253 Error(ERR_INFO, "zip file '%s' successfully extracted!", zip_filename);
3255 // first zip file entry contains top level directory
3256 char *top_dir = zip_entries[0];
3258 // remove trailing directory separator from top level directory
3259 top_dir[strlen(top_dir) - 1] = '\0';
3264 static void ProcessZipFilesInDirectory(char *directory, int tree_type)
3267 DirectoryEntry *dir_entry;
3269 if ((dir = openDirectory(directory)) == NULL)
3271 // display error if directory is main "options.graphics_directory" etc.
3272 if (tree_type == TREE_TYPE_LEVEL_DIR ||
3273 directory == OPTIONS_ARTWORK_DIRECTORY(tree_type))
3274 Error(ERR_WARN, "cannot read directory '%s'", directory);
3279 while ((dir_entry = readDirectory(dir)) != NULL) // loop all entries
3281 // skip non-zip files (and also directories with zip extension)
3282 if (!strSuffixLower(dir_entry->basename, ".zip") || dir_entry->is_directory)
3285 char *zip_filename = getPath2(directory, dir_entry->basename);
3286 char *zip_filename_extracted = getStringCat2(zip_filename, ".extracted");
3287 char *zip_filename_rejected = getStringCat2(zip_filename, ".rejected");
3289 // check if zip file hasn't already been extracted or rejected
3290 if (!fileExists(zip_filename_extracted) &&
3291 !fileExists(zip_filename_rejected))
3293 char *top_dir = ExtractZipFileIntoDirectory(zip_filename, directory,
3295 char *marker_filename = (top_dir != NULL ? zip_filename_extracted :
3296 zip_filename_rejected);
3299 // create empty file to mark zip file as extracted or rejected
3300 if ((marker_file = fopen(marker_filename, MODE_WRITE)))
3301 fclose(marker_file);
3304 free(zip_filename_extracted);
3305 free(zip_filename_rejected);
3309 closeDirectory(dir);
3312 // forward declaration for recursive call by "LoadLevelInfoFromLevelDir()"
3313 static void LoadLevelInfoFromLevelDir(TreeInfo **, TreeInfo *, char *);
3315 static boolean LoadLevelInfoFromLevelConf(TreeInfo **node_first,
3316 TreeInfo *node_parent,
3317 char *level_directory,
3318 char *directory_name)
3320 char *directory_path = getPath2(level_directory, directory_name);
3321 char *filename = getPath2(directory_path, LEVELINFO_FILENAME);
3322 SetupFileHash *setup_file_hash;
3323 LevelDirTree *leveldir_new = NULL;
3326 // unless debugging, silently ignore directories without "levelinfo.conf"
3327 if (!options.debug && !fileExists(filename))
3329 free(directory_path);
3335 setup_file_hash = loadSetupFileHash(filename);
3337 if (setup_file_hash == NULL)
3339 #if DEBUG_NO_CONFIG_FILE
3340 Error(ERR_WARN, "ignoring level directory '%s'", directory_path);
3343 free(directory_path);
3349 leveldir_new = newTreeInfo();
3352 setTreeInfoToDefaultsFromParent(leveldir_new, node_parent);
3354 setTreeInfoToDefaults(leveldir_new, TREE_TYPE_LEVEL_DIR);
3356 leveldir_new->subdir = getStringCopy(directory_name);
3358 // set all structure fields according to the token/value pairs
3359 ldi = *leveldir_new;
3360 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
3361 setSetupInfo(levelinfo_tokens, i,
3362 getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
3363 *leveldir_new = ldi;
3365 if (strEqual(leveldir_new->name, ANONYMOUS_NAME))
3366 setString(&leveldir_new->name, leveldir_new->subdir);
3368 if (leveldir_new->identifier == NULL)
3369 leveldir_new->identifier = getStringCopy(leveldir_new->subdir);
3371 if (leveldir_new->name_sorting == NULL)
3372 leveldir_new->name_sorting = getStringCopy(leveldir_new->name);
3374 if (node_parent == NULL) // top level group
3376 leveldir_new->basepath = getStringCopy(level_directory);
3377 leveldir_new->fullpath = getStringCopy(leveldir_new->subdir);
3379 else // sub level group
3381 leveldir_new->basepath = getStringCopy(node_parent->basepath);
3382 leveldir_new->fullpath = getPath2(node_parent->fullpath, directory_name);
3385 leveldir_new->last_level =
3386 leveldir_new->first_level + leveldir_new->levels - 1;
3388 leveldir_new->in_user_dir =
3389 (!strEqual(leveldir_new->basepath, options.level_directory));
3391 // adjust some settings if user's private level directory was detected
3392 if (leveldir_new->sort_priority == LEVELCLASS_UNDEFINED &&
3393 leveldir_new->in_user_dir &&
3394 (strEqual(leveldir_new->subdir, getLoginName()) ||
3395 strEqual(leveldir_new->name, getLoginName()) ||
3396 strEqual(leveldir_new->author, getRealName())))
3398 leveldir_new->sort_priority = LEVELCLASS_PRIVATE_START;
3399 leveldir_new->readonly = FALSE;
3402 leveldir_new->user_defined =
3403 (leveldir_new->in_user_dir && IS_LEVELCLASS_PRIVATE(leveldir_new));
3405 leveldir_new->color = LEVELCOLOR(leveldir_new);
3407 setString(&leveldir_new->class_desc, getLevelClassDescription(leveldir_new));
3409 leveldir_new->handicap_level = // set handicap to default value
3410 (leveldir_new->user_defined || !leveldir_new->handicap ?
3411 leveldir_new->last_level : leveldir_new->first_level);
3413 DrawInitText(leveldir_new->name, 150, FC_YELLOW);
3415 pushTreeInfo(node_first, leveldir_new);
3417 freeSetupFileHash(setup_file_hash);
3419 if (leveldir_new->level_group)
3421 // create node to link back to current level directory
3422 createParentTreeInfoNode(leveldir_new);
3424 // recursively step into sub-directory and look for more level series
3425 LoadLevelInfoFromLevelDir(&leveldir_new->node_group,
3426 leveldir_new, directory_path);
3429 free(directory_path);
3435 static void LoadLevelInfoFromLevelDir(TreeInfo **node_first,
3436 TreeInfo *node_parent,
3437 char *level_directory)
3439 // ---------- 1st stage: process any level set zip files ----------
3441 ProcessZipFilesInDirectory(level_directory, TREE_TYPE_LEVEL_DIR);
3443 // ---------- 2nd stage: check for level set directories ----------
3446 DirectoryEntry *dir_entry;
3447 boolean valid_entry_found = FALSE;
3449 if ((dir = openDirectory(level_directory)) == NULL)
3451 Error(ERR_WARN, "cannot read level directory '%s'", level_directory);
3456 while ((dir_entry = readDirectory(dir)) != NULL) // loop all entries
3458 char *directory_name = dir_entry->basename;
3459 char *directory_path = getPath2(level_directory, directory_name);
3461 // skip entries for current and parent directory
3462 if (strEqual(directory_name, ".") ||
3463 strEqual(directory_name, ".."))
3465 free(directory_path);
3470 // find out if directory entry is itself a directory
3471 if (!dir_entry->is_directory) // not a directory
3473 free(directory_path);
3478 free(directory_path);
3480 if (strEqual(directory_name, GRAPHICS_DIRECTORY) ||
3481 strEqual(directory_name, SOUNDS_DIRECTORY) ||
3482 strEqual(directory_name, MUSIC_DIRECTORY))
3485 valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
3490 closeDirectory(dir);
3492 // special case: top level directory may directly contain "levelinfo.conf"
3493 if (node_parent == NULL && !valid_entry_found)
3495 // check if this directory directly contains a file "levelinfo.conf"
3496 valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
3497 level_directory, ".");
3500 if (!valid_entry_found)
3501 Error(ERR_WARN, "cannot find any valid level series in directory '%s'",
3505 boolean AdjustGraphicsForEMC(void)
3507 boolean settings_changed = FALSE;
3509 settings_changed |= adjustTreeGraphicsForEMC(leveldir_first_all);
3510 settings_changed |= adjustTreeGraphicsForEMC(leveldir_first);
3512 return settings_changed;
3515 boolean AdjustSoundsForEMC(void)
3517 boolean settings_changed = FALSE;
3519 settings_changed |= adjustTreeSoundsForEMC(leveldir_first_all);
3520 settings_changed |= adjustTreeSoundsForEMC(leveldir_first);
3522 return settings_changed;
3525 void LoadLevelInfo(void)
3527 InitUserLevelDirectory(getLoginName());
3529 DrawInitText("Loading level series", 120, FC_GREEN);
3531 LoadLevelInfoFromLevelDir(&leveldir_first, NULL, options.level_directory);
3532 LoadLevelInfoFromLevelDir(&leveldir_first, NULL, getUserLevelDir(NULL));
3534 leveldir_first = createTopTreeInfoNode(leveldir_first);
3536 /* after loading all level set information, clone the level directory tree
3537 and remove all level sets without levels (these may still contain artwork
3538 to be offered in the setup menu as "custom artwork", and are therefore
3539 checked for existing artwork in the function "LoadLevelArtworkInfo()") */
3540 leveldir_first_all = leveldir_first;
3541 cloneTree(&leveldir_first, leveldir_first_all, TRUE);
3543 AdjustGraphicsForEMC();
3544 AdjustSoundsForEMC();
3546 // before sorting, the first entries will be from the user directory
3547 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
3549 if (leveldir_first == NULL)
3550 Error(ERR_EXIT, "cannot find any valid level series in any directory");
3552 sortTreeInfo(&leveldir_first);
3554 #if ENABLE_UNUSED_CODE
3555 dumpTreeInfo(leveldir_first, 0);
3559 static boolean LoadArtworkInfoFromArtworkConf(TreeInfo **node_first,
3560 TreeInfo *node_parent,
3561 char *base_directory,
3562 char *directory_name, int type)
3564 char *directory_path = getPath2(base_directory, directory_name);
3565 char *filename = getPath2(directory_path, ARTWORKINFO_FILENAME(type));
3566 SetupFileHash *setup_file_hash = NULL;
3567 TreeInfo *artwork_new = NULL;
3570 if (fileExists(filename))
3571 setup_file_hash = loadSetupFileHash(filename);
3573 if (setup_file_hash == NULL) // no config file -- look for artwork files
3576 DirectoryEntry *dir_entry;
3577 boolean valid_file_found = FALSE;
3579 if ((dir = openDirectory(directory_path)) != NULL)
3581 while ((dir_entry = readDirectory(dir)) != NULL)
3583 if (FileIsArtworkType(dir_entry->filename, type))
3585 valid_file_found = TRUE;
3591 closeDirectory(dir);
3594 if (!valid_file_found)
3596 #if DEBUG_NO_CONFIG_FILE
3597 if (!strEqual(directory_name, "."))
3598 Error(ERR_WARN, "ignoring artwork directory '%s'", directory_path);
3601 free(directory_path);
3608 artwork_new = newTreeInfo();
3611 setTreeInfoToDefaultsFromParent(artwork_new, node_parent);
3613 setTreeInfoToDefaults(artwork_new, type);
3615 artwork_new->subdir = getStringCopy(directory_name);
3617 if (setup_file_hash) // (before defining ".color" and ".class_desc")
3619 // set all structure fields according to the token/value pairs
3621 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
3622 setSetupInfo(levelinfo_tokens, i,
3623 getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
3626 if (strEqual(artwork_new->name, ANONYMOUS_NAME))
3627 setString(&artwork_new->name, artwork_new->subdir);
3629 if (artwork_new->identifier == NULL)
3630 artwork_new->identifier = getStringCopy(artwork_new->subdir);
3632 if (artwork_new->name_sorting == NULL)
3633 artwork_new->name_sorting = getStringCopy(artwork_new->name);
3636 if (node_parent == NULL) // top level group
3638 artwork_new->basepath = getStringCopy(base_directory);
3639 artwork_new->fullpath = getStringCopy(artwork_new->subdir);
3641 else // sub level group
3643 artwork_new->basepath = getStringCopy(node_parent->basepath);
3644 artwork_new->fullpath = getPath2(node_parent->fullpath, directory_name);
3647 artwork_new->in_user_dir =
3648 (!strEqual(artwork_new->basepath, OPTIONS_ARTWORK_DIRECTORY(type)));
3650 // (may use ".sort_priority" from "setup_file_hash" above)
3651 artwork_new->color = ARTWORKCOLOR(artwork_new);
3653 setString(&artwork_new->class_desc, getLevelClassDescription(artwork_new));
3655 if (setup_file_hash == NULL) // (after determining ".user_defined")
3657 if (strEqual(artwork_new->subdir, "."))
3659 if (artwork_new->user_defined)
3661 setString(&artwork_new->identifier, "private");
3662 artwork_new->sort_priority = ARTWORKCLASS_PRIVATE;
3666 setString(&artwork_new->identifier, "classic");
3667 artwork_new->sort_priority = ARTWORKCLASS_CLASSICS;
3670 // set to new values after changing ".sort_priority"
3671 artwork_new->color = ARTWORKCOLOR(artwork_new);
3673 setString(&artwork_new->class_desc,
3674 getLevelClassDescription(artwork_new));
3678 setString(&artwork_new->identifier, artwork_new->subdir);
3681 setString(&artwork_new->name, artwork_new->identifier);
3682 setString(&artwork_new->name_sorting, artwork_new->name);
3685 pushTreeInfo(node_first, artwork_new);
3687 freeSetupFileHash(setup_file_hash);
3689 free(directory_path);
3695 static void LoadArtworkInfoFromArtworkDir(TreeInfo **node_first,
3696 TreeInfo *node_parent,
3697 char *base_directory, int type)
3699 // ---------- 1st stage: process any artwork set zip files ----------
3701 ProcessZipFilesInDirectory(base_directory, type);
3703 // ---------- 2nd stage: check for artwork set directories ----------
3706 DirectoryEntry *dir_entry;
3707 boolean valid_entry_found = FALSE;
3709 if ((dir = openDirectory(base_directory)) == NULL)
3711 // display error if directory is main "options.graphics_directory" etc.
3712 if (base_directory == OPTIONS_ARTWORK_DIRECTORY(type))
3713 Error(ERR_WARN, "cannot read directory '%s'", base_directory);
3718 while ((dir_entry = readDirectory(dir)) != NULL) // loop all entries
3720 char *directory_name = dir_entry->basename;
3721 char *directory_path = getPath2(base_directory, directory_name);
3723 // skip directory entries for current and parent directory
3724 if (strEqual(directory_name, ".") ||
3725 strEqual(directory_name, ".."))
3727 free(directory_path);
3732 // skip directory entries which are not a directory
3733 if (!dir_entry->is_directory) // not a directory
3735 free(directory_path);
3740 free(directory_path);
3742 // check if this directory contains artwork with or without config file
3743 valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
3745 directory_name, type);
3748 closeDirectory(dir);
3750 // check if this directory directly contains artwork itself
3751 valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
3752 base_directory, ".",
3754 if (!valid_entry_found)
3755 Error(ERR_WARN, "cannot find any valid artwork in directory '%s'",
3759 static TreeInfo *getDummyArtworkInfo(int type)
3761 // this is only needed when there is completely no artwork available
3762 TreeInfo *artwork_new = newTreeInfo();
3764 setTreeInfoToDefaults(artwork_new, type);
3766 setString(&artwork_new->subdir, UNDEFINED_FILENAME);
3767 setString(&artwork_new->fullpath, UNDEFINED_FILENAME);
3768 setString(&artwork_new->basepath, UNDEFINED_FILENAME);
3770 setString(&artwork_new->identifier, UNDEFINED_FILENAME);
3771 setString(&artwork_new->name, UNDEFINED_FILENAME);
3772 setString(&artwork_new->name_sorting, UNDEFINED_FILENAME);
3777 void LoadArtworkInfo(void)
3779 LoadArtworkInfoCache();
3781 DrawInitText("Looking for custom artwork", 120, FC_GREEN);
3783 LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
3784 options.graphics_directory,
3785 TREE_TYPE_GRAPHICS_DIR);
3786 LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
3787 getUserGraphicsDir(),
3788 TREE_TYPE_GRAPHICS_DIR);
3790 LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
3791 options.sounds_directory,
3792 TREE_TYPE_SOUNDS_DIR);
3793 LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
3795 TREE_TYPE_SOUNDS_DIR);
3797 LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
3798 options.music_directory,
3799 TREE_TYPE_MUSIC_DIR);
3800 LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
3802 TREE_TYPE_MUSIC_DIR);
3804 if (artwork.gfx_first == NULL)
3805 artwork.gfx_first = getDummyArtworkInfo(TREE_TYPE_GRAPHICS_DIR);
3806 if (artwork.snd_first == NULL)
3807 artwork.snd_first = getDummyArtworkInfo(TREE_TYPE_SOUNDS_DIR);
3808 if (artwork.mus_first == NULL)
3809 artwork.mus_first = getDummyArtworkInfo(TREE_TYPE_MUSIC_DIR);
3811 // before sorting, the first entries will be from the user directory
3812 artwork.gfx_current =
3813 getTreeInfoFromIdentifier(artwork.gfx_first, setup.graphics_set);
3814 if (artwork.gfx_current == NULL)
3815 artwork.gfx_current =
3816 getTreeInfoFromIdentifier(artwork.gfx_first, GFX_DEFAULT_SUBDIR);
3817 if (artwork.gfx_current == NULL)
3818 artwork.gfx_current = getFirstValidTreeInfoEntry(artwork.gfx_first);
3820 artwork.snd_current =
3821 getTreeInfoFromIdentifier(artwork.snd_first, setup.sounds_set);
3822 if (artwork.snd_current == NULL)
3823 artwork.snd_current =
3824 getTreeInfoFromIdentifier(artwork.snd_first, SND_DEFAULT_SUBDIR);
3825 if (artwork.snd_current == NULL)
3826 artwork.snd_current = getFirstValidTreeInfoEntry(artwork.snd_first);
3828 artwork.mus_current =
3829 getTreeInfoFromIdentifier(artwork.mus_first, setup.music_set);
3830 if (artwork.mus_current == NULL)
3831 artwork.mus_current =
3832 getTreeInfoFromIdentifier(artwork.mus_first, MUS_DEFAULT_SUBDIR);
3833 if (artwork.mus_current == NULL)
3834 artwork.mus_current = getFirstValidTreeInfoEntry(artwork.mus_first);
3836 artwork.gfx_current_identifier = artwork.gfx_current->identifier;
3837 artwork.snd_current_identifier = artwork.snd_current->identifier;
3838 artwork.mus_current_identifier = artwork.mus_current->identifier;
3840 #if ENABLE_UNUSED_CODE
3841 printf("graphics set == %s\n\n", artwork.gfx_current_identifier);
3842 printf("sounds set == %s\n\n", artwork.snd_current_identifier);
3843 printf("music set == %s\n\n", artwork.mus_current_identifier);
3846 sortTreeInfo(&artwork.gfx_first);
3847 sortTreeInfo(&artwork.snd_first);
3848 sortTreeInfo(&artwork.mus_first);
3850 #if ENABLE_UNUSED_CODE
3851 dumpTreeInfo(artwork.gfx_first, 0);
3852 dumpTreeInfo(artwork.snd_first, 0);
3853 dumpTreeInfo(artwork.mus_first, 0);
3857 static void LoadArtworkInfoFromLevelInfo(ArtworkDirTree **artwork_node,
3858 LevelDirTree *level_node)
3860 int type = (*artwork_node)->type;
3862 // recursively check all level directories for artwork sub-directories
3866 // check all tree entries for artwork, but skip parent link entries
3867 if (!level_node->parent_link)
3869 TreeInfo *artwork_new = getArtworkInfoCacheEntry(level_node, type);
3870 boolean cached = (artwork_new != NULL);
3874 pushTreeInfo(artwork_node, artwork_new);
3878 TreeInfo *topnode_last = *artwork_node;
3879 char *path = getPath2(getLevelDirFromTreeInfo(level_node),
3880 ARTWORK_DIRECTORY(type));
3882 LoadArtworkInfoFromArtworkDir(artwork_node, NULL, path, type);
3884 if (topnode_last != *artwork_node) // check for newly added node
3886 artwork_new = *artwork_node;
3888 setString(&artwork_new->identifier, level_node->subdir);
3889 setString(&artwork_new->name, level_node->name);
3890 setString(&artwork_new->name_sorting, level_node->name_sorting);
3892 artwork_new->sort_priority = level_node->sort_priority;
3893 artwork_new->color = LEVELCOLOR(artwork_new);
3899 // insert artwork info (from old cache or filesystem) into new cache
3900 if (artwork_new != NULL)
3901 setArtworkInfoCacheEntry(artwork_new, level_node, type);
3904 DrawInitText(level_node->name, 150, FC_YELLOW);
3906 if (level_node->node_group != NULL)
3907 LoadArtworkInfoFromLevelInfo(artwork_node, level_node->node_group);
3909 level_node = level_node->next;
3913 void LoadLevelArtworkInfo(void)
3915 print_timestamp_init("LoadLevelArtworkInfo");
3917 DrawInitText("Looking for custom level artwork", 120, FC_GREEN);
3919 print_timestamp_time("DrawTimeText");
3921 LoadArtworkInfoFromLevelInfo(&artwork.gfx_first, leveldir_first_all);
3922 print_timestamp_time("LoadArtworkInfoFromLevelInfo (gfx)");
3923 LoadArtworkInfoFromLevelInfo(&artwork.snd_first, leveldir_first_all);
3924 print_timestamp_time("LoadArtworkInfoFromLevelInfo (snd)");
3925 LoadArtworkInfoFromLevelInfo(&artwork.mus_first, leveldir_first_all);
3926 print_timestamp_time("LoadArtworkInfoFromLevelInfo (mus)");
3928 SaveArtworkInfoCache();
3930 print_timestamp_time("SaveArtworkInfoCache");
3932 // needed for reloading level artwork not known at ealier stage
3934 if (!strEqual(artwork.gfx_current_identifier, setup.graphics_set))
3936 artwork.gfx_current =
3937 getTreeInfoFromIdentifier(artwork.gfx_first, setup.graphics_set);
3938 if (artwork.gfx_current == NULL)
3939 artwork.gfx_current =
3940 getTreeInfoFromIdentifier(artwork.gfx_first, GFX_DEFAULT_SUBDIR);
3941 if (artwork.gfx_current == NULL)
3942 artwork.gfx_current = getFirstValidTreeInfoEntry(artwork.gfx_first);
3945 if (!strEqual(artwork.snd_current_identifier, setup.sounds_set))
3947 artwork.snd_current =
3948 getTreeInfoFromIdentifier(artwork.snd_first, setup.sounds_set);
3949 if (artwork.snd_current == NULL)
3950 artwork.snd_current =
3951 getTreeInfoFromIdentifier(artwork.snd_first, SND_DEFAULT_SUBDIR);
3952 if (artwork.snd_current == NULL)
3953 artwork.snd_current = getFirstValidTreeInfoEntry(artwork.snd_first);
3956 if (!strEqual(artwork.mus_current_identifier, setup.music_set))
3958 artwork.mus_current =
3959 getTreeInfoFromIdentifier(artwork.mus_first, setup.music_set);
3960 if (artwork.mus_current == NULL)
3961 artwork.mus_current =
3962 getTreeInfoFromIdentifier(artwork.mus_first, MUS_DEFAULT_SUBDIR);
3963 if (artwork.mus_current == NULL)
3964 artwork.mus_current = getFirstValidTreeInfoEntry(artwork.mus_first);
3967 print_timestamp_time("getTreeInfoFromIdentifier");
3969 sortTreeInfo(&artwork.gfx_first);
3970 sortTreeInfo(&artwork.snd_first);
3971 sortTreeInfo(&artwork.mus_first);
3973 print_timestamp_time("sortTreeInfo");
3975 #if ENABLE_UNUSED_CODE
3976 dumpTreeInfo(artwork.gfx_first, 0);
3977 dumpTreeInfo(artwork.snd_first, 0);
3978 dumpTreeInfo(artwork.mus_first, 0);
3981 print_timestamp_done("LoadLevelArtworkInfo");
3984 static boolean AddTreeSetToTreeInfoExt(TreeInfo *tree_node_old, char *tree_dir,
3985 char *tree_subdir_new, int type)
3987 if (tree_node_old == NULL)
3989 if (type == TREE_TYPE_LEVEL_DIR)
3991 // get level info tree node of personal user level set
3992 tree_node_old = getTreeInfoFromIdentifier(leveldir_first, getLoginName());
3994 // this may happen if "setup.internal.create_user_levelset" is FALSE
3995 // or if file "levelinfo.conf" is missing in personal user level set
3996 if (tree_node_old == NULL)
3997 tree_node_old = leveldir_first->node_group;
4001 // get artwork info tree node of first artwork set
4002 tree_node_old = ARTWORK_FIRST_NODE(artwork, type);
4006 if (tree_dir == NULL)
4007 tree_dir = TREE_USERDIR(type);
4009 if (tree_node_old == NULL ||
4011 tree_subdir_new == NULL) // should not happen
4014 int draw_deactivation_mask = GetDrawDeactivationMask();
4016 // override draw deactivation mask (temporarily disable drawing)
4017 SetDrawDeactivationMask(REDRAW_ALL);
4019 if (type == TREE_TYPE_LEVEL_DIR)
4021 // load new level set config and add it next to first user level set
4022 LoadLevelInfoFromLevelConf(&tree_node_old->next,
4023 tree_node_old->node_parent,
4024 tree_dir, tree_subdir_new);
4028 // load new artwork set config and add it next to first artwork set
4029 LoadArtworkInfoFromArtworkConf(&tree_node_old->next,
4030 tree_node_old->node_parent,
4031 tree_dir, tree_subdir_new, type);
4034 // set draw deactivation mask to previous value
4035 SetDrawDeactivationMask(draw_deactivation_mask);
4037 // get first node of level or artwork info tree
4038 TreeInfo **tree_node_first = TREE_FIRST_NODE_PTR(type);
4040 // get tree info node of newly added level or artwork set
4041 TreeInfo *tree_node_new = getTreeInfoFromIdentifier(*tree_node_first,
4044 if (tree_node_new == NULL) // should not happen
4047 // correct top link and parent node link of newly created tree node
4048 tree_node_new->node_top = tree_node_old->node_top;
4049 tree_node_new->node_parent = tree_node_old->node_parent;
4051 // sort tree info to adjust position of newly added tree set
4052 sortTreeInfo(tree_node_first);
4057 void AddTreeSetToTreeInfo(TreeInfo *tree_node, char *tree_dir,
4058 char *tree_subdir_new, int type)
4060 if (!AddTreeSetToTreeInfoExt(tree_node, tree_dir, tree_subdir_new, type))
4061 Error(ERR_EXIT, "internal tree info structure corrupted -- aborting");
4064 void AddUserLevelSetToLevelInfo(char *level_subdir_new)
4066 AddTreeSetToTreeInfo(NULL, NULL, level_subdir_new, TREE_TYPE_LEVEL_DIR);
4069 char *getArtworkIdentifierForUserLevelSet(int type)
4071 char *classic_artwork_set = getClassicArtworkSet(type);
4073 // check for custom artwork configured in "levelinfo.conf"
4074 char *leveldir_artwork_set =
4075 *LEVELDIR_ARTWORK_SET_PTR(leveldir_current, type);
4076 boolean has_leveldir_artwork_set =
4077 (leveldir_artwork_set != NULL && !strEqual(leveldir_artwork_set,
4078 classic_artwork_set));
4080 // check for custom artwork in sub-directory "graphics" etc.
4081 TreeInfo *artwork_first_node = ARTWORK_FIRST_NODE(artwork, type);
4082 char *leveldir_identifier = leveldir_current->identifier;
4083 boolean has_artwork_subdir =
4084 (getTreeInfoFromIdentifier(artwork_first_node,
4085 leveldir_identifier) != NULL);
4087 return (has_leveldir_artwork_set ? leveldir_artwork_set :
4088 has_artwork_subdir ? leveldir_identifier :
4089 classic_artwork_set);
4092 TreeInfo *getArtworkTreeInfoForUserLevelSet(int type)
4094 char *artwork_set = getArtworkIdentifierForUserLevelSet(type);
4095 TreeInfo *artwork_first_node = ARTWORK_FIRST_NODE(artwork, type);
4096 TreeInfo *ti = getTreeInfoFromIdentifier(artwork_first_node, artwork_set);
4100 ti = getTreeInfoFromIdentifier(artwork_first_node,
4101 ARTWORK_DEFAULT_SUBDIR(type));
4103 Error(ERR_EXIT, "cannot find default graphics -- should not happen");
4109 boolean checkIfCustomArtworkExistsForCurrentLevelSet(void)
4111 char *graphics_set =
4112 getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_GRAPHICS);
4114 getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_SOUNDS);
4116 getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_MUSIC);
4118 return (!strEqual(graphics_set, GFX_CLASSIC_SUBDIR) ||
4119 !strEqual(sounds_set, SND_CLASSIC_SUBDIR) ||
4120 !strEqual(music_set, MUS_CLASSIC_SUBDIR));
4123 boolean UpdateUserLevelSet(char *level_subdir, char *level_name,
4124 char *level_author, int num_levels)
4126 char *filename = getPath2(getUserLevelDir(level_subdir), LEVELINFO_FILENAME);
4127 char *filename_tmp = getStringCat2(filename, ".tmp");
4129 FILE *file_tmp = NULL;
4130 char line[MAX_LINE_LEN];
4131 boolean success = FALSE;
4132 LevelDirTree *leveldir = getTreeInfoFromIdentifier(leveldir_first,
4134 // update values in level directory tree
4136 if (level_name != NULL)
4137 setString(&leveldir->name, level_name);
4139 if (level_author != NULL)
4140 setString(&leveldir->author, level_author);
4142 if (num_levels != -1)
4143 leveldir->levels = num_levels;
4145 // update values that depend on other values
4147 setString(&leveldir->name_sorting, leveldir->name);
4149 leveldir->last_level = leveldir->first_level + leveldir->levels - 1;
4151 // sort order of level sets may have changed
4152 sortTreeInfo(&leveldir_first);
4154 if ((file = fopen(filename, MODE_READ)) &&
4155 (file_tmp = fopen(filename_tmp, MODE_WRITE)))
4157 while (fgets(line, MAX_LINE_LEN, file))
4159 if (strPrefix(line, "name:") && level_name != NULL)
4160 fprintf(file_tmp, "%-32s%s\n", "name:", level_name);
4161 else if (strPrefix(line, "author:") && level_author != NULL)
4162 fprintf(file_tmp, "%-32s%s\n", "author:", level_author);
4163 else if (strPrefix(line, "levels:") && num_levels != -1)
4164 fprintf(file_tmp, "%-32s%d\n", "levels:", num_levels);
4166 fputs(line, file_tmp);
4179 success = (rename(filename_tmp, filename) == 0);
4187 boolean CreateUserLevelSet(char *level_subdir, char *level_name,
4188 char *level_author, int num_levels,
4189 boolean use_artwork_set)
4191 LevelDirTree *level_info;
4196 // create user level sub-directory, if needed
4197 createDirectory(getUserLevelDir(level_subdir), "user level", PERMS_PRIVATE);
4199 filename = getPath2(getUserLevelDir(level_subdir), LEVELINFO_FILENAME);
4201 if (!(file = fopen(filename, MODE_WRITE)))
4203 Error(ERR_WARN, "cannot write level info file '%s'", filename);
4209 level_info = newTreeInfo();
4211 // always start with reliable default values
4212 setTreeInfoToDefaults(level_info, TREE_TYPE_LEVEL_DIR);
4214 setString(&level_info->name, level_name);
4215 setString(&level_info->author, level_author);
4216 level_info->levels = num_levels;
4217 level_info->first_level = 1;
4218 level_info->sort_priority = LEVELCLASS_PRIVATE_START;
4219 level_info->readonly = FALSE;
4221 if (use_artwork_set)
4223 level_info->graphics_set =
4224 getStringCopy(getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_GRAPHICS));
4225 level_info->sounds_set =
4226 getStringCopy(getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_SOUNDS));
4227 level_info->music_set =
4228 getStringCopy(getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_MUSIC));
4231 token_value_position = TOKEN_VALUE_POSITION_SHORT;
4233 fprintFileHeader(file, LEVELINFO_FILENAME);
4236 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
4238 if (i == LEVELINFO_TOKEN_NAME ||
4239 i == LEVELINFO_TOKEN_AUTHOR ||
4240 i == LEVELINFO_TOKEN_LEVELS ||
4241 i == LEVELINFO_TOKEN_FIRST_LEVEL ||
4242 i == LEVELINFO_TOKEN_SORT_PRIORITY ||
4243 i == LEVELINFO_TOKEN_READONLY ||
4244 (use_artwork_set && (i == LEVELINFO_TOKEN_GRAPHICS_SET ||
4245 i == LEVELINFO_TOKEN_SOUNDS_SET ||
4246 i == LEVELINFO_TOKEN_MUSIC_SET)))
4247 fprintf(file, "%s\n", getSetupLine(levelinfo_tokens, "", i));
4249 // just to make things nicer :)
4250 if (i == LEVELINFO_TOKEN_AUTHOR ||
4251 i == LEVELINFO_TOKEN_FIRST_LEVEL ||
4252 (use_artwork_set && i == LEVELINFO_TOKEN_READONLY))
4253 fprintf(file, "\n");
4256 token_value_position = TOKEN_VALUE_POSITION_DEFAULT;
4260 SetFilePermissions(filename, PERMS_PRIVATE);
4262 freeTreeInfo(level_info);
4268 static void SaveUserLevelInfo(void)
4270 CreateUserLevelSet(getLoginName(), getLoginName(), getRealName(), 100, FALSE);
4273 char *getSetupValue(int type, void *value)
4275 static char value_string[MAX_LINE_LEN];
4283 strcpy(value_string, (*(boolean *)value ? "true" : "false"));
4287 strcpy(value_string, (*(boolean *)value ? "on" : "off"));
4291 strcpy(value_string, (*(int *)value == AUTO ? "auto" :
4292 *(int *)value == FALSE ? "off" : "on"));
4296 strcpy(value_string, (*(boolean *)value ? "yes" : "no"));
4299 case TYPE_YES_NO_AUTO:
4300 strcpy(value_string, (*(int *)value == AUTO ? "auto" :
4301 *(int *)value == FALSE ? "no" : "yes"));
4305 strcpy(value_string, (*(boolean *)value ? "AGA" : "ECS"));
4309 strcpy(value_string, getKeyNameFromKey(*(Key *)value));
4313 strcpy(value_string, getX11KeyNameFromKey(*(Key *)value));
4317 sprintf(value_string, "%d", *(int *)value);
4321 if (*(char **)value == NULL)
4324 strcpy(value_string, *(char **)value);
4328 sprintf(value_string, "player_%d", *(int *)value + 1);
4332 value_string[0] = '\0';
4336 if (type & TYPE_GHOSTED)
4337 strcpy(value_string, "n/a");
4339 return value_string;
4342 char *getSetupLine(struct TokenInfo *token_info, char *prefix, int token_nr)
4346 static char token_string[MAX_LINE_LEN];
4347 int token_type = token_info[token_nr].type;
4348 void *setup_value = token_info[token_nr].value;
4349 char *token_text = token_info[token_nr].text;
4350 char *value_string = getSetupValue(token_type, setup_value);
4352 // build complete token string
4353 sprintf(token_string, "%s%s", prefix, token_text);
4355 // build setup entry line
4356 line = getFormattedSetupEntry(token_string, value_string);
4358 if (token_type == TYPE_KEY_X11)
4360 Key key = *(Key *)setup_value;
4361 char *keyname = getKeyNameFromKey(key);
4363 // add comment, if useful
4364 if (!strEqual(keyname, "(undefined)") &&
4365 !strEqual(keyname, "(unknown)"))
4367 // add at least one whitespace
4369 for (i = strlen(line); i < token_comment_position; i++)
4373 strcat(line, keyname);
4380 void LoadLevelSetup_LastSeries(void)
4382 // --------------------------------------------------------------------------
4383 // ~/.<program>/levelsetup.conf
4384 // --------------------------------------------------------------------------
4386 char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
4387 SetupFileHash *level_setup_hash = NULL;
4389 // always start with reliable default values
4390 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
4392 if (!strEqual(DEFAULT_LEVELSET, UNDEFINED_LEVELSET))
4394 leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
4396 if (leveldir_current == NULL)
4397 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
4400 if ((level_setup_hash = loadSetupFileHash(filename)))
4402 char *last_level_series =
4403 getHashEntry(level_setup_hash, TOKEN_STR_LAST_LEVEL_SERIES);
4405 leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
4407 if (leveldir_current == NULL)
4408 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
4410 freeSetupFileHash(level_setup_hash);
4414 Error(ERR_DEBUG, "using default setup values");
4420 static void SaveLevelSetup_LastSeries_Ext(boolean deactivate_last_level_series)
4422 // --------------------------------------------------------------------------
4423 // ~/.<program>/levelsetup.conf
4424 // --------------------------------------------------------------------------
4426 // check if the current level directory structure is available at this point
4427 if (leveldir_current == NULL)
4430 char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
4431 char *level_subdir = leveldir_current->subdir;
4434 InitUserDataDirectory();
4436 if (!(file = fopen(filename, MODE_WRITE)))
4438 Error(ERR_WARN, "cannot write setup file '%s'", filename);
4445 fprintFileHeader(file, LEVELSETUP_FILENAME);
4447 if (deactivate_last_level_series)
4448 fprintf(file, "# %s\n# ", "the following level set may have caused a problem and was deactivated");
4450 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_LEVEL_SERIES,
4455 SetFilePermissions(filename, PERMS_PRIVATE);
4460 void SaveLevelSetup_LastSeries(void)
4462 SaveLevelSetup_LastSeries_Ext(FALSE);
4465 void SaveLevelSetup_LastSeries_Deactivate(void)
4467 SaveLevelSetup_LastSeries_Ext(TRUE);
4470 static void checkSeriesInfo(void)
4472 static char *level_directory = NULL;
4475 DirectoryEntry *dir_entry;
4478 checked_free(level_directory);
4480 // check for more levels besides the 'levels' field of 'levelinfo.conf'
4482 level_directory = getPath2((leveldir_current->in_user_dir ?
4483 getUserLevelDir(NULL) :
4484 options.level_directory),
4485 leveldir_current->fullpath);
4487 if ((dir = openDirectory(level_directory)) == NULL)
4489 Error(ERR_WARN, "cannot read level directory '%s'", level_directory);
4495 while ((dir_entry = readDirectory(dir)) != NULL) // loop all entries
4497 if (strlen(dir_entry->basename) > 4 &&
4498 dir_entry->basename[3] == '.' &&
4499 strEqual(&dir_entry->basename[4], LEVELFILE_EXTENSION))
4501 char levelnum_str[4];
4504 strncpy(levelnum_str, dir_entry->basename, 3);
4505 levelnum_str[3] = '\0';
4507 levelnum_value = atoi(levelnum_str);
4509 if (levelnum_value < leveldir_current->first_level)
4511 Error(ERR_WARN, "additional level %d found", levelnum_value);
4512 leveldir_current->first_level = levelnum_value;
4514 else if (levelnum_value > leveldir_current->last_level)
4516 Error(ERR_WARN, "additional level %d found", levelnum_value);
4517 leveldir_current->last_level = levelnum_value;
4523 closeDirectory(dir);
4526 void LoadLevelSetup_SeriesInfo(void)
4529 SetupFileHash *level_setup_hash = NULL;
4530 char *level_subdir = leveldir_current->subdir;
4533 // always start with reliable default values
4534 level_nr = leveldir_current->first_level;
4536 for (i = 0; i < MAX_LEVELS; i++)
4538 LevelStats_setPlayed(i, 0);
4539 LevelStats_setSolved(i, 0);
4544 // --------------------------------------------------------------------------
4545 // ~/.<program>/levelsetup/<level series>/levelsetup.conf
4546 // --------------------------------------------------------------------------
4548 level_subdir = leveldir_current->subdir;
4550 filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
4552 if ((level_setup_hash = loadSetupFileHash(filename)))
4556 // get last played level in this level set
4558 token_value = getHashEntry(level_setup_hash, TOKEN_STR_LAST_PLAYED_LEVEL);
4562 level_nr = atoi(token_value);
4564 if (level_nr < leveldir_current->first_level)
4565 level_nr = leveldir_current->first_level;
4566 if (level_nr > leveldir_current->last_level)
4567 level_nr = leveldir_current->last_level;
4570 // get handicap level in this level set
4572 token_value = getHashEntry(level_setup_hash, TOKEN_STR_HANDICAP_LEVEL);
4576 int level_nr = atoi(token_value);
4578 if (level_nr < leveldir_current->first_level)
4579 level_nr = leveldir_current->first_level;
4580 if (level_nr > leveldir_current->last_level + 1)
4581 level_nr = leveldir_current->last_level;
4583 if (leveldir_current->user_defined || !leveldir_current->handicap)
4584 level_nr = leveldir_current->last_level;
4586 leveldir_current->handicap_level = level_nr;
4589 // get number of played and solved levels in this level set
4591 BEGIN_HASH_ITERATION(level_setup_hash, itr)
4593 char *token = HASH_ITERATION_TOKEN(itr);
4594 char *value = HASH_ITERATION_VALUE(itr);
4596 if (strlen(token) == 3 &&
4597 token[0] >= '0' && token[0] <= '9' &&
4598 token[1] >= '0' && token[1] <= '9' &&
4599 token[2] >= '0' && token[2] <= '9')
4601 int level_nr = atoi(token);
4604 LevelStats_setPlayed(level_nr, atoi(value)); // read 1st column
4606 value = strchr(value, ' ');
4609 LevelStats_setSolved(level_nr, atoi(value)); // read 2nd column
4612 END_HASH_ITERATION(hash, itr)
4614 freeSetupFileHash(level_setup_hash);
4618 Error(ERR_DEBUG, "using default setup values");
4624 void SaveLevelSetup_SeriesInfo(void)
4627 char *level_subdir = leveldir_current->subdir;
4628 char *level_nr_str = int2str(level_nr, 0);
4629 char *handicap_level_str = int2str(leveldir_current->handicap_level, 0);
4633 // --------------------------------------------------------------------------
4634 // ~/.<program>/levelsetup/<level series>/levelsetup.conf
4635 // --------------------------------------------------------------------------
4637 InitLevelSetupDirectory(level_subdir);
4639 filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
4641 if (!(file = fopen(filename, MODE_WRITE)))
4643 Error(ERR_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++;