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 (!fileExists(filename)) // file does not exist
3030 return (atoi(timestamp_string) != 0);
3032 if (stat(filename, &file_status) != 0) // cannot stat file
3035 return (file_status.st_mtime != atoi(timestamp_string));
3038 static TreeInfo *getArtworkInfoCacheEntry(LevelDirTree *level_node, int type)
3040 char *identifier = level_node->subdir;
3041 char *type_string = ARTWORK_DIRECTORY(type);
3042 char *token_prefix = getCacheTokenPrefix(type_string, identifier);
3043 char *token_main = getCacheToken(token_prefix, "CACHED");
3044 char *cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
3045 boolean cached = (cache_entry != NULL && strEqual(cache_entry, "true"));
3046 TreeInfo *artwork_info = NULL;
3048 if (!use_artworkinfo_cache)
3051 if (optional_tokens_hash == NULL)
3055 // create hash from list of optional tokens (for quick access)
3056 optional_tokens_hash = newSetupFileHash();
3057 for (i = 0; optional_tokens[i] != NULL; i++)
3058 setHashEntry(optional_tokens_hash, optional_tokens[i], "");
3065 artwork_info = newTreeInfo();
3066 setTreeInfoToDefaults(artwork_info, type);
3068 // set all structure fields according to the token/value pairs
3069 ldi = *artwork_info;
3070 for (i = 0; artworkinfo_tokens[i].type != -1; i++)
3072 char *token_suffix = artworkinfo_tokens[i].text;
3073 char *token = getCacheToken(token_prefix, token_suffix);
3074 char *value = getHashEntry(artworkinfo_cache_old, token);
3076 (getHashEntry(optional_tokens_hash, token_suffix) != NULL);
3078 setSetupInfo(artworkinfo_tokens, i, value);
3080 // check if cache entry for this item is mandatory, but missing
3081 if (value == NULL && !optional)
3083 Warn("missing cache entry '%s'", token);
3089 *artwork_info = ldi;
3094 char *filename_levelinfo = getPath2(getLevelDirFromTreeInfo(level_node),
3095 LEVELINFO_FILENAME);
3096 char *filename_artworkinfo = getPath2(getSetupArtworkDir(artwork_info),
3097 ARTWORKINFO_FILENAME(type));
3099 // check if corresponding "levelinfo.conf" file has changed
3100 token_main = getCacheToken(token_prefix, "TIMESTAMP_LEVELINFO");
3101 cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
3103 if (modifiedFileTimestamp(filename_levelinfo, cache_entry))
3106 // check if corresponding "<artworkinfo>.conf" file has changed
3107 token_main = getCacheToken(token_prefix, "TIMESTAMP_ARTWORKINFO");
3108 cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
3110 if (modifiedFileTimestamp(filename_artworkinfo, cache_entry))
3113 checked_free(filename_levelinfo);
3114 checked_free(filename_artworkinfo);
3117 if (!cached && artwork_info != NULL)
3119 freeTreeInfo(artwork_info);
3124 return artwork_info;
3127 static void setArtworkInfoCacheEntry(TreeInfo *artwork_info,
3128 LevelDirTree *level_node, int type)
3130 char *identifier = level_node->subdir;
3131 char *type_string = ARTWORK_DIRECTORY(type);
3132 char *token_prefix = getCacheTokenPrefix(type_string, identifier);
3133 char *token_main = getCacheToken(token_prefix, "CACHED");
3134 boolean set_cache_timestamps = TRUE;
3137 setHashEntry(artworkinfo_cache_new, token_main, "true");
3139 if (set_cache_timestamps)
3141 char *filename_levelinfo = getPath2(getLevelDirFromTreeInfo(level_node),
3142 LEVELINFO_FILENAME);
3143 char *filename_artworkinfo = getPath2(getSetupArtworkDir(artwork_info),
3144 ARTWORKINFO_FILENAME(type));
3145 char *timestamp_levelinfo = getFileTimestampString(filename_levelinfo);
3146 char *timestamp_artworkinfo = getFileTimestampString(filename_artworkinfo);
3148 token_main = getCacheToken(token_prefix, "TIMESTAMP_LEVELINFO");
3149 setHashEntry(artworkinfo_cache_new, token_main, timestamp_levelinfo);
3151 token_main = getCacheToken(token_prefix, "TIMESTAMP_ARTWORKINFO");
3152 setHashEntry(artworkinfo_cache_new, token_main, timestamp_artworkinfo);
3154 checked_free(filename_levelinfo);
3155 checked_free(filename_artworkinfo);
3156 checked_free(timestamp_levelinfo);
3157 checked_free(timestamp_artworkinfo);
3160 ldi = *artwork_info;
3161 for (i = 0; artworkinfo_tokens[i].type != -1; i++)
3163 char *token = getCacheToken(token_prefix, artworkinfo_tokens[i].text);
3164 char *value = getSetupValue(artworkinfo_tokens[i].type,
3165 artworkinfo_tokens[i].value);
3167 setHashEntry(artworkinfo_cache_new, token, value);
3172 // ----------------------------------------------------------------------------
3173 // functions for loading level info and custom artwork info
3174 // ----------------------------------------------------------------------------
3176 int GetZipFileTreeType(char *zip_filename)
3178 static char *top_dir_path = NULL;
3179 static char *top_dir_conf_filename[NUM_BASE_TREE_TYPES] = { NULL };
3180 static char *conf_basename[NUM_BASE_TREE_TYPES] =
3182 GRAPHICSINFO_FILENAME,
3183 SOUNDSINFO_FILENAME,
3189 checked_free(top_dir_path);
3190 top_dir_path = NULL;
3192 for (j = 0; j < NUM_BASE_TREE_TYPES; j++)
3194 checked_free(top_dir_conf_filename[j]);
3195 top_dir_conf_filename[j] = NULL;
3198 char **zip_entries = zip_list(zip_filename);
3200 // check if zip file successfully opened
3201 if (zip_entries == NULL || zip_entries[0] == NULL)
3202 return TREE_TYPE_UNDEFINED;
3204 // first zip file entry is expected to be top level directory
3205 char *top_dir = zip_entries[0];
3207 // check if valid top level directory found in zip file
3208 if (!strSuffix(top_dir, "/"))
3209 return TREE_TYPE_UNDEFINED;
3211 // get filenames of valid configuration files in top level directory
3212 for (j = 0; j < NUM_BASE_TREE_TYPES; j++)
3213 top_dir_conf_filename[j] = getStringCat2(top_dir, conf_basename[j]);
3215 int tree_type = TREE_TYPE_UNDEFINED;
3218 while (zip_entries[e] != NULL)
3220 // check if every zip file entry is below top level directory
3221 if (!strPrefix(zip_entries[e], top_dir))
3222 return TREE_TYPE_UNDEFINED;
3224 // check if this zip file entry is a valid configuration filename
3225 for (j = 0; j < NUM_BASE_TREE_TYPES; j++)
3227 if (strEqual(zip_entries[e], top_dir_conf_filename[j]))
3229 // only exactly one valid configuration file allowed
3230 if (tree_type != TREE_TYPE_UNDEFINED)
3231 return TREE_TYPE_UNDEFINED;
3243 static boolean CheckZipFileForDirectory(char *zip_filename, char *directory,
3246 static char *top_dir_path = NULL;
3247 static char *top_dir_conf_filename = NULL;
3249 checked_free(top_dir_path);
3250 checked_free(top_dir_conf_filename);
3252 top_dir_path = NULL;
3253 top_dir_conf_filename = NULL;
3255 char *conf_basename = (tree_type == TREE_TYPE_LEVEL_DIR ? LEVELINFO_FILENAME :
3256 ARTWORKINFO_FILENAME(tree_type));
3258 // check if valid configuration filename determined
3259 if (conf_basename == NULL || strEqual(conf_basename, ""))
3262 char **zip_entries = zip_list(zip_filename);
3264 // check if zip file successfully opened
3265 if (zip_entries == NULL || zip_entries[0] == NULL)
3268 // first zip file entry is expected to be top level directory
3269 char *top_dir = zip_entries[0];
3271 // check if valid top level directory found in zip file
3272 if (!strSuffix(top_dir, "/"))
3275 // get path of extracted top level directory
3276 top_dir_path = getPath2(directory, top_dir);
3278 // remove trailing directory separator from top level directory path
3279 // (required to be able to check for file and directory in next step)
3280 top_dir_path[strlen(top_dir_path) - 1] = '\0';
3282 // check if zip file's top level directory already exists in target directory
3283 if (fileExists(top_dir_path)) // (checks for file and directory)
3286 // get filename of configuration file in top level directory
3287 top_dir_conf_filename = getStringCat2(top_dir, conf_basename);
3289 boolean found_top_dir_conf_filename = FALSE;
3292 while (zip_entries[i] != NULL)
3294 // check if every zip file entry is below top level directory
3295 if (!strPrefix(zip_entries[i], top_dir))
3298 // check if this zip file entry is the configuration filename
3299 if (strEqual(zip_entries[i], top_dir_conf_filename))
3300 found_top_dir_conf_filename = TRUE;
3305 // check if valid configuration filename was found in zip file
3306 if (!found_top_dir_conf_filename)
3312 char *ExtractZipFileIntoDirectory(char *zip_filename, char *directory,
3315 boolean zip_file_valid = CheckZipFileForDirectory(zip_filename, directory,
3318 if (!zip_file_valid)
3320 Warn("zip file '%s' rejected!", zip_filename);
3325 char **zip_entries = zip_extract(zip_filename, directory);
3327 if (zip_entries == NULL)
3329 Warn("zip file '%s' could not be extracted!", zip_filename);
3334 Info("zip file '%s' successfully extracted!", zip_filename);
3336 // first zip file entry contains top level directory
3337 char *top_dir = zip_entries[0];
3339 // remove trailing directory separator from top level directory
3340 top_dir[strlen(top_dir) - 1] = '\0';
3345 static void ProcessZipFilesInDirectory(char *directory, int tree_type)
3348 DirectoryEntry *dir_entry;
3350 if ((dir = openDirectory(directory)) == NULL)
3352 // display error if directory is main "options.graphics_directory" etc.
3353 if (tree_type == TREE_TYPE_LEVEL_DIR ||
3354 directory == OPTIONS_ARTWORK_DIRECTORY(tree_type))
3355 Warn("cannot read directory '%s'", directory);
3360 while ((dir_entry = readDirectory(dir)) != NULL) // loop all entries
3362 // skip non-zip files (and also directories with zip extension)
3363 if (!strSuffixLower(dir_entry->basename, ".zip") || dir_entry->is_directory)
3366 char *zip_filename = getPath2(directory, dir_entry->basename);
3367 char *zip_filename_extracted = getStringCat2(zip_filename, ".extracted");
3368 char *zip_filename_rejected = getStringCat2(zip_filename, ".rejected");
3370 // check if zip file hasn't already been extracted or rejected
3371 if (!fileExists(zip_filename_extracted) &&
3372 !fileExists(zip_filename_rejected))
3374 char *top_dir = ExtractZipFileIntoDirectory(zip_filename, directory,
3376 char *marker_filename = (top_dir != NULL ? zip_filename_extracted :
3377 zip_filename_rejected);
3380 // create empty file to mark zip file as extracted or rejected
3381 if ((marker_file = fopen(marker_filename, MODE_WRITE)))
3382 fclose(marker_file);
3385 free(zip_filename_extracted);
3386 free(zip_filename_rejected);
3390 closeDirectory(dir);
3393 // forward declaration for recursive call by "LoadLevelInfoFromLevelDir()"
3394 static void LoadLevelInfoFromLevelDir(TreeInfo **, TreeInfo *, char *);
3396 static boolean LoadLevelInfoFromLevelConf(TreeInfo **node_first,
3397 TreeInfo *node_parent,
3398 char *level_directory,
3399 char *directory_name)
3401 char *directory_path = getPath2(level_directory, directory_name);
3402 char *filename = getPath2(directory_path, LEVELINFO_FILENAME);
3403 SetupFileHash *setup_file_hash;
3404 LevelDirTree *leveldir_new = NULL;
3407 // unless debugging, silently ignore directories without "levelinfo.conf"
3408 if (!options.debug && !fileExists(filename))
3410 free(directory_path);
3416 setup_file_hash = loadSetupFileHash(filename);
3418 if (setup_file_hash == NULL)
3420 #if DEBUG_NO_CONFIG_FILE
3421 Debug("setup", "ignoring level directory '%s'", directory_path);
3424 free(directory_path);
3430 leveldir_new = newTreeInfo();
3433 setTreeInfoToDefaultsFromParent(leveldir_new, node_parent);
3435 setTreeInfoToDefaults(leveldir_new, TREE_TYPE_LEVEL_DIR);
3437 leveldir_new->subdir = getStringCopy(directory_name);
3439 // set all structure fields according to the token/value pairs
3440 ldi = *leveldir_new;
3441 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
3442 setSetupInfo(levelinfo_tokens, i,
3443 getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
3444 *leveldir_new = ldi;
3446 if (strEqual(leveldir_new->name, ANONYMOUS_NAME))
3447 setString(&leveldir_new->name, leveldir_new->subdir);
3449 if (leveldir_new->identifier == NULL)
3450 leveldir_new->identifier = getStringCopy(leveldir_new->subdir);
3452 if (leveldir_new->name_sorting == NULL)
3453 leveldir_new->name_sorting = getStringCopy(leveldir_new->name);
3455 if (node_parent == NULL) // top level group
3457 leveldir_new->basepath = getStringCopy(level_directory);
3458 leveldir_new->fullpath = getStringCopy(leveldir_new->subdir);
3460 else // sub level group
3462 leveldir_new->basepath = getStringCopy(node_parent->basepath);
3463 leveldir_new->fullpath = getPath2(node_parent->fullpath, directory_name);
3466 leveldir_new->last_level =
3467 leveldir_new->first_level + leveldir_new->levels - 1;
3469 leveldir_new->in_user_dir =
3470 (!strEqual(leveldir_new->basepath, options.level_directory));
3472 // adjust some settings if user's private level directory was detected
3473 if (leveldir_new->sort_priority == LEVELCLASS_UNDEFINED &&
3474 leveldir_new->in_user_dir &&
3475 (strEqual(leveldir_new->subdir, getLoginName()) ||
3476 strEqual(leveldir_new->name, getLoginName()) ||
3477 strEqual(leveldir_new->author, getRealName())))
3479 leveldir_new->sort_priority = LEVELCLASS_PRIVATE_START;
3480 leveldir_new->readonly = FALSE;
3483 leveldir_new->user_defined =
3484 (leveldir_new->in_user_dir && IS_LEVELCLASS_PRIVATE(leveldir_new));
3486 leveldir_new->color = LEVELCOLOR(leveldir_new);
3488 setString(&leveldir_new->class_desc, getLevelClassDescription(leveldir_new));
3490 leveldir_new->handicap_level = // set handicap to default value
3491 (leveldir_new->user_defined || !leveldir_new->handicap ?
3492 leveldir_new->last_level : leveldir_new->first_level);
3494 DrawInitText(leveldir_new->name, 150, FC_YELLOW);
3496 pushTreeInfo(node_first, leveldir_new);
3498 freeSetupFileHash(setup_file_hash);
3500 if (leveldir_new->level_group)
3502 // create node to link back to current level directory
3503 createParentTreeInfoNode(leveldir_new);
3505 // recursively step into sub-directory and look for more level series
3506 LoadLevelInfoFromLevelDir(&leveldir_new->node_group,
3507 leveldir_new, directory_path);
3510 free(directory_path);
3516 static void LoadLevelInfoFromLevelDir(TreeInfo **node_first,
3517 TreeInfo *node_parent,
3518 char *level_directory)
3520 // ---------- 1st stage: process any level set zip files ----------
3522 ProcessZipFilesInDirectory(level_directory, TREE_TYPE_LEVEL_DIR);
3524 // ---------- 2nd stage: check for level set directories ----------
3527 DirectoryEntry *dir_entry;
3528 boolean valid_entry_found = FALSE;
3530 if ((dir = openDirectory(level_directory)) == NULL)
3532 Warn("cannot read level directory '%s'", level_directory);
3537 while ((dir_entry = readDirectory(dir)) != NULL) // loop all entries
3539 char *directory_name = dir_entry->basename;
3540 char *directory_path = getPath2(level_directory, directory_name);
3542 // skip entries for current and parent directory
3543 if (strEqual(directory_name, ".") ||
3544 strEqual(directory_name, ".."))
3546 free(directory_path);
3551 // find out if directory entry is itself a directory
3552 if (!dir_entry->is_directory) // not a directory
3554 free(directory_path);
3559 free(directory_path);
3561 if (strEqual(directory_name, GRAPHICS_DIRECTORY) ||
3562 strEqual(directory_name, SOUNDS_DIRECTORY) ||
3563 strEqual(directory_name, MUSIC_DIRECTORY))
3566 valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
3571 closeDirectory(dir);
3573 // special case: top level directory may directly contain "levelinfo.conf"
3574 if (node_parent == NULL && !valid_entry_found)
3576 // check if this directory directly contains a file "levelinfo.conf"
3577 valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
3578 level_directory, ".");
3581 if (!valid_entry_found)
3582 Warn("cannot find any valid level series in directory '%s'",
3586 boolean AdjustGraphicsForEMC(void)
3588 boolean settings_changed = FALSE;
3590 settings_changed |= adjustTreeGraphicsForEMC(leveldir_first_all);
3591 settings_changed |= adjustTreeGraphicsForEMC(leveldir_first);
3593 return settings_changed;
3596 boolean AdjustSoundsForEMC(void)
3598 boolean settings_changed = FALSE;
3600 settings_changed |= adjustTreeSoundsForEMC(leveldir_first_all);
3601 settings_changed |= adjustTreeSoundsForEMC(leveldir_first);
3603 return settings_changed;
3606 void LoadLevelInfo(void)
3608 InitUserLevelDirectory(getLoginName());
3610 DrawInitText("Loading level series", 120, FC_GREEN);
3612 LoadLevelInfoFromLevelDir(&leveldir_first, NULL, options.level_directory);
3613 LoadLevelInfoFromLevelDir(&leveldir_first, NULL, getUserLevelDir(NULL));
3615 leveldir_first = createTopTreeInfoNode(leveldir_first);
3617 /* after loading all level set information, clone the level directory tree
3618 and remove all level sets without levels (these may still contain artwork
3619 to be offered in the setup menu as "custom artwork", and are therefore
3620 checked for existing artwork in the function "LoadLevelArtworkInfo()") */
3621 leveldir_first_all = leveldir_first;
3622 cloneTree(&leveldir_first, leveldir_first_all, TRUE);
3624 AdjustGraphicsForEMC();
3625 AdjustSoundsForEMC();
3627 // before sorting, the first entries will be from the user directory
3628 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
3630 if (leveldir_first == NULL)
3631 Fail("cannot find any valid level series in any directory");
3633 sortTreeInfo(&leveldir_first);
3635 #if ENABLE_UNUSED_CODE
3636 dumpTreeInfo(leveldir_first, 0);
3640 static boolean LoadArtworkInfoFromArtworkConf(TreeInfo **node_first,
3641 TreeInfo *node_parent,
3642 char *base_directory,
3643 char *directory_name, int type)
3645 char *directory_path = getPath2(base_directory, directory_name);
3646 char *filename = getPath2(directory_path, ARTWORKINFO_FILENAME(type));
3647 SetupFileHash *setup_file_hash = NULL;
3648 TreeInfo *artwork_new = NULL;
3651 if (fileExists(filename))
3652 setup_file_hash = loadSetupFileHash(filename);
3654 if (setup_file_hash == NULL) // no config file -- look for artwork files
3657 DirectoryEntry *dir_entry;
3658 boolean valid_file_found = FALSE;
3660 if ((dir = openDirectory(directory_path)) != NULL)
3662 while ((dir_entry = readDirectory(dir)) != NULL)
3664 if (FileIsArtworkType(dir_entry->filename, type))
3666 valid_file_found = TRUE;
3672 closeDirectory(dir);
3675 if (!valid_file_found)
3677 #if DEBUG_NO_CONFIG_FILE
3678 if (!strEqual(directory_name, "."))
3679 Debug("setup", "ignoring artwork directory '%s'", directory_path);
3682 free(directory_path);
3689 artwork_new = newTreeInfo();
3692 setTreeInfoToDefaultsFromParent(artwork_new, node_parent);
3694 setTreeInfoToDefaults(artwork_new, type);
3696 artwork_new->subdir = getStringCopy(directory_name);
3698 if (setup_file_hash) // (before defining ".color" and ".class_desc")
3700 // set all structure fields according to the token/value pairs
3702 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
3703 setSetupInfo(levelinfo_tokens, i,
3704 getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
3707 if (strEqual(artwork_new->name, ANONYMOUS_NAME))
3708 setString(&artwork_new->name, artwork_new->subdir);
3710 if (artwork_new->identifier == NULL)
3711 artwork_new->identifier = getStringCopy(artwork_new->subdir);
3713 if (artwork_new->name_sorting == NULL)
3714 artwork_new->name_sorting = getStringCopy(artwork_new->name);
3717 if (node_parent == NULL) // top level group
3719 artwork_new->basepath = getStringCopy(base_directory);
3720 artwork_new->fullpath = getStringCopy(artwork_new->subdir);
3722 else // sub level group
3724 artwork_new->basepath = getStringCopy(node_parent->basepath);
3725 artwork_new->fullpath = getPath2(node_parent->fullpath, directory_name);
3728 artwork_new->in_user_dir =
3729 (!strEqual(artwork_new->basepath, OPTIONS_ARTWORK_DIRECTORY(type)));
3731 // (may use ".sort_priority" from "setup_file_hash" above)
3732 artwork_new->color = ARTWORKCOLOR(artwork_new);
3734 setString(&artwork_new->class_desc, getLevelClassDescription(artwork_new));
3736 if (setup_file_hash == NULL) // (after determining ".user_defined")
3738 if (strEqual(artwork_new->subdir, "."))
3740 if (artwork_new->user_defined)
3742 setString(&artwork_new->identifier, "private");
3743 artwork_new->sort_priority = ARTWORKCLASS_PRIVATE;
3747 setString(&artwork_new->identifier, "classic");
3748 artwork_new->sort_priority = ARTWORKCLASS_CLASSICS;
3751 // set to new values after changing ".sort_priority"
3752 artwork_new->color = ARTWORKCOLOR(artwork_new);
3754 setString(&artwork_new->class_desc,
3755 getLevelClassDescription(artwork_new));
3759 setString(&artwork_new->identifier, artwork_new->subdir);
3762 setString(&artwork_new->name, artwork_new->identifier);
3763 setString(&artwork_new->name_sorting, artwork_new->name);
3766 pushTreeInfo(node_first, artwork_new);
3768 freeSetupFileHash(setup_file_hash);
3770 free(directory_path);
3776 static void LoadArtworkInfoFromArtworkDir(TreeInfo **node_first,
3777 TreeInfo *node_parent,
3778 char *base_directory, int type)
3780 // ---------- 1st stage: process any artwork set zip files ----------
3782 ProcessZipFilesInDirectory(base_directory, type);
3784 // ---------- 2nd stage: check for artwork set directories ----------
3787 DirectoryEntry *dir_entry;
3788 boolean valid_entry_found = FALSE;
3790 if ((dir = openDirectory(base_directory)) == NULL)
3792 // display error if directory is main "options.graphics_directory" etc.
3793 if (base_directory == OPTIONS_ARTWORK_DIRECTORY(type))
3794 Warn("cannot read directory '%s'", base_directory);
3799 while ((dir_entry = readDirectory(dir)) != NULL) // loop all entries
3801 char *directory_name = dir_entry->basename;
3802 char *directory_path = getPath2(base_directory, directory_name);
3804 // skip directory entries for current and parent directory
3805 if (strEqual(directory_name, ".") ||
3806 strEqual(directory_name, ".."))
3808 free(directory_path);
3813 // skip directory entries which are not a directory
3814 if (!dir_entry->is_directory) // not a directory
3816 free(directory_path);
3821 free(directory_path);
3823 // check if this directory contains artwork with or without config file
3824 valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
3826 directory_name, type);
3829 closeDirectory(dir);
3831 // check if this directory directly contains artwork itself
3832 valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
3833 base_directory, ".",
3835 if (!valid_entry_found)
3836 Warn("cannot find any valid artwork in directory '%s'", base_directory);
3839 static TreeInfo *getDummyArtworkInfo(int type)
3841 // this is only needed when there is completely no artwork available
3842 TreeInfo *artwork_new = newTreeInfo();
3844 setTreeInfoToDefaults(artwork_new, type);
3846 setString(&artwork_new->subdir, UNDEFINED_FILENAME);
3847 setString(&artwork_new->fullpath, UNDEFINED_FILENAME);
3848 setString(&artwork_new->basepath, UNDEFINED_FILENAME);
3850 setString(&artwork_new->identifier, UNDEFINED_FILENAME);
3851 setString(&artwork_new->name, UNDEFINED_FILENAME);
3852 setString(&artwork_new->name_sorting, UNDEFINED_FILENAME);
3857 void SetCurrentArtwork(int type)
3859 ArtworkDirTree **current_ptr = ARTWORK_CURRENT_PTR(artwork, type);
3860 ArtworkDirTree *first_node = ARTWORK_FIRST_NODE(artwork, type);
3861 char *setup_set = SETUP_ARTWORK_SET(setup, type);
3862 char *default_subdir = ARTWORK_DEFAULT_SUBDIR(type);
3864 // set current artwork to artwork configured in setup menu
3865 *current_ptr = getTreeInfoFromIdentifier(first_node, setup_set);
3867 // if not found, set current artwork to default artwork
3868 if (*current_ptr == NULL)
3869 *current_ptr = getTreeInfoFromIdentifier(first_node, default_subdir);
3871 // if not found, set current artwork to first artwork in tree
3872 if (*current_ptr == NULL)
3873 *current_ptr = getFirstValidTreeInfoEntry(first_node);
3876 void ChangeCurrentArtworkIfNeeded(int type)
3878 char *current_identifier = ARTWORK_CURRENT_IDENTIFIER(artwork, type);
3879 char *setup_set = SETUP_ARTWORK_SET(setup, type);
3881 if (!strEqual(current_identifier, setup_set))
3882 SetCurrentArtwork(type);
3885 void LoadArtworkInfo(void)
3887 LoadArtworkInfoCache();
3889 DrawInitText("Looking for custom artwork", 120, FC_GREEN);
3891 LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
3892 options.graphics_directory,
3893 TREE_TYPE_GRAPHICS_DIR);
3894 LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
3895 getUserGraphicsDir(),
3896 TREE_TYPE_GRAPHICS_DIR);
3898 LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
3899 options.sounds_directory,
3900 TREE_TYPE_SOUNDS_DIR);
3901 LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
3903 TREE_TYPE_SOUNDS_DIR);
3905 LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
3906 options.music_directory,
3907 TREE_TYPE_MUSIC_DIR);
3908 LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
3910 TREE_TYPE_MUSIC_DIR);
3912 if (artwork.gfx_first == NULL)
3913 artwork.gfx_first = getDummyArtworkInfo(TREE_TYPE_GRAPHICS_DIR);
3914 if (artwork.snd_first == NULL)
3915 artwork.snd_first = getDummyArtworkInfo(TREE_TYPE_SOUNDS_DIR);
3916 if (artwork.mus_first == NULL)
3917 artwork.mus_first = getDummyArtworkInfo(TREE_TYPE_MUSIC_DIR);
3919 // before sorting, the first entries will be from the user directory
3920 SetCurrentArtwork(ARTWORK_TYPE_GRAPHICS);
3921 SetCurrentArtwork(ARTWORK_TYPE_SOUNDS);
3922 SetCurrentArtwork(ARTWORK_TYPE_MUSIC);
3924 artwork.gfx_current_identifier = artwork.gfx_current->identifier;
3925 artwork.snd_current_identifier = artwork.snd_current->identifier;
3926 artwork.mus_current_identifier = artwork.mus_current->identifier;
3928 #if ENABLE_UNUSED_CODE
3929 Debug("setup:LoadArtworkInfo", "graphics set == %s",
3930 artwork.gfx_current_identifier);
3931 Debug("setup:LoadArtworkInfo", "sounds set == %s",
3932 artwork.snd_current_identifier);
3933 Debug("setup:LoadArtworkInfo", "music set == %s",
3934 artwork.mus_current_identifier);
3937 sortTreeInfo(&artwork.gfx_first);
3938 sortTreeInfo(&artwork.snd_first);
3939 sortTreeInfo(&artwork.mus_first);
3941 #if ENABLE_UNUSED_CODE
3942 dumpTreeInfo(artwork.gfx_first, 0);
3943 dumpTreeInfo(artwork.snd_first, 0);
3944 dumpTreeInfo(artwork.mus_first, 0);
3948 static void MoveArtworkInfoIntoSubTree(ArtworkDirTree **artwork_node)
3950 ArtworkDirTree *artwork_new = newTreeInfo();
3951 char *top_node_name = "dedicated custom artwork";
3953 setTreeInfoToDefaults(artwork_new, (*artwork_node)->type);
3955 artwork_new->level_group = TRUE;
3957 setString(&artwork_new->identifier, top_node_name);
3958 setString(&artwork_new->name, top_node_name);
3959 setString(&artwork_new->name_sorting, top_node_name);
3961 // create node to link back to current custom artwork directory
3962 createParentTreeInfoNode(artwork_new);
3964 // move existing custom artwork tree into newly created sub-tree
3965 artwork_new->node_group->next = *artwork_node;
3967 // change custom artwork tree to contain only newly created node
3968 *artwork_node = artwork_new;
3971 static void LoadArtworkInfoFromLevelInfoExt(ArtworkDirTree **artwork_node,
3972 ArtworkDirTree *node_parent,
3973 LevelDirTree *level_node,
3974 boolean empty_level_set_mode)
3976 int type = (*artwork_node)->type;
3978 // recursively check all level directories for artwork sub-directories
3982 boolean empty_level_set = (level_node->levels == 0);
3984 // check all tree entries for artwork, but skip parent link entries
3985 if (!level_node->parent_link && empty_level_set == empty_level_set_mode)
3987 TreeInfo *artwork_new = getArtworkInfoCacheEntry(level_node, type);
3988 boolean cached = (artwork_new != NULL);
3992 pushTreeInfo(artwork_node, artwork_new);
3996 TreeInfo *topnode_last = *artwork_node;
3997 char *path = getPath2(getLevelDirFromTreeInfo(level_node),
3998 ARTWORK_DIRECTORY(type));
4000 LoadArtworkInfoFromArtworkDir(artwork_node, NULL, path, type);
4002 if (topnode_last != *artwork_node) // check for newly added node
4004 artwork_new = *artwork_node;
4006 setString(&artwork_new->identifier, level_node->subdir);
4007 setString(&artwork_new->name, level_node->name);
4008 setString(&artwork_new->name_sorting, level_node->name_sorting);
4010 artwork_new->sort_priority = level_node->sort_priority;
4011 artwork_new->color = LEVELCOLOR(artwork_new);
4013 update_artworkinfo_cache = TRUE;
4019 // insert artwork info (from old cache or filesystem) into new cache
4020 if (artwork_new != NULL)
4021 setArtworkInfoCacheEntry(artwork_new, level_node, type);
4024 DrawInitText(level_node->name, 150, FC_YELLOW);
4026 if (level_node->node_group != NULL)
4028 TreeInfo *artwork_new = newTreeInfo();
4031 setTreeInfoToDefaultsFromParent(artwork_new, node_parent);
4033 setTreeInfoToDefaults(artwork_new, type);
4035 artwork_new->level_group = TRUE;
4037 setString(&artwork_new->identifier, level_node->subdir);
4039 if (node_parent == NULL) // check for top tree node
4041 char *top_node_name = (empty_level_set_mode ?
4042 "artwork-only level sets" :
4043 "artwork from level sets");
4045 setString(&artwork_new->name, top_node_name);
4046 setString(&artwork_new->name_sorting, top_node_name);
4050 setString(&artwork_new->name, level_node->name);
4051 setString(&artwork_new->name_sorting, level_node->name_sorting);
4054 pushTreeInfo(artwork_node, artwork_new);
4056 // create node to link back to current custom artwork directory
4057 createParentTreeInfoNode(artwork_new);
4059 // recursively step into sub-directory and look for more custom artwork
4060 LoadArtworkInfoFromLevelInfoExt(&artwork_new->node_group, artwork_new,
4061 level_node->node_group,
4062 empty_level_set_mode);
4064 // if sub-tree has no custom artwork at all, remove it
4065 if (artwork_new->node_group->next == NULL)
4066 removeTreeInfo(artwork_node);
4069 level_node = level_node->next;
4073 static void LoadArtworkInfoFromLevelInfo(ArtworkDirTree **artwork_node)
4075 // move peviously loaded artwork tree into separate sub-tree
4076 MoveArtworkInfoIntoSubTree(artwork_node);
4078 // load artwork from level sets into separate sub-trees
4079 LoadArtworkInfoFromLevelInfoExt(artwork_node, NULL, leveldir_first_all, TRUE);
4080 LoadArtworkInfoFromLevelInfoExt(artwork_node, NULL, leveldir_first_all, FALSE);
4082 // add top tree node over all three separate sub-trees
4083 *artwork_node = createTopTreeInfoNode(*artwork_node);
4085 // set all parent links (back links) in complete artwork tree
4086 setTreeInfoParentNodes(*artwork_node, NULL);
4089 void LoadLevelArtworkInfo(void)
4091 print_timestamp_init("LoadLevelArtworkInfo");
4093 DrawInitText("Looking for custom level artwork", 120, FC_GREEN);
4095 print_timestamp_time("DrawTimeText");
4097 LoadArtworkInfoFromLevelInfo(&artwork.gfx_first);
4098 print_timestamp_time("LoadArtworkInfoFromLevelInfo (gfx)");
4099 LoadArtworkInfoFromLevelInfo(&artwork.snd_first);
4100 print_timestamp_time("LoadArtworkInfoFromLevelInfo (snd)");
4101 LoadArtworkInfoFromLevelInfo(&artwork.mus_first);
4102 print_timestamp_time("LoadArtworkInfoFromLevelInfo (mus)");
4104 SaveArtworkInfoCache();
4106 print_timestamp_time("SaveArtworkInfoCache");
4108 // needed for reloading level artwork not known at ealier stage
4109 ChangeCurrentArtworkIfNeeded(ARTWORK_TYPE_GRAPHICS);
4110 ChangeCurrentArtworkIfNeeded(ARTWORK_TYPE_SOUNDS);
4111 ChangeCurrentArtworkIfNeeded(ARTWORK_TYPE_MUSIC);
4113 print_timestamp_time("getTreeInfoFromIdentifier");
4115 sortTreeInfo(&artwork.gfx_first);
4116 sortTreeInfo(&artwork.snd_first);
4117 sortTreeInfo(&artwork.mus_first);
4119 print_timestamp_time("sortTreeInfo");
4121 #if ENABLE_UNUSED_CODE
4122 dumpTreeInfo(artwork.gfx_first, 0);
4123 dumpTreeInfo(artwork.snd_first, 0);
4124 dumpTreeInfo(artwork.mus_first, 0);
4127 print_timestamp_done("LoadLevelArtworkInfo");
4130 static boolean AddTreeSetToTreeInfoExt(TreeInfo *tree_node_old, char *tree_dir,
4131 char *tree_subdir_new, int type)
4133 if (tree_node_old == NULL)
4135 if (type == TREE_TYPE_LEVEL_DIR)
4137 // get level info tree node of personal user level set
4138 tree_node_old = getTreeInfoFromIdentifier(leveldir_first, getLoginName());
4140 // this may happen if "setup.internal.create_user_levelset" is FALSE
4141 // or if file "levelinfo.conf" is missing in personal user level set
4142 if (tree_node_old == NULL)
4143 tree_node_old = leveldir_first->node_group;
4147 // get artwork info tree node of first artwork set
4148 tree_node_old = ARTWORK_FIRST_NODE(artwork, type);
4152 if (tree_dir == NULL)
4153 tree_dir = TREE_USERDIR(type);
4155 if (tree_node_old == NULL ||
4157 tree_subdir_new == NULL) // should not happen
4160 int draw_deactivation_mask = GetDrawDeactivationMask();
4162 // override draw deactivation mask (temporarily disable drawing)
4163 SetDrawDeactivationMask(REDRAW_ALL);
4165 if (type == TREE_TYPE_LEVEL_DIR)
4167 // load new level set config and add it next to first user level set
4168 LoadLevelInfoFromLevelConf(&tree_node_old->next,
4169 tree_node_old->node_parent,
4170 tree_dir, tree_subdir_new);
4174 // load new artwork set config and add it next to first artwork set
4175 LoadArtworkInfoFromArtworkConf(&tree_node_old->next,
4176 tree_node_old->node_parent,
4177 tree_dir, tree_subdir_new, type);
4180 // set draw deactivation mask to previous value
4181 SetDrawDeactivationMask(draw_deactivation_mask);
4183 // get first node of level or artwork info tree
4184 TreeInfo **tree_node_first = TREE_FIRST_NODE_PTR(type);
4186 // get tree info node of newly added level or artwork set
4187 TreeInfo *tree_node_new = getTreeInfoFromIdentifier(*tree_node_first,
4190 if (tree_node_new == NULL) // should not happen
4193 // correct top link and parent node link of newly created tree node
4194 tree_node_new->node_top = tree_node_old->node_top;
4195 tree_node_new->node_parent = tree_node_old->node_parent;
4197 // sort tree info to adjust position of newly added tree set
4198 sortTreeInfo(tree_node_first);
4203 void AddTreeSetToTreeInfo(TreeInfo *tree_node, char *tree_dir,
4204 char *tree_subdir_new, int type)
4206 if (!AddTreeSetToTreeInfoExt(tree_node, tree_dir, tree_subdir_new, type))
4207 Fail("internal tree info structure corrupted -- aborting");
4210 void AddUserLevelSetToLevelInfo(char *level_subdir_new)
4212 AddTreeSetToTreeInfo(NULL, NULL, level_subdir_new, TREE_TYPE_LEVEL_DIR);
4215 char *getArtworkIdentifierForUserLevelSet(int type)
4217 char *classic_artwork_set = getClassicArtworkSet(type);
4219 // check for custom artwork configured in "levelinfo.conf"
4220 char *leveldir_artwork_set =
4221 *LEVELDIR_ARTWORK_SET_PTR(leveldir_current, type);
4222 boolean has_leveldir_artwork_set =
4223 (leveldir_artwork_set != NULL && !strEqual(leveldir_artwork_set,
4224 classic_artwork_set));
4226 // check for custom artwork in sub-directory "graphics" etc.
4227 TreeInfo *artwork_first_node = ARTWORK_FIRST_NODE(artwork, type);
4228 char *leveldir_identifier = leveldir_current->identifier;
4229 boolean has_artwork_subdir =
4230 (getTreeInfoFromIdentifier(artwork_first_node,
4231 leveldir_identifier) != NULL);
4233 return (has_leveldir_artwork_set ? leveldir_artwork_set :
4234 has_artwork_subdir ? leveldir_identifier :
4235 classic_artwork_set);
4238 TreeInfo *getArtworkTreeInfoForUserLevelSet(int type)
4240 char *artwork_set = getArtworkIdentifierForUserLevelSet(type);
4241 TreeInfo *artwork_first_node = ARTWORK_FIRST_NODE(artwork, type);
4242 TreeInfo *ti = getTreeInfoFromIdentifier(artwork_first_node, artwork_set);
4246 ti = getTreeInfoFromIdentifier(artwork_first_node,
4247 ARTWORK_DEFAULT_SUBDIR(type));
4249 Fail("cannot find default graphics -- should not happen");
4255 boolean checkIfCustomArtworkExistsForCurrentLevelSet(void)
4257 char *graphics_set =
4258 getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_GRAPHICS);
4260 getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_SOUNDS);
4262 getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_MUSIC);
4264 return (!strEqual(graphics_set, GFX_CLASSIC_SUBDIR) ||
4265 !strEqual(sounds_set, SND_CLASSIC_SUBDIR) ||
4266 !strEqual(music_set, MUS_CLASSIC_SUBDIR));
4269 boolean UpdateUserLevelSet(char *level_subdir, char *level_name,
4270 char *level_author, int num_levels)
4272 char *filename = getPath2(getUserLevelDir(level_subdir), LEVELINFO_FILENAME);
4273 char *filename_tmp = getStringCat2(filename, ".tmp");
4275 FILE *file_tmp = NULL;
4276 char line[MAX_LINE_LEN];
4277 boolean success = FALSE;
4278 LevelDirTree *leveldir = getTreeInfoFromIdentifier(leveldir_first,
4280 // update values in level directory tree
4282 if (level_name != NULL)
4283 setString(&leveldir->name, level_name);
4285 if (level_author != NULL)
4286 setString(&leveldir->author, level_author);
4288 if (num_levels != -1)
4289 leveldir->levels = num_levels;
4291 // update values that depend on other values
4293 setString(&leveldir->name_sorting, leveldir->name);
4295 leveldir->last_level = leveldir->first_level + leveldir->levels - 1;
4297 // sort order of level sets may have changed
4298 sortTreeInfo(&leveldir_first);
4300 if ((file = fopen(filename, MODE_READ)) &&
4301 (file_tmp = fopen(filename_tmp, MODE_WRITE)))
4303 while (fgets(line, MAX_LINE_LEN, file))
4305 if (strPrefix(line, "name:") && level_name != NULL)
4306 fprintf(file_tmp, "%-32s%s\n", "name:", level_name);
4307 else if (strPrefix(line, "author:") && level_author != NULL)
4308 fprintf(file_tmp, "%-32s%s\n", "author:", level_author);
4309 else if (strPrefix(line, "levels:") && num_levels != -1)
4310 fprintf(file_tmp, "%-32s%d\n", "levels:", num_levels);
4312 fputs(line, file_tmp);
4325 success = (rename(filename_tmp, filename) == 0);
4333 boolean CreateUserLevelSet(char *level_subdir, char *level_name,
4334 char *level_author, int num_levels,
4335 boolean use_artwork_set)
4337 LevelDirTree *level_info;
4342 // create user level sub-directory, if needed
4343 createDirectory(getUserLevelDir(level_subdir), "user level", PERMS_PRIVATE);
4345 filename = getPath2(getUserLevelDir(level_subdir), LEVELINFO_FILENAME);
4347 if (!(file = fopen(filename, MODE_WRITE)))
4349 Warn("cannot write level info file '%s'", filename);
4356 level_info = newTreeInfo();
4358 // always start with reliable default values
4359 setTreeInfoToDefaults(level_info, TREE_TYPE_LEVEL_DIR);
4361 setString(&level_info->name, level_name);
4362 setString(&level_info->author, level_author);
4363 level_info->levels = num_levels;
4364 level_info->first_level = 1;
4365 level_info->sort_priority = LEVELCLASS_PRIVATE_START;
4366 level_info->readonly = FALSE;
4368 if (use_artwork_set)
4370 level_info->graphics_set =
4371 getStringCopy(getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_GRAPHICS));
4372 level_info->sounds_set =
4373 getStringCopy(getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_SOUNDS));
4374 level_info->music_set =
4375 getStringCopy(getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_MUSIC));
4378 token_value_position = TOKEN_VALUE_POSITION_SHORT;
4380 fprintFileHeader(file, LEVELINFO_FILENAME);
4383 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
4385 if (i == LEVELINFO_TOKEN_NAME ||
4386 i == LEVELINFO_TOKEN_AUTHOR ||
4387 i == LEVELINFO_TOKEN_LEVELS ||
4388 i == LEVELINFO_TOKEN_FIRST_LEVEL ||
4389 i == LEVELINFO_TOKEN_SORT_PRIORITY ||
4390 i == LEVELINFO_TOKEN_READONLY ||
4391 (use_artwork_set && (i == LEVELINFO_TOKEN_GRAPHICS_SET ||
4392 i == LEVELINFO_TOKEN_SOUNDS_SET ||
4393 i == LEVELINFO_TOKEN_MUSIC_SET)))
4394 fprintf(file, "%s\n", getSetupLine(levelinfo_tokens, "", i));
4396 // just to make things nicer :)
4397 if (i == LEVELINFO_TOKEN_AUTHOR ||
4398 i == LEVELINFO_TOKEN_FIRST_LEVEL ||
4399 (use_artwork_set && i == LEVELINFO_TOKEN_READONLY))
4400 fprintf(file, "\n");
4403 token_value_position = TOKEN_VALUE_POSITION_DEFAULT;
4407 SetFilePermissions(filename, PERMS_PRIVATE);
4409 freeTreeInfo(level_info);
4415 static void SaveUserLevelInfo(void)
4417 CreateUserLevelSet(getLoginName(), getLoginName(), getRealName(), 100, FALSE);
4420 char *getSetupValue(int type, void *value)
4422 static char value_string[MAX_LINE_LEN];
4430 strcpy(value_string, (*(boolean *)value ? "true" : "false"));
4434 strcpy(value_string, (*(boolean *)value ? "on" : "off"));
4438 strcpy(value_string, (*(int *)value == AUTO ? "auto" :
4439 *(int *)value == FALSE ? "off" : "on"));
4443 strcpy(value_string, (*(boolean *)value ? "yes" : "no"));
4446 case TYPE_YES_NO_AUTO:
4447 strcpy(value_string, (*(int *)value == AUTO ? "auto" :
4448 *(int *)value == FALSE ? "no" : "yes"));
4452 strcpy(value_string, (*(boolean *)value ? "AGA" : "ECS"));
4456 strcpy(value_string, getKeyNameFromKey(*(Key *)value));
4460 strcpy(value_string, getX11KeyNameFromKey(*(Key *)value));
4464 sprintf(value_string, "%d", *(int *)value);
4468 if (*(char **)value == NULL)
4471 strcpy(value_string, *(char **)value);
4475 sprintf(value_string, "player_%d", *(int *)value + 1);
4479 value_string[0] = '\0';
4483 if (type & TYPE_GHOSTED)
4484 strcpy(value_string, "n/a");
4486 return value_string;
4489 char *getSetupLine(struct TokenInfo *token_info, char *prefix, int token_nr)
4493 static char token_string[MAX_LINE_LEN];
4494 int token_type = token_info[token_nr].type;
4495 void *setup_value = token_info[token_nr].value;
4496 char *token_text = token_info[token_nr].text;
4497 char *value_string = getSetupValue(token_type, setup_value);
4499 // build complete token string
4500 sprintf(token_string, "%s%s", prefix, token_text);
4502 // build setup entry line
4503 line = getFormattedSetupEntry(token_string, value_string);
4505 if (token_type == TYPE_KEY_X11)
4507 Key key = *(Key *)setup_value;
4508 char *keyname = getKeyNameFromKey(key);
4510 // add comment, if useful
4511 if (!strEqual(keyname, "(undefined)") &&
4512 !strEqual(keyname, "(unknown)"))
4514 // add at least one whitespace
4516 for (i = strlen(line); i < token_comment_position; i++)
4520 strcat(line, keyname);
4527 static void InitLastPlayedLevels_ParentNode(void)
4529 LevelDirTree **leveldir_top = &leveldir_first->node_group->next;
4530 LevelDirTree *leveldir_new = NULL;
4532 // check if parent node for last played levels already exists
4533 if (strEqual((*leveldir_top)->identifier, TOKEN_STR_LAST_LEVEL_SERIES))
4536 leveldir_new = newTreeInfo();
4538 setTreeInfoToDefaultsFromParent(leveldir_new, leveldir_first);
4540 leveldir_new->level_group = TRUE;
4542 setString(&leveldir_new->identifier, TOKEN_STR_LAST_LEVEL_SERIES);
4543 setString(&leveldir_new->name, "<< (last played level sets)");
4545 pushTreeInfo(leveldir_top, leveldir_new);
4547 // create node to link back to current level directory
4548 createParentTreeInfoNode(leveldir_new);
4551 void UpdateLastPlayedLevels_TreeInfo(void)
4553 char **last_level_series = setup.level_setup.last_level_series;
4554 boolean reset_leveldir_current = FALSE;
4555 LevelDirTree *leveldir_last;
4556 TreeInfo **node_new = NULL;
4559 if (last_level_series[0] == NULL)
4562 InitLastPlayedLevels_ParentNode();
4564 // check if current level set is from "last played" sub-tree to be rebuilt
4565 reset_leveldir_current = strEqual(leveldir_current->node_parent->identifier,
4566 TOKEN_STR_LAST_LEVEL_SERIES);
4568 leveldir_last = getTreeInfoFromIdentifierExt(leveldir_first,
4569 TOKEN_STR_LAST_LEVEL_SERIES,
4571 if (leveldir_last == NULL)
4574 node_new = &leveldir_last->node_group->next;
4576 freeTreeInfo(*node_new);
4578 for (i = 0; last_level_series[i] != NULL; i++)
4580 LevelDirTree *node_last = getTreeInfoFromIdentifier(leveldir_first,
4581 last_level_series[i]);
4583 *node_new = getTreeInfoCopy(node_last); // copy complete node
4585 (*node_new)->node_top = &leveldir_first; // correct top node link
4586 (*node_new)->node_parent = leveldir_last; // correct parent node link
4588 (*node_new)->node_group = NULL;
4589 (*node_new)->next = NULL;
4591 (*node_new)->cl_first = -1; // force setting tree cursor
4593 node_new = &((*node_new)->next);
4596 if (reset_leveldir_current)
4597 leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
4598 last_level_series[0]);
4601 static void UpdateLastPlayedLevels_List(void)
4603 char **last_level_series = setup.level_setup.last_level_series;
4604 int pos = MAX_LEVELDIR_HISTORY - 1;
4607 // search for potentially already existing entry in list of level sets
4608 for (i = 0; last_level_series[i] != NULL; i++)
4609 if (strEqual(last_level_series[i], leveldir_current->identifier))
4612 // move list of level sets one entry down (using potentially free entry)
4613 for (i = pos; i > 0; i--)
4614 setString(&last_level_series[i], last_level_series[i - 1]);
4616 // put last played level set at top position
4617 setString(&last_level_series[0], leveldir_current->identifier);
4620 void LoadLevelSetup_LastSeries(void)
4622 // --------------------------------------------------------------------------
4623 // ~/.<program>/levelsetup.conf
4624 // --------------------------------------------------------------------------
4626 char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
4627 SetupFileHash *level_setup_hash = NULL;
4631 // always start with reliable default values
4632 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
4634 // start with empty history of last played level sets
4635 setString(&setup.level_setup.last_level_series[0], NULL);
4637 if (!strEqual(DEFAULT_LEVELSET, UNDEFINED_LEVELSET))
4639 leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
4641 if (leveldir_current == NULL)
4642 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
4645 if ((level_setup_hash = loadSetupFileHash(filename)))
4647 char *last_level_series =
4648 getHashEntry(level_setup_hash, TOKEN_STR_LAST_LEVEL_SERIES);
4650 leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
4652 if (leveldir_current == NULL)
4653 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
4655 for (i = 0; i < MAX_LEVELDIR_HISTORY; i++)
4657 char token[strlen(TOKEN_STR_LAST_LEVEL_SERIES) + 10];
4658 LevelDirTree *leveldir_last;
4660 sprintf(token, "%s.%03d", TOKEN_STR_LAST_LEVEL_SERIES, i);
4662 last_level_series = getHashEntry(level_setup_hash, token);
4664 leveldir_last = getTreeInfoFromIdentifier(leveldir_first,
4666 if (leveldir_last != NULL)
4667 setString(&setup.level_setup.last_level_series[pos++],
4671 setString(&setup.level_setup.last_level_series[pos], NULL);
4673 freeSetupFileHash(level_setup_hash);
4677 Debug("setup", "using default setup values");
4683 static void SaveLevelSetup_LastSeries_Ext(boolean deactivate_last_level_series)
4685 // --------------------------------------------------------------------------
4686 // ~/.<program>/levelsetup.conf
4687 // --------------------------------------------------------------------------
4689 // check if the current level directory structure is available at this point
4690 if (leveldir_current == NULL)
4693 char **last_level_series = setup.level_setup.last_level_series;
4694 char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
4698 InitUserDataDirectory();
4700 UpdateLastPlayedLevels_List();
4702 if (!(file = fopen(filename, MODE_WRITE)))
4704 Warn("cannot write setup file '%s'", filename);
4711 fprintFileHeader(file, LEVELSETUP_FILENAME);
4713 if (deactivate_last_level_series)
4714 fprintf(file, "# %s\n# ", "the following level set may have caused a problem and was deactivated");
4716 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_LAST_LEVEL_SERIES,
4717 leveldir_current->identifier));
4719 for (i = 0; last_level_series[i] != NULL; i++)
4721 char token[strlen(TOKEN_STR_LAST_LEVEL_SERIES) + 10];
4723 sprintf(token, "%s.%03d", TOKEN_STR_LAST_LEVEL_SERIES, i);
4725 fprintf(file, "%s\n", getFormattedSetupEntry(token, last_level_series[i]));
4730 SetFilePermissions(filename, PERMS_PRIVATE);
4735 void SaveLevelSetup_LastSeries(void)
4737 SaveLevelSetup_LastSeries_Ext(FALSE);
4740 void SaveLevelSetup_LastSeries_Deactivate(void)
4742 SaveLevelSetup_LastSeries_Ext(TRUE);
4745 static void checkSeriesInfo(void)
4747 static char *level_directory = NULL;
4750 DirectoryEntry *dir_entry;
4753 checked_free(level_directory);
4755 // check for more levels besides the 'levels' field of 'levelinfo.conf'
4757 level_directory = getPath2((leveldir_current->in_user_dir ?
4758 getUserLevelDir(NULL) :
4759 options.level_directory),
4760 leveldir_current->fullpath);
4762 if ((dir = openDirectory(level_directory)) == NULL)
4764 Warn("cannot read level directory '%s'", level_directory);
4770 while ((dir_entry = readDirectory(dir)) != NULL) // loop all entries
4772 if (strlen(dir_entry->basename) > 4 &&
4773 dir_entry->basename[3] == '.' &&
4774 strEqual(&dir_entry->basename[4], LEVELFILE_EXTENSION))
4776 char levelnum_str[4];
4779 strncpy(levelnum_str, dir_entry->basename, 3);
4780 levelnum_str[3] = '\0';
4782 levelnum_value = atoi(levelnum_str);
4784 if (levelnum_value < leveldir_current->first_level)
4786 Warn("additional level %d found", levelnum_value);
4788 leveldir_current->first_level = levelnum_value;
4790 else if (levelnum_value > leveldir_current->last_level)
4792 Warn("additional level %d found", levelnum_value);
4794 leveldir_current->last_level = levelnum_value;
4800 closeDirectory(dir);
4803 void LoadLevelSetup_SeriesInfo(void)
4806 SetupFileHash *level_setup_hash = NULL;
4807 char *level_subdir = leveldir_current->subdir;
4810 // always start with reliable default values
4811 level_nr = leveldir_current->first_level;
4813 for (i = 0; i < MAX_LEVELS; i++)
4815 LevelStats_setPlayed(i, 0);
4816 LevelStats_setSolved(i, 0);
4821 // --------------------------------------------------------------------------
4822 // ~/.<program>/levelsetup/<level series>/levelsetup.conf
4823 // --------------------------------------------------------------------------
4825 level_subdir = leveldir_current->subdir;
4827 filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
4829 if ((level_setup_hash = loadSetupFileHash(filename)))
4833 // get last played level in this level set
4835 token_value = getHashEntry(level_setup_hash, TOKEN_STR_LAST_PLAYED_LEVEL);
4839 level_nr = atoi(token_value);
4841 if (level_nr < leveldir_current->first_level)
4842 level_nr = leveldir_current->first_level;
4843 if (level_nr > leveldir_current->last_level)
4844 level_nr = leveldir_current->last_level;
4847 // get handicap level in this level set
4849 token_value = getHashEntry(level_setup_hash, TOKEN_STR_HANDICAP_LEVEL);
4853 int level_nr = atoi(token_value);
4855 if (level_nr < leveldir_current->first_level)
4856 level_nr = leveldir_current->first_level;
4857 if (level_nr > leveldir_current->last_level + 1)
4858 level_nr = leveldir_current->last_level;
4860 if (leveldir_current->user_defined || !leveldir_current->handicap)
4861 level_nr = leveldir_current->last_level;
4863 leveldir_current->handicap_level = level_nr;
4866 // get number of played and solved levels in this level set
4868 BEGIN_HASH_ITERATION(level_setup_hash, itr)
4870 char *token = HASH_ITERATION_TOKEN(itr);
4871 char *value = HASH_ITERATION_VALUE(itr);
4873 if (strlen(token) == 3 &&
4874 token[0] >= '0' && token[0] <= '9' &&
4875 token[1] >= '0' && token[1] <= '9' &&
4876 token[2] >= '0' && token[2] <= '9')
4878 int level_nr = atoi(token);
4881 LevelStats_setPlayed(level_nr, atoi(value)); // read 1st column
4883 value = strchr(value, ' ');
4886 LevelStats_setSolved(level_nr, atoi(value)); // read 2nd column
4889 END_HASH_ITERATION(hash, itr)
4891 freeSetupFileHash(level_setup_hash);
4895 Debug("setup", "using default setup values");
4901 void SaveLevelSetup_SeriesInfo(void)
4904 char *level_subdir = leveldir_current->subdir;
4905 char *level_nr_str = int2str(level_nr, 0);
4906 char *handicap_level_str = int2str(leveldir_current->handicap_level, 0);
4910 // --------------------------------------------------------------------------
4911 // ~/.<program>/levelsetup/<level series>/levelsetup.conf
4912 // --------------------------------------------------------------------------
4914 InitLevelSetupDirectory(level_subdir);
4916 filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
4918 if (!(file = fopen(filename, MODE_WRITE)))
4920 Warn("cannot write setup file '%s'", filename);
4927 fprintFileHeader(file, LEVELSETUP_FILENAME);
4929 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_PLAYED_LEVEL,
4931 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_HANDICAP_LEVEL,
4932 handicap_level_str));
4934 for (i = leveldir_current->first_level; i <= leveldir_current->last_level;
4937 if (LevelStats_getPlayed(i) > 0 ||
4938 LevelStats_getSolved(i) > 0)
4943 sprintf(token, "%03d", i);
4944 sprintf(value, "%d %d", LevelStats_getPlayed(i), LevelStats_getSolved(i));
4946 fprintf(file, "%s\n", getFormattedSetupEntry(token, value));
4952 SetFilePermissions(filename, PERMS_PRIVATE);
4957 int LevelStats_getPlayed(int nr)
4959 return (nr >= 0 && nr < MAX_LEVELS ? level_stats[nr].played : 0);
4962 int LevelStats_getSolved(int nr)
4964 return (nr >= 0 && nr < MAX_LEVELS ? level_stats[nr].solved : 0);
4967 void LevelStats_setPlayed(int nr, int value)
4969 if (nr >= 0 && nr < MAX_LEVELS)
4970 level_stats[nr].played = value;
4973 void LevelStats_setSolved(int nr, int value)
4975 if (nr >= 0 && nr < MAX_LEVELS)
4976 level_stats[nr].solved = value;
4979 void LevelStats_incPlayed(int nr)
4981 if (nr >= 0 && nr < MAX_LEVELS)
4982 level_stats[nr].played++;
4985 void LevelStats_incSolved(int nr)
4987 if (nr >= 0 && nr < MAX_LEVELS)
4988 level_stats[nr].solved++;
4991 void LoadUserSetup(void)
4993 // --------------------------------------------------------------------------
4994 // ~/.<program>/usersetup.conf
4995 // --------------------------------------------------------------------------
4997 char *filename = getPath2(getMainUserGameDataDir(), USERSETUP_FILENAME);
4998 SetupFileHash *user_setup_hash = NULL;
5000 // always start with reliable default values
5003 if ((user_setup_hash = loadSetupFileHash(filename)))
5007 // get last selected user number
5008 token_value = getHashEntry(user_setup_hash, TOKEN_STR_LAST_USER);
5011 user.nr = MIN(MAX(0, atoi(token_value)), MAX_PLAYER_NAMES - 1);
5013 freeSetupFileHash(user_setup_hash);
5017 Debug("setup", "using default setup values");
5023 void SaveUserSetup(void)
5025 // --------------------------------------------------------------------------
5026 // ~/.<program>/usersetup.conf
5027 // --------------------------------------------------------------------------
5029 char *filename = getPath2(getMainUserGameDataDir(), USERSETUP_FILENAME);
5032 InitMainUserDataDirectory();
5034 if (!(file = fopen(filename, MODE_WRITE)))
5036 Warn("cannot write setup file '%s'", filename);
5043 fprintFileHeader(file, USERSETUP_FILENAME);
5045 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_USER,
5049 SetFilePermissions(filename, PERMS_PRIVATE);