1 // ============================================================================
2 // Artsoft Retro-Game Library
3 // ----------------------------------------------------------------------------
4 // (c) 1995-2014 by Artsoft Entertainment
7 // https://www.artsoft.org/
8 // ----------------------------------------------------------------------------
10 // ============================================================================
12 #include <sys/types.h>
26 #include "zip/miniunz.h"
29 #define ENABLE_UNUSED_CODE FALSE // for currently unused functions
30 #define DEBUG_NO_CONFIG_FILE FALSE // for extra-verbose debug output
32 #define NUM_LEVELCLASS_DESC 8
34 static char *levelclass_desc[NUM_LEVELCLASS_DESC] =
47 #define LEVELCOLOR(n) (IS_LEVELCLASS_TUTORIAL(n) ? FC_BLUE : \
48 IS_LEVELCLASS_CLASSICS(n) ? FC_RED : \
49 IS_LEVELCLASS_BD(n) ? FC_YELLOW : \
50 IS_LEVELCLASS_EM(n) ? FC_YELLOW : \
51 IS_LEVELCLASS_SP(n) ? FC_YELLOW : \
52 IS_LEVELCLASS_DX(n) ? FC_YELLOW : \
53 IS_LEVELCLASS_SB(n) ? FC_YELLOW : \
54 IS_LEVELCLASS_CONTRIB(n) ? FC_GREEN : \
55 IS_LEVELCLASS_PRIVATE(n) ? FC_RED : \
58 #define LEVELSORTING(n) (IS_LEVELCLASS_TUTORIAL(n) ? 0 : \
59 IS_LEVELCLASS_CLASSICS(n) ? 1 : \
60 IS_LEVELCLASS_BD(n) ? 2 : \
61 IS_LEVELCLASS_EM(n) ? 3 : \
62 IS_LEVELCLASS_SP(n) ? 4 : \
63 IS_LEVELCLASS_DX(n) ? 5 : \
64 IS_LEVELCLASS_SB(n) ? 6 : \
65 IS_LEVELCLASS_CONTRIB(n) ? 7 : \
66 IS_LEVELCLASS_PRIVATE(n) ? 8 : \
69 #define ARTWORKCOLOR(n) (IS_ARTWORKCLASS_CLASSICS(n) ? FC_RED : \
70 IS_ARTWORKCLASS_CONTRIB(n) ? FC_GREEN : \
71 IS_ARTWORKCLASS_PRIVATE(n) ? FC_RED : \
72 IS_ARTWORKCLASS_LEVEL(n) ? FC_YELLOW : \
75 #define ARTWORKSORTING(n) (IS_ARTWORKCLASS_CLASSICS(n) ? 0 : \
76 IS_ARTWORKCLASS_LEVEL(n) ? 1 : \
77 IS_ARTWORKCLASS_CONTRIB(n) ? 2 : \
78 IS_ARTWORKCLASS_PRIVATE(n) ? 3 : \
81 #define TOKEN_VALUE_POSITION_SHORT 32
82 #define TOKEN_VALUE_POSITION_DEFAULT 40
83 #define TOKEN_COMMENT_POSITION_DEFAULT 60
85 #define MAX_COOKIE_LEN 256
88 static void setTreeInfoToDefaults(TreeInfo *, int);
89 static TreeInfo *getTreeInfoCopy(TreeInfo *ti);
90 static int compareTreeInfoEntries(const void *, const void *);
92 static int token_value_position = TOKEN_VALUE_POSITION_DEFAULT;
93 static int token_comment_position = TOKEN_COMMENT_POSITION_DEFAULT;
95 static SetupFileHash *artworkinfo_cache_old = NULL;
96 static SetupFileHash *artworkinfo_cache_new = NULL;
97 static SetupFileHash *optional_tokens_hash = NULL;
98 static boolean use_artworkinfo_cache = TRUE;
99 static boolean update_artworkinfo_cache = FALSE;
102 // ----------------------------------------------------------------------------
104 // ----------------------------------------------------------------------------
106 static char *getLevelClassDescription(TreeInfo *ti)
108 int position = ti->sort_priority / 100;
110 if (position >= 0 && position < NUM_LEVELCLASS_DESC)
111 return levelclass_desc[position];
113 return "Unknown Level Class";
116 static char *getScoreDir(char *level_subdir)
118 static char *score_dir = NULL;
119 static char *score_level_dir = NULL;
120 char *score_subdir = SCORES_DIRECTORY;
122 if (score_dir == NULL)
124 if (program.global_scores)
125 score_dir = getPath2(getCommonDataDir(), score_subdir);
127 score_dir = getPath2(getMainUserGameDataDir(), score_subdir);
130 if (level_subdir != NULL)
132 checked_free(score_level_dir);
134 score_level_dir = getPath2(score_dir, level_subdir);
136 return score_level_dir;
142 static char *getUserSubdir(int nr)
144 static char user_subdir[16] = { 0 };
146 sprintf(user_subdir, "%03d", nr);
151 static char *getUserDir(int nr)
153 static char *user_dir = NULL;
154 char *main_data_dir = getMainUserGameDataDir();
155 char *users_subdir = USERS_DIRECTORY;
156 char *user_subdir = getUserSubdir(nr);
158 checked_free(user_dir);
161 user_dir = getPath3(main_data_dir, users_subdir, user_subdir);
163 user_dir = getPath2(main_data_dir, users_subdir);
168 static char *getLevelSetupDir(char *level_subdir)
170 static char *levelsetup_dir = NULL;
171 char *data_dir = getUserGameDataDir();
172 char *levelsetup_subdir = LEVELSETUP_DIRECTORY;
174 checked_free(levelsetup_dir);
176 if (level_subdir != NULL)
177 levelsetup_dir = getPath3(data_dir, levelsetup_subdir, level_subdir);
179 levelsetup_dir = getPath2(data_dir, levelsetup_subdir);
181 return levelsetup_dir;
184 static char *getCacheDir(void)
186 static char *cache_dir = NULL;
188 if (cache_dir == NULL)
189 cache_dir = getPath2(getMainUserGameDataDir(), CACHE_DIRECTORY);
194 static char *getNetworkDir(void)
196 static char *network_dir = NULL;
198 if (network_dir == NULL)
199 network_dir = getPath2(getMainUserGameDataDir(), NETWORK_DIRECTORY);
204 char *getLevelDirFromTreeInfo(TreeInfo *node)
206 static char *level_dir = NULL;
209 return options.level_directory;
211 checked_free(level_dir);
213 level_dir = getPath2((node->in_user_dir ? getUserLevelDir(NULL) :
214 options.level_directory), node->fullpath);
219 char *getUserLevelDir(char *level_subdir)
221 static char *userlevel_dir = NULL;
222 char *data_dir = getMainUserGameDataDir();
223 char *userlevel_subdir = LEVELS_DIRECTORY;
225 checked_free(userlevel_dir);
227 if (level_subdir != NULL)
228 userlevel_dir = getPath3(data_dir, userlevel_subdir, level_subdir);
230 userlevel_dir = getPath2(data_dir, userlevel_subdir);
232 return userlevel_dir;
235 char *getNetworkLevelDir(char *level_subdir)
237 static char *network_level_dir = NULL;
238 char *data_dir = getNetworkDir();
239 char *networklevel_subdir = LEVELS_DIRECTORY;
241 checked_free(network_level_dir);
243 if (level_subdir != NULL)
244 network_level_dir = getPath3(data_dir, networklevel_subdir, level_subdir);
246 network_level_dir = getPath2(data_dir, networklevel_subdir);
248 return network_level_dir;
251 char *getCurrentLevelDir(void)
253 return getLevelDirFromTreeInfo(leveldir_current);
256 char *getNewUserLevelSubdir(void)
258 static char *new_level_subdir = NULL;
259 char *subdir_prefix = getLoginName();
260 char subdir_suffix[10];
261 int max_suffix_number = 1000;
264 while (++i < max_suffix_number)
266 sprintf(subdir_suffix, "_%d", i);
268 checked_free(new_level_subdir);
269 new_level_subdir = getStringCat2(subdir_prefix, subdir_suffix);
271 if (!directoryExists(getUserLevelDir(new_level_subdir)))
275 return new_level_subdir;
278 static char *getTapeDir(char *level_subdir)
280 static char *tape_dir = NULL;
281 char *data_dir = getUserGameDataDir();
282 char *tape_subdir = TAPES_DIRECTORY;
284 checked_free(tape_dir);
286 if (level_subdir != NULL)
287 tape_dir = getPath3(data_dir, tape_subdir, level_subdir);
289 tape_dir = getPath2(data_dir, tape_subdir);
294 static char *getSolutionTapeDir(void)
296 static char *tape_dir = NULL;
297 char *data_dir = getCurrentLevelDir();
298 char *tape_subdir = TAPES_DIRECTORY;
300 checked_free(tape_dir);
302 tape_dir = getPath2(data_dir, tape_subdir);
307 static char *getDefaultGraphicsDir(char *graphics_subdir)
309 static char *graphics_dir = NULL;
311 if (graphics_subdir == NULL)
312 return options.graphics_directory;
314 checked_free(graphics_dir);
316 graphics_dir = getPath2(options.graphics_directory, graphics_subdir);
321 static char *getDefaultSoundsDir(char *sounds_subdir)
323 static char *sounds_dir = NULL;
325 if (sounds_subdir == NULL)
326 return options.sounds_directory;
328 checked_free(sounds_dir);
330 sounds_dir = getPath2(options.sounds_directory, sounds_subdir);
335 static char *getDefaultMusicDir(char *music_subdir)
337 static char *music_dir = NULL;
339 if (music_subdir == NULL)
340 return options.music_directory;
342 checked_free(music_dir);
344 music_dir = getPath2(options.music_directory, music_subdir);
349 static char *getClassicArtworkSet(int type)
351 return (type == TREE_TYPE_GRAPHICS_DIR ? GFX_CLASSIC_SUBDIR :
352 type == TREE_TYPE_SOUNDS_DIR ? SND_CLASSIC_SUBDIR :
353 type == TREE_TYPE_MUSIC_DIR ? MUS_CLASSIC_SUBDIR : "");
356 static char *getClassicArtworkDir(int type)
358 return (type == TREE_TYPE_GRAPHICS_DIR ?
359 getDefaultGraphicsDir(GFX_CLASSIC_SUBDIR) :
360 type == TREE_TYPE_SOUNDS_DIR ?
361 getDefaultSoundsDir(SND_CLASSIC_SUBDIR) :
362 type == TREE_TYPE_MUSIC_DIR ?
363 getDefaultMusicDir(MUS_CLASSIC_SUBDIR) : "");
366 char *getUserGraphicsDir(void)
368 static char *usergraphics_dir = NULL;
370 if (usergraphics_dir == NULL)
371 usergraphics_dir = getPath2(getMainUserGameDataDir(), GRAPHICS_DIRECTORY);
373 return usergraphics_dir;
376 char *getUserSoundsDir(void)
378 static char *usersounds_dir = NULL;
380 if (usersounds_dir == NULL)
381 usersounds_dir = getPath2(getMainUserGameDataDir(), SOUNDS_DIRECTORY);
383 return usersounds_dir;
386 char *getUserMusicDir(void)
388 static char *usermusic_dir = NULL;
390 if (usermusic_dir == NULL)
391 usermusic_dir = getPath2(getMainUserGameDataDir(), MUSIC_DIRECTORY);
393 return usermusic_dir;
396 static char *getSetupArtworkDir(TreeInfo *ti)
398 static char *artwork_dir = NULL;
403 checked_free(artwork_dir);
405 artwork_dir = getPath2(ti->basepath, ti->fullpath);
410 char *setLevelArtworkDir(TreeInfo *ti)
412 char **artwork_path_ptr, **artwork_set_ptr;
413 TreeInfo *level_artwork;
415 if (ti == NULL || leveldir_current == NULL)
418 artwork_path_ptr = LEVELDIR_ARTWORK_PATH_PTR(leveldir_current, ti->type);
419 artwork_set_ptr = LEVELDIR_ARTWORK_SET_PTR( leveldir_current, ti->type);
421 checked_free(*artwork_path_ptr);
423 if ((level_artwork = getTreeInfoFromIdentifier(ti, *artwork_set_ptr)))
425 *artwork_path_ptr = getStringCopy(getSetupArtworkDir(level_artwork));
430 No (or non-existing) artwork configured in "levelinfo.conf". This would
431 normally result in using the artwork configured in the setup menu. But
432 if an artwork subdirectory exists (which might contain custom artwork
433 or an artwork configuration file), this level artwork must be treated
434 as relative to the default "classic" artwork, not to the artwork that
435 is currently configured in the setup menu.
437 Update: For "special" versions of R'n'D (like "R'n'D jue"), do not use
438 the "default" artwork (which would be "jue0" for "R'n'D jue"), but use
439 the real "classic" artwork from the original R'n'D (like "gfx_classic").
442 char *dir = getPath2(getCurrentLevelDir(), ARTWORK_DIRECTORY(ti->type));
444 checked_free(*artwork_set_ptr);
446 if (directoryExists(dir))
448 *artwork_path_ptr = getStringCopy(getClassicArtworkDir(ti->type));
449 *artwork_set_ptr = getStringCopy(getClassicArtworkSet(ti->type));
453 *artwork_path_ptr = getStringCopy(UNDEFINED_FILENAME);
454 *artwork_set_ptr = NULL;
460 return *artwork_set_ptr;
463 static char *getLevelArtworkSet(int type)
465 if (leveldir_current == NULL)
468 return LEVELDIR_ARTWORK_SET(leveldir_current, type);
471 static char *getLevelArtworkDir(int type)
473 if (leveldir_current == NULL)
474 return UNDEFINED_FILENAME;
476 return LEVELDIR_ARTWORK_PATH(leveldir_current, type);
479 char *getProgramMainDataPath(char *command_filename, char *base_path)
481 // check if the program's main data base directory is configured
482 if (!strEqual(base_path, "."))
483 return getStringCopy(base_path);
485 /* if the program is configured to start from current directory (default),
486 determine program package directory from program binary (some versions
487 of KDE/Konqueror and Mac OS X (especially "Mavericks") apparently do not
488 set the current working directory to the program package directory) */
489 char *main_data_path = getBasePath(command_filename);
491 #if defined(PLATFORM_MACOSX)
492 if (strSuffix(main_data_path, MAC_APP_BINARY_SUBDIR))
494 char *main_data_path_old = main_data_path;
496 // cut relative path to Mac OS X application binary directory from path
497 main_data_path[strlen(main_data_path) -
498 strlen(MAC_APP_BINARY_SUBDIR)] = '\0';
500 // cut trailing path separator from path (but not if path is root directory)
501 if (strSuffix(main_data_path, "/") && !strEqual(main_data_path, "/"))
502 main_data_path[strlen(main_data_path) - 1] = '\0';
504 // replace empty path with current directory
505 if (strEqual(main_data_path, ""))
506 main_data_path = ".";
508 // add relative path to Mac OS X application resources directory to path
509 main_data_path = getPath2(main_data_path, MAC_APP_FILES_SUBDIR);
511 free(main_data_path_old);
515 return main_data_path;
518 char *getProgramConfigFilename(char *command_filename)
520 static char *config_filename_1 = NULL;
521 static char *config_filename_2 = NULL;
522 static char *config_filename_3 = NULL;
523 static boolean initialized = FALSE;
527 char *command_filename_1 = getStringCopy(command_filename);
529 // strip trailing executable suffix from command filename
530 if (strSuffix(command_filename_1, ".exe"))
531 command_filename_1[strlen(command_filename_1) - 4] = '\0';
533 char *ro_base_path = getProgramMainDataPath(command_filename, RO_BASE_PATH);
534 char *conf_directory = getPath2(ro_base_path, CONF_DIRECTORY);
536 char *command_basepath = getBasePath(command_filename);
537 char *command_basename = getBaseNameNoSuffix(command_filename);
538 char *command_filename_2 = getPath2(command_basepath, command_basename);
540 config_filename_1 = getStringCat2(command_filename_1, ".conf");
541 config_filename_2 = getStringCat2(command_filename_2, ".conf");
542 config_filename_3 = getPath2(conf_directory, SETUP_FILENAME);
544 checked_free(ro_base_path);
545 checked_free(conf_directory);
547 checked_free(command_basepath);
548 checked_free(command_basename);
550 checked_free(command_filename_1);
551 checked_free(command_filename_2);
556 // 1st try: look for config file that exactly matches the binary filename
557 if (fileExists(config_filename_1))
558 return config_filename_1;
560 // 2nd try: look for config file that matches binary filename without suffix
561 if (fileExists(config_filename_2))
562 return config_filename_2;
564 // 3rd try: return setup config filename in global program config directory
565 return config_filename_3;
568 char *getTapeFilename(int nr)
570 static char *filename = NULL;
571 char basename[MAX_FILENAME_LEN];
573 checked_free(filename);
575 sprintf(basename, "%03d.%s", nr, TAPEFILE_EXTENSION);
576 filename = getPath2(getTapeDir(leveldir_current->subdir), basename);
581 char *getSolutionTapeFilename(int nr)
583 static char *filename = NULL;
584 char basename[MAX_FILENAME_LEN];
586 checked_free(filename);
588 sprintf(basename, "%03d.%s", nr, TAPEFILE_EXTENSION);
589 filename = getPath2(getSolutionTapeDir(), basename);
591 if (!fileExists(filename))
593 static char *filename_sln = NULL;
595 checked_free(filename_sln);
597 sprintf(basename, "%03d.sln", nr);
598 filename_sln = getPath2(getSolutionTapeDir(), basename);
600 if (fileExists(filename_sln))
607 char *getScoreFilename(int nr)
609 static char *filename = NULL;
610 char basename[MAX_FILENAME_LEN];
612 checked_free(filename);
614 sprintf(basename, "%03d.%s", nr, SCOREFILE_EXTENSION);
616 // used instead of "leveldir_current->subdir" (for network games)
617 filename = getPath2(getScoreDir(levelset.identifier), basename);
622 char *getSetupFilename(void)
624 static char *filename = NULL;
626 checked_free(filename);
628 filename = getPath2(getSetupDir(), SETUP_FILENAME);
633 char *getDefaultSetupFilename(void)
635 return program.config_filename;
638 char *getEditorSetupFilename(void)
640 static char *filename = NULL;
642 checked_free(filename);
643 filename = getPath2(getCurrentLevelDir(), EDITORSETUP_FILENAME);
645 if (fileExists(filename))
648 checked_free(filename);
649 filename = getPath2(getSetupDir(), EDITORSETUP_FILENAME);
654 char *getHelpAnimFilename(void)
656 static char *filename = NULL;
658 checked_free(filename);
660 filename = getPath2(getCurrentLevelDir(), HELPANIM_FILENAME);
665 char *getHelpTextFilename(void)
667 static char *filename = NULL;
669 checked_free(filename);
671 filename = getPath2(getCurrentLevelDir(), HELPTEXT_FILENAME);
676 char *getLevelSetInfoFilename(void)
678 static char *filename = NULL;
693 for (i = 0; basenames[i] != NULL; i++)
695 checked_free(filename);
696 filename = getPath2(getCurrentLevelDir(), basenames[i]);
698 if (fileExists(filename))
705 static char *getLevelSetTitleMessageBasename(int nr, boolean initial)
707 static char basename[32];
709 sprintf(basename, "%s_%d.txt",
710 (initial ? "titlemessage_initial" : "titlemessage"), nr + 1);
715 char *getLevelSetTitleMessageFilename(int nr, boolean initial)
717 static char *filename = NULL;
719 boolean skip_setup_artwork = FALSE;
721 checked_free(filename);
723 basename = getLevelSetTitleMessageBasename(nr, initial);
725 if (!gfx.override_level_graphics)
727 // 1st try: look for special artwork in current level series directory
728 filename = getPath3(getCurrentLevelDir(), GRAPHICS_DIRECTORY, basename);
729 if (fileExists(filename))
734 // 2nd try: look for message file in current level set directory
735 filename = getPath2(getCurrentLevelDir(), basename);
736 if (fileExists(filename))
741 // check if there is special artwork configured in level series config
742 if (getLevelArtworkSet(ARTWORK_TYPE_GRAPHICS) != NULL)
744 // 3rd try: look for special artwork configured in level series config
745 filename = getPath2(getLevelArtworkDir(ARTWORK_TYPE_GRAPHICS), basename);
746 if (fileExists(filename))
751 // take missing artwork configured in level set config from default
752 skip_setup_artwork = TRUE;
756 if (!skip_setup_artwork)
758 // 4th try: look for special artwork in configured artwork directory
759 filename = getPath2(getSetupArtworkDir(artwork.gfx_current), basename);
760 if (fileExists(filename))
766 // 5th try: look for default artwork in new default artwork directory
767 filename = getPath2(getDefaultGraphicsDir(GFX_DEFAULT_SUBDIR), basename);
768 if (fileExists(filename))
773 // 6th try: look for default artwork in old default artwork directory
774 filename = getPath2(options.graphics_directory, basename);
775 if (fileExists(filename))
778 return NULL; // cannot find specified artwork file anywhere
781 static char *getCorrectedArtworkBasename(char *basename)
786 char *getCustomImageFilename(char *basename)
788 static char *filename = NULL;
789 boolean skip_setup_artwork = FALSE;
791 checked_free(filename);
793 basename = getCorrectedArtworkBasename(basename);
795 if (!gfx.override_level_graphics)
797 // 1st try: look for special artwork in current level series directory
798 filename = getImg3(getCurrentLevelDir(), GRAPHICS_DIRECTORY, basename);
799 if (fileExists(filename))
804 // check if there is special artwork configured in level series config
805 if (getLevelArtworkSet(ARTWORK_TYPE_GRAPHICS) != NULL)
807 // 2nd try: look for special artwork configured in level series config
808 filename = getImg2(getLevelArtworkDir(ARTWORK_TYPE_GRAPHICS), basename);
809 if (fileExists(filename))
814 // take missing artwork configured in level set config from default
815 skip_setup_artwork = TRUE;
819 if (!skip_setup_artwork)
821 // 3rd try: look for special artwork in configured artwork directory
822 filename = getImg2(getSetupArtworkDir(artwork.gfx_current), basename);
823 if (fileExists(filename))
829 // 4th try: look for default artwork in new default artwork directory
830 filename = getImg2(getDefaultGraphicsDir(GFX_DEFAULT_SUBDIR), basename);
831 if (fileExists(filename))
836 // 5th try: look for default artwork in old default artwork directory
837 filename = getImg2(options.graphics_directory, basename);
838 if (fileExists(filename))
841 if (!strEqual(GFX_FALLBACK_FILENAME, UNDEFINED_FILENAME))
845 Warn("cannot find artwork file '%s' (using fallback)", basename);
847 // 6th try: look for fallback artwork in old default artwork directory
848 // (needed to prevent errors when trying to access unused artwork files)
849 filename = getImg2(options.graphics_directory, GFX_FALLBACK_FILENAME);
850 if (fileExists(filename))
854 return NULL; // cannot find specified artwork file anywhere
857 char *getCustomSoundFilename(char *basename)
859 static char *filename = NULL;
860 boolean skip_setup_artwork = FALSE;
862 checked_free(filename);
864 basename = getCorrectedArtworkBasename(basename);
866 if (!gfx.override_level_sounds)
868 // 1st try: look for special artwork in current level series directory
869 filename = getPath3(getCurrentLevelDir(), SOUNDS_DIRECTORY, basename);
870 if (fileExists(filename))
875 // check if there is special artwork configured in level series config
876 if (getLevelArtworkSet(ARTWORK_TYPE_SOUNDS) != NULL)
878 // 2nd try: look for special artwork configured in level series config
879 filename = getPath2(getLevelArtworkDir(TREE_TYPE_SOUNDS_DIR), basename);
880 if (fileExists(filename))
885 // take missing artwork configured in level set config from default
886 skip_setup_artwork = TRUE;
890 if (!skip_setup_artwork)
892 // 3rd try: look for special artwork in configured artwork directory
893 filename = getPath2(getSetupArtworkDir(artwork.snd_current), basename);
894 if (fileExists(filename))
900 // 4th try: look for default artwork in new default artwork directory
901 filename = getPath2(getDefaultSoundsDir(SND_DEFAULT_SUBDIR), basename);
902 if (fileExists(filename))
907 // 5th try: look for default artwork in old default artwork directory
908 filename = getPath2(options.sounds_directory, basename);
909 if (fileExists(filename))
912 if (!strEqual(SND_FALLBACK_FILENAME, UNDEFINED_FILENAME))
916 Warn("cannot find artwork file '%s' (using fallback)", basename);
918 // 6th try: look for fallback artwork in old default artwork directory
919 // (needed to prevent errors when trying to access unused artwork files)
920 filename = getPath2(options.sounds_directory, SND_FALLBACK_FILENAME);
921 if (fileExists(filename))
925 return NULL; // cannot find specified artwork file anywhere
928 char *getCustomMusicFilename(char *basename)
930 static char *filename = NULL;
931 boolean skip_setup_artwork = FALSE;
933 checked_free(filename);
935 basename = getCorrectedArtworkBasename(basename);
937 if (!gfx.override_level_music)
939 // 1st try: look for special artwork in current level series directory
940 filename = getPath3(getCurrentLevelDir(), MUSIC_DIRECTORY, basename);
941 if (fileExists(filename))
946 // check if there is special artwork configured in level series config
947 if (getLevelArtworkSet(ARTWORK_TYPE_MUSIC) != NULL)
949 // 2nd try: look for special artwork configured in level series config
950 filename = getPath2(getLevelArtworkDir(TREE_TYPE_MUSIC_DIR), basename);
951 if (fileExists(filename))
956 // take missing artwork configured in level set config from default
957 skip_setup_artwork = TRUE;
961 if (!skip_setup_artwork)
963 // 3rd try: look for special artwork in configured artwork directory
964 filename = getPath2(getSetupArtworkDir(artwork.mus_current), basename);
965 if (fileExists(filename))
971 // 4th try: look for default artwork in new default artwork directory
972 filename = getPath2(getDefaultMusicDir(MUS_DEFAULT_SUBDIR), basename);
973 if (fileExists(filename))
978 // 5th try: look for default artwork in old default artwork directory
979 filename = getPath2(options.music_directory, basename);
980 if (fileExists(filename))
983 if (!strEqual(MUS_FALLBACK_FILENAME, UNDEFINED_FILENAME))
987 Warn("cannot find artwork file '%s' (using fallback)", basename);
989 // 6th try: look for fallback artwork in old default artwork directory
990 // (needed to prevent errors when trying to access unused artwork files)
991 filename = getPath2(options.music_directory, MUS_FALLBACK_FILENAME);
992 if (fileExists(filename))
996 return NULL; // cannot find specified artwork file anywhere
999 char *getCustomArtworkFilename(char *basename, int type)
1001 if (type == ARTWORK_TYPE_GRAPHICS)
1002 return getCustomImageFilename(basename);
1003 else if (type == ARTWORK_TYPE_SOUNDS)
1004 return getCustomSoundFilename(basename);
1005 else if (type == ARTWORK_TYPE_MUSIC)
1006 return getCustomMusicFilename(basename);
1008 return UNDEFINED_FILENAME;
1011 char *getCustomArtworkConfigFilename(int type)
1013 return getCustomArtworkFilename(ARTWORKINFO_FILENAME(type), type);
1016 char *getCustomArtworkLevelConfigFilename(int type)
1018 static char *filename = NULL;
1020 checked_free(filename);
1022 filename = getPath2(getLevelArtworkDir(type), ARTWORKINFO_FILENAME(type));
1027 char *getCustomMusicDirectory(void)
1029 static char *directory = NULL;
1030 boolean skip_setup_artwork = FALSE;
1032 checked_free(directory);
1034 if (!gfx.override_level_music)
1036 // 1st try: look for special artwork in current level series directory
1037 directory = getPath2(getCurrentLevelDir(), MUSIC_DIRECTORY);
1038 if (directoryExists(directory))
1043 // check if there is special artwork configured in level series config
1044 if (getLevelArtworkSet(ARTWORK_TYPE_MUSIC) != NULL)
1046 // 2nd try: look for special artwork configured in level series config
1047 directory = getStringCopy(getLevelArtworkDir(TREE_TYPE_MUSIC_DIR));
1048 if (directoryExists(directory))
1053 // take missing artwork configured in level set config from default
1054 skip_setup_artwork = TRUE;
1058 if (!skip_setup_artwork)
1060 // 3rd try: look for special artwork in configured artwork directory
1061 directory = getStringCopy(getSetupArtworkDir(artwork.mus_current));
1062 if (directoryExists(directory))
1068 // 4th try: look for default artwork in new default artwork directory
1069 directory = getStringCopy(getDefaultMusicDir(MUS_DEFAULT_SUBDIR));
1070 if (directoryExists(directory))
1075 // 5th try: look for default artwork in old default artwork directory
1076 directory = getStringCopy(options.music_directory);
1077 if (directoryExists(directory))
1080 return NULL; // cannot find specified artwork file anywhere
1083 void InitTapeDirectory(char *level_subdir)
1085 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
1086 createDirectory(getTapeDir(NULL), "main tape", PERMS_PRIVATE);
1087 createDirectory(getTapeDir(level_subdir), "level tape", PERMS_PRIVATE);
1090 void InitScoreDirectory(char *level_subdir)
1092 int permissions = (program.global_scores ? PERMS_PUBLIC : PERMS_PRIVATE);
1094 if (program.global_scores)
1095 createDirectory(getCommonDataDir(), "common data", permissions);
1097 createDirectory(getMainUserGameDataDir(), "main user data", permissions);
1099 createDirectory(getScoreDir(NULL), "main score", permissions);
1100 createDirectory(getScoreDir(level_subdir), "level score", permissions);
1103 static void SaveUserLevelInfo(void);
1105 void InitUserLevelDirectory(char *level_subdir)
1107 if (!directoryExists(getUserLevelDir(level_subdir)))
1109 createDirectory(getMainUserGameDataDir(), "main user data", PERMS_PRIVATE);
1110 createDirectory(getUserLevelDir(NULL), "main user level", PERMS_PRIVATE);
1111 createDirectory(getUserLevelDir(level_subdir), "user level", PERMS_PRIVATE);
1113 if (setup.internal.create_user_levelset)
1114 SaveUserLevelInfo();
1118 void InitNetworkLevelDirectory(char *level_subdir)
1120 if (!directoryExists(getNetworkLevelDir(level_subdir)))
1122 createDirectory(getMainUserGameDataDir(), "main user data", PERMS_PRIVATE);
1123 createDirectory(getNetworkDir(), "network data", PERMS_PRIVATE);
1124 createDirectory(getNetworkLevelDir(NULL), "main network level", PERMS_PRIVATE);
1125 createDirectory(getNetworkLevelDir(level_subdir), "network level", PERMS_PRIVATE);
1129 void InitLevelSetupDirectory(char *level_subdir)
1131 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
1132 createDirectory(getLevelSetupDir(NULL), "main level setup", PERMS_PRIVATE);
1133 createDirectory(getLevelSetupDir(level_subdir), "level setup", PERMS_PRIVATE);
1136 static void InitCacheDirectory(void)
1138 createDirectory(getMainUserGameDataDir(), "main user data", PERMS_PRIVATE);
1139 createDirectory(getCacheDir(), "cache data", PERMS_PRIVATE);
1143 // ----------------------------------------------------------------------------
1144 // some functions to handle lists of level and artwork directories
1145 // ----------------------------------------------------------------------------
1147 TreeInfo *newTreeInfo(void)
1149 return checked_calloc(sizeof(TreeInfo));
1152 TreeInfo *newTreeInfo_setDefaults(int type)
1154 TreeInfo *ti = newTreeInfo();
1156 setTreeInfoToDefaults(ti, type);
1161 void pushTreeInfo(TreeInfo **node_first, TreeInfo *node_new)
1163 node_new->next = *node_first;
1164 *node_first = node_new;
1167 void removeTreeInfo(TreeInfo **node_first)
1169 TreeInfo *node_old = *node_first;
1171 *node_first = node_old->next;
1172 node_old->next = NULL;
1174 freeTreeInfo(node_old);
1177 int numTreeInfo(TreeInfo *node)
1190 boolean validLevelSeries(TreeInfo *node)
1192 return (node != NULL && !node->node_group && !node->parent_link);
1195 TreeInfo *getFirstValidTreeInfoEntry(TreeInfo *node)
1200 if (node->node_group) // enter level group (step down into tree)
1201 return getFirstValidTreeInfoEntry(node->node_group);
1202 else if (node->parent_link) // skip start entry of level group
1204 if (node->next) // get first real level series entry
1205 return getFirstValidTreeInfoEntry(node->next);
1206 else // leave empty level group and go on
1207 return getFirstValidTreeInfoEntry(node->node_parent->next);
1209 else // this seems to be a regular level series
1213 TreeInfo *getTreeInfoFirstGroupEntry(TreeInfo *node)
1218 if (node->node_parent == NULL) // top level group
1219 return *node->node_top;
1220 else // sub level group
1221 return node->node_parent->node_group;
1224 int numTreeInfoInGroup(TreeInfo *node)
1226 return numTreeInfo(getTreeInfoFirstGroupEntry(node));
1229 int getPosFromTreeInfo(TreeInfo *node)
1231 TreeInfo *node_cmp = getTreeInfoFirstGroupEntry(node);
1236 if (node_cmp == node)
1240 node_cmp = node_cmp->next;
1246 TreeInfo *getTreeInfoFromPos(TreeInfo *node, int pos)
1248 TreeInfo *node_default = node;
1260 return node_default;
1263 static TreeInfo *getTreeInfoFromIdentifierExt(TreeInfo *node, char *identifier,
1264 boolean include_node_groups)
1266 if (identifier == NULL)
1271 if (node->node_group)
1273 if (include_node_groups && strEqual(identifier, node->identifier))
1276 TreeInfo *node_group = getTreeInfoFromIdentifierExt(node->node_group,
1278 include_node_groups);
1282 else if (!node->parent_link)
1284 if (strEqual(identifier, node->identifier))
1294 TreeInfo *getTreeInfoFromIdentifier(TreeInfo *node, char *identifier)
1296 return getTreeInfoFromIdentifierExt(node, identifier, FALSE);
1299 static TreeInfo *cloneTreeNode(TreeInfo **node_top, TreeInfo *node_parent,
1300 TreeInfo *node, boolean skip_sets_without_levels)
1307 if (!node->parent_link && !node->level_group &&
1308 skip_sets_without_levels && node->levels == 0)
1309 return cloneTreeNode(node_top, node_parent, node->next,
1310 skip_sets_without_levels);
1312 node_new = getTreeInfoCopy(node); // copy complete node
1314 node_new->node_top = node_top; // correct top node link
1315 node_new->node_parent = node_parent; // correct parent node link
1317 if (node->level_group)
1318 node_new->node_group = cloneTreeNode(node_top, node_new, node->node_group,
1319 skip_sets_without_levels);
1321 node_new->next = cloneTreeNode(node_top, node_parent, node->next,
1322 skip_sets_without_levels);
1327 static void cloneTree(TreeInfo **ti_new, TreeInfo *ti, boolean skip_empty_sets)
1329 TreeInfo *ti_cloned = cloneTreeNode(ti_new, NULL, ti, skip_empty_sets);
1331 *ti_new = ti_cloned;
1334 static boolean adjustTreeGraphicsForEMC(TreeInfo *node)
1336 boolean settings_changed = FALSE;
1340 boolean want_ecs = (setup.prefer_aga_graphics == FALSE);
1341 boolean want_aga = (setup.prefer_aga_graphics == TRUE);
1342 boolean has_only_ecs = (!node->graphics_set && !node->graphics_set_aga);
1343 boolean has_only_aga = (!node->graphics_set && !node->graphics_set_ecs);
1344 char *graphics_set = NULL;
1346 if (node->graphics_set_ecs && (want_ecs || has_only_ecs))
1347 graphics_set = node->graphics_set_ecs;
1349 if (node->graphics_set_aga && (want_aga || has_only_aga))
1350 graphics_set = node->graphics_set_aga;
1352 if (graphics_set && !strEqual(node->graphics_set, graphics_set))
1354 setString(&node->graphics_set, graphics_set);
1355 settings_changed = TRUE;
1358 if (node->node_group != NULL)
1359 settings_changed |= adjustTreeGraphicsForEMC(node->node_group);
1364 return settings_changed;
1367 static boolean adjustTreeSoundsForEMC(TreeInfo *node)
1369 boolean settings_changed = FALSE;
1373 boolean want_default = (setup.prefer_lowpass_sounds == FALSE);
1374 boolean want_lowpass = (setup.prefer_lowpass_sounds == TRUE);
1375 boolean has_only_default = (!node->sounds_set && !node->sounds_set_lowpass);
1376 boolean has_only_lowpass = (!node->sounds_set && !node->sounds_set_default);
1377 char *sounds_set = NULL;
1379 if (node->sounds_set_default && (want_default || has_only_default))
1380 sounds_set = node->sounds_set_default;
1382 if (node->sounds_set_lowpass && (want_lowpass || has_only_lowpass))
1383 sounds_set = node->sounds_set_lowpass;
1385 if (sounds_set && !strEqual(node->sounds_set, sounds_set))
1387 setString(&node->sounds_set, sounds_set);
1388 settings_changed = TRUE;
1391 if (node->node_group != NULL)
1392 settings_changed |= adjustTreeSoundsForEMC(node->node_group);
1397 return settings_changed;
1400 void dumpTreeInfo(TreeInfo *node, int depth)
1402 char bullet_list[] = { '-', '*', 'o' };
1406 Debug("tree", "Dumping TreeInfo:");
1410 char bullet = bullet_list[depth % ARRAY_SIZE(bullet_list)];
1412 for (i = 0; i < depth * 2; i++)
1413 DebugContinued("", " ");
1415 DebugContinued("tree", "%c '%s' ['%s] [PARENT: '%s'] %s\n",
1416 bullet, node->name, node->identifier,
1417 (node->node_parent ? node->node_parent->identifier : "-"),
1418 (node->node_group ? "[GROUP]" : ""));
1421 // use for dumping artwork info tree
1422 Debug("tree", "subdir == '%s' ['%s', '%s'] [%d])",
1423 node->subdir, node->fullpath, node->basepath, node->in_user_dir);
1426 if (node->node_group != NULL)
1427 dumpTreeInfo(node->node_group, depth + 1);
1433 void sortTreeInfoBySortFunction(TreeInfo **node_first,
1434 int (*compare_function)(const void *,
1437 int num_nodes = numTreeInfo(*node_first);
1438 TreeInfo **sort_array;
1439 TreeInfo *node = *node_first;
1445 // allocate array for sorting structure pointers
1446 sort_array = checked_calloc(num_nodes * sizeof(TreeInfo *));
1448 // writing structure pointers to sorting array
1449 while (i < num_nodes && node) // double boundary check...
1451 sort_array[i] = node;
1457 // sorting the structure pointers in the sorting array
1458 qsort(sort_array, num_nodes, sizeof(TreeInfo *),
1461 // update the linkage of list elements with the sorted node array
1462 for (i = 0; i < num_nodes - 1; i++)
1463 sort_array[i]->next = sort_array[i + 1];
1464 sort_array[num_nodes - 1]->next = NULL;
1466 // update the linkage of the main list anchor pointer
1467 *node_first = sort_array[0];
1471 // now recursively sort the level group structures
1475 if (node->node_group != NULL)
1476 sortTreeInfoBySortFunction(&node->node_group, compare_function);
1482 void sortTreeInfo(TreeInfo **node_first)
1484 sortTreeInfoBySortFunction(node_first, compareTreeInfoEntries);
1488 // ============================================================================
1489 // some stuff from "files.c"
1490 // ============================================================================
1492 #if defined(PLATFORM_WIN32)
1494 #define S_IRGRP S_IRUSR
1497 #define S_IROTH S_IRUSR
1500 #define S_IWGRP S_IWUSR
1503 #define S_IWOTH S_IWUSR
1506 #define S_IXGRP S_IXUSR
1509 #define S_IXOTH S_IXUSR
1512 #define S_IRWXG (S_IRGRP | S_IWGRP | S_IXGRP)
1517 #endif // PLATFORM_WIN32
1519 // file permissions for newly written files
1520 #define MODE_R_ALL (S_IRUSR | S_IRGRP | S_IROTH)
1521 #define MODE_W_ALL (S_IWUSR | S_IWGRP | S_IWOTH)
1522 #define MODE_X_ALL (S_IXUSR | S_IXGRP | S_IXOTH)
1524 #define MODE_W_PRIVATE (S_IWUSR)
1525 #define MODE_W_PUBLIC_FILE (S_IWUSR | S_IWGRP)
1526 #define MODE_W_PUBLIC_DIR (S_IWUSR | S_IWGRP | S_ISGID)
1528 #define DIR_PERMS_PRIVATE (MODE_R_ALL | MODE_X_ALL | MODE_W_PRIVATE)
1529 #define DIR_PERMS_PUBLIC (MODE_R_ALL | MODE_X_ALL | MODE_W_PUBLIC_DIR)
1530 #define DIR_PERMS_PUBLIC_ALL (MODE_R_ALL | MODE_X_ALL | MODE_W_ALL)
1532 #define FILE_PERMS_PRIVATE (MODE_R_ALL | MODE_W_PRIVATE)
1533 #define FILE_PERMS_PUBLIC (MODE_R_ALL | MODE_W_PUBLIC_FILE)
1534 #define FILE_PERMS_PUBLIC_ALL (MODE_R_ALL | MODE_W_ALL)
1537 char *getHomeDir(void)
1539 static char *dir = NULL;
1541 #if defined(PLATFORM_WIN32)
1544 dir = checked_malloc(MAX_PATH + 1);
1546 if (!SUCCEEDED(SHGetFolderPath(NULL, CSIDL_PERSONAL, NULL, 0, dir)))
1549 #elif defined(PLATFORM_UNIX)
1552 if ((dir = getenv("HOME")) == NULL)
1554 dir = getUnixHomeDir();
1557 dir = getStringCopy(dir);
1569 char *getCommonDataDir(void)
1571 static char *common_data_dir = NULL;
1573 #if defined(PLATFORM_WIN32)
1574 if (common_data_dir == NULL)
1576 char *dir = checked_malloc(MAX_PATH + 1);
1578 if (SUCCEEDED(SHGetFolderPath(NULL, CSIDL_COMMON_DOCUMENTS, NULL, 0, dir))
1579 && !strEqual(dir, "")) // empty for Windows 95/98
1580 common_data_dir = getPath2(dir, program.userdata_subdir);
1582 common_data_dir = options.rw_base_directory;
1585 if (common_data_dir == NULL)
1586 common_data_dir = options.rw_base_directory;
1589 return common_data_dir;
1592 char *getPersonalDataDir(void)
1594 static char *personal_data_dir = NULL;
1596 #if defined(PLATFORM_MACOSX)
1597 if (personal_data_dir == NULL)
1598 personal_data_dir = getPath2(getHomeDir(), "Documents");
1600 if (personal_data_dir == NULL)
1601 personal_data_dir = getHomeDir();
1604 return personal_data_dir;
1607 char *getMainUserGameDataDir(void)
1609 static char *main_user_data_dir = NULL;
1611 #if defined(PLATFORM_ANDROID)
1612 if (main_user_data_dir == NULL)
1613 main_user_data_dir = (char *)(SDL_AndroidGetExternalStorageState() &
1614 SDL_ANDROID_EXTERNAL_STORAGE_WRITE ?
1615 SDL_AndroidGetExternalStoragePath() :
1616 SDL_AndroidGetInternalStoragePath());
1618 if (main_user_data_dir == NULL)
1619 main_user_data_dir = getPath2(getPersonalDataDir(),
1620 program.userdata_subdir);
1623 return main_user_data_dir;
1626 char *getUserGameDataDir(void)
1629 return getMainUserGameDataDir();
1631 return getUserDir(user.nr);
1634 char *getSetupDir(void)
1636 return getUserGameDataDir();
1639 static mode_t posix_umask(mode_t mask)
1641 #if defined(PLATFORM_UNIX)
1648 static int posix_mkdir(const char *pathname, mode_t mode)
1650 #if defined(PLATFORM_WIN32)
1651 return mkdir(pathname);
1653 return mkdir(pathname, mode);
1657 static boolean posix_process_running_setgid(void)
1659 #if defined(PLATFORM_UNIX)
1660 return (getgid() != getegid());
1666 void createDirectory(char *dir, char *text, int permission_class)
1668 if (directoryExists(dir))
1671 // leave "other" permissions in umask untouched, but ensure group parts
1672 // of USERDATA_DIR_MODE are not masked
1673 mode_t dir_mode = (permission_class == PERMS_PRIVATE ?
1674 DIR_PERMS_PRIVATE : DIR_PERMS_PUBLIC);
1675 mode_t last_umask = posix_umask(0);
1676 mode_t group_umask = ~(dir_mode & S_IRWXG);
1677 int running_setgid = posix_process_running_setgid();
1679 if (permission_class == PERMS_PUBLIC)
1681 // if we're setgid, protect files against "other"
1682 // else keep umask(0) to make the dir world-writable
1685 posix_umask(last_umask & group_umask);
1687 dir_mode = DIR_PERMS_PUBLIC_ALL;
1690 if (posix_mkdir(dir, dir_mode) != 0)
1691 Warn("cannot create %s directory '%s': %s", text, dir, strerror(errno));
1693 if (permission_class == PERMS_PUBLIC && !running_setgid)
1694 chmod(dir, dir_mode);
1696 posix_umask(last_umask); // restore previous umask
1699 void InitMainUserDataDirectory(void)
1701 createDirectory(getMainUserGameDataDir(), "main user data", PERMS_PRIVATE);
1704 void InitUserDataDirectory(void)
1706 createDirectory(getMainUserGameDataDir(), "main user data", PERMS_PRIVATE);
1710 createDirectory(getUserDir(-1), "users", PERMS_PRIVATE);
1711 createDirectory(getUserDir(user.nr), "user data", PERMS_PRIVATE);
1715 void SetFilePermissions(char *filename, int permission_class)
1717 int running_setgid = posix_process_running_setgid();
1718 int perms = (permission_class == PERMS_PRIVATE ?
1719 FILE_PERMS_PRIVATE : FILE_PERMS_PUBLIC);
1721 if (permission_class == PERMS_PUBLIC && !running_setgid)
1722 perms = FILE_PERMS_PUBLIC_ALL;
1724 chmod(filename, perms);
1727 char *getCookie(char *file_type)
1729 static char cookie[MAX_COOKIE_LEN + 1];
1731 if (strlen(program.cookie_prefix) + 1 +
1732 strlen(file_type) + strlen("_FILE_VERSION_x.x") > MAX_COOKIE_LEN)
1733 return "[COOKIE ERROR]"; // should never happen
1735 sprintf(cookie, "%s_%s_FILE_VERSION_%d.%d",
1736 program.cookie_prefix, file_type,
1737 program.version_super, program.version_major);
1742 void fprintFileHeader(FILE *file, char *basename)
1744 char *prefix = "# ";
1747 fprintf_line_with_prefix(file, prefix, sep1, 77);
1748 fprintf(file, "%s%s\n", prefix, basename);
1749 fprintf_line_with_prefix(file, prefix, sep1, 77);
1750 fprintf(file, "\n");
1753 int getFileVersionFromCookieString(const char *cookie)
1755 const char *ptr_cookie1, *ptr_cookie2;
1756 const char *pattern1 = "_FILE_VERSION_";
1757 const char *pattern2 = "?.?";
1758 const int len_cookie = strlen(cookie);
1759 const int len_pattern1 = strlen(pattern1);
1760 const int len_pattern2 = strlen(pattern2);
1761 const int len_pattern = len_pattern1 + len_pattern2;
1762 int version_super, version_major;
1764 if (len_cookie <= len_pattern)
1767 ptr_cookie1 = &cookie[len_cookie - len_pattern];
1768 ptr_cookie2 = &cookie[len_cookie - len_pattern2];
1770 if (strncmp(ptr_cookie1, pattern1, len_pattern1) != 0)
1773 if (ptr_cookie2[0] < '0' || ptr_cookie2[0] > '9' ||
1774 ptr_cookie2[1] != '.' ||
1775 ptr_cookie2[2] < '0' || ptr_cookie2[2] > '9')
1778 version_super = ptr_cookie2[0] - '0';
1779 version_major = ptr_cookie2[2] - '0';
1781 return VERSION_IDENT(version_super, version_major, 0, 0);
1784 boolean checkCookieString(const char *cookie, const char *template)
1786 const char *pattern = "_FILE_VERSION_?.?";
1787 const int len_cookie = strlen(cookie);
1788 const int len_template = strlen(template);
1789 const int len_pattern = strlen(pattern);
1791 if (len_cookie != len_template)
1794 if (strncmp(cookie, template, len_cookie - len_pattern) != 0)
1801 // ----------------------------------------------------------------------------
1802 // setup file list and hash handling functions
1803 // ----------------------------------------------------------------------------
1805 char *getFormattedSetupEntry(char *token, char *value)
1808 static char entry[MAX_LINE_LEN];
1810 // if value is an empty string, just return token without value
1814 // start with the token and some spaces to format output line
1815 sprintf(entry, "%s:", token);
1816 for (i = strlen(entry); i < token_value_position; i++)
1819 // continue with the token's value
1820 strcat(entry, value);
1825 SetupFileList *newSetupFileList(char *token, char *value)
1827 SetupFileList *new = checked_malloc(sizeof(SetupFileList));
1829 new->token = getStringCopy(token);
1830 new->value = getStringCopy(value);
1837 void freeSetupFileList(SetupFileList *list)
1842 checked_free(list->token);
1843 checked_free(list->value);
1846 freeSetupFileList(list->next);
1851 char *getListEntry(SetupFileList *list, char *token)
1856 if (strEqual(list->token, token))
1859 return getListEntry(list->next, token);
1862 SetupFileList *setListEntry(SetupFileList *list, char *token, char *value)
1867 if (strEqual(list->token, token))
1869 checked_free(list->value);
1871 list->value = getStringCopy(value);
1875 else if (list->next == NULL)
1876 return (list->next = newSetupFileList(token, value));
1878 return setListEntry(list->next, token, value);
1881 SetupFileList *addListEntry(SetupFileList *list, char *token, char *value)
1886 if (list->next == NULL)
1887 return (list->next = newSetupFileList(token, value));
1889 return addListEntry(list->next, token, value);
1892 #if ENABLE_UNUSED_CODE
1894 static void printSetupFileList(SetupFileList *list)
1899 Debug("setup:printSetupFileList", "token: '%s'", list->token);
1900 Debug("setup:printSetupFileList", "value: '%s'", list->value);
1902 printSetupFileList(list->next);
1908 DEFINE_HASHTABLE_INSERT(insert_hash_entry, char, char);
1909 DEFINE_HASHTABLE_SEARCH(search_hash_entry, char, char);
1910 DEFINE_HASHTABLE_CHANGE(change_hash_entry, char, char);
1911 DEFINE_HASHTABLE_REMOVE(remove_hash_entry, char, char);
1913 #define insert_hash_entry hashtable_insert
1914 #define search_hash_entry hashtable_search
1915 #define change_hash_entry hashtable_change
1916 #define remove_hash_entry hashtable_remove
1919 unsigned int get_hash_from_key(void *key)
1924 This algorithm (k=33) was first reported by Dan Bernstein many years ago in
1925 'comp.lang.c'. Another version of this algorithm (now favored by Bernstein)
1926 uses XOR: hash(i) = hash(i - 1) * 33 ^ str[i]; the magic of number 33 (why
1927 it works better than many other constants, prime or not) has never been
1928 adequately explained.
1930 If you just want to have a good hash function, and cannot wait, djb2
1931 is one of the best string hash functions i know. It has excellent
1932 distribution and speed on many different sets of keys and table sizes.
1933 You are not likely to do better with one of the "well known" functions
1934 such as PJW, K&R, etc.
1936 Ozan (oz) Yigit [http://www.cs.yorku.ca/~oz/hash.html]
1939 char *str = (char *)key;
1940 unsigned int hash = 5381;
1943 while ((c = *str++))
1944 hash = ((hash << 5) + hash) + c; // hash * 33 + c
1949 static int keys_are_equal(void *key1, void *key2)
1951 return (strEqual((char *)key1, (char *)key2));
1954 SetupFileHash *newSetupFileHash(void)
1956 SetupFileHash *new_hash =
1957 create_hashtable(16, 0.75, get_hash_from_key, keys_are_equal);
1959 if (new_hash == NULL)
1960 Fail("create_hashtable() failed -- out of memory");
1965 void freeSetupFileHash(SetupFileHash *hash)
1970 hashtable_destroy(hash, 1); // 1 == also free values stored in hash
1973 char *getHashEntry(SetupFileHash *hash, char *token)
1978 return search_hash_entry(hash, token);
1981 void setHashEntry(SetupFileHash *hash, char *token, char *value)
1988 value_copy = getStringCopy(value);
1990 // change value; if it does not exist, insert it as new
1991 if (!change_hash_entry(hash, token, value_copy))
1992 if (!insert_hash_entry(hash, getStringCopy(token), value_copy))
1993 Fail("cannot insert into hash -- aborting");
1996 char *removeHashEntry(SetupFileHash *hash, char *token)
2001 return remove_hash_entry(hash, token);
2004 #if ENABLE_UNUSED_CODE
2006 static void printSetupFileHash(SetupFileHash *hash)
2008 BEGIN_HASH_ITERATION(hash, itr)
2010 Debug("setup:printSetupFileHash", "token: '%s'", HASH_ITERATION_TOKEN(itr));
2011 Debug("setup:printSetupFileHash", "value: '%s'", HASH_ITERATION_VALUE(itr));
2013 END_HASH_ITERATION(hash, itr)
2018 #define ALLOW_TOKEN_VALUE_SEPARATOR_BEING_WHITESPACE 1
2019 #define CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING 0
2020 #define CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH 0
2022 static boolean token_value_separator_found = FALSE;
2023 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2024 static boolean token_value_separator_warning = FALSE;
2026 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2027 static boolean token_already_exists_warning = FALSE;
2030 static boolean getTokenValueFromSetupLineExt(char *line,
2031 char **token_ptr, char **value_ptr,
2032 char *filename, char *line_raw,
2034 boolean separator_required)
2036 static char line_copy[MAX_LINE_LEN + 1], line_raw_copy[MAX_LINE_LEN + 1];
2037 char *token, *value, *line_ptr;
2039 // when externally invoked via ReadTokenValueFromLine(), copy line buffers
2040 if (line_raw == NULL)
2042 strncpy(line_copy, line, MAX_LINE_LEN);
2043 line_copy[MAX_LINE_LEN] = '\0';
2046 strcpy(line_raw_copy, line_copy);
2047 line_raw = line_raw_copy;
2050 // cut trailing comment from input line
2051 for (line_ptr = line; *line_ptr; line_ptr++)
2053 if (*line_ptr == '#')
2060 // cut trailing whitespaces from input line
2061 for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
2062 if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
2065 // ignore empty lines
2069 // cut leading whitespaces from token
2070 for (token = line; *token; token++)
2071 if (*token != ' ' && *token != '\t')
2074 // start with empty value as reliable default
2077 token_value_separator_found = FALSE;
2079 // find end of token to determine start of value
2080 for (line_ptr = token; *line_ptr; line_ptr++)
2082 // first look for an explicit token/value separator, like ':' or '='
2083 if (*line_ptr == ':' || *line_ptr == '=')
2085 *line_ptr = '\0'; // terminate token string
2086 value = line_ptr + 1; // set beginning of value
2088 token_value_separator_found = TRUE;
2094 #if ALLOW_TOKEN_VALUE_SEPARATOR_BEING_WHITESPACE
2095 // fallback: if no token/value separator found, also allow whitespaces
2096 if (!token_value_separator_found && !separator_required)
2098 for (line_ptr = token; *line_ptr; line_ptr++)
2100 if (*line_ptr == ' ' || *line_ptr == '\t')
2102 *line_ptr = '\0'; // terminate token string
2103 value = line_ptr + 1; // set beginning of value
2105 token_value_separator_found = TRUE;
2111 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2112 if (token_value_separator_found)
2114 if (!token_value_separator_warning)
2116 Debug("setup", "---");
2118 if (filename != NULL)
2120 Debug("setup", "missing token/value separator(s) in config file:");
2121 Debug("setup", "- config file: '%s'", filename);
2125 Debug("setup", "missing token/value separator(s):");
2128 token_value_separator_warning = TRUE;
2131 if (filename != NULL)
2132 Debug("setup", "- line %d: '%s'", line_nr, line_raw);
2134 Debug("setup", "- line: '%s'", line_raw);
2140 // cut trailing whitespaces from token
2141 for (line_ptr = &token[strlen(token)]; line_ptr >= token; line_ptr--)
2142 if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
2145 // cut leading whitespaces from value
2146 for (; *value; value++)
2147 if (*value != ' ' && *value != '\t')
2156 boolean getTokenValueFromSetupLine(char *line, char **token, char **value)
2158 // while the internal (old) interface does not require a token/value
2159 // separator (for downwards compatibility with existing files which
2160 // don't use them), it is mandatory for the external (new) interface
2162 return getTokenValueFromSetupLineExt(line, token, value, NULL, NULL, 0, TRUE);
2165 static boolean loadSetupFileData(void *setup_file_data, char *filename,
2166 boolean top_recursion_level, boolean is_hash)
2168 static SetupFileHash *include_filename_hash = NULL;
2169 char line[MAX_LINE_LEN], line_raw[MAX_LINE_LEN], previous_line[MAX_LINE_LEN];
2170 char *token, *value, *line_ptr;
2171 void *insert_ptr = NULL;
2172 boolean read_continued_line = FALSE;
2174 int line_nr = 0, token_count = 0, include_count = 0;
2176 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2177 token_value_separator_warning = FALSE;
2180 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2181 token_already_exists_warning = FALSE;
2184 if (!(file = openFile(filename, MODE_READ)))
2186 #if DEBUG_NO_CONFIG_FILE
2187 Debug("setup", "cannot open configuration file '%s'", filename);
2193 // use "insert pointer" to store list end for constant insertion complexity
2195 insert_ptr = setup_file_data;
2197 // on top invocation, create hash to mark included files (to prevent loops)
2198 if (top_recursion_level)
2199 include_filename_hash = newSetupFileHash();
2201 // mark this file as already included (to prevent including it again)
2202 setHashEntry(include_filename_hash, getBaseNamePtr(filename), "true");
2204 while (!checkEndOfFile(file))
2206 // read next line of input file
2207 if (!getStringFromFile(file, line, MAX_LINE_LEN))
2210 // check if line was completely read and is terminated by line break
2211 if (strlen(line) > 0 && line[strlen(line) - 1] == '\n')
2214 // cut trailing line break (this can be newline and/or carriage return)
2215 for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
2216 if ((*line_ptr == '\n' || *line_ptr == '\r') && *(line_ptr + 1) == '\0')
2219 // copy raw input line for later use (mainly debugging output)
2220 strcpy(line_raw, line);
2222 if (read_continued_line)
2224 // append new line to existing line, if there is enough space
2225 if (strlen(previous_line) + strlen(line_ptr) < MAX_LINE_LEN)
2226 strcat(previous_line, line_ptr);
2228 strcpy(line, previous_line); // copy storage buffer to line
2230 read_continued_line = FALSE;
2233 // if the last character is '\', continue at next line
2234 if (strlen(line) > 0 && line[strlen(line) - 1] == '\\')
2236 line[strlen(line) - 1] = '\0'; // cut off trailing backslash
2237 strcpy(previous_line, line); // copy line to storage buffer
2239 read_continued_line = TRUE;
2244 if (!getTokenValueFromSetupLineExt(line, &token, &value, filename,
2245 line_raw, line_nr, FALSE))
2250 if (strEqual(token, "include"))
2252 if (getHashEntry(include_filename_hash, value) == NULL)
2254 char *basepath = getBasePath(filename);
2255 char *basename = getBaseName(value);
2256 char *filename_include = getPath2(basepath, basename);
2258 loadSetupFileData(setup_file_data, filename_include, FALSE, is_hash);
2262 free(filename_include);
2268 Warn("ignoring already processed file '%s'", value);
2275 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2277 getHashEntry((SetupFileHash *)setup_file_data, token);
2279 if (old_value != NULL)
2281 if (!token_already_exists_warning)
2283 Debug("setup", "---");
2284 Debug("setup", "duplicate token(s) found in config file:");
2285 Debug("setup", "- config file: '%s'", filename);
2287 token_already_exists_warning = TRUE;
2290 Debug("setup", "- token: '%s' (in line %d)", token, line_nr);
2291 Debug("setup", " old value: '%s'", old_value);
2292 Debug("setup", " new value: '%s'", value);
2296 setHashEntry((SetupFileHash *)setup_file_data, token, value);
2300 insert_ptr = addListEntry((SetupFileList *)insert_ptr, token, value);
2310 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2311 if (token_value_separator_warning)
2312 Debug("setup", "---");
2315 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2316 if (token_already_exists_warning)
2317 Debug("setup", "---");
2320 if (token_count == 0 && include_count == 0)
2321 Warn("configuration file '%s' is empty", filename);
2323 if (top_recursion_level)
2324 freeSetupFileHash(include_filename_hash);
2329 static void saveSetupFileHash(SetupFileHash *hash, char *filename)
2333 if (!(file = fopen(filename, MODE_WRITE)))
2335 Warn("cannot write configuration file '%s'", filename);
2340 BEGIN_HASH_ITERATION(hash, itr)
2342 fprintf(file, "%s\n", getFormattedSetupEntry(HASH_ITERATION_TOKEN(itr),
2343 HASH_ITERATION_VALUE(itr)));
2345 END_HASH_ITERATION(hash, itr)
2350 SetupFileList *loadSetupFileList(char *filename)
2352 SetupFileList *setup_file_list = newSetupFileList("", "");
2353 SetupFileList *first_valid_list_entry;
2355 if (!loadSetupFileData(setup_file_list, filename, TRUE, FALSE))
2357 freeSetupFileList(setup_file_list);
2362 first_valid_list_entry = setup_file_list->next;
2364 // free empty list header
2365 setup_file_list->next = NULL;
2366 freeSetupFileList(setup_file_list);
2368 return first_valid_list_entry;
2371 SetupFileHash *loadSetupFileHash(char *filename)
2373 SetupFileHash *setup_file_hash = newSetupFileHash();
2375 if (!loadSetupFileData(setup_file_hash, filename, TRUE, TRUE))
2377 freeSetupFileHash(setup_file_hash);
2382 return setup_file_hash;
2386 // ============================================================================
2388 // ============================================================================
2390 #define TOKEN_STR_LAST_LEVEL_SERIES "last_level_series"
2391 #define TOKEN_STR_LAST_PLAYED_LEVEL "last_played_level"
2392 #define TOKEN_STR_HANDICAP_LEVEL "handicap_level"
2393 #define TOKEN_STR_LAST_USER "last_user"
2395 // level directory info
2396 #define LEVELINFO_TOKEN_IDENTIFIER 0
2397 #define LEVELINFO_TOKEN_NAME 1
2398 #define LEVELINFO_TOKEN_NAME_SORTING 2
2399 #define LEVELINFO_TOKEN_AUTHOR 3
2400 #define LEVELINFO_TOKEN_YEAR 4
2401 #define LEVELINFO_TOKEN_PROGRAM_TITLE 5
2402 #define LEVELINFO_TOKEN_PROGRAM_COPYRIGHT 6
2403 #define LEVELINFO_TOKEN_PROGRAM_COMPANY 7
2404 #define LEVELINFO_TOKEN_IMPORTED_FROM 8
2405 #define LEVELINFO_TOKEN_IMPORTED_BY 9
2406 #define LEVELINFO_TOKEN_TESTED_BY 10
2407 #define LEVELINFO_TOKEN_LEVELS 11
2408 #define LEVELINFO_TOKEN_FIRST_LEVEL 12
2409 #define LEVELINFO_TOKEN_SORT_PRIORITY 13
2410 #define LEVELINFO_TOKEN_LATEST_ENGINE 14
2411 #define LEVELINFO_TOKEN_LEVEL_GROUP 15
2412 #define LEVELINFO_TOKEN_READONLY 16
2413 #define LEVELINFO_TOKEN_GRAPHICS_SET_ECS 17
2414 #define LEVELINFO_TOKEN_GRAPHICS_SET_AGA 18
2415 #define LEVELINFO_TOKEN_GRAPHICS_SET 19
2416 #define LEVELINFO_TOKEN_SOUNDS_SET_DEFAULT 20
2417 #define LEVELINFO_TOKEN_SOUNDS_SET_LOWPASS 21
2418 #define LEVELINFO_TOKEN_SOUNDS_SET 22
2419 #define LEVELINFO_TOKEN_MUSIC_SET 23
2420 #define LEVELINFO_TOKEN_FILENAME 24
2421 #define LEVELINFO_TOKEN_FILETYPE 25
2422 #define LEVELINFO_TOKEN_SPECIAL_FLAGS 26
2423 #define LEVELINFO_TOKEN_HANDICAP 27
2424 #define LEVELINFO_TOKEN_SKIP_LEVELS 28
2425 #define LEVELINFO_TOKEN_USE_EMC_TILES 29
2427 #define NUM_LEVELINFO_TOKENS 30
2429 static LevelDirTree ldi;
2431 static struct TokenInfo levelinfo_tokens[] =
2433 // level directory info
2434 { TYPE_STRING, &ldi.identifier, "identifier" },
2435 { TYPE_STRING, &ldi.name, "name" },
2436 { TYPE_STRING, &ldi.name_sorting, "name_sorting" },
2437 { TYPE_STRING, &ldi.author, "author" },
2438 { TYPE_STRING, &ldi.year, "year" },
2439 { TYPE_STRING, &ldi.program_title, "program_title" },
2440 { TYPE_STRING, &ldi.program_copyright, "program_copyright" },
2441 { TYPE_STRING, &ldi.program_company, "program_company" },
2442 { TYPE_STRING, &ldi.imported_from, "imported_from" },
2443 { TYPE_STRING, &ldi.imported_by, "imported_by" },
2444 { TYPE_STRING, &ldi.tested_by, "tested_by" },
2445 { TYPE_INTEGER, &ldi.levels, "levels" },
2446 { TYPE_INTEGER, &ldi.first_level, "first_level" },
2447 { TYPE_INTEGER, &ldi.sort_priority, "sort_priority" },
2448 { TYPE_BOOLEAN, &ldi.latest_engine, "latest_engine" },
2449 { TYPE_BOOLEAN, &ldi.level_group, "level_group" },
2450 { TYPE_BOOLEAN, &ldi.readonly, "readonly" },
2451 { TYPE_STRING, &ldi.graphics_set_ecs, "graphics_set.ecs" },
2452 { TYPE_STRING, &ldi.graphics_set_aga, "graphics_set.aga" },
2453 { TYPE_STRING, &ldi.graphics_set, "graphics_set" },
2454 { TYPE_STRING, &ldi.sounds_set_default,"sounds_set.default" },
2455 { TYPE_STRING, &ldi.sounds_set_lowpass,"sounds_set.lowpass" },
2456 { TYPE_STRING, &ldi.sounds_set, "sounds_set" },
2457 { TYPE_STRING, &ldi.music_set, "music_set" },
2458 { TYPE_STRING, &ldi.level_filename, "filename" },
2459 { TYPE_STRING, &ldi.level_filetype, "filetype" },
2460 { TYPE_STRING, &ldi.special_flags, "special_flags" },
2461 { TYPE_BOOLEAN, &ldi.handicap, "handicap" },
2462 { TYPE_BOOLEAN, &ldi.skip_levels, "skip_levels" },
2463 { TYPE_BOOLEAN, &ldi.use_emc_tiles, "use_emc_tiles" }
2466 static struct TokenInfo artworkinfo_tokens[] =
2468 // artwork directory info
2469 { TYPE_STRING, &ldi.identifier, "identifier" },
2470 { TYPE_STRING, &ldi.subdir, "subdir" },
2471 { TYPE_STRING, &ldi.name, "name" },
2472 { TYPE_STRING, &ldi.name_sorting, "name_sorting" },
2473 { TYPE_STRING, &ldi.author, "author" },
2474 { TYPE_STRING, &ldi.program_title, "program_title" },
2475 { TYPE_STRING, &ldi.program_copyright, "program_copyright" },
2476 { TYPE_STRING, &ldi.program_company, "program_company" },
2477 { TYPE_INTEGER, &ldi.sort_priority, "sort_priority" },
2478 { TYPE_STRING, &ldi.basepath, "basepath" },
2479 { TYPE_STRING, &ldi.fullpath, "fullpath" },
2480 { TYPE_BOOLEAN, &ldi.in_user_dir, "in_user_dir" },
2481 { TYPE_INTEGER, &ldi.color, "color" },
2482 { TYPE_STRING, &ldi.class_desc, "class_desc" },
2487 static char *optional_tokens[] =
2490 "program_copyright",
2496 static void setTreeInfoToDefaults(TreeInfo *ti, int type)
2500 ti->node_top = (ti->type == TREE_TYPE_LEVEL_DIR ? &leveldir_first :
2501 ti->type == TREE_TYPE_GRAPHICS_DIR ? &artwork.gfx_first :
2502 ti->type == TREE_TYPE_SOUNDS_DIR ? &artwork.snd_first :
2503 ti->type == TREE_TYPE_MUSIC_DIR ? &artwork.mus_first :
2506 ti->node_parent = NULL;
2507 ti->node_group = NULL;
2514 ti->fullpath = NULL;
2515 ti->basepath = NULL;
2516 ti->identifier = NULL;
2517 ti->name = getStringCopy(ANONYMOUS_NAME);
2518 ti->name_sorting = NULL;
2519 ti->author = getStringCopy(ANONYMOUS_NAME);
2522 ti->program_title = NULL;
2523 ti->program_copyright = NULL;
2524 ti->program_company = NULL;
2526 ti->sort_priority = LEVELCLASS_UNDEFINED; // default: least priority
2527 ti->latest_engine = FALSE; // default: get from level
2528 ti->parent_link = FALSE;
2529 ti->in_user_dir = FALSE;
2530 ti->user_defined = FALSE;
2532 ti->class_desc = NULL;
2534 ti->infotext = getStringCopy(TREE_INFOTEXT(ti->type));
2536 if (ti->type == TREE_TYPE_LEVEL_DIR)
2538 ti->imported_from = NULL;
2539 ti->imported_by = NULL;
2540 ti->tested_by = NULL;
2542 ti->graphics_set_ecs = NULL;
2543 ti->graphics_set_aga = NULL;
2544 ti->graphics_set = NULL;
2545 ti->sounds_set_default = NULL;
2546 ti->sounds_set_lowpass = NULL;
2547 ti->sounds_set = NULL;
2548 ti->music_set = NULL;
2549 ti->graphics_path = getStringCopy(UNDEFINED_FILENAME);
2550 ti->sounds_path = getStringCopy(UNDEFINED_FILENAME);
2551 ti->music_path = getStringCopy(UNDEFINED_FILENAME);
2553 ti->level_filename = NULL;
2554 ti->level_filetype = NULL;
2556 ti->special_flags = NULL;
2559 ti->first_level = 0;
2561 ti->level_group = FALSE;
2562 ti->handicap_level = 0;
2563 ti->readonly = TRUE;
2564 ti->handicap = TRUE;
2565 ti->skip_levels = FALSE;
2567 ti->use_emc_tiles = FALSE;
2571 static void setTreeInfoToDefaultsFromParent(TreeInfo *ti, TreeInfo *parent)
2575 Warn("setTreeInfoToDefaultsFromParent(): parent == NULL");
2577 setTreeInfoToDefaults(ti, TREE_TYPE_UNDEFINED);
2582 // copy all values from the parent structure
2584 ti->type = parent->type;
2586 ti->node_top = parent->node_top;
2587 ti->node_parent = parent;
2588 ti->node_group = NULL;
2595 ti->fullpath = NULL;
2596 ti->basepath = NULL;
2597 ti->identifier = NULL;
2598 ti->name = getStringCopy(ANONYMOUS_NAME);
2599 ti->name_sorting = NULL;
2600 ti->author = getStringCopy(parent->author);
2601 ti->year = getStringCopy(parent->year);
2603 ti->program_title = getStringCopy(parent->program_title);
2604 ti->program_copyright = getStringCopy(parent->program_copyright);
2605 ti->program_company = getStringCopy(parent->program_company);
2607 ti->sort_priority = parent->sort_priority;
2608 ti->latest_engine = parent->latest_engine;
2609 ti->parent_link = FALSE;
2610 ti->in_user_dir = parent->in_user_dir;
2611 ti->user_defined = parent->user_defined;
2612 ti->color = parent->color;
2613 ti->class_desc = getStringCopy(parent->class_desc);
2615 ti->infotext = getStringCopy(parent->infotext);
2617 if (ti->type == TREE_TYPE_LEVEL_DIR)
2619 ti->imported_from = getStringCopy(parent->imported_from);
2620 ti->imported_by = getStringCopy(parent->imported_by);
2621 ti->tested_by = getStringCopy(parent->tested_by);
2623 ti->graphics_set_ecs = getStringCopy(parent->graphics_set_ecs);
2624 ti->graphics_set_aga = getStringCopy(parent->graphics_set_aga);
2625 ti->graphics_set = getStringCopy(parent->graphics_set);
2626 ti->sounds_set_default = getStringCopy(parent->sounds_set_default);
2627 ti->sounds_set_lowpass = getStringCopy(parent->sounds_set_lowpass);
2628 ti->sounds_set = getStringCopy(parent->sounds_set);
2629 ti->music_set = getStringCopy(parent->music_set);
2630 ti->graphics_path = getStringCopy(UNDEFINED_FILENAME);
2631 ti->sounds_path = getStringCopy(UNDEFINED_FILENAME);
2632 ti->music_path = getStringCopy(UNDEFINED_FILENAME);
2634 ti->level_filename = getStringCopy(parent->level_filename);
2635 ti->level_filetype = getStringCopy(parent->level_filetype);
2637 ti->special_flags = getStringCopy(parent->special_flags);
2639 ti->levels = parent->levels;
2640 ti->first_level = parent->first_level;
2641 ti->last_level = parent->last_level;
2642 ti->level_group = FALSE;
2643 ti->handicap_level = parent->handicap_level;
2644 ti->readonly = parent->readonly;
2645 ti->handicap = parent->handicap;
2646 ti->skip_levels = parent->skip_levels;
2648 ti->use_emc_tiles = parent->use_emc_tiles;
2652 static TreeInfo *getTreeInfoCopy(TreeInfo *ti)
2654 TreeInfo *ti_copy = newTreeInfo();
2656 // copy all values from the original structure
2658 ti_copy->type = ti->type;
2660 ti_copy->node_top = ti->node_top;
2661 ti_copy->node_parent = ti->node_parent;
2662 ti_copy->node_group = ti->node_group;
2663 ti_copy->next = ti->next;
2665 ti_copy->cl_first = ti->cl_first;
2666 ti_copy->cl_cursor = ti->cl_cursor;
2668 ti_copy->subdir = getStringCopy(ti->subdir);
2669 ti_copy->fullpath = getStringCopy(ti->fullpath);
2670 ti_copy->basepath = getStringCopy(ti->basepath);
2671 ti_copy->identifier = getStringCopy(ti->identifier);
2672 ti_copy->name = getStringCopy(ti->name);
2673 ti_copy->name_sorting = getStringCopy(ti->name_sorting);
2674 ti_copy->author = getStringCopy(ti->author);
2675 ti_copy->year = getStringCopy(ti->year);
2677 ti_copy->program_title = getStringCopy(ti->program_title);
2678 ti_copy->program_copyright = getStringCopy(ti->program_copyright);
2679 ti_copy->program_company = getStringCopy(ti->program_company);
2681 ti_copy->imported_from = getStringCopy(ti->imported_from);
2682 ti_copy->imported_by = getStringCopy(ti->imported_by);
2683 ti_copy->tested_by = getStringCopy(ti->tested_by);
2685 ti_copy->graphics_set_ecs = getStringCopy(ti->graphics_set_ecs);
2686 ti_copy->graphics_set_aga = getStringCopy(ti->graphics_set_aga);
2687 ti_copy->graphics_set = getStringCopy(ti->graphics_set);
2688 ti_copy->sounds_set_default = getStringCopy(ti->sounds_set_default);
2689 ti_copy->sounds_set_lowpass = getStringCopy(ti->sounds_set_lowpass);
2690 ti_copy->sounds_set = getStringCopy(ti->sounds_set);
2691 ti_copy->music_set = getStringCopy(ti->music_set);
2692 ti_copy->graphics_path = getStringCopy(ti->graphics_path);
2693 ti_copy->sounds_path = getStringCopy(ti->sounds_path);
2694 ti_copy->music_path = getStringCopy(ti->music_path);
2696 ti_copy->level_filename = getStringCopy(ti->level_filename);
2697 ti_copy->level_filetype = getStringCopy(ti->level_filetype);
2699 ti_copy->special_flags = getStringCopy(ti->special_flags);
2701 ti_copy->levels = ti->levels;
2702 ti_copy->first_level = ti->first_level;
2703 ti_copy->last_level = ti->last_level;
2704 ti_copy->sort_priority = ti->sort_priority;
2706 ti_copy->latest_engine = ti->latest_engine;
2708 ti_copy->level_group = ti->level_group;
2709 ti_copy->parent_link = ti->parent_link;
2710 ti_copy->in_user_dir = ti->in_user_dir;
2711 ti_copy->user_defined = ti->user_defined;
2712 ti_copy->readonly = ti->readonly;
2713 ti_copy->handicap = ti->handicap;
2714 ti_copy->skip_levels = ti->skip_levels;
2716 ti_copy->use_emc_tiles = ti->use_emc_tiles;
2718 ti_copy->color = ti->color;
2719 ti_copy->class_desc = getStringCopy(ti->class_desc);
2720 ti_copy->handicap_level = ti->handicap_level;
2722 ti_copy->infotext = getStringCopy(ti->infotext);
2727 void freeTreeInfo(TreeInfo *ti)
2732 checked_free(ti->subdir);
2733 checked_free(ti->fullpath);
2734 checked_free(ti->basepath);
2735 checked_free(ti->identifier);
2737 checked_free(ti->name);
2738 checked_free(ti->name_sorting);
2739 checked_free(ti->author);
2740 checked_free(ti->year);
2742 checked_free(ti->program_title);
2743 checked_free(ti->program_copyright);
2744 checked_free(ti->program_company);
2746 checked_free(ti->class_desc);
2748 checked_free(ti->infotext);
2750 if (ti->type == TREE_TYPE_LEVEL_DIR)
2752 checked_free(ti->imported_from);
2753 checked_free(ti->imported_by);
2754 checked_free(ti->tested_by);
2756 checked_free(ti->graphics_set_ecs);
2757 checked_free(ti->graphics_set_aga);
2758 checked_free(ti->graphics_set);
2759 checked_free(ti->sounds_set_default);
2760 checked_free(ti->sounds_set_lowpass);
2761 checked_free(ti->sounds_set);
2762 checked_free(ti->music_set);
2764 checked_free(ti->graphics_path);
2765 checked_free(ti->sounds_path);
2766 checked_free(ti->music_path);
2768 checked_free(ti->level_filename);
2769 checked_free(ti->level_filetype);
2771 checked_free(ti->special_flags);
2774 // recursively free child node
2776 freeTreeInfo(ti->node_group);
2778 // recursively free next node
2780 freeTreeInfo(ti->next);
2785 void setSetupInfo(struct TokenInfo *token_info,
2786 int token_nr, char *token_value)
2788 int token_type = token_info[token_nr].type;
2789 void *setup_value = token_info[token_nr].value;
2791 if (token_value == NULL)
2794 // set setup field to corresponding token value
2799 *(boolean *)setup_value = get_boolean_from_string(token_value);
2803 *(int *)setup_value = get_switch3_from_string(token_value);
2807 *(Key *)setup_value = getKeyFromKeyName(token_value);
2811 *(Key *)setup_value = getKeyFromX11KeyName(token_value);
2815 *(int *)setup_value = get_integer_from_string(token_value);
2819 checked_free(*(char **)setup_value);
2820 *(char **)setup_value = getStringCopy(token_value);
2824 *(int *)setup_value = get_player_nr_from_string(token_value);
2832 static int compareTreeInfoEntries(const void *object1, const void *object2)
2834 const TreeInfo *entry1 = *((TreeInfo **)object1);
2835 const TreeInfo *entry2 = *((TreeInfo **)object2);
2836 int class_sorting1 = 0, class_sorting2 = 0;
2839 if (entry1->type == TREE_TYPE_LEVEL_DIR)
2841 class_sorting1 = LEVELSORTING(entry1);
2842 class_sorting2 = LEVELSORTING(entry2);
2844 else if (entry1->type == TREE_TYPE_GRAPHICS_DIR ||
2845 entry1->type == TREE_TYPE_SOUNDS_DIR ||
2846 entry1->type == TREE_TYPE_MUSIC_DIR)
2848 class_sorting1 = ARTWORKSORTING(entry1);
2849 class_sorting2 = ARTWORKSORTING(entry2);
2852 if (entry1->parent_link || entry2->parent_link)
2853 compare_result = (entry1->parent_link ? -1 : +1);
2854 else if (entry1->sort_priority == entry2->sort_priority)
2856 char *name1 = getStringToLower(entry1->name_sorting);
2857 char *name2 = getStringToLower(entry2->name_sorting);
2859 compare_result = strcmp(name1, name2);
2864 else if (class_sorting1 == class_sorting2)
2865 compare_result = entry1->sort_priority - entry2->sort_priority;
2867 compare_result = class_sorting1 - class_sorting2;
2869 return compare_result;
2872 static TreeInfo *createParentTreeInfoNode(TreeInfo *node_parent)
2876 if (node_parent == NULL)
2879 ti_new = newTreeInfo();
2880 setTreeInfoToDefaults(ti_new, node_parent->type);
2882 ti_new->node_parent = node_parent;
2883 ti_new->parent_link = TRUE;
2885 setString(&ti_new->identifier, node_parent->identifier);
2886 setString(&ti_new->name, BACKLINK_TEXT_PARENT);
2887 setString(&ti_new->name_sorting, ti_new->name);
2889 setString(&ti_new->subdir, STRING_PARENT_DIRECTORY);
2890 setString(&ti_new->fullpath, node_parent->fullpath);
2892 ti_new->sort_priority = node_parent->sort_priority;
2893 ti_new->latest_engine = node_parent->latest_engine;
2895 setString(&ti_new->class_desc, getLevelClassDescription(ti_new));
2897 pushTreeInfo(&node_parent->node_group, ti_new);
2902 static TreeInfo *createTopTreeInfoNode(TreeInfo *node_first)
2904 if (node_first == NULL)
2907 TreeInfo *ti_new = newTreeInfo();
2908 int type = node_first->type;
2910 setTreeInfoToDefaults(ti_new, type);
2912 ti_new->node_parent = NULL;
2913 ti_new->parent_link = FALSE;
2915 setString(&ti_new->identifier, node_first->identifier);
2916 setString(&ti_new->name, TREE_INFOTEXT(type));
2917 setString(&ti_new->name_sorting, ti_new->name);
2919 setString(&ti_new->subdir, STRING_TOP_DIRECTORY);
2920 setString(&ti_new->fullpath, ".");
2922 ti_new->sort_priority = node_first->sort_priority;;
2923 ti_new->latest_engine = node_first->latest_engine;
2925 setString(&ti_new->class_desc, TREE_INFOTEXT(type));
2927 ti_new->node_group = node_first;
2928 ti_new->level_group = TRUE;
2930 TreeInfo *ti_new2 = createParentTreeInfoNode(ti_new);
2932 setString(&ti_new2->name, TREE_BACKLINK_TEXT(type));
2933 setString(&ti_new2->name_sorting, ti_new2->name);
2938 static void setTreeInfoParentNodes(TreeInfo *node, TreeInfo *node_parent)
2942 if (node->node_group)
2943 setTreeInfoParentNodes(node->node_group, node);
2945 node->node_parent = node_parent;
2952 // ----------------------------------------------------------------------------
2953 // functions for handling level and custom artwork info cache
2954 // ----------------------------------------------------------------------------
2956 static void LoadArtworkInfoCache(void)
2958 InitCacheDirectory();
2960 if (artworkinfo_cache_old == NULL)
2962 char *filename = getPath2(getCacheDir(), ARTWORKINFO_CACHE_FILE);
2964 // try to load artwork info hash from already existing cache file
2965 artworkinfo_cache_old = loadSetupFileHash(filename);
2967 // if no artwork info cache file was found, start with empty hash
2968 if (artworkinfo_cache_old == NULL)
2969 artworkinfo_cache_old = newSetupFileHash();
2974 if (artworkinfo_cache_new == NULL)
2975 artworkinfo_cache_new = newSetupFileHash();
2977 update_artworkinfo_cache = FALSE;
2980 static void SaveArtworkInfoCache(void)
2982 if (!update_artworkinfo_cache)
2985 char *filename = getPath2(getCacheDir(), ARTWORKINFO_CACHE_FILE);
2987 InitCacheDirectory();
2989 saveSetupFileHash(artworkinfo_cache_new, filename);
2994 static char *getCacheTokenPrefix(char *prefix1, char *prefix2)
2996 static char *prefix = NULL;
2998 checked_free(prefix);
3000 prefix = getStringCat2WithSeparator(prefix1, prefix2, ".");
3005 // (identical to above function, but separate string buffer needed -- nasty)
3006 static char *getCacheToken(char *prefix, char *suffix)
3008 static char *token = NULL;
3010 checked_free(token);
3012 token = getStringCat2WithSeparator(prefix, suffix, ".");
3017 static char *getFileTimestampString(char *filename)
3019 return getStringCopy(i_to_a(getFileTimestampEpochSeconds(filename)));
3022 static boolean modifiedFileTimestamp(char *filename, char *timestamp_string)
3024 struct stat file_status;
3026 if (timestamp_string == NULL)
3029 if (stat(filename, &file_status) != 0) // cannot stat file
3032 return (file_status.st_mtime != atoi(timestamp_string));
3035 static TreeInfo *getArtworkInfoCacheEntry(LevelDirTree *level_node, int type)
3037 char *identifier = level_node->subdir;
3038 char *type_string = ARTWORK_DIRECTORY(type);
3039 char *token_prefix = getCacheTokenPrefix(type_string, identifier);
3040 char *token_main = getCacheToken(token_prefix, "CACHED");
3041 char *cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
3042 boolean cached = (cache_entry != NULL && strEqual(cache_entry, "true"));
3043 TreeInfo *artwork_info = NULL;
3045 if (!use_artworkinfo_cache)
3048 if (optional_tokens_hash == NULL)
3052 // create hash from list of optional tokens (for quick access)
3053 optional_tokens_hash = newSetupFileHash();
3054 for (i = 0; optional_tokens[i] != NULL; i++)
3055 setHashEntry(optional_tokens_hash, optional_tokens[i], "");
3062 artwork_info = newTreeInfo();
3063 setTreeInfoToDefaults(artwork_info, type);
3065 // set all structure fields according to the token/value pairs
3066 ldi = *artwork_info;
3067 for (i = 0; artworkinfo_tokens[i].type != -1; i++)
3069 char *token_suffix = artworkinfo_tokens[i].text;
3070 char *token = getCacheToken(token_prefix, token_suffix);
3071 char *value = getHashEntry(artworkinfo_cache_old, token);
3073 (getHashEntry(optional_tokens_hash, token_suffix) != NULL);
3075 setSetupInfo(artworkinfo_tokens, i, value);
3077 // check if cache entry for this item is mandatory, but missing
3078 if (value == NULL && !optional)
3080 Warn("missing cache entry '%s'", token);
3086 *artwork_info = ldi;
3091 char *filename_levelinfo = getPath2(getLevelDirFromTreeInfo(level_node),
3092 LEVELINFO_FILENAME);
3093 char *filename_artworkinfo = getPath2(getSetupArtworkDir(artwork_info),
3094 ARTWORKINFO_FILENAME(type));
3096 // check if corresponding "levelinfo.conf" file has changed
3097 token_main = getCacheToken(token_prefix, "TIMESTAMP_LEVELINFO");
3098 cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
3100 if (modifiedFileTimestamp(filename_levelinfo, cache_entry))
3103 // check if corresponding "<artworkinfo>.conf" file has changed
3104 token_main = getCacheToken(token_prefix, "TIMESTAMP_ARTWORKINFO");
3105 cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
3107 if (modifiedFileTimestamp(filename_artworkinfo, cache_entry))
3110 checked_free(filename_levelinfo);
3111 checked_free(filename_artworkinfo);
3114 if (!cached && artwork_info != NULL)
3116 freeTreeInfo(artwork_info);
3121 return artwork_info;
3124 static void setArtworkInfoCacheEntry(TreeInfo *artwork_info,
3125 LevelDirTree *level_node, int type)
3127 char *identifier = level_node->subdir;
3128 char *type_string = ARTWORK_DIRECTORY(type);
3129 char *token_prefix = getCacheTokenPrefix(type_string, identifier);
3130 char *token_main = getCacheToken(token_prefix, "CACHED");
3131 boolean set_cache_timestamps = TRUE;
3134 setHashEntry(artworkinfo_cache_new, token_main, "true");
3136 if (set_cache_timestamps)
3138 char *filename_levelinfo = getPath2(getLevelDirFromTreeInfo(level_node),
3139 LEVELINFO_FILENAME);
3140 char *filename_artworkinfo = getPath2(getSetupArtworkDir(artwork_info),
3141 ARTWORKINFO_FILENAME(type));
3142 char *timestamp_levelinfo = getFileTimestampString(filename_levelinfo);
3143 char *timestamp_artworkinfo = getFileTimestampString(filename_artworkinfo);
3145 token_main = getCacheToken(token_prefix, "TIMESTAMP_LEVELINFO");
3146 setHashEntry(artworkinfo_cache_new, token_main, timestamp_levelinfo);
3148 token_main = getCacheToken(token_prefix, "TIMESTAMP_ARTWORKINFO");
3149 setHashEntry(artworkinfo_cache_new, token_main, timestamp_artworkinfo);
3151 checked_free(filename_levelinfo);
3152 checked_free(filename_artworkinfo);
3153 checked_free(timestamp_levelinfo);
3154 checked_free(timestamp_artworkinfo);
3157 ldi = *artwork_info;
3158 for (i = 0; artworkinfo_tokens[i].type != -1; i++)
3160 char *token = getCacheToken(token_prefix, artworkinfo_tokens[i].text);
3161 char *value = getSetupValue(artworkinfo_tokens[i].type,
3162 artworkinfo_tokens[i].value);
3164 setHashEntry(artworkinfo_cache_new, token, value);
3169 // ----------------------------------------------------------------------------
3170 // functions for loading level info and custom artwork info
3171 // ----------------------------------------------------------------------------
3173 int GetZipFileTreeType(char *zip_filename)
3175 static char *top_dir_path = NULL;
3176 static char *top_dir_conf_filename[NUM_BASE_TREE_TYPES] = { NULL };
3177 static char *conf_basename[NUM_BASE_TREE_TYPES] =
3179 GRAPHICSINFO_FILENAME,
3180 SOUNDSINFO_FILENAME,
3186 checked_free(top_dir_path);
3187 top_dir_path = NULL;
3189 for (j = 0; j < NUM_BASE_TREE_TYPES; j++)
3191 checked_free(top_dir_conf_filename[j]);
3192 top_dir_conf_filename[j] = NULL;
3195 char **zip_entries = zip_list(zip_filename);
3197 // check if zip file successfully opened
3198 if (zip_entries == NULL || zip_entries[0] == NULL)
3199 return TREE_TYPE_UNDEFINED;
3201 // first zip file entry is expected to be top level directory
3202 char *top_dir = zip_entries[0];
3204 // check if valid top level directory found in zip file
3205 if (!strSuffix(top_dir, "/"))
3206 return TREE_TYPE_UNDEFINED;
3208 // get filenames of valid configuration files in top level directory
3209 for (j = 0; j < NUM_BASE_TREE_TYPES; j++)
3210 top_dir_conf_filename[j] = getStringCat2(top_dir, conf_basename[j]);
3212 int tree_type = TREE_TYPE_UNDEFINED;
3215 while (zip_entries[e] != NULL)
3217 // check if every zip file entry is below top level directory
3218 if (!strPrefix(zip_entries[e], top_dir))
3219 return TREE_TYPE_UNDEFINED;
3221 // check if this zip file entry is a valid configuration filename
3222 for (j = 0; j < NUM_BASE_TREE_TYPES; j++)
3224 if (strEqual(zip_entries[e], top_dir_conf_filename[j]))
3226 // only exactly one valid configuration file allowed
3227 if (tree_type != TREE_TYPE_UNDEFINED)
3228 return TREE_TYPE_UNDEFINED;
3240 static boolean CheckZipFileForDirectory(char *zip_filename, char *directory,
3243 static char *top_dir_path = NULL;
3244 static char *top_dir_conf_filename = NULL;
3246 checked_free(top_dir_path);
3247 checked_free(top_dir_conf_filename);
3249 top_dir_path = NULL;
3250 top_dir_conf_filename = NULL;
3252 char *conf_basename = (tree_type == TREE_TYPE_LEVEL_DIR ? LEVELINFO_FILENAME :
3253 ARTWORKINFO_FILENAME(tree_type));
3255 // check if valid configuration filename determined
3256 if (conf_basename == NULL || strEqual(conf_basename, ""))
3259 char **zip_entries = zip_list(zip_filename);
3261 // check if zip file successfully opened
3262 if (zip_entries == NULL || zip_entries[0] == NULL)
3265 // first zip file entry is expected to be top level directory
3266 char *top_dir = zip_entries[0];
3268 // check if valid top level directory found in zip file
3269 if (!strSuffix(top_dir, "/"))
3272 // get path of extracted top level directory
3273 top_dir_path = getPath2(directory, top_dir);
3275 // remove trailing directory separator from top level directory path
3276 // (required to be able to check for file and directory in next step)
3277 top_dir_path[strlen(top_dir_path) - 1] = '\0';
3279 // check if zip file's top level directory already exists in target directory
3280 if (fileExists(top_dir_path)) // (checks for file and directory)
3283 // get filename of configuration file in top level directory
3284 top_dir_conf_filename = getStringCat2(top_dir, conf_basename);
3286 boolean found_top_dir_conf_filename = FALSE;
3289 while (zip_entries[i] != NULL)
3291 // check if every zip file entry is below top level directory
3292 if (!strPrefix(zip_entries[i], top_dir))
3295 // check if this zip file entry is the configuration filename
3296 if (strEqual(zip_entries[i], top_dir_conf_filename))
3297 found_top_dir_conf_filename = TRUE;
3302 // check if valid configuration filename was found in zip file
3303 if (!found_top_dir_conf_filename)
3309 char *ExtractZipFileIntoDirectory(char *zip_filename, char *directory,
3312 boolean zip_file_valid = CheckZipFileForDirectory(zip_filename, directory,
3315 if (!zip_file_valid)
3317 Warn("zip file '%s' rejected!", zip_filename);
3322 char **zip_entries = zip_extract(zip_filename, directory);
3324 if (zip_entries == NULL)
3326 Warn("zip file '%s' could not be extracted!", zip_filename);
3331 Info("zip file '%s' successfully extracted!", zip_filename);
3333 // first zip file entry contains top level directory
3334 char *top_dir = zip_entries[0];
3336 // remove trailing directory separator from top level directory
3337 top_dir[strlen(top_dir) - 1] = '\0';
3342 static void ProcessZipFilesInDirectory(char *directory, int tree_type)
3345 DirectoryEntry *dir_entry;
3347 if ((dir = openDirectory(directory)) == NULL)
3349 // display error if directory is main "options.graphics_directory" etc.
3350 if (tree_type == TREE_TYPE_LEVEL_DIR ||
3351 directory == OPTIONS_ARTWORK_DIRECTORY(tree_type))
3352 Warn("cannot read directory '%s'", directory);
3357 while ((dir_entry = readDirectory(dir)) != NULL) // loop all entries
3359 // skip non-zip files (and also directories with zip extension)
3360 if (!strSuffixLower(dir_entry->basename, ".zip") || dir_entry->is_directory)
3363 char *zip_filename = getPath2(directory, dir_entry->basename);
3364 char *zip_filename_extracted = getStringCat2(zip_filename, ".extracted");
3365 char *zip_filename_rejected = getStringCat2(zip_filename, ".rejected");
3367 // check if zip file hasn't already been extracted or rejected
3368 if (!fileExists(zip_filename_extracted) &&
3369 !fileExists(zip_filename_rejected))
3371 char *top_dir = ExtractZipFileIntoDirectory(zip_filename, directory,
3373 char *marker_filename = (top_dir != NULL ? zip_filename_extracted :
3374 zip_filename_rejected);
3377 // create empty file to mark zip file as extracted or rejected
3378 if ((marker_file = fopen(marker_filename, MODE_WRITE)))
3379 fclose(marker_file);
3382 free(zip_filename_extracted);
3383 free(zip_filename_rejected);
3387 closeDirectory(dir);
3390 // forward declaration for recursive call by "LoadLevelInfoFromLevelDir()"
3391 static void LoadLevelInfoFromLevelDir(TreeInfo **, TreeInfo *, char *);
3393 static boolean LoadLevelInfoFromLevelConf(TreeInfo **node_first,
3394 TreeInfo *node_parent,
3395 char *level_directory,
3396 char *directory_name)
3398 char *directory_path = getPath2(level_directory, directory_name);
3399 char *filename = getPath2(directory_path, LEVELINFO_FILENAME);
3400 SetupFileHash *setup_file_hash;
3401 LevelDirTree *leveldir_new = NULL;
3404 // unless debugging, silently ignore directories without "levelinfo.conf"
3405 if (!options.debug && !fileExists(filename))
3407 free(directory_path);
3413 setup_file_hash = loadSetupFileHash(filename);
3415 if (setup_file_hash == NULL)
3417 #if DEBUG_NO_CONFIG_FILE
3418 Debug("setup", "ignoring level directory '%s'", directory_path);
3421 free(directory_path);
3427 leveldir_new = newTreeInfo();
3430 setTreeInfoToDefaultsFromParent(leveldir_new, node_parent);
3432 setTreeInfoToDefaults(leveldir_new, TREE_TYPE_LEVEL_DIR);
3434 leveldir_new->subdir = getStringCopy(directory_name);
3436 // set all structure fields according to the token/value pairs
3437 ldi = *leveldir_new;
3438 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
3439 setSetupInfo(levelinfo_tokens, i,
3440 getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
3441 *leveldir_new = ldi;
3443 if (strEqual(leveldir_new->name, ANONYMOUS_NAME))
3444 setString(&leveldir_new->name, leveldir_new->subdir);
3446 if (leveldir_new->identifier == NULL)
3447 leveldir_new->identifier = getStringCopy(leveldir_new->subdir);
3449 if (leveldir_new->name_sorting == NULL)
3450 leveldir_new->name_sorting = getStringCopy(leveldir_new->name);
3452 if (node_parent == NULL) // top level group
3454 leveldir_new->basepath = getStringCopy(level_directory);
3455 leveldir_new->fullpath = getStringCopy(leveldir_new->subdir);
3457 else // sub level group
3459 leveldir_new->basepath = getStringCopy(node_parent->basepath);
3460 leveldir_new->fullpath = getPath2(node_parent->fullpath, directory_name);
3463 leveldir_new->last_level =
3464 leveldir_new->first_level + leveldir_new->levels - 1;
3466 leveldir_new->in_user_dir =
3467 (!strEqual(leveldir_new->basepath, options.level_directory));
3469 // adjust some settings if user's private level directory was detected
3470 if (leveldir_new->sort_priority == LEVELCLASS_UNDEFINED &&
3471 leveldir_new->in_user_dir &&
3472 (strEqual(leveldir_new->subdir, getLoginName()) ||
3473 strEqual(leveldir_new->name, getLoginName()) ||
3474 strEqual(leveldir_new->author, getRealName())))
3476 leveldir_new->sort_priority = LEVELCLASS_PRIVATE_START;
3477 leveldir_new->readonly = FALSE;
3480 leveldir_new->user_defined =
3481 (leveldir_new->in_user_dir && IS_LEVELCLASS_PRIVATE(leveldir_new));
3483 leveldir_new->color = LEVELCOLOR(leveldir_new);
3485 setString(&leveldir_new->class_desc, getLevelClassDescription(leveldir_new));
3487 leveldir_new->handicap_level = // set handicap to default value
3488 (leveldir_new->user_defined || !leveldir_new->handicap ?
3489 leveldir_new->last_level : leveldir_new->first_level);
3491 DrawInitText(leveldir_new->name, 150, FC_YELLOW);
3493 pushTreeInfo(node_first, leveldir_new);
3495 freeSetupFileHash(setup_file_hash);
3497 if (leveldir_new->level_group)
3499 // create node to link back to current level directory
3500 createParentTreeInfoNode(leveldir_new);
3502 // recursively step into sub-directory and look for more level series
3503 LoadLevelInfoFromLevelDir(&leveldir_new->node_group,
3504 leveldir_new, directory_path);
3507 free(directory_path);
3513 static void LoadLevelInfoFromLevelDir(TreeInfo **node_first,
3514 TreeInfo *node_parent,
3515 char *level_directory)
3517 // ---------- 1st stage: process any level set zip files ----------
3519 ProcessZipFilesInDirectory(level_directory, TREE_TYPE_LEVEL_DIR);
3521 // ---------- 2nd stage: check for level set directories ----------
3524 DirectoryEntry *dir_entry;
3525 boolean valid_entry_found = FALSE;
3527 if ((dir = openDirectory(level_directory)) == NULL)
3529 Warn("cannot read level directory '%s'", level_directory);
3534 while ((dir_entry = readDirectory(dir)) != NULL) // loop all entries
3536 char *directory_name = dir_entry->basename;
3537 char *directory_path = getPath2(level_directory, directory_name);
3539 // skip entries for current and parent directory
3540 if (strEqual(directory_name, ".") ||
3541 strEqual(directory_name, ".."))
3543 free(directory_path);
3548 // find out if directory entry is itself a directory
3549 if (!dir_entry->is_directory) // not a directory
3551 free(directory_path);
3556 free(directory_path);
3558 if (strEqual(directory_name, GRAPHICS_DIRECTORY) ||
3559 strEqual(directory_name, SOUNDS_DIRECTORY) ||
3560 strEqual(directory_name, MUSIC_DIRECTORY))
3563 valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
3568 closeDirectory(dir);
3570 // special case: top level directory may directly contain "levelinfo.conf"
3571 if (node_parent == NULL && !valid_entry_found)
3573 // check if this directory directly contains a file "levelinfo.conf"
3574 valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
3575 level_directory, ".");
3578 if (!valid_entry_found)
3579 Warn("cannot find any valid level series in directory '%s'",
3583 boolean AdjustGraphicsForEMC(void)
3585 boolean settings_changed = FALSE;
3587 settings_changed |= adjustTreeGraphicsForEMC(leveldir_first_all);
3588 settings_changed |= adjustTreeGraphicsForEMC(leveldir_first);
3590 return settings_changed;
3593 boolean AdjustSoundsForEMC(void)
3595 boolean settings_changed = FALSE;
3597 settings_changed |= adjustTreeSoundsForEMC(leveldir_first_all);
3598 settings_changed |= adjustTreeSoundsForEMC(leveldir_first);
3600 return settings_changed;
3603 void LoadLevelInfo(void)
3605 InitUserLevelDirectory(getLoginName());
3607 DrawInitText("Loading level series", 120, FC_GREEN);
3609 LoadLevelInfoFromLevelDir(&leveldir_first, NULL, options.level_directory);
3610 LoadLevelInfoFromLevelDir(&leveldir_first, NULL, getUserLevelDir(NULL));
3612 leveldir_first = createTopTreeInfoNode(leveldir_first);
3614 /* after loading all level set information, clone the level directory tree
3615 and remove all level sets without levels (these may still contain artwork
3616 to be offered in the setup menu as "custom artwork", and are therefore
3617 checked for existing artwork in the function "LoadLevelArtworkInfo()") */
3618 leveldir_first_all = leveldir_first;
3619 cloneTree(&leveldir_first, leveldir_first_all, TRUE);
3621 AdjustGraphicsForEMC();
3622 AdjustSoundsForEMC();
3624 // before sorting, the first entries will be from the user directory
3625 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
3627 if (leveldir_first == NULL)
3628 Fail("cannot find any valid level series in any directory");
3630 sortTreeInfo(&leveldir_first);
3632 #if ENABLE_UNUSED_CODE
3633 dumpTreeInfo(leveldir_first, 0);
3637 static boolean LoadArtworkInfoFromArtworkConf(TreeInfo **node_first,
3638 TreeInfo *node_parent,
3639 char *base_directory,
3640 char *directory_name, int type)
3642 char *directory_path = getPath2(base_directory, directory_name);
3643 char *filename = getPath2(directory_path, ARTWORKINFO_FILENAME(type));
3644 SetupFileHash *setup_file_hash = NULL;
3645 TreeInfo *artwork_new = NULL;
3648 if (fileExists(filename))
3649 setup_file_hash = loadSetupFileHash(filename);
3651 if (setup_file_hash == NULL) // no config file -- look for artwork files
3654 DirectoryEntry *dir_entry;
3655 boolean valid_file_found = FALSE;
3657 if ((dir = openDirectory(directory_path)) != NULL)
3659 while ((dir_entry = readDirectory(dir)) != NULL)
3661 if (FileIsArtworkType(dir_entry->filename, type))
3663 valid_file_found = TRUE;
3669 closeDirectory(dir);
3672 if (!valid_file_found)
3674 #if DEBUG_NO_CONFIG_FILE
3675 if (!strEqual(directory_name, "."))
3676 Debug("setup", "ignoring artwork directory '%s'", directory_path);
3679 free(directory_path);
3686 artwork_new = newTreeInfo();
3689 setTreeInfoToDefaultsFromParent(artwork_new, node_parent);
3691 setTreeInfoToDefaults(artwork_new, type);
3693 artwork_new->subdir = getStringCopy(directory_name);
3695 if (setup_file_hash) // (before defining ".color" and ".class_desc")
3697 // set all structure fields according to the token/value pairs
3699 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
3700 setSetupInfo(levelinfo_tokens, i,
3701 getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
3704 if (strEqual(artwork_new->name, ANONYMOUS_NAME))
3705 setString(&artwork_new->name, artwork_new->subdir);
3707 if (artwork_new->identifier == NULL)
3708 artwork_new->identifier = getStringCopy(artwork_new->subdir);
3710 if (artwork_new->name_sorting == NULL)
3711 artwork_new->name_sorting = getStringCopy(artwork_new->name);
3714 if (node_parent == NULL) // top level group
3716 artwork_new->basepath = getStringCopy(base_directory);
3717 artwork_new->fullpath = getStringCopy(artwork_new->subdir);
3719 else // sub level group
3721 artwork_new->basepath = getStringCopy(node_parent->basepath);
3722 artwork_new->fullpath = getPath2(node_parent->fullpath, directory_name);
3725 artwork_new->in_user_dir =
3726 (!strEqual(artwork_new->basepath, OPTIONS_ARTWORK_DIRECTORY(type)));
3728 // (may use ".sort_priority" from "setup_file_hash" above)
3729 artwork_new->color = ARTWORKCOLOR(artwork_new);
3731 setString(&artwork_new->class_desc, getLevelClassDescription(artwork_new));
3733 if (setup_file_hash == NULL) // (after determining ".user_defined")
3735 if (strEqual(artwork_new->subdir, "."))
3737 if (artwork_new->user_defined)
3739 setString(&artwork_new->identifier, "private");
3740 artwork_new->sort_priority = ARTWORKCLASS_PRIVATE;
3744 setString(&artwork_new->identifier, "classic");
3745 artwork_new->sort_priority = ARTWORKCLASS_CLASSICS;
3748 // set to new values after changing ".sort_priority"
3749 artwork_new->color = ARTWORKCOLOR(artwork_new);
3751 setString(&artwork_new->class_desc,
3752 getLevelClassDescription(artwork_new));
3756 setString(&artwork_new->identifier, artwork_new->subdir);
3759 setString(&artwork_new->name, artwork_new->identifier);
3760 setString(&artwork_new->name_sorting, artwork_new->name);
3763 pushTreeInfo(node_first, artwork_new);
3765 freeSetupFileHash(setup_file_hash);
3767 free(directory_path);
3773 static void LoadArtworkInfoFromArtworkDir(TreeInfo **node_first,
3774 TreeInfo *node_parent,
3775 char *base_directory, int type)
3777 // ---------- 1st stage: process any artwork set zip files ----------
3779 ProcessZipFilesInDirectory(base_directory, type);
3781 // ---------- 2nd stage: check for artwork set directories ----------
3784 DirectoryEntry *dir_entry;
3785 boolean valid_entry_found = FALSE;
3787 if ((dir = openDirectory(base_directory)) == NULL)
3789 // display error if directory is main "options.graphics_directory" etc.
3790 if (base_directory == OPTIONS_ARTWORK_DIRECTORY(type))
3791 Warn("cannot read directory '%s'", base_directory);
3796 while ((dir_entry = readDirectory(dir)) != NULL) // loop all entries
3798 char *directory_name = dir_entry->basename;
3799 char *directory_path = getPath2(base_directory, directory_name);
3801 // skip directory entries for current and parent directory
3802 if (strEqual(directory_name, ".") ||
3803 strEqual(directory_name, ".."))
3805 free(directory_path);
3810 // skip directory entries which are not a directory
3811 if (!dir_entry->is_directory) // not a directory
3813 free(directory_path);
3818 free(directory_path);
3820 // check if this directory contains artwork with or without config file
3821 valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
3823 directory_name, type);
3826 closeDirectory(dir);
3828 // check if this directory directly contains artwork itself
3829 valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
3830 base_directory, ".",
3832 if (!valid_entry_found)
3833 Warn("cannot find any valid artwork in directory '%s'", base_directory);
3836 static TreeInfo *getDummyArtworkInfo(int type)
3838 // this is only needed when there is completely no artwork available
3839 TreeInfo *artwork_new = newTreeInfo();
3841 setTreeInfoToDefaults(artwork_new, type);
3843 setString(&artwork_new->subdir, UNDEFINED_FILENAME);
3844 setString(&artwork_new->fullpath, UNDEFINED_FILENAME);
3845 setString(&artwork_new->basepath, UNDEFINED_FILENAME);
3847 setString(&artwork_new->identifier, UNDEFINED_FILENAME);
3848 setString(&artwork_new->name, UNDEFINED_FILENAME);
3849 setString(&artwork_new->name_sorting, UNDEFINED_FILENAME);
3854 void SetCurrentArtwork(int type)
3856 ArtworkDirTree **current_ptr = ARTWORK_CURRENT_PTR(artwork, type);
3857 ArtworkDirTree *first_node = ARTWORK_FIRST_NODE(artwork, type);
3858 char *setup_set = SETUP_ARTWORK_SET(setup, type);
3859 char *default_subdir = ARTWORK_DEFAULT_SUBDIR(type);
3861 // set current artwork to artwork configured in setup menu
3862 *current_ptr = getTreeInfoFromIdentifier(first_node, setup_set);
3864 // if not found, set current artwork to default artwork
3865 if (*current_ptr == NULL)
3866 *current_ptr = getTreeInfoFromIdentifier(first_node, default_subdir);
3868 // if not found, set current artwork to first artwork in tree
3869 if (*current_ptr == NULL)
3870 *current_ptr = getFirstValidTreeInfoEntry(first_node);
3873 void ChangeCurrentArtworkIfNeeded(int type)
3875 char *current_identifier = ARTWORK_CURRENT_IDENTIFIER(artwork, type);
3876 char *setup_set = SETUP_ARTWORK_SET(setup, type);
3878 if (!strEqual(current_identifier, setup_set))
3879 SetCurrentArtwork(type);
3882 void LoadArtworkInfo(void)
3884 LoadArtworkInfoCache();
3886 DrawInitText("Looking for custom artwork", 120, FC_GREEN);
3888 LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
3889 options.graphics_directory,
3890 TREE_TYPE_GRAPHICS_DIR);
3891 LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
3892 getUserGraphicsDir(),
3893 TREE_TYPE_GRAPHICS_DIR);
3895 LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
3896 options.sounds_directory,
3897 TREE_TYPE_SOUNDS_DIR);
3898 LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
3900 TREE_TYPE_SOUNDS_DIR);
3902 LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
3903 options.music_directory,
3904 TREE_TYPE_MUSIC_DIR);
3905 LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
3907 TREE_TYPE_MUSIC_DIR);
3909 if (artwork.gfx_first == NULL)
3910 artwork.gfx_first = getDummyArtworkInfo(TREE_TYPE_GRAPHICS_DIR);
3911 if (artwork.snd_first == NULL)
3912 artwork.snd_first = getDummyArtworkInfo(TREE_TYPE_SOUNDS_DIR);
3913 if (artwork.mus_first == NULL)
3914 artwork.mus_first = getDummyArtworkInfo(TREE_TYPE_MUSIC_DIR);
3916 // before sorting, the first entries will be from the user directory
3917 SetCurrentArtwork(ARTWORK_TYPE_GRAPHICS);
3918 SetCurrentArtwork(ARTWORK_TYPE_SOUNDS);
3919 SetCurrentArtwork(ARTWORK_TYPE_MUSIC);
3921 artwork.gfx_current_identifier = artwork.gfx_current->identifier;
3922 artwork.snd_current_identifier = artwork.snd_current->identifier;
3923 artwork.mus_current_identifier = artwork.mus_current->identifier;
3925 #if ENABLE_UNUSED_CODE
3926 Debug("setup:LoadArtworkInfo", "graphics set == %s",
3927 artwork.gfx_current_identifier);
3928 Debug("setup:LoadArtworkInfo", "sounds set == %s",
3929 artwork.snd_current_identifier);
3930 Debug("setup:LoadArtworkInfo", "music set == %s",
3931 artwork.mus_current_identifier);
3934 sortTreeInfo(&artwork.gfx_first);
3935 sortTreeInfo(&artwork.snd_first);
3936 sortTreeInfo(&artwork.mus_first);
3938 #if ENABLE_UNUSED_CODE
3939 dumpTreeInfo(artwork.gfx_first, 0);
3940 dumpTreeInfo(artwork.snd_first, 0);
3941 dumpTreeInfo(artwork.mus_first, 0);
3945 static void MoveArtworkInfoIntoSubTree(ArtworkDirTree **artwork_node)
3947 ArtworkDirTree *artwork_new = newTreeInfo();
3948 char *top_node_name = "dedicated custom artwork";
3950 setTreeInfoToDefaults(artwork_new, (*artwork_node)->type);
3952 artwork_new->level_group = TRUE;
3954 setString(&artwork_new->identifier, top_node_name);
3955 setString(&artwork_new->name, top_node_name);
3956 setString(&artwork_new->name_sorting, top_node_name);
3958 // create node to link back to current custom artwork directory
3959 createParentTreeInfoNode(artwork_new);
3961 // move existing custom artwork tree into newly created sub-tree
3962 artwork_new->node_group->next = *artwork_node;
3964 // change custom artwork tree to contain only newly created node
3965 *artwork_node = artwork_new;
3968 static void LoadArtworkInfoFromLevelInfoExt(ArtworkDirTree **artwork_node,
3969 ArtworkDirTree *node_parent,
3970 LevelDirTree *level_node,
3971 boolean empty_level_set_mode)
3973 int type = (*artwork_node)->type;
3975 // recursively check all level directories for artwork sub-directories
3979 boolean empty_level_set = (level_node->levels == 0);
3981 // check all tree entries for artwork, but skip parent link entries
3982 if (!level_node->parent_link && empty_level_set == empty_level_set_mode)
3984 TreeInfo *artwork_new = getArtworkInfoCacheEntry(level_node, type);
3985 boolean cached = (artwork_new != NULL);
3989 pushTreeInfo(artwork_node, artwork_new);
3993 TreeInfo *topnode_last = *artwork_node;
3994 char *path = getPath2(getLevelDirFromTreeInfo(level_node),
3995 ARTWORK_DIRECTORY(type));
3997 LoadArtworkInfoFromArtworkDir(artwork_node, NULL, path, type);
3999 if (topnode_last != *artwork_node) // check for newly added node
4001 artwork_new = *artwork_node;
4003 setString(&artwork_new->identifier, level_node->subdir);
4004 setString(&artwork_new->name, level_node->name);
4005 setString(&artwork_new->name_sorting, level_node->name_sorting);
4007 artwork_new->sort_priority = level_node->sort_priority;
4008 artwork_new->color = LEVELCOLOR(artwork_new);
4010 update_artworkinfo_cache = TRUE;
4016 // insert artwork info (from old cache or filesystem) into new cache
4017 if (artwork_new != NULL)
4018 setArtworkInfoCacheEntry(artwork_new, level_node, type);
4021 DrawInitText(level_node->name, 150, FC_YELLOW);
4023 if (level_node->node_group != NULL)
4025 TreeInfo *artwork_new = newTreeInfo();
4028 setTreeInfoToDefaultsFromParent(artwork_new, node_parent);
4030 setTreeInfoToDefaults(artwork_new, type);
4032 artwork_new->level_group = TRUE;
4034 setString(&artwork_new->identifier, level_node->subdir);
4036 if (node_parent == NULL) // check for top tree node
4038 char *top_node_name = (empty_level_set_mode ?
4039 "artwork-only level sets" :
4040 "artwork from level sets");
4042 setString(&artwork_new->name, top_node_name);
4043 setString(&artwork_new->name_sorting, top_node_name);
4047 setString(&artwork_new->name, level_node->name);
4048 setString(&artwork_new->name_sorting, level_node->name_sorting);
4051 pushTreeInfo(artwork_node, artwork_new);
4053 // create node to link back to current custom artwork directory
4054 createParentTreeInfoNode(artwork_new);
4056 // recursively step into sub-directory and look for more custom artwork
4057 LoadArtworkInfoFromLevelInfoExt(&artwork_new->node_group, artwork_new,
4058 level_node->node_group,
4059 empty_level_set_mode);
4061 // if sub-tree has no custom artwork at all, remove it
4062 if (artwork_new->node_group->next == NULL)
4063 removeTreeInfo(artwork_node);
4066 level_node = level_node->next;
4070 static void LoadArtworkInfoFromLevelInfo(ArtworkDirTree **artwork_node)
4072 // move peviously loaded artwork tree into separate sub-tree
4073 MoveArtworkInfoIntoSubTree(artwork_node);
4075 // load artwork from level sets into separate sub-trees
4076 LoadArtworkInfoFromLevelInfoExt(artwork_node, NULL, leveldir_first_all, TRUE);
4077 LoadArtworkInfoFromLevelInfoExt(artwork_node, NULL, leveldir_first_all, FALSE);
4079 // add top tree node over all three separate sub-trees
4080 *artwork_node = createTopTreeInfoNode(*artwork_node);
4082 // set all parent links (back links) in complete artwork tree
4083 setTreeInfoParentNodes(*artwork_node, NULL);
4086 void LoadLevelArtworkInfo(void)
4088 print_timestamp_init("LoadLevelArtworkInfo");
4090 DrawInitText("Looking for custom level artwork", 120, FC_GREEN);
4092 print_timestamp_time("DrawTimeText");
4094 LoadArtworkInfoFromLevelInfo(&artwork.gfx_first);
4095 print_timestamp_time("LoadArtworkInfoFromLevelInfo (gfx)");
4096 LoadArtworkInfoFromLevelInfo(&artwork.snd_first);
4097 print_timestamp_time("LoadArtworkInfoFromLevelInfo (snd)");
4098 LoadArtworkInfoFromLevelInfo(&artwork.mus_first);
4099 print_timestamp_time("LoadArtworkInfoFromLevelInfo (mus)");
4101 SaveArtworkInfoCache();
4103 print_timestamp_time("SaveArtworkInfoCache");
4105 // needed for reloading level artwork not known at ealier stage
4106 ChangeCurrentArtworkIfNeeded(ARTWORK_TYPE_GRAPHICS);
4107 ChangeCurrentArtworkIfNeeded(ARTWORK_TYPE_SOUNDS);
4108 ChangeCurrentArtworkIfNeeded(ARTWORK_TYPE_MUSIC);
4110 print_timestamp_time("getTreeInfoFromIdentifier");
4112 sortTreeInfo(&artwork.gfx_first);
4113 sortTreeInfo(&artwork.snd_first);
4114 sortTreeInfo(&artwork.mus_first);
4116 print_timestamp_time("sortTreeInfo");
4118 #if ENABLE_UNUSED_CODE
4119 dumpTreeInfo(artwork.gfx_first, 0);
4120 dumpTreeInfo(artwork.snd_first, 0);
4121 dumpTreeInfo(artwork.mus_first, 0);
4124 print_timestamp_done("LoadLevelArtworkInfo");
4127 static boolean AddTreeSetToTreeInfoExt(TreeInfo *tree_node_old, char *tree_dir,
4128 char *tree_subdir_new, int type)
4130 if (tree_node_old == NULL)
4132 if (type == TREE_TYPE_LEVEL_DIR)
4134 // get level info tree node of personal user level set
4135 tree_node_old = getTreeInfoFromIdentifier(leveldir_first, getLoginName());
4137 // this may happen if "setup.internal.create_user_levelset" is FALSE
4138 // or if file "levelinfo.conf" is missing in personal user level set
4139 if (tree_node_old == NULL)
4140 tree_node_old = leveldir_first->node_group;
4144 // get artwork info tree node of first artwork set
4145 tree_node_old = ARTWORK_FIRST_NODE(artwork, type);
4149 if (tree_dir == NULL)
4150 tree_dir = TREE_USERDIR(type);
4152 if (tree_node_old == NULL ||
4154 tree_subdir_new == NULL) // should not happen
4157 int draw_deactivation_mask = GetDrawDeactivationMask();
4159 // override draw deactivation mask (temporarily disable drawing)
4160 SetDrawDeactivationMask(REDRAW_ALL);
4162 if (type == TREE_TYPE_LEVEL_DIR)
4164 // load new level set config and add it next to first user level set
4165 LoadLevelInfoFromLevelConf(&tree_node_old->next,
4166 tree_node_old->node_parent,
4167 tree_dir, tree_subdir_new);
4171 // load new artwork set config and add it next to first artwork set
4172 LoadArtworkInfoFromArtworkConf(&tree_node_old->next,
4173 tree_node_old->node_parent,
4174 tree_dir, tree_subdir_new, type);
4177 // set draw deactivation mask to previous value
4178 SetDrawDeactivationMask(draw_deactivation_mask);
4180 // get first node of level or artwork info tree
4181 TreeInfo **tree_node_first = TREE_FIRST_NODE_PTR(type);
4183 // get tree info node of newly added level or artwork set
4184 TreeInfo *tree_node_new = getTreeInfoFromIdentifier(*tree_node_first,
4187 if (tree_node_new == NULL) // should not happen
4190 // correct top link and parent node link of newly created tree node
4191 tree_node_new->node_top = tree_node_old->node_top;
4192 tree_node_new->node_parent = tree_node_old->node_parent;
4194 // sort tree info to adjust position of newly added tree set
4195 sortTreeInfo(tree_node_first);
4200 void AddTreeSetToTreeInfo(TreeInfo *tree_node, char *tree_dir,
4201 char *tree_subdir_new, int type)
4203 if (!AddTreeSetToTreeInfoExt(tree_node, tree_dir, tree_subdir_new, type))
4204 Fail("internal tree info structure corrupted -- aborting");
4207 void AddUserLevelSetToLevelInfo(char *level_subdir_new)
4209 AddTreeSetToTreeInfo(NULL, NULL, level_subdir_new, TREE_TYPE_LEVEL_DIR);
4212 char *getArtworkIdentifierForUserLevelSet(int type)
4214 char *classic_artwork_set = getClassicArtworkSet(type);
4216 // check for custom artwork configured in "levelinfo.conf"
4217 char *leveldir_artwork_set =
4218 *LEVELDIR_ARTWORK_SET_PTR(leveldir_current, type);
4219 boolean has_leveldir_artwork_set =
4220 (leveldir_artwork_set != NULL && !strEqual(leveldir_artwork_set,
4221 classic_artwork_set));
4223 // check for custom artwork in sub-directory "graphics" etc.
4224 TreeInfo *artwork_first_node = ARTWORK_FIRST_NODE(artwork, type);
4225 char *leveldir_identifier = leveldir_current->identifier;
4226 boolean has_artwork_subdir =
4227 (getTreeInfoFromIdentifier(artwork_first_node,
4228 leveldir_identifier) != NULL);
4230 return (has_leveldir_artwork_set ? leveldir_artwork_set :
4231 has_artwork_subdir ? leveldir_identifier :
4232 classic_artwork_set);
4235 TreeInfo *getArtworkTreeInfoForUserLevelSet(int type)
4237 char *artwork_set = getArtworkIdentifierForUserLevelSet(type);
4238 TreeInfo *artwork_first_node = ARTWORK_FIRST_NODE(artwork, type);
4239 TreeInfo *ti = getTreeInfoFromIdentifier(artwork_first_node, artwork_set);
4243 ti = getTreeInfoFromIdentifier(artwork_first_node,
4244 ARTWORK_DEFAULT_SUBDIR(type));
4246 Fail("cannot find default graphics -- should not happen");
4252 boolean checkIfCustomArtworkExistsForCurrentLevelSet(void)
4254 char *graphics_set =
4255 getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_GRAPHICS);
4257 getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_SOUNDS);
4259 getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_MUSIC);
4261 return (!strEqual(graphics_set, GFX_CLASSIC_SUBDIR) ||
4262 !strEqual(sounds_set, SND_CLASSIC_SUBDIR) ||
4263 !strEqual(music_set, MUS_CLASSIC_SUBDIR));
4266 boolean UpdateUserLevelSet(char *level_subdir, char *level_name,
4267 char *level_author, int num_levels)
4269 char *filename = getPath2(getUserLevelDir(level_subdir), LEVELINFO_FILENAME);
4270 char *filename_tmp = getStringCat2(filename, ".tmp");
4272 FILE *file_tmp = NULL;
4273 char line[MAX_LINE_LEN];
4274 boolean success = FALSE;
4275 LevelDirTree *leveldir = getTreeInfoFromIdentifier(leveldir_first,
4277 // update values in level directory tree
4279 if (level_name != NULL)
4280 setString(&leveldir->name, level_name);
4282 if (level_author != NULL)
4283 setString(&leveldir->author, level_author);
4285 if (num_levels != -1)
4286 leveldir->levels = num_levels;
4288 // update values that depend on other values
4290 setString(&leveldir->name_sorting, leveldir->name);
4292 leveldir->last_level = leveldir->first_level + leveldir->levels - 1;
4294 // sort order of level sets may have changed
4295 sortTreeInfo(&leveldir_first);
4297 if ((file = fopen(filename, MODE_READ)) &&
4298 (file_tmp = fopen(filename_tmp, MODE_WRITE)))
4300 while (fgets(line, MAX_LINE_LEN, file))
4302 if (strPrefix(line, "name:") && level_name != NULL)
4303 fprintf(file_tmp, "%-32s%s\n", "name:", level_name);
4304 else if (strPrefix(line, "author:") && level_author != NULL)
4305 fprintf(file_tmp, "%-32s%s\n", "author:", level_author);
4306 else if (strPrefix(line, "levels:") && num_levels != -1)
4307 fprintf(file_tmp, "%-32s%d\n", "levels:", num_levels);
4309 fputs(line, file_tmp);
4322 success = (rename(filename_tmp, filename) == 0);
4330 boolean CreateUserLevelSet(char *level_subdir, char *level_name,
4331 char *level_author, int num_levels,
4332 boolean use_artwork_set)
4334 LevelDirTree *level_info;
4339 // create user level sub-directory, if needed
4340 createDirectory(getUserLevelDir(level_subdir), "user level", PERMS_PRIVATE);
4342 filename = getPath2(getUserLevelDir(level_subdir), LEVELINFO_FILENAME);
4344 if (!(file = fopen(filename, MODE_WRITE)))
4346 Warn("cannot write level info file '%s'", filename);
4353 level_info = newTreeInfo();
4355 // always start with reliable default values
4356 setTreeInfoToDefaults(level_info, TREE_TYPE_LEVEL_DIR);
4358 setString(&level_info->name, level_name);
4359 setString(&level_info->author, level_author);
4360 level_info->levels = num_levels;
4361 level_info->first_level = 1;
4362 level_info->sort_priority = LEVELCLASS_PRIVATE_START;
4363 level_info->readonly = FALSE;
4365 if (use_artwork_set)
4367 level_info->graphics_set =
4368 getStringCopy(getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_GRAPHICS));
4369 level_info->sounds_set =
4370 getStringCopy(getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_SOUNDS));
4371 level_info->music_set =
4372 getStringCopy(getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_MUSIC));
4375 token_value_position = TOKEN_VALUE_POSITION_SHORT;
4377 fprintFileHeader(file, LEVELINFO_FILENAME);
4380 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
4382 if (i == LEVELINFO_TOKEN_NAME ||
4383 i == LEVELINFO_TOKEN_AUTHOR ||
4384 i == LEVELINFO_TOKEN_LEVELS ||
4385 i == LEVELINFO_TOKEN_FIRST_LEVEL ||
4386 i == LEVELINFO_TOKEN_SORT_PRIORITY ||
4387 i == LEVELINFO_TOKEN_READONLY ||
4388 (use_artwork_set && (i == LEVELINFO_TOKEN_GRAPHICS_SET ||
4389 i == LEVELINFO_TOKEN_SOUNDS_SET ||
4390 i == LEVELINFO_TOKEN_MUSIC_SET)))
4391 fprintf(file, "%s\n", getSetupLine(levelinfo_tokens, "", i));
4393 // just to make things nicer :)
4394 if (i == LEVELINFO_TOKEN_AUTHOR ||
4395 i == LEVELINFO_TOKEN_FIRST_LEVEL ||
4396 (use_artwork_set && i == LEVELINFO_TOKEN_READONLY))
4397 fprintf(file, "\n");
4400 token_value_position = TOKEN_VALUE_POSITION_DEFAULT;
4404 SetFilePermissions(filename, PERMS_PRIVATE);
4406 freeTreeInfo(level_info);
4412 static void SaveUserLevelInfo(void)
4414 CreateUserLevelSet(getLoginName(), getLoginName(), getRealName(), 100, FALSE);
4417 char *getSetupValue(int type, void *value)
4419 static char value_string[MAX_LINE_LEN];
4427 strcpy(value_string, (*(boolean *)value ? "true" : "false"));
4431 strcpy(value_string, (*(boolean *)value ? "on" : "off"));
4435 strcpy(value_string, (*(int *)value == AUTO ? "auto" :
4436 *(int *)value == FALSE ? "off" : "on"));
4440 strcpy(value_string, (*(boolean *)value ? "yes" : "no"));
4443 case TYPE_YES_NO_AUTO:
4444 strcpy(value_string, (*(int *)value == AUTO ? "auto" :
4445 *(int *)value == FALSE ? "no" : "yes"));
4449 strcpy(value_string, (*(boolean *)value ? "AGA" : "ECS"));
4453 strcpy(value_string, getKeyNameFromKey(*(Key *)value));
4457 strcpy(value_string, getX11KeyNameFromKey(*(Key *)value));
4461 sprintf(value_string, "%d", *(int *)value);
4465 if (*(char **)value == NULL)
4468 strcpy(value_string, *(char **)value);
4472 sprintf(value_string, "player_%d", *(int *)value + 1);
4476 value_string[0] = '\0';
4480 if (type & TYPE_GHOSTED)
4481 strcpy(value_string, "n/a");
4483 return value_string;
4486 char *getSetupLine(struct TokenInfo *token_info, char *prefix, int token_nr)
4490 static char token_string[MAX_LINE_LEN];
4491 int token_type = token_info[token_nr].type;
4492 void *setup_value = token_info[token_nr].value;
4493 char *token_text = token_info[token_nr].text;
4494 char *value_string = getSetupValue(token_type, setup_value);
4496 // build complete token string
4497 sprintf(token_string, "%s%s", prefix, token_text);
4499 // build setup entry line
4500 line = getFormattedSetupEntry(token_string, value_string);
4502 if (token_type == TYPE_KEY_X11)
4504 Key key = *(Key *)setup_value;
4505 char *keyname = getKeyNameFromKey(key);
4507 // add comment, if useful
4508 if (!strEqual(keyname, "(undefined)") &&
4509 !strEqual(keyname, "(unknown)"))
4511 // add at least one whitespace
4513 for (i = strlen(line); i < token_comment_position; i++)
4517 strcat(line, keyname);
4524 static void InitLastPlayedLevels_ParentNode(void)
4526 LevelDirTree **leveldir_top = &leveldir_first->node_group->next;
4527 LevelDirTree *leveldir_new = NULL;
4529 // check if parent node for last played levels already exists
4530 if (strEqual((*leveldir_top)->identifier, TOKEN_STR_LAST_LEVEL_SERIES))
4533 leveldir_new = newTreeInfo();
4535 setTreeInfoToDefaultsFromParent(leveldir_new, leveldir_first);
4537 leveldir_new->level_group = TRUE;
4539 setString(&leveldir_new->identifier, TOKEN_STR_LAST_LEVEL_SERIES);
4540 setString(&leveldir_new->name, "<< (last played level sets)");
4542 pushTreeInfo(leveldir_top, leveldir_new);
4544 // create node to link back to current level directory
4545 createParentTreeInfoNode(leveldir_new);
4548 void UpdateLastPlayedLevels_TreeInfo(void)
4550 char **last_level_series = setup.level_setup.last_level_series;
4551 boolean reset_leveldir_current = FALSE;
4552 LevelDirTree *leveldir_last;
4553 TreeInfo **node_new = NULL;
4556 if (last_level_series[0] == NULL)
4559 InitLastPlayedLevels_ParentNode();
4561 // check if current level set is from "last played" sub-tree to be rebuilt
4562 reset_leveldir_current = strEqual(leveldir_current->node_parent->identifier,
4563 TOKEN_STR_LAST_LEVEL_SERIES);
4565 leveldir_last = getTreeInfoFromIdentifierExt(leveldir_first,
4566 TOKEN_STR_LAST_LEVEL_SERIES,
4568 if (leveldir_last == NULL)
4571 node_new = &leveldir_last->node_group->next;
4573 freeTreeInfo(*node_new);
4575 for (i = 0; last_level_series[i] != NULL; i++)
4577 LevelDirTree *node_last = getTreeInfoFromIdentifier(leveldir_first,
4578 last_level_series[i]);
4580 *node_new = getTreeInfoCopy(node_last); // copy complete node
4582 (*node_new)->node_top = &leveldir_first; // correct top node link
4583 (*node_new)->node_parent = leveldir_last; // correct parent node link
4585 (*node_new)->node_group = NULL;
4586 (*node_new)->next = NULL;
4588 (*node_new)->cl_first = -1; // force setting tree cursor
4590 node_new = &((*node_new)->next);
4593 if (reset_leveldir_current)
4594 leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
4595 last_level_series[0]);
4598 static void UpdateLastPlayedLevels_List(void)
4600 char **last_level_series = setup.level_setup.last_level_series;
4601 int pos = MAX_LEVELDIR_HISTORY - 1;
4604 // search for potentially already existing entry in list of level sets
4605 for (i = 0; last_level_series[i] != NULL; i++)
4606 if (strEqual(last_level_series[i], leveldir_current->identifier))
4609 // move list of level sets one entry down (using potentially free entry)
4610 for (i = pos; i > 0; i--)
4611 setString(&last_level_series[i], last_level_series[i - 1]);
4613 // put last played level set at top position
4614 setString(&last_level_series[0], leveldir_current->identifier);
4617 void LoadLevelSetup_LastSeries(void)
4619 // --------------------------------------------------------------------------
4620 // ~/.<program>/levelsetup.conf
4621 // --------------------------------------------------------------------------
4623 char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
4624 SetupFileHash *level_setup_hash = NULL;
4628 // always start with reliable default values
4629 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
4631 // start with empty history of last played level sets
4632 setString(&setup.level_setup.last_level_series[0], NULL);
4634 if (!strEqual(DEFAULT_LEVELSET, UNDEFINED_LEVELSET))
4636 leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
4638 if (leveldir_current == NULL)
4639 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
4642 if ((level_setup_hash = loadSetupFileHash(filename)))
4644 char *last_level_series =
4645 getHashEntry(level_setup_hash, TOKEN_STR_LAST_LEVEL_SERIES);
4647 leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
4649 if (leveldir_current == NULL)
4650 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
4652 for (i = 0; i < MAX_LEVELDIR_HISTORY; i++)
4654 char token[strlen(TOKEN_STR_LAST_LEVEL_SERIES) + 10];
4655 LevelDirTree *leveldir_last;
4657 sprintf(token, "%s.%03d", TOKEN_STR_LAST_LEVEL_SERIES, i);
4659 last_level_series = getHashEntry(level_setup_hash, token);
4661 leveldir_last = getTreeInfoFromIdentifier(leveldir_first,
4663 if (leveldir_last != NULL)
4664 setString(&setup.level_setup.last_level_series[pos++],
4668 setString(&setup.level_setup.last_level_series[pos], NULL);
4670 freeSetupFileHash(level_setup_hash);
4674 Debug("setup", "using default setup values");
4680 static void SaveLevelSetup_LastSeries_Ext(boolean deactivate_last_level_series)
4682 // --------------------------------------------------------------------------
4683 // ~/.<program>/levelsetup.conf
4684 // --------------------------------------------------------------------------
4686 // check if the current level directory structure is available at this point
4687 if (leveldir_current == NULL)
4690 char **last_level_series = setup.level_setup.last_level_series;
4691 char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
4695 InitUserDataDirectory();
4697 UpdateLastPlayedLevels_List();
4699 if (!(file = fopen(filename, MODE_WRITE)))
4701 Warn("cannot write setup file '%s'", filename);
4708 fprintFileHeader(file, LEVELSETUP_FILENAME);
4710 if (deactivate_last_level_series)
4711 fprintf(file, "# %s\n# ", "the following level set may have caused a problem and was deactivated");
4713 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_LAST_LEVEL_SERIES,
4714 leveldir_current->identifier));
4716 for (i = 0; last_level_series[i] != NULL; i++)
4718 char token[strlen(TOKEN_STR_LAST_LEVEL_SERIES) + 10];
4720 sprintf(token, "%s.%03d", TOKEN_STR_LAST_LEVEL_SERIES, i);
4722 fprintf(file, "%s\n", getFormattedSetupEntry(token, last_level_series[i]));
4727 SetFilePermissions(filename, PERMS_PRIVATE);
4732 void SaveLevelSetup_LastSeries(void)
4734 SaveLevelSetup_LastSeries_Ext(FALSE);
4737 void SaveLevelSetup_LastSeries_Deactivate(void)
4739 SaveLevelSetup_LastSeries_Ext(TRUE);
4742 static void checkSeriesInfo(void)
4744 static char *level_directory = NULL;
4747 DirectoryEntry *dir_entry;
4750 checked_free(level_directory);
4752 // check for more levels besides the 'levels' field of 'levelinfo.conf'
4754 level_directory = getPath2((leveldir_current->in_user_dir ?
4755 getUserLevelDir(NULL) :
4756 options.level_directory),
4757 leveldir_current->fullpath);
4759 if ((dir = openDirectory(level_directory)) == NULL)
4761 Warn("cannot read level directory '%s'", level_directory);
4767 while ((dir_entry = readDirectory(dir)) != NULL) // loop all entries
4769 if (strlen(dir_entry->basename) > 4 &&
4770 dir_entry->basename[3] == '.' &&
4771 strEqual(&dir_entry->basename[4], LEVELFILE_EXTENSION))
4773 char levelnum_str[4];
4776 strncpy(levelnum_str, dir_entry->basename, 3);
4777 levelnum_str[3] = '\0';
4779 levelnum_value = atoi(levelnum_str);
4781 if (levelnum_value < leveldir_current->first_level)
4783 Warn("additional level %d found", levelnum_value);
4785 leveldir_current->first_level = levelnum_value;
4787 else if (levelnum_value > leveldir_current->last_level)
4789 Warn("additional level %d found", levelnum_value);
4791 leveldir_current->last_level = levelnum_value;
4797 closeDirectory(dir);
4800 void LoadLevelSetup_SeriesInfo(void)
4803 SetupFileHash *level_setup_hash = NULL;
4804 char *level_subdir = leveldir_current->subdir;
4807 // always start with reliable default values
4808 level_nr = leveldir_current->first_level;
4810 for (i = 0; i < MAX_LEVELS; i++)
4812 LevelStats_setPlayed(i, 0);
4813 LevelStats_setSolved(i, 0);
4818 // --------------------------------------------------------------------------
4819 // ~/.<program>/levelsetup/<level series>/levelsetup.conf
4820 // --------------------------------------------------------------------------
4822 level_subdir = leveldir_current->subdir;
4824 filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
4826 if ((level_setup_hash = loadSetupFileHash(filename)))
4830 // get last played level in this level set
4832 token_value = getHashEntry(level_setup_hash, TOKEN_STR_LAST_PLAYED_LEVEL);
4836 level_nr = atoi(token_value);
4838 if (level_nr < leveldir_current->first_level)
4839 level_nr = leveldir_current->first_level;
4840 if (level_nr > leveldir_current->last_level)
4841 level_nr = leveldir_current->last_level;
4844 // get handicap level in this level set
4846 token_value = getHashEntry(level_setup_hash, TOKEN_STR_HANDICAP_LEVEL);
4850 int level_nr = atoi(token_value);
4852 if (level_nr < leveldir_current->first_level)
4853 level_nr = leveldir_current->first_level;
4854 if (level_nr > leveldir_current->last_level + 1)
4855 level_nr = leveldir_current->last_level;
4857 if (leveldir_current->user_defined || !leveldir_current->handicap)
4858 level_nr = leveldir_current->last_level;
4860 leveldir_current->handicap_level = level_nr;
4863 // get number of played and solved levels in this level set
4865 BEGIN_HASH_ITERATION(level_setup_hash, itr)
4867 char *token = HASH_ITERATION_TOKEN(itr);
4868 char *value = HASH_ITERATION_VALUE(itr);
4870 if (strlen(token) == 3 &&
4871 token[0] >= '0' && token[0] <= '9' &&
4872 token[1] >= '0' && token[1] <= '9' &&
4873 token[2] >= '0' && token[2] <= '9')
4875 int level_nr = atoi(token);
4878 LevelStats_setPlayed(level_nr, atoi(value)); // read 1st column
4880 value = strchr(value, ' ');
4883 LevelStats_setSolved(level_nr, atoi(value)); // read 2nd column
4886 END_HASH_ITERATION(hash, itr)
4888 freeSetupFileHash(level_setup_hash);
4892 Debug("setup", "using default setup values");
4898 void SaveLevelSetup_SeriesInfo(void)
4901 char *level_subdir = leveldir_current->subdir;
4902 char *level_nr_str = int2str(level_nr, 0);
4903 char *handicap_level_str = int2str(leveldir_current->handicap_level, 0);
4907 // --------------------------------------------------------------------------
4908 // ~/.<program>/levelsetup/<level series>/levelsetup.conf
4909 // --------------------------------------------------------------------------
4911 InitLevelSetupDirectory(level_subdir);
4913 filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
4915 if (!(file = fopen(filename, MODE_WRITE)))
4917 Warn("cannot write setup file '%s'", filename);
4924 fprintFileHeader(file, LEVELSETUP_FILENAME);
4926 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_PLAYED_LEVEL,
4928 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_HANDICAP_LEVEL,
4929 handicap_level_str));
4931 for (i = leveldir_current->first_level; i <= leveldir_current->last_level;
4934 if (LevelStats_getPlayed(i) > 0 ||
4935 LevelStats_getSolved(i) > 0)
4940 sprintf(token, "%03d", i);
4941 sprintf(value, "%d %d", LevelStats_getPlayed(i), LevelStats_getSolved(i));
4943 fprintf(file, "%s\n", getFormattedSetupEntry(token, value));
4949 SetFilePermissions(filename, PERMS_PRIVATE);
4954 int LevelStats_getPlayed(int nr)
4956 return (nr >= 0 && nr < MAX_LEVELS ? level_stats[nr].played : 0);
4959 int LevelStats_getSolved(int nr)
4961 return (nr >= 0 && nr < MAX_LEVELS ? level_stats[nr].solved : 0);
4964 void LevelStats_setPlayed(int nr, int value)
4966 if (nr >= 0 && nr < MAX_LEVELS)
4967 level_stats[nr].played = value;
4970 void LevelStats_setSolved(int nr, int value)
4972 if (nr >= 0 && nr < MAX_LEVELS)
4973 level_stats[nr].solved = value;
4976 void LevelStats_incPlayed(int nr)
4978 if (nr >= 0 && nr < MAX_LEVELS)
4979 level_stats[nr].played++;
4982 void LevelStats_incSolved(int nr)
4984 if (nr >= 0 && nr < MAX_LEVELS)
4985 level_stats[nr].solved++;
4988 void LoadUserSetup(void)
4990 // --------------------------------------------------------------------------
4991 // ~/.<program>/usersetup.conf
4992 // --------------------------------------------------------------------------
4994 char *filename = getPath2(getMainUserGameDataDir(), USERSETUP_FILENAME);
4995 SetupFileHash *user_setup_hash = NULL;
4997 // always start with reliable default values
5000 if ((user_setup_hash = loadSetupFileHash(filename)))
5004 // get last selected user number
5005 token_value = getHashEntry(user_setup_hash, TOKEN_STR_LAST_USER);
5008 user.nr = MIN(MAX(0, atoi(token_value)), MAX_PLAYER_NAMES - 1);
5010 freeSetupFileHash(user_setup_hash);
5014 Debug("setup", "using default setup values");
5020 void SaveUserSetup(void)
5022 // --------------------------------------------------------------------------
5023 // ~/.<program>/usersetup.conf
5024 // --------------------------------------------------------------------------
5026 char *filename = getPath2(getMainUserGameDataDir(), USERSETUP_FILENAME);
5029 InitMainUserDataDirectory();
5031 if (!(file = fopen(filename, MODE_WRITE)))
5033 Warn("cannot write setup file '%s'", filename);
5040 fprintFileHeader(file, USERSETUP_FILENAME);
5042 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_USER,
5046 SetFilePermissions(filename, PERMS_PRIVATE);