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 int compareSetupFileData(const void *object1, const void *object2)
2331 const struct ConfigInfo *entry1 = (struct ConfigInfo *)object1;
2332 const struct ConfigInfo *entry2 = (struct ConfigInfo *)object2;
2334 return strcmp(entry1->token, entry2->token);
2337 static void saveSetupFileHash(SetupFileHash *hash, char *filename)
2339 int item_count = hashtable_count(hash);
2340 int item_size = sizeof(struct ConfigInfo);
2341 struct ConfigInfo *sort_array = checked_malloc(item_count * item_size);
2345 // copy string pointers from hash to array
2346 BEGIN_HASH_ITERATION(hash, itr)
2348 sort_array[i].token = HASH_ITERATION_TOKEN(itr);
2349 sort_array[i].value = HASH_ITERATION_VALUE(itr);
2353 if (i > item_count) // should never happen
2356 END_HASH_ITERATION(hash, itr)
2358 // sort string pointers from hash in array
2359 qsort(sort_array, item_count, item_size, compareSetupFileData);
2361 if (!(file = fopen(filename, MODE_WRITE)))
2363 Warn("cannot write configuration file '%s'", filename);
2368 for (i = 0; i < item_count; i++)
2369 fprintf(file, "%s\n", getFormattedSetupEntry(sort_array[i].token,
2370 sort_array[i].value));
2373 checked_free(sort_array);
2376 SetupFileList *loadSetupFileList(char *filename)
2378 SetupFileList *setup_file_list = newSetupFileList("", "");
2379 SetupFileList *first_valid_list_entry;
2381 if (!loadSetupFileData(setup_file_list, filename, TRUE, FALSE))
2383 freeSetupFileList(setup_file_list);
2388 first_valid_list_entry = setup_file_list->next;
2390 // free empty list header
2391 setup_file_list->next = NULL;
2392 freeSetupFileList(setup_file_list);
2394 return first_valid_list_entry;
2397 SetupFileHash *loadSetupFileHash(char *filename)
2399 SetupFileHash *setup_file_hash = newSetupFileHash();
2401 if (!loadSetupFileData(setup_file_hash, filename, TRUE, TRUE))
2403 freeSetupFileHash(setup_file_hash);
2408 return setup_file_hash;
2412 // ============================================================================
2414 // ============================================================================
2416 #define TOKEN_STR_LAST_LEVEL_SERIES "last_level_series"
2417 #define TOKEN_STR_LAST_PLAYED_LEVEL "last_played_level"
2418 #define TOKEN_STR_HANDICAP_LEVEL "handicap_level"
2419 #define TOKEN_STR_LAST_USER "last_user"
2421 // level directory info
2422 #define LEVELINFO_TOKEN_IDENTIFIER 0
2423 #define LEVELINFO_TOKEN_NAME 1
2424 #define LEVELINFO_TOKEN_NAME_SORTING 2
2425 #define LEVELINFO_TOKEN_AUTHOR 3
2426 #define LEVELINFO_TOKEN_YEAR 4
2427 #define LEVELINFO_TOKEN_PROGRAM_TITLE 5
2428 #define LEVELINFO_TOKEN_PROGRAM_COPYRIGHT 6
2429 #define LEVELINFO_TOKEN_PROGRAM_COMPANY 7
2430 #define LEVELINFO_TOKEN_IMPORTED_FROM 8
2431 #define LEVELINFO_TOKEN_IMPORTED_BY 9
2432 #define LEVELINFO_TOKEN_TESTED_BY 10
2433 #define LEVELINFO_TOKEN_LEVELS 11
2434 #define LEVELINFO_TOKEN_FIRST_LEVEL 12
2435 #define LEVELINFO_TOKEN_SORT_PRIORITY 13
2436 #define LEVELINFO_TOKEN_LATEST_ENGINE 14
2437 #define LEVELINFO_TOKEN_LEVEL_GROUP 15
2438 #define LEVELINFO_TOKEN_READONLY 16
2439 #define LEVELINFO_TOKEN_GRAPHICS_SET_ECS 17
2440 #define LEVELINFO_TOKEN_GRAPHICS_SET_AGA 18
2441 #define LEVELINFO_TOKEN_GRAPHICS_SET 19
2442 #define LEVELINFO_TOKEN_SOUNDS_SET_DEFAULT 20
2443 #define LEVELINFO_TOKEN_SOUNDS_SET_LOWPASS 21
2444 #define LEVELINFO_TOKEN_SOUNDS_SET 22
2445 #define LEVELINFO_TOKEN_MUSIC_SET 23
2446 #define LEVELINFO_TOKEN_FILENAME 24
2447 #define LEVELINFO_TOKEN_FILETYPE 25
2448 #define LEVELINFO_TOKEN_SPECIAL_FLAGS 26
2449 #define LEVELINFO_TOKEN_HANDICAP 27
2450 #define LEVELINFO_TOKEN_SKIP_LEVELS 28
2451 #define LEVELINFO_TOKEN_USE_EMC_TILES 29
2453 #define NUM_LEVELINFO_TOKENS 30
2455 static LevelDirTree ldi;
2457 static struct TokenInfo levelinfo_tokens[] =
2459 // level directory info
2460 { TYPE_STRING, &ldi.identifier, "identifier" },
2461 { TYPE_STRING, &ldi.name, "name" },
2462 { TYPE_STRING, &ldi.name_sorting, "name_sorting" },
2463 { TYPE_STRING, &ldi.author, "author" },
2464 { TYPE_STRING, &ldi.year, "year" },
2465 { TYPE_STRING, &ldi.program_title, "program_title" },
2466 { TYPE_STRING, &ldi.program_copyright, "program_copyright" },
2467 { TYPE_STRING, &ldi.program_company, "program_company" },
2468 { TYPE_STRING, &ldi.imported_from, "imported_from" },
2469 { TYPE_STRING, &ldi.imported_by, "imported_by" },
2470 { TYPE_STRING, &ldi.tested_by, "tested_by" },
2471 { TYPE_INTEGER, &ldi.levels, "levels" },
2472 { TYPE_INTEGER, &ldi.first_level, "first_level" },
2473 { TYPE_INTEGER, &ldi.sort_priority, "sort_priority" },
2474 { TYPE_BOOLEAN, &ldi.latest_engine, "latest_engine" },
2475 { TYPE_BOOLEAN, &ldi.level_group, "level_group" },
2476 { TYPE_BOOLEAN, &ldi.readonly, "readonly" },
2477 { TYPE_STRING, &ldi.graphics_set_ecs, "graphics_set.ecs" },
2478 { TYPE_STRING, &ldi.graphics_set_aga, "graphics_set.aga" },
2479 { TYPE_STRING, &ldi.graphics_set, "graphics_set" },
2480 { TYPE_STRING, &ldi.sounds_set_default,"sounds_set.default" },
2481 { TYPE_STRING, &ldi.sounds_set_lowpass,"sounds_set.lowpass" },
2482 { TYPE_STRING, &ldi.sounds_set, "sounds_set" },
2483 { TYPE_STRING, &ldi.music_set, "music_set" },
2484 { TYPE_STRING, &ldi.level_filename, "filename" },
2485 { TYPE_STRING, &ldi.level_filetype, "filetype" },
2486 { TYPE_STRING, &ldi.special_flags, "special_flags" },
2487 { TYPE_BOOLEAN, &ldi.handicap, "handicap" },
2488 { TYPE_BOOLEAN, &ldi.skip_levels, "skip_levels" },
2489 { TYPE_BOOLEAN, &ldi.use_emc_tiles, "use_emc_tiles" }
2492 static struct TokenInfo artworkinfo_tokens[] =
2494 // artwork directory info
2495 { TYPE_STRING, &ldi.identifier, "identifier" },
2496 { TYPE_STRING, &ldi.subdir, "subdir" },
2497 { TYPE_STRING, &ldi.name, "name" },
2498 { TYPE_STRING, &ldi.name_sorting, "name_sorting" },
2499 { TYPE_STRING, &ldi.author, "author" },
2500 { TYPE_STRING, &ldi.program_title, "program_title" },
2501 { TYPE_STRING, &ldi.program_copyright, "program_copyright" },
2502 { TYPE_STRING, &ldi.program_company, "program_company" },
2503 { TYPE_INTEGER, &ldi.sort_priority, "sort_priority" },
2504 { TYPE_STRING, &ldi.basepath, "basepath" },
2505 { TYPE_STRING, &ldi.fullpath, "fullpath" },
2506 { TYPE_BOOLEAN, &ldi.in_user_dir, "in_user_dir" },
2507 { TYPE_INTEGER, &ldi.color, "color" },
2508 { TYPE_STRING, &ldi.class_desc, "class_desc" },
2513 static char *optional_tokens[] =
2516 "program_copyright",
2522 static void setTreeInfoToDefaults(TreeInfo *ti, int type)
2526 ti->node_top = (ti->type == TREE_TYPE_LEVEL_DIR ? &leveldir_first :
2527 ti->type == TREE_TYPE_GRAPHICS_DIR ? &artwork.gfx_first :
2528 ti->type == TREE_TYPE_SOUNDS_DIR ? &artwork.snd_first :
2529 ti->type == TREE_TYPE_MUSIC_DIR ? &artwork.mus_first :
2532 ti->node_parent = NULL;
2533 ti->node_group = NULL;
2540 ti->fullpath = NULL;
2541 ti->basepath = NULL;
2542 ti->identifier = NULL;
2543 ti->name = getStringCopy(ANONYMOUS_NAME);
2544 ti->name_sorting = NULL;
2545 ti->author = getStringCopy(ANONYMOUS_NAME);
2548 ti->program_title = NULL;
2549 ti->program_copyright = NULL;
2550 ti->program_company = NULL;
2552 ti->sort_priority = LEVELCLASS_UNDEFINED; // default: least priority
2553 ti->latest_engine = FALSE; // default: get from level
2554 ti->parent_link = FALSE;
2555 ti->in_user_dir = FALSE;
2556 ti->user_defined = FALSE;
2558 ti->class_desc = NULL;
2560 ti->infotext = getStringCopy(TREE_INFOTEXT(ti->type));
2562 if (ti->type == TREE_TYPE_LEVEL_DIR)
2564 ti->imported_from = NULL;
2565 ti->imported_by = NULL;
2566 ti->tested_by = NULL;
2568 ti->graphics_set_ecs = NULL;
2569 ti->graphics_set_aga = NULL;
2570 ti->graphics_set = NULL;
2571 ti->sounds_set_default = NULL;
2572 ti->sounds_set_lowpass = NULL;
2573 ti->sounds_set = NULL;
2574 ti->music_set = NULL;
2575 ti->graphics_path = getStringCopy(UNDEFINED_FILENAME);
2576 ti->sounds_path = getStringCopy(UNDEFINED_FILENAME);
2577 ti->music_path = getStringCopy(UNDEFINED_FILENAME);
2579 ti->level_filename = NULL;
2580 ti->level_filetype = NULL;
2582 ti->special_flags = NULL;
2585 ti->first_level = 0;
2587 ti->level_group = FALSE;
2588 ti->handicap_level = 0;
2589 ti->readonly = TRUE;
2590 ti->handicap = TRUE;
2591 ti->skip_levels = FALSE;
2593 ti->use_emc_tiles = FALSE;
2597 static void setTreeInfoToDefaultsFromParent(TreeInfo *ti, TreeInfo *parent)
2601 Warn("setTreeInfoToDefaultsFromParent(): parent == NULL");
2603 setTreeInfoToDefaults(ti, TREE_TYPE_UNDEFINED);
2608 // copy all values from the parent structure
2610 ti->type = parent->type;
2612 ti->node_top = parent->node_top;
2613 ti->node_parent = parent;
2614 ti->node_group = NULL;
2621 ti->fullpath = NULL;
2622 ti->basepath = NULL;
2623 ti->identifier = NULL;
2624 ti->name = getStringCopy(ANONYMOUS_NAME);
2625 ti->name_sorting = NULL;
2626 ti->author = getStringCopy(parent->author);
2627 ti->year = getStringCopy(parent->year);
2629 ti->program_title = getStringCopy(parent->program_title);
2630 ti->program_copyright = getStringCopy(parent->program_copyright);
2631 ti->program_company = getStringCopy(parent->program_company);
2633 ti->sort_priority = parent->sort_priority;
2634 ti->latest_engine = parent->latest_engine;
2635 ti->parent_link = FALSE;
2636 ti->in_user_dir = parent->in_user_dir;
2637 ti->user_defined = parent->user_defined;
2638 ti->color = parent->color;
2639 ti->class_desc = getStringCopy(parent->class_desc);
2641 ti->infotext = getStringCopy(parent->infotext);
2643 if (ti->type == TREE_TYPE_LEVEL_DIR)
2645 ti->imported_from = getStringCopy(parent->imported_from);
2646 ti->imported_by = getStringCopy(parent->imported_by);
2647 ti->tested_by = getStringCopy(parent->tested_by);
2649 ti->graphics_set_ecs = getStringCopy(parent->graphics_set_ecs);
2650 ti->graphics_set_aga = getStringCopy(parent->graphics_set_aga);
2651 ti->graphics_set = getStringCopy(parent->graphics_set);
2652 ti->sounds_set_default = getStringCopy(parent->sounds_set_default);
2653 ti->sounds_set_lowpass = getStringCopy(parent->sounds_set_lowpass);
2654 ti->sounds_set = getStringCopy(parent->sounds_set);
2655 ti->music_set = getStringCopy(parent->music_set);
2656 ti->graphics_path = getStringCopy(UNDEFINED_FILENAME);
2657 ti->sounds_path = getStringCopy(UNDEFINED_FILENAME);
2658 ti->music_path = getStringCopy(UNDEFINED_FILENAME);
2660 ti->level_filename = getStringCopy(parent->level_filename);
2661 ti->level_filetype = getStringCopy(parent->level_filetype);
2663 ti->special_flags = getStringCopy(parent->special_flags);
2665 ti->levels = parent->levels;
2666 ti->first_level = parent->first_level;
2667 ti->last_level = parent->last_level;
2668 ti->level_group = FALSE;
2669 ti->handicap_level = parent->handicap_level;
2670 ti->readonly = parent->readonly;
2671 ti->handicap = parent->handicap;
2672 ti->skip_levels = parent->skip_levels;
2674 ti->use_emc_tiles = parent->use_emc_tiles;
2678 static TreeInfo *getTreeInfoCopy(TreeInfo *ti)
2680 TreeInfo *ti_copy = newTreeInfo();
2682 // copy all values from the original structure
2684 ti_copy->type = ti->type;
2686 ti_copy->node_top = ti->node_top;
2687 ti_copy->node_parent = ti->node_parent;
2688 ti_copy->node_group = ti->node_group;
2689 ti_copy->next = ti->next;
2691 ti_copy->cl_first = ti->cl_first;
2692 ti_copy->cl_cursor = ti->cl_cursor;
2694 ti_copy->subdir = getStringCopy(ti->subdir);
2695 ti_copy->fullpath = getStringCopy(ti->fullpath);
2696 ti_copy->basepath = getStringCopy(ti->basepath);
2697 ti_copy->identifier = getStringCopy(ti->identifier);
2698 ti_copy->name = getStringCopy(ti->name);
2699 ti_copy->name_sorting = getStringCopy(ti->name_sorting);
2700 ti_copy->author = getStringCopy(ti->author);
2701 ti_copy->year = getStringCopy(ti->year);
2703 ti_copy->program_title = getStringCopy(ti->program_title);
2704 ti_copy->program_copyright = getStringCopy(ti->program_copyright);
2705 ti_copy->program_company = getStringCopy(ti->program_company);
2707 ti_copy->imported_from = getStringCopy(ti->imported_from);
2708 ti_copy->imported_by = getStringCopy(ti->imported_by);
2709 ti_copy->tested_by = getStringCopy(ti->tested_by);
2711 ti_copy->graphics_set_ecs = getStringCopy(ti->graphics_set_ecs);
2712 ti_copy->graphics_set_aga = getStringCopy(ti->graphics_set_aga);
2713 ti_copy->graphics_set = getStringCopy(ti->graphics_set);
2714 ti_copy->sounds_set_default = getStringCopy(ti->sounds_set_default);
2715 ti_copy->sounds_set_lowpass = getStringCopy(ti->sounds_set_lowpass);
2716 ti_copy->sounds_set = getStringCopy(ti->sounds_set);
2717 ti_copy->music_set = getStringCopy(ti->music_set);
2718 ti_copy->graphics_path = getStringCopy(ti->graphics_path);
2719 ti_copy->sounds_path = getStringCopy(ti->sounds_path);
2720 ti_copy->music_path = getStringCopy(ti->music_path);
2722 ti_copy->level_filename = getStringCopy(ti->level_filename);
2723 ti_copy->level_filetype = getStringCopy(ti->level_filetype);
2725 ti_copy->special_flags = getStringCopy(ti->special_flags);
2727 ti_copy->levels = ti->levels;
2728 ti_copy->first_level = ti->first_level;
2729 ti_copy->last_level = ti->last_level;
2730 ti_copy->sort_priority = ti->sort_priority;
2732 ti_copy->latest_engine = ti->latest_engine;
2734 ti_copy->level_group = ti->level_group;
2735 ti_copy->parent_link = ti->parent_link;
2736 ti_copy->in_user_dir = ti->in_user_dir;
2737 ti_copy->user_defined = ti->user_defined;
2738 ti_copy->readonly = ti->readonly;
2739 ti_copy->handicap = ti->handicap;
2740 ti_copy->skip_levels = ti->skip_levels;
2742 ti_copy->use_emc_tiles = ti->use_emc_tiles;
2744 ti_copy->color = ti->color;
2745 ti_copy->class_desc = getStringCopy(ti->class_desc);
2746 ti_copy->handicap_level = ti->handicap_level;
2748 ti_copy->infotext = getStringCopy(ti->infotext);
2753 void freeTreeInfo(TreeInfo *ti)
2758 checked_free(ti->subdir);
2759 checked_free(ti->fullpath);
2760 checked_free(ti->basepath);
2761 checked_free(ti->identifier);
2763 checked_free(ti->name);
2764 checked_free(ti->name_sorting);
2765 checked_free(ti->author);
2766 checked_free(ti->year);
2768 checked_free(ti->program_title);
2769 checked_free(ti->program_copyright);
2770 checked_free(ti->program_company);
2772 checked_free(ti->class_desc);
2774 checked_free(ti->infotext);
2776 if (ti->type == TREE_TYPE_LEVEL_DIR)
2778 checked_free(ti->imported_from);
2779 checked_free(ti->imported_by);
2780 checked_free(ti->tested_by);
2782 checked_free(ti->graphics_set_ecs);
2783 checked_free(ti->graphics_set_aga);
2784 checked_free(ti->graphics_set);
2785 checked_free(ti->sounds_set_default);
2786 checked_free(ti->sounds_set_lowpass);
2787 checked_free(ti->sounds_set);
2788 checked_free(ti->music_set);
2790 checked_free(ti->graphics_path);
2791 checked_free(ti->sounds_path);
2792 checked_free(ti->music_path);
2794 checked_free(ti->level_filename);
2795 checked_free(ti->level_filetype);
2797 checked_free(ti->special_flags);
2800 // recursively free child node
2802 freeTreeInfo(ti->node_group);
2804 // recursively free next node
2806 freeTreeInfo(ti->next);
2811 void setSetupInfo(struct TokenInfo *token_info,
2812 int token_nr, char *token_value)
2814 int token_type = token_info[token_nr].type;
2815 void *setup_value = token_info[token_nr].value;
2817 if (token_value == NULL)
2820 // set setup field to corresponding token value
2825 *(boolean *)setup_value = get_boolean_from_string(token_value);
2829 *(int *)setup_value = get_switch3_from_string(token_value);
2833 *(Key *)setup_value = getKeyFromKeyName(token_value);
2837 *(Key *)setup_value = getKeyFromX11KeyName(token_value);
2841 *(int *)setup_value = get_integer_from_string(token_value);
2845 checked_free(*(char **)setup_value);
2846 *(char **)setup_value = getStringCopy(token_value);
2850 *(int *)setup_value = get_player_nr_from_string(token_value);
2858 static int compareTreeInfoEntries(const void *object1, const void *object2)
2860 const TreeInfo *entry1 = *((TreeInfo **)object1);
2861 const TreeInfo *entry2 = *((TreeInfo **)object2);
2862 int class_sorting1 = 0, class_sorting2 = 0;
2865 if (entry1->type == TREE_TYPE_LEVEL_DIR)
2867 class_sorting1 = LEVELSORTING(entry1);
2868 class_sorting2 = LEVELSORTING(entry2);
2870 else if (entry1->type == TREE_TYPE_GRAPHICS_DIR ||
2871 entry1->type == TREE_TYPE_SOUNDS_DIR ||
2872 entry1->type == TREE_TYPE_MUSIC_DIR)
2874 class_sorting1 = ARTWORKSORTING(entry1);
2875 class_sorting2 = ARTWORKSORTING(entry2);
2878 if (entry1->parent_link || entry2->parent_link)
2879 compare_result = (entry1->parent_link ? -1 : +1);
2880 else if (entry1->sort_priority == entry2->sort_priority)
2881 compare_result = strcasecmp(entry1->name_sorting, entry2->name_sorting);
2882 else if (class_sorting1 == class_sorting2)
2883 compare_result = entry1->sort_priority - entry2->sort_priority;
2885 compare_result = class_sorting1 - class_sorting2;
2887 return compare_result;
2890 static TreeInfo *createParentTreeInfoNode(TreeInfo *node_parent)
2894 if (node_parent == NULL)
2897 ti_new = newTreeInfo();
2898 setTreeInfoToDefaults(ti_new, node_parent->type);
2900 ti_new->node_parent = node_parent;
2901 ti_new->parent_link = TRUE;
2903 setString(&ti_new->identifier, node_parent->identifier);
2904 setString(&ti_new->name, BACKLINK_TEXT_PARENT);
2905 setString(&ti_new->name_sorting, ti_new->name);
2907 setString(&ti_new->subdir, STRING_PARENT_DIRECTORY);
2908 setString(&ti_new->fullpath, node_parent->fullpath);
2910 ti_new->sort_priority = node_parent->sort_priority;
2911 ti_new->latest_engine = node_parent->latest_engine;
2913 setString(&ti_new->class_desc, getLevelClassDescription(ti_new));
2915 pushTreeInfo(&node_parent->node_group, ti_new);
2920 static TreeInfo *createTopTreeInfoNode(TreeInfo *node_first)
2922 if (node_first == NULL)
2925 TreeInfo *ti_new = newTreeInfo();
2926 int type = node_first->type;
2928 setTreeInfoToDefaults(ti_new, type);
2930 ti_new->node_parent = NULL;
2931 ti_new->parent_link = FALSE;
2933 setString(&ti_new->identifier, node_first->identifier);
2934 setString(&ti_new->name, TREE_INFOTEXT(type));
2935 setString(&ti_new->name_sorting, ti_new->name);
2937 setString(&ti_new->subdir, STRING_TOP_DIRECTORY);
2938 setString(&ti_new->fullpath, ".");
2940 ti_new->sort_priority = node_first->sort_priority;;
2941 ti_new->latest_engine = node_first->latest_engine;
2943 setString(&ti_new->class_desc, TREE_INFOTEXT(type));
2945 ti_new->node_group = node_first;
2946 ti_new->level_group = TRUE;
2948 TreeInfo *ti_new2 = createParentTreeInfoNode(ti_new);
2950 setString(&ti_new2->name, TREE_BACKLINK_TEXT(type));
2951 setString(&ti_new2->name_sorting, ti_new2->name);
2956 static void setTreeInfoParentNodes(TreeInfo *node, TreeInfo *node_parent)
2960 if (node->node_group)
2961 setTreeInfoParentNodes(node->node_group, node);
2963 node->node_parent = node_parent;
2970 // ----------------------------------------------------------------------------
2971 // functions for handling level and custom artwork info cache
2972 // ----------------------------------------------------------------------------
2974 static void LoadArtworkInfoCache(void)
2976 InitCacheDirectory();
2978 if (artworkinfo_cache_old == NULL)
2980 char *filename = getPath2(getCacheDir(), ARTWORKINFO_CACHE_FILE);
2982 // try to load artwork info hash from already existing cache file
2983 artworkinfo_cache_old = loadSetupFileHash(filename);
2985 // if no artwork info cache file was found, start with empty hash
2986 if (artworkinfo_cache_old == NULL)
2987 artworkinfo_cache_old = newSetupFileHash();
2992 if (artworkinfo_cache_new == NULL)
2993 artworkinfo_cache_new = newSetupFileHash();
2995 update_artworkinfo_cache = FALSE;
2998 static void SaveArtworkInfoCache(void)
3000 if (!update_artworkinfo_cache)
3003 char *filename = getPath2(getCacheDir(), ARTWORKINFO_CACHE_FILE);
3005 InitCacheDirectory();
3007 saveSetupFileHash(artworkinfo_cache_new, filename);
3012 static char *getCacheTokenPrefix(char *prefix1, char *prefix2)
3014 static char *prefix = NULL;
3016 checked_free(prefix);
3018 prefix = getStringCat2WithSeparator(prefix1, prefix2, ".");
3023 // (identical to above function, but separate string buffer needed -- nasty)
3024 static char *getCacheToken(char *prefix, char *suffix)
3026 static char *token = NULL;
3028 checked_free(token);
3030 token = getStringCat2WithSeparator(prefix, suffix, ".");
3035 static char *getFileTimestampString(char *filename)
3037 return getStringCopy(i_to_a(getFileTimestampEpochSeconds(filename)));
3040 static boolean modifiedFileTimestamp(char *filename, char *timestamp_string)
3042 struct stat file_status;
3044 if (timestamp_string == NULL)
3047 if (!fileExists(filename)) // file does not exist
3048 return (atoi(timestamp_string) != 0);
3050 if (stat(filename, &file_status) != 0) // cannot stat file
3053 return (file_status.st_mtime != atoi(timestamp_string));
3056 static TreeInfo *getArtworkInfoCacheEntry(LevelDirTree *level_node, int type)
3058 char *identifier = level_node->subdir;
3059 char *type_string = ARTWORK_DIRECTORY(type);
3060 char *token_prefix = getCacheTokenPrefix(type_string, identifier);
3061 char *token_main = getCacheToken(token_prefix, "CACHED");
3062 char *cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
3063 boolean cached = (cache_entry != NULL && strEqual(cache_entry, "true"));
3064 TreeInfo *artwork_info = NULL;
3066 if (!use_artworkinfo_cache)
3069 if (optional_tokens_hash == NULL)
3073 // create hash from list of optional tokens (for quick access)
3074 optional_tokens_hash = newSetupFileHash();
3075 for (i = 0; optional_tokens[i] != NULL; i++)
3076 setHashEntry(optional_tokens_hash, optional_tokens[i], "");
3083 artwork_info = newTreeInfo();
3084 setTreeInfoToDefaults(artwork_info, type);
3086 // set all structure fields according to the token/value pairs
3087 ldi = *artwork_info;
3088 for (i = 0; artworkinfo_tokens[i].type != -1; i++)
3090 char *token_suffix = artworkinfo_tokens[i].text;
3091 char *token = getCacheToken(token_prefix, token_suffix);
3092 char *value = getHashEntry(artworkinfo_cache_old, token);
3094 (getHashEntry(optional_tokens_hash, token_suffix) != NULL);
3096 setSetupInfo(artworkinfo_tokens, i, value);
3098 // check if cache entry for this item is mandatory, but missing
3099 if (value == NULL && !optional)
3101 Warn("missing cache entry '%s'", token);
3107 *artwork_info = ldi;
3112 char *filename_levelinfo = getPath2(getLevelDirFromTreeInfo(level_node),
3113 LEVELINFO_FILENAME);
3114 char *filename_artworkinfo = getPath2(getSetupArtworkDir(artwork_info),
3115 ARTWORKINFO_FILENAME(type));
3117 // check if corresponding "levelinfo.conf" file has changed
3118 token_main = getCacheToken(token_prefix, "TIMESTAMP_LEVELINFO");
3119 cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
3121 if (modifiedFileTimestamp(filename_levelinfo, cache_entry))
3124 // check if corresponding "<artworkinfo>.conf" file has changed
3125 token_main = getCacheToken(token_prefix, "TIMESTAMP_ARTWORKINFO");
3126 cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
3128 if (modifiedFileTimestamp(filename_artworkinfo, cache_entry))
3131 checked_free(filename_levelinfo);
3132 checked_free(filename_artworkinfo);
3135 if (!cached && artwork_info != NULL)
3137 freeTreeInfo(artwork_info);
3142 return artwork_info;
3145 static void setArtworkInfoCacheEntry(TreeInfo *artwork_info,
3146 LevelDirTree *level_node, int type)
3148 char *identifier = level_node->subdir;
3149 char *type_string = ARTWORK_DIRECTORY(type);
3150 char *token_prefix = getCacheTokenPrefix(type_string, identifier);
3151 char *token_main = getCacheToken(token_prefix, "CACHED");
3152 boolean set_cache_timestamps = TRUE;
3155 setHashEntry(artworkinfo_cache_new, token_main, "true");
3157 if (set_cache_timestamps)
3159 char *filename_levelinfo = getPath2(getLevelDirFromTreeInfo(level_node),
3160 LEVELINFO_FILENAME);
3161 char *filename_artworkinfo = getPath2(getSetupArtworkDir(artwork_info),
3162 ARTWORKINFO_FILENAME(type));
3163 char *timestamp_levelinfo = getFileTimestampString(filename_levelinfo);
3164 char *timestamp_artworkinfo = getFileTimestampString(filename_artworkinfo);
3166 token_main = getCacheToken(token_prefix, "TIMESTAMP_LEVELINFO");
3167 setHashEntry(artworkinfo_cache_new, token_main, timestamp_levelinfo);
3169 token_main = getCacheToken(token_prefix, "TIMESTAMP_ARTWORKINFO");
3170 setHashEntry(artworkinfo_cache_new, token_main, timestamp_artworkinfo);
3172 checked_free(filename_levelinfo);
3173 checked_free(filename_artworkinfo);
3174 checked_free(timestamp_levelinfo);
3175 checked_free(timestamp_artworkinfo);
3178 ldi = *artwork_info;
3179 for (i = 0; artworkinfo_tokens[i].type != -1; i++)
3181 char *token = getCacheToken(token_prefix, artworkinfo_tokens[i].text);
3182 char *value = getSetupValue(artworkinfo_tokens[i].type,
3183 artworkinfo_tokens[i].value);
3185 setHashEntry(artworkinfo_cache_new, token, value);
3190 // ----------------------------------------------------------------------------
3191 // functions for loading level info and custom artwork info
3192 // ----------------------------------------------------------------------------
3194 int GetZipFileTreeType(char *zip_filename)
3196 static char *top_dir_path = NULL;
3197 static char *top_dir_conf_filename[NUM_BASE_TREE_TYPES] = { NULL };
3198 static char *conf_basename[NUM_BASE_TREE_TYPES] =
3200 GRAPHICSINFO_FILENAME,
3201 SOUNDSINFO_FILENAME,
3207 checked_free(top_dir_path);
3208 top_dir_path = NULL;
3210 for (j = 0; j < NUM_BASE_TREE_TYPES; j++)
3212 checked_free(top_dir_conf_filename[j]);
3213 top_dir_conf_filename[j] = NULL;
3216 char **zip_entries = zip_list(zip_filename);
3218 // check if zip file successfully opened
3219 if (zip_entries == NULL || zip_entries[0] == NULL)
3220 return TREE_TYPE_UNDEFINED;
3222 // first zip file entry is expected to be top level directory
3223 char *top_dir = zip_entries[0];
3225 // check if valid top level directory found in zip file
3226 if (!strSuffix(top_dir, "/"))
3227 return TREE_TYPE_UNDEFINED;
3229 // get filenames of valid configuration files in top level directory
3230 for (j = 0; j < NUM_BASE_TREE_TYPES; j++)
3231 top_dir_conf_filename[j] = getStringCat2(top_dir, conf_basename[j]);
3233 int tree_type = TREE_TYPE_UNDEFINED;
3236 while (zip_entries[e] != NULL)
3238 // check if every zip file entry is below top level directory
3239 if (!strPrefix(zip_entries[e], top_dir))
3240 return TREE_TYPE_UNDEFINED;
3242 // check if this zip file entry is a valid configuration filename
3243 for (j = 0; j < NUM_BASE_TREE_TYPES; j++)
3245 if (strEqual(zip_entries[e], top_dir_conf_filename[j]))
3247 // only exactly one valid configuration file allowed
3248 if (tree_type != TREE_TYPE_UNDEFINED)
3249 return TREE_TYPE_UNDEFINED;
3261 static boolean CheckZipFileForDirectory(char *zip_filename, char *directory,
3264 static char *top_dir_path = NULL;
3265 static char *top_dir_conf_filename = NULL;
3267 checked_free(top_dir_path);
3268 checked_free(top_dir_conf_filename);
3270 top_dir_path = NULL;
3271 top_dir_conf_filename = NULL;
3273 char *conf_basename = (tree_type == TREE_TYPE_LEVEL_DIR ? LEVELINFO_FILENAME :
3274 ARTWORKINFO_FILENAME(tree_type));
3276 // check if valid configuration filename determined
3277 if (conf_basename == NULL || strEqual(conf_basename, ""))
3280 char **zip_entries = zip_list(zip_filename);
3282 // check if zip file successfully opened
3283 if (zip_entries == NULL || zip_entries[0] == NULL)
3286 // first zip file entry is expected to be top level directory
3287 char *top_dir = zip_entries[0];
3289 // check if valid top level directory found in zip file
3290 if (!strSuffix(top_dir, "/"))
3293 // get path of extracted top level directory
3294 top_dir_path = getPath2(directory, top_dir);
3296 // remove trailing directory separator from top level directory path
3297 // (required to be able to check for file and directory in next step)
3298 top_dir_path[strlen(top_dir_path) - 1] = '\0';
3300 // check if zip file's top level directory already exists in target directory
3301 if (fileExists(top_dir_path)) // (checks for file and directory)
3304 // get filename of configuration file in top level directory
3305 top_dir_conf_filename = getStringCat2(top_dir, conf_basename);
3307 boolean found_top_dir_conf_filename = FALSE;
3310 while (zip_entries[i] != NULL)
3312 // check if every zip file entry is below top level directory
3313 if (!strPrefix(zip_entries[i], top_dir))
3316 // check if this zip file entry is the configuration filename
3317 if (strEqual(zip_entries[i], top_dir_conf_filename))
3318 found_top_dir_conf_filename = TRUE;
3323 // check if valid configuration filename was found in zip file
3324 if (!found_top_dir_conf_filename)
3330 char *ExtractZipFileIntoDirectory(char *zip_filename, char *directory,
3333 boolean zip_file_valid = CheckZipFileForDirectory(zip_filename, directory,
3336 if (!zip_file_valid)
3338 Warn("zip file '%s' rejected!", zip_filename);
3343 char **zip_entries = zip_extract(zip_filename, directory);
3345 if (zip_entries == NULL)
3347 Warn("zip file '%s' could not be extracted!", zip_filename);
3352 Info("zip file '%s' successfully extracted!", zip_filename);
3354 // first zip file entry contains top level directory
3355 char *top_dir = zip_entries[0];
3357 // remove trailing directory separator from top level directory
3358 top_dir[strlen(top_dir) - 1] = '\0';
3363 static void ProcessZipFilesInDirectory(char *directory, int tree_type)
3366 DirectoryEntry *dir_entry;
3368 if ((dir = openDirectory(directory)) == NULL)
3370 // display error if directory is main "options.graphics_directory" etc.
3371 if (tree_type == TREE_TYPE_LEVEL_DIR ||
3372 directory == OPTIONS_ARTWORK_DIRECTORY(tree_type))
3373 Warn("cannot read directory '%s'", directory);
3378 while ((dir_entry = readDirectory(dir)) != NULL) // loop all entries
3380 // skip non-zip files (and also directories with zip extension)
3381 if (!strSuffixLower(dir_entry->basename, ".zip") || dir_entry->is_directory)
3384 char *zip_filename = getPath2(directory, dir_entry->basename);
3385 char *zip_filename_extracted = getStringCat2(zip_filename, ".extracted");
3386 char *zip_filename_rejected = getStringCat2(zip_filename, ".rejected");
3388 // check if zip file hasn't already been extracted or rejected
3389 if (!fileExists(zip_filename_extracted) &&
3390 !fileExists(zip_filename_rejected))
3392 char *top_dir = ExtractZipFileIntoDirectory(zip_filename, directory,
3394 char *marker_filename = (top_dir != NULL ? zip_filename_extracted :
3395 zip_filename_rejected);
3398 // create empty file to mark zip file as extracted or rejected
3399 if ((marker_file = fopen(marker_filename, MODE_WRITE)))
3400 fclose(marker_file);
3403 free(zip_filename_extracted);
3404 free(zip_filename_rejected);
3408 closeDirectory(dir);
3411 // forward declaration for recursive call by "LoadLevelInfoFromLevelDir()"
3412 static void LoadLevelInfoFromLevelDir(TreeInfo **, TreeInfo *, char *);
3414 static boolean LoadLevelInfoFromLevelConf(TreeInfo **node_first,
3415 TreeInfo *node_parent,
3416 char *level_directory,
3417 char *directory_name)
3419 char *directory_path = getPath2(level_directory, directory_name);
3420 char *filename = getPath2(directory_path, LEVELINFO_FILENAME);
3421 SetupFileHash *setup_file_hash;
3422 LevelDirTree *leveldir_new = NULL;
3425 // unless debugging, silently ignore directories without "levelinfo.conf"
3426 if (!options.debug && !fileExists(filename))
3428 free(directory_path);
3434 setup_file_hash = loadSetupFileHash(filename);
3436 if (setup_file_hash == NULL)
3438 #if DEBUG_NO_CONFIG_FILE
3439 Debug("setup", "ignoring level directory '%s'", directory_path);
3442 free(directory_path);
3448 leveldir_new = newTreeInfo();
3451 setTreeInfoToDefaultsFromParent(leveldir_new, node_parent);
3453 setTreeInfoToDefaults(leveldir_new, TREE_TYPE_LEVEL_DIR);
3455 leveldir_new->subdir = getStringCopy(directory_name);
3457 // set all structure fields according to the token/value pairs
3458 ldi = *leveldir_new;
3459 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
3460 setSetupInfo(levelinfo_tokens, i,
3461 getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
3462 *leveldir_new = ldi;
3464 if (strEqual(leveldir_new->name, ANONYMOUS_NAME))
3465 setString(&leveldir_new->name, leveldir_new->subdir);
3467 if (leveldir_new->identifier == NULL)
3468 leveldir_new->identifier = getStringCopy(leveldir_new->subdir);
3470 if (leveldir_new->name_sorting == NULL)
3471 leveldir_new->name_sorting = getStringCopy(leveldir_new->name);
3473 if (node_parent == NULL) // top level group
3475 leveldir_new->basepath = getStringCopy(level_directory);
3476 leveldir_new->fullpath = getStringCopy(leveldir_new->subdir);
3478 else // sub level group
3480 leveldir_new->basepath = getStringCopy(node_parent->basepath);
3481 leveldir_new->fullpath = getPath2(node_parent->fullpath, directory_name);
3484 leveldir_new->last_level =
3485 leveldir_new->first_level + leveldir_new->levels - 1;
3487 leveldir_new->in_user_dir =
3488 (!strEqual(leveldir_new->basepath, options.level_directory));
3490 // adjust some settings if user's private level directory was detected
3491 if (leveldir_new->sort_priority == LEVELCLASS_UNDEFINED &&
3492 leveldir_new->in_user_dir &&
3493 (strEqual(leveldir_new->subdir, getLoginName()) ||
3494 strEqual(leveldir_new->name, getLoginName()) ||
3495 strEqual(leveldir_new->author, getRealName())))
3497 leveldir_new->sort_priority = LEVELCLASS_PRIVATE_START;
3498 leveldir_new->readonly = FALSE;
3501 leveldir_new->user_defined =
3502 (leveldir_new->in_user_dir && IS_LEVELCLASS_PRIVATE(leveldir_new));
3504 leveldir_new->color = LEVELCOLOR(leveldir_new);
3506 setString(&leveldir_new->class_desc, getLevelClassDescription(leveldir_new));
3508 leveldir_new->handicap_level = // set handicap to default value
3509 (leveldir_new->user_defined || !leveldir_new->handicap ?
3510 leveldir_new->last_level : leveldir_new->first_level);
3512 DrawInitText(leveldir_new->name, 150, FC_YELLOW);
3514 pushTreeInfo(node_first, leveldir_new);
3516 freeSetupFileHash(setup_file_hash);
3518 if (leveldir_new->level_group)
3520 // create node to link back to current level directory
3521 createParentTreeInfoNode(leveldir_new);
3523 // recursively step into sub-directory and look for more level series
3524 LoadLevelInfoFromLevelDir(&leveldir_new->node_group,
3525 leveldir_new, directory_path);
3528 free(directory_path);
3534 static void LoadLevelInfoFromLevelDir(TreeInfo **node_first,
3535 TreeInfo *node_parent,
3536 char *level_directory)
3538 // ---------- 1st stage: process any level set zip files ----------
3540 ProcessZipFilesInDirectory(level_directory, TREE_TYPE_LEVEL_DIR);
3542 // ---------- 2nd stage: check for level set directories ----------
3545 DirectoryEntry *dir_entry;
3546 boolean valid_entry_found = FALSE;
3548 if ((dir = openDirectory(level_directory)) == NULL)
3550 Warn("cannot read level directory '%s'", level_directory);
3555 while ((dir_entry = readDirectory(dir)) != NULL) // loop all entries
3557 char *directory_name = dir_entry->basename;
3558 char *directory_path = getPath2(level_directory, directory_name);
3560 // skip entries for current and parent directory
3561 if (strEqual(directory_name, ".") ||
3562 strEqual(directory_name, ".."))
3564 free(directory_path);
3569 // find out if directory entry is itself a directory
3570 if (!dir_entry->is_directory) // not a directory
3572 free(directory_path);
3577 free(directory_path);
3579 if (strEqual(directory_name, GRAPHICS_DIRECTORY) ||
3580 strEqual(directory_name, SOUNDS_DIRECTORY) ||
3581 strEqual(directory_name, MUSIC_DIRECTORY))
3584 valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
3589 closeDirectory(dir);
3591 // special case: top level directory may directly contain "levelinfo.conf"
3592 if (node_parent == NULL && !valid_entry_found)
3594 // check if this directory directly contains a file "levelinfo.conf"
3595 valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
3596 level_directory, ".");
3599 if (!valid_entry_found)
3600 Warn("cannot find any valid level series in directory '%s'",
3604 boolean AdjustGraphicsForEMC(void)
3606 boolean settings_changed = FALSE;
3608 settings_changed |= adjustTreeGraphicsForEMC(leveldir_first_all);
3609 settings_changed |= adjustTreeGraphicsForEMC(leveldir_first);
3611 return settings_changed;
3614 boolean AdjustSoundsForEMC(void)
3616 boolean settings_changed = FALSE;
3618 settings_changed |= adjustTreeSoundsForEMC(leveldir_first_all);
3619 settings_changed |= adjustTreeSoundsForEMC(leveldir_first);
3621 return settings_changed;
3624 void LoadLevelInfo(void)
3626 InitUserLevelDirectory(getLoginName());
3628 DrawInitText("Loading level series", 120, FC_GREEN);
3630 LoadLevelInfoFromLevelDir(&leveldir_first, NULL, options.level_directory);
3631 LoadLevelInfoFromLevelDir(&leveldir_first, NULL, getUserLevelDir(NULL));
3633 leveldir_first = createTopTreeInfoNode(leveldir_first);
3635 /* after loading all level set information, clone the level directory tree
3636 and remove all level sets without levels (these may still contain artwork
3637 to be offered in the setup menu as "custom artwork", and are therefore
3638 checked for existing artwork in the function "LoadLevelArtworkInfo()") */
3639 leveldir_first_all = leveldir_first;
3640 cloneTree(&leveldir_first, leveldir_first_all, TRUE);
3642 AdjustGraphicsForEMC();
3643 AdjustSoundsForEMC();
3645 // before sorting, the first entries will be from the user directory
3646 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
3648 if (leveldir_first == NULL)
3649 Fail("cannot find any valid level series in any directory");
3651 sortTreeInfo(&leveldir_first);
3653 #if ENABLE_UNUSED_CODE
3654 dumpTreeInfo(leveldir_first, 0);
3658 static boolean LoadArtworkInfoFromArtworkConf(TreeInfo **node_first,
3659 TreeInfo *node_parent,
3660 char *base_directory,
3661 char *directory_name, int type)
3663 char *directory_path = getPath2(base_directory, directory_name);
3664 char *filename = getPath2(directory_path, ARTWORKINFO_FILENAME(type));
3665 SetupFileHash *setup_file_hash = NULL;
3666 TreeInfo *artwork_new = NULL;
3669 if (fileExists(filename))
3670 setup_file_hash = loadSetupFileHash(filename);
3672 if (setup_file_hash == NULL) // no config file -- look for artwork files
3675 DirectoryEntry *dir_entry;
3676 boolean valid_file_found = FALSE;
3678 if ((dir = openDirectory(directory_path)) != NULL)
3680 while ((dir_entry = readDirectory(dir)) != NULL)
3682 if (FileIsArtworkType(dir_entry->filename, type))
3684 valid_file_found = TRUE;
3690 closeDirectory(dir);
3693 if (!valid_file_found)
3695 #if DEBUG_NO_CONFIG_FILE
3696 if (!strEqual(directory_name, "."))
3697 Debug("setup", "ignoring artwork directory '%s'", directory_path);
3700 free(directory_path);
3707 artwork_new = newTreeInfo();
3710 setTreeInfoToDefaultsFromParent(artwork_new, node_parent);
3712 setTreeInfoToDefaults(artwork_new, type);
3714 artwork_new->subdir = getStringCopy(directory_name);
3716 if (setup_file_hash) // (before defining ".color" and ".class_desc")
3718 // set all structure fields according to the token/value pairs
3720 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
3721 setSetupInfo(levelinfo_tokens, i,
3722 getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
3725 if (strEqual(artwork_new->name, ANONYMOUS_NAME))
3726 setString(&artwork_new->name, artwork_new->subdir);
3728 if (artwork_new->identifier == NULL)
3729 artwork_new->identifier = getStringCopy(artwork_new->subdir);
3731 if (artwork_new->name_sorting == NULL)
3732 artwork_new->name_sorting = getStringCopy(artwork_new->name);
3735 if (node_parent == NULL) // top level group
3737 artwork_new->basepath = getStringCopy(base_directory);
3738 artwork_new->fullpath = getStringCopy(artwork_new->subdir);
3740 else // sub level group
3742 artwork_new->basepath = getStringCopy(node_parent->basepath);
3743 artwork_new->fullpath = getPath2(node_parent->fullpath, directory_name);
3746 artwork_new->in_user_dir =
3747 (!strEqual(artwork_new->basepath, OPTIONS_ARTWORK_DIRECTORY(type)));
3749 // (may use ".sort_priority" from "setup_file_hash" above)
3750 artwork_new->color = ARTWORKCOLOR(artwork_new);
3752 setString(&artwork_new->class_desc, getLevelClassDescription(artwork_new));
3754 if (setup_file_hash == NULL) // (after determining ".user_defined")
3756 if (strEqual(artwork_new->subdir, "."))
3758 if (artwork_new->user_defined)
3760 setString(&artwork_new->identifier, "private");
3761 artwork_new->sort_priority = ARTWORKCLASS_PRIVATE;
3765 setString(&artwork_new->identifier, "classic");
3766 artwork_new->sort_priority = ARTWORKCLASS_CLASSICS;
3769 // set to new values after changing ".sort_priority"
3770 artwork_new->color = ARTWORKCOLOR(artwork_new);
3772 setString(&artwork_new->class_desc,
3773 getLevelClassDescription(artwork_new));
3777 setString(&artwork_new->identifier, artwork_new->subdir);
3780 setString(&artwork_new->name, artwork_new->identifier);
3781 setString(&artwork_new->name_sorting, artwork_new->name);
3784 pushTreeInfo(node_first, artwork_new);
3786 freeSetupFileHash(setup_file_hash);
3788 free(directory_path);
3794 static void LoadArtworkInfoFromArtworkDir(TreeInfo **node_first,
3795 TreeInfo *node_parent,
3796 char *base_directory, int type)
3798 // ---------- 1st stage: process any artwork set zip files ----------
3800 ProcessZipFilesInDirectory(base_directory, type);
3802 // ---------- 2nd stage: check for artwork set directories ----------
3805 DirectoryEntry *dir_entry;
3806 boolean valid_entry_found = FALSE;
3808 if ((dir = openDirectory(base_directory)) == NULL)
3810 // display error if directory is main "options.graphics_directory" etc.
3811 if (base_directory == OPTIONS_ARTWORK_DIRECTORY(type))
3812 Warn("cannot read directory '%s'", base_directory);
3817 while ((dir_entry = readDirectory(dir)) != NULL) // loop all entries
3819 char *directory_name = dir_entry->basename;
3820 char *directory_path = getPath2(base_directory, directory_name);
3822 // skip directory entries for current and parent directory
3823 if (strEqual(directory_name, ".") ||
3824 strEqual(directory_name, ".."))
3826 free(directory_path);
3831 // skip directory entries which are not a directory
3832 if (!dir_entry->is_directory) // not a directory
3834 free(directory_path);
3839 free(directory_path);
3841 // check if this directory contains artwork with or without config file
3842 valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
3844 directory_name, type);
3847 closeDirectory(dir);
3849 // check if this directory directly contains artwork itself
3850 valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
3851 base_directory, ".",
3853 if (!valid_entry_found)
3854 Warn("cannot find any valid artwork in directory '%s'", base_directory);
3857 static TreeInfo *getDummyArtworkInfo(int type)
3859 // this is only needed when there is completely no artwork available
3860 TreeInfo *artwork_new = newTreeInfo();
3862 setTreeInfoToDefaults(artwork_new, type);
3864 setString(&artwork_new->subdir, UNDEFINED_FILENAME);
3865 setString(&artwork_new->fullpath, UNDEFINED_FILENAME);
3866 setString(&artwork_new->basepath, UNDEFINED_FILENAME);
3868 setString(&artwork_new->identifier, UNDEFINED_FILENAME);
3869 setString(&artwork_new->name, UNDEFINED_FILENAME);
3870 setString(&artwork_new->name_sorting, UNDEFINED_FILENAME);
3875 void SetCurrentArtwork(int type)
3877 ArtworkDirTree **current_ptr = ARTWORK_CURRENT_PTR(artwork, type);
3878 ArtworkDirTree *first_node = ARTWORK_FIRST_NODE(artwork, type);
3879 char *setup_set = SETUP_ARTWORK_SET(setup, type);
3880 char *default_subdir = ARTWORK_DEFAULT_SUBDIR(type);
3882 // set current artwork to artwork configured in setup menu
3883 *current_ptr = getTreeInfoFromIdentifier(first_node, setup_set);
3885 // if not found, set current artwork to default artwork
3886 if (*current_ptr == NULL)
3887 *current_ptr = getTreeInfoFromIdentifier(first_node, default_subdir);
3889 // if not found, set current artwork to first artwork in tree
3890 if (*current_ptr == NULL)
3891 *current_ptr = getFirstValidTreeInfoEntry(first_node);
3894 void ChangeCurrentArtworkIfNeeded(int type)
3896 char *current_identifier = ARTWORK_CURRENT_IDENTIFIER(artwork, type);
3897 char *setup_set = SETUP_ARTWORK_SET(setup, type);
3899 if (!strEqual(current_identifier, setup_set))
3900 SetCurrentArtwork(type);
3903 void LoadArtworkInfo(void)
3905 LoadArtworkInfoCache();
3907 DrawInitText("Looking for custom artwork", 120, FC_GREEN);
3909 LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
3910 options.graphics_directory,
3911 TREE_TYPE_GRAPHICS_DIR);
3912 LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
3913 getUserGraphicsDir(),
3914 TREE_TYPE_GRAPHICS_DIR);
3916 LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
3917 options.sounds_directory,
3918 TREE_TYPE_SOUNDS_DIR);
3919 LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
3921 TREE_TYPE_SOUNDS_DIR);
3923 LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
3924 options.music_directory,
3925 TREE_TYPE_MUSIC_DIR);
3926 LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
3928 TREE_TYPE_MUSIC_DIR);
3930 if (artwork.gfx_first == NULL)
3931 artwork.gfx_first = getDummyArtworkInfo(TREE_TYPE_GRAPHICS_DIR);
3932 if (artwork.snd_first == NULL)
3933 artwork.snd_first = getDummyArtworkInfo(TREE_TYPE_SOUNDS_DIR);
3934 if (artwork.mus_first == NULL)
3935 artwork.mus_first = getDummyArtworkInfo(TREE_TYPE_MUSIC_DIR);
3937 // before sorting, the first entries will be from the user directory
3938 SetCurrentArtwork(ARTWORK_TYPE_GRAPHICS);
3939 SetCurrentArtwork(ARTWORK_TYPE_SOUNDS);
3940 SetCurrentArtwork(ARTWORK_TYPE_MUSIC);
3942 artwork.gfx_current_identifier = artwork.gfx_current->identifier;
3943 artwork.snd_current_identifier = artwork.snd_current->identifier;
3944 artwork.mus_current_identifier = artwork.mus_current->identifier;
3946 #if ENABLE_UNUSED_CODE
3947 Debug("setup:LoadArtworkInfo", "graphics set == %s",
3948 artwork.gfx_current_identifier);
3949 Debug("setup:LoadArtworkInfo", "sounds set == %s",
3950 artwork.snd_current_identifier);
3951 Debug("setup:LoadArtworkInfo", "music set == %s",
3952 artwork.mus_current_identifier);
3955 sortTreeInfo(&artwork.gfx_first);
3956 sortTreeInfo(&artwork.snd_first);
3957 sortTreeInfo(&artwork.mus_first);
3959 #if ENABLE_UNUSED_CODE
3960 dumpTreeInfo(artwork.gfx_first, 0);
3961 dumpTreeInfo(artwork.snd_first, 0);
3962 dumpTreeInfo(artwork.mus_first, 0);
3966 static void MoveArtworkInfoIntoSubTree(ArtworkDirTree **artwork_node)
3968 ArtworkDirTree *artwork_new = newTreeInfo();
3969 char *top_node_name = "standalone artwork";
3971 setTreeInfoToDefaults(artwork_new, (*artwork_node)->type);
3973 artwork_new->level_group = TRUE;
3975 setString(&artwork_new->identifier, top_node_name);
3976 setString(&artwork_new->name, top_node_name);
3977 setString(&artwork_new->name_sorting, top_node_name);
3979 // create node to link back to current custom artwork directory
3980 createParentTreeInfoNode(artwork_new);
3982 // move existing custom artwork tree into newly created sub-tree
3983 artwork_new->node_group->next = *artwork_node;
3985 // change custom artwork tree to contain only newly created node
3986 *artwork_node = artwork_new;
3989 static void LoadArtworkInfoFromLevelInfoExt(ArtworkDirTree **artwork_node,
3990 ArtworkDirTree *node_parent,
3991 LevelDirTree *level_node,
3992 boolean empty_level_set_mode)
3994 int type = (*artwork_node)->type;
3996 // recursively check all level directories for artwork sub-directories
4000 boolean empty_level_set = (level_node->levels == 0);
4002 // check all tree entries for artwork, but skip parent link entries
4003 if (!level_node->parent_link && empty_level_set == empty_level_set_mode)
4005 TreeInfo *artwork_new = getArtworkInfoCacheEntry(level_node, type);
4006 boolean cached = (artwork_new != NULL);
4010 pushTreeInfo(artwork_node, artwork_new);
4014 TreeInfo *topnode_last = *artwork_node;
4015 char *path = getPath2(getLevelDirFromTreeInfo(level_node),
4016 ARTWORK_DIRECTORY(type));
4018 LoadArtworkInfoFromArtworkDir(artwork_node, NULL, path, type);
4020 if (topnode_last != *artwork_node) // check for newly added node
4022 artwork_new = *artwork_node;
4024 setString(&artwork_new->identifier, level_node->subdir);
4025 setString(&artwork_new->name, level_node->name);
4026 setString(&artwork_new->name_sorting, level_node->name_sorting);
4028 artwork_new->sort_priority = level_node->sort_priority;
4029 artwork_new->color = LEVELCOLOR(artwork_new);
4031 update_artworkinfo_cache = TRUE;
4037 // insert artwork info (from old cache or filesystem) into new cache
4038 if (artwork_new != NULL)
4039 setArtworkInfoCacheEntry(artwork_new, level_node, type);
4042 DrawInitText(level_node->name, 150, FC_YELLOW);
4044 if (level_node->node_group != NULL)
4046 TreeInfo *artwork_new = newTreeInfo();
4049 setTreeInfoToDefaultsFromParent(artwork_new, node_parent);
4051 setTreeInfoToDefaults(artwork_new, type);
4053 artwork_new->level_group = TRUE;
4055 setString(&artwork_new->identifier, level_node->subdir);
4057 if (node_parent == NULL) // check for top tree node
4059 char *top_node_name = (empty_level_set_mode ?
4060 "artwork for certain level sets" :
4061 "artwork included in level sets");
4063 setString(&artwork_new->name, top_node_name);
4064 setString(&artwork_new->name_sorting, top_node_name);
4068 setString(&artwork_new->name, level_node->name);
4069 setString(&artwork_new->name_sorting, level_node->name_sorting);
4072 pushTreeInfo(artwork_node, artwork_new);
4074 // create node to link back to current custom artwork directory
4075 createParentTreeInfoNode(artwork_new);
4077 // recursively step into sub-directory and look for more custom artwork
4078 LoadArtworkInfoFromLevelInfoExt(&artwork_new->node_group, artwork_new,
4079 level_node->node_group,
4080 empty_level_set_mode);
4082 // if sub-tree has no custom artwork at all, remove it
4083 if (artwork_new->node_group->next == NULL)
4084 removeTreeInfo(artwork_node);
4087 level_node = level_node->next;
4091 static void LoadArtworkInfoFromLevelInfo(ArtworkDirTree **artwork_node)
4093 // move peviously loaded artwork tree into separate sub-tree
4094 MoveArtworkInfoIntoSubTree(artwork_node);
4096 // load artwork from level sets into separate sub-trees
4097 LoadArtworkInfoFromLevelInfoExt(artwork_node, NULL, leveldir_first_all, TRUE);
4098 LoadArtworkInfoFromLevelInfoExt(artwork_node, NULL, leveldir_first_all, FALSE);
4100 // add top tree node over all three separate sub-trees
4101 *artwork_node = createTopTreeInfoNode(*artwork_node);
4103 // set all parent links (back links) in complete artwork tree
4104 setTreeInfoParentNodes(*artwork_node, NULL);
4107 void LoadLevelArtworkInfo(void)
4109 print_timestamp_init("LoadLevelArtworkInfo");
4111 DrawInitText("Looking for custom level artwork", 120, FC_GREEN);
4113 print_timestamp_time("DrawTimeText");
4115 LoadArtworkInfoFromLevelInfo(&artwork.gfx_first);
4116 print_timestamp_time("LoadArtworkInfoFromLevelInfo (gfx)");
4117 LoadArtworkInfoFromLevelInfo(&artwork.snd_first);
4118 print_timestamp_time("LoadArtworkInfoFromLevelInfo (snd)");
4119 LoadArtworkInfoFromLevelInfo(&artwork.mus_first);
4120 print_timestamp_time("LoadArtworkInfoFromLevelInfo (mus)");
4122 SaveArtworkInfoCache();
4124 print_timestamp_time("SaveArtworkInfoCache");
4126 // needed for reloading level artwork not known at ealier stage
4127 ChangeCurrentArtworkIfNeeded(ARTWORK_TYPE_GRAPHICS);
4128 ChangeCurrentArtworkIfNeeded(ARTWORK_TYPE_SOUNDS);
4129 ChangeCurrentArtworkIfNeeded(ARTWORK_TYPE_MUSIC);
4131 print_timestamp_time("getTreeInfoFromIdentifier");
4133 sortTreeInfo(&artwork.gfx_first);
4134 sortTreeInfo(&artwork.snd_first);
4135 sortTreeInfo(&artwork.mus_first);
4137 print_timestamp_time("sortTreeInfo");
4139 #if ENABLE_UNUSED_CODE
4140 dumpTreeInfo(artwork.gfx_first, 0);
4141 dumpTreeInfo(artwork.snd_first, 0);
4142 dumpTreeInfo(artwork.mus_first, 0);
4145 print_timestamp_done("LoadLevelArtworkInfo");
4148 static boolean AddTreeSetToTreeInfoExt(TreeInfo *tree_node_old, char *tree_dir,
4149 char *tree_subdir_new, int type)
4151 if (tree_node_old == NULL)
4153 if (type == TREE_TYPE_LEVEL_DIR)
4155 // get level info tree node of personal user level set
4156 tree_node_old = getTreeInfoFromIdentifier(leveldir_first, getLoginName());
4158 // this may happen if "setup.internal.create_user_levelset" is FALSE
4159 // or if file "levelinfo.conf" is missing in personal user level set
4160 if (tree_node_old == NULL)
4161 tree_node_old = leveldir_first->node_group;
4165 // get artwork info tree node of first artwork set
4166 tree_node_old = ARTWORK_FIRST_NODE(artwork, type);
4170 if (tree_dir == NULL)
4171 tree_dir = TREE_USERDIR(type);
4173 if (tree_node_old == NULL ||
4175 tree_subdir_new == NULL) // should not happen
4178 int draw_deactivation_mask = GetDrawDeactivationMask();
4180 // override draw deactivation mask (temporarily disable drawing)
4181 SetDrawDeactivationMask(REDRAW_ALL);
4183 if (type == TREE_TYPE_LEVEL_DIR)
4185 // load new level set config and add it next to first user level set
4186 LoadLevelInfoFromLevelConf(&tree_node_old->next,
4187 tree_node_old->node_parent,
4188 tree_dir, tree_subdir_new);
4192 // load new artwork set config and add it next to first artwork set
4193 LoadArtworkInfoFromArtworkConf(&tree_node_old->next,
4194 tree_node_old->node_parent,
4195 tree_dir, tree_subdir_new, type);
4198 // set draw deactivation mask to previous value
4199 SetDrawDeactivationMask(draw_deactivation_mask);
4201 // get first node of level or artwork info tree
4202 TreeInfo **tree_node_first = TREE_FIRST_NODE_PTR(type);
4204 // get tree info node of newly added level or artwork set
4205 TreeInfo *tree_node_new = getTreeInfoFromIdentifier(*tree_node_first,
4208 if (tree_node_new == NULL) // should not happen
4211 // correct top link and parent node link of newly created tree node
4212 tree_node_new->node_top = tree_node_old->node_top;
4213 tree_node_new->node_parent = tree_node_old->node_parent;
4215 // sort tree info to adjust position of newly added tree set
4216 sortTreeInfo(tree_node_first);
4221 void AddTreeSetToTreeInfo(TreeInfo *tree_node, char *tree_dir,
4222 char *tree_subdir_new, int type)
4224 if (!AddTreeSetToTreeInfoExt(tree_node, tree_dir, tree_subdir_new, type))
4225 Fail("internal tree info structure corrupted -- aborting");
4228 void AddUserLevelSetToLevelInfo(char *level_subdir_new)
4230 AddTreeSetToTreeInfo(NULL, NULL, level_subdir_new, TREE_TYPE_LEVEL_DIR);
4233 char *getArtworkIdentifierForUserLevelSet(int type)
4235 char *classic_artwork_set = getClassicArtworkSet(type);
4237 // check for custom artwork configured in "levelinfo.conf"
4238 char *leveldir_artwork_set =
4239 *LEVELDIR_ARTWORK_SET_PTR(leveldir_current, type);
4240 boolean has_leveldir_artwork_set =
4241 (leveldir_artwork_set != NULL && !strEqual(leveldir_artwork_set,
4242 classic_artwork_set));
4244 // check for custom artwork in sub-directory "graphics" etc.
4245 TreeInfo *artwork_first_node = ARTWORK_FIRST_NODE(artwork, type);
4246 char *leveldir_identifier = leveldir_current->identifier;
4247 boolean has_artwork_subdir =
4248 (getTreeInfoFromIdentifier(artwork_first_node,
4249 leveldir_identifier) != NULL);
4251 return (has_leveldir_artwork_set ? leveldir_artwork_set :
4252 has_artwork_subdir ? leveldir_identifier :
4253 classic_artwork_set);
4256 TreeInfo *getArtworkTreeInfoForUserLevelSet(int type)
4258 char *artwork_set = getArtworkIdentifierForUserLevelSet(type);
4259 TreeInfo *artwork_first_node = ARTWORK_FIRST_NODE(artwork, type);
4260 TreeInfo *ti = getTreeInfoFromIdentifier(artwork_first_node, artwork_set);
4264 ti = getTreeInfoFromIdentifier(artwork_first_node,
4265 ARTWORK_DEFAULT_SUBDIR(type));
4267 Fail("cannot find default graphics -- should not happen");
4273 boolean checkIfCustomArtworkExistsForCurrentLevelSet(void)
4275 char *graphics_set =
4276 getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_GRAPHICS);
4278 getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_SOUNDS);
4280 getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_MUSIC);
4282 return (!strEqual(graphics_set, GFX_CLASSIC_SUBDIR) ||
4283 !strEqual(sounds_set, SND_CLASSIC_SUBDIR) ||
4284 !strEqual(music_set, MUS_CLASSIC_SUBDIR));
4287 boolean UpdateUserLevelSet(char *level_subdir, char *level_name,
4288 char *level_author, int num_levels)
4290 char *filename = getPath2(getUserLevelDir(level_subdir), LEVELINFO_FILENAME);
4291 char *filename_tmp = getStringCat2(filename, ".tmp");
4293 FILE *file_tmp = NULL;
4294 char line[MAX_LINE_LEN];
4295 boolean success = FALSE;
4296 LevelDirTree *leveldir = getTreeInfoFromIdentifier(leveldir_first,
4298 // update values in level directory tree
4300 if (level_name != NULL)
4301 setString(&leveldir->name, level_name);
4303 if (level_author != NULL)
4304 setString(&leveldir->author, level_author);
4306 if (num_levels != -1)
4307 leveldir->levels = num_levels;
4309 // update values that depend on other values
4311 setString(&leveldir->name_sorting, leveldir->name);
4313 leveldir->last_level = leveldir->first_level + leveldir->levels - 1;
4315 // sort order of level sets may have changed
4316 sortTreeInfo(&leveldir_first);
4318 if ((file = fopen(filename, MODE_READ)) &&
4319 (file_tmp = fopen(filename_tmp, MODE_WRITE)))
4321 while (fgets(line, MAX_LINE_LEN, file))
4323 if (strPrefix(line, "name:") && level_name != NULL)
4324 fprintf(file_tmp, "%-32s%s\n", "name:", level_name);
4325 else if (strPrefix(line, "author:") && level_author != NULL)
4326 fprintf(file_tmp, "%-32s%s\n", "author:", level_author);
4327 else if (strPrefix(line, "levels:") && num_levels != -1)
4328 fprintf(file_tmp, "%-32s%d\n", "levels:", num_levels);
4330 fputs(line, file_tmp);
4343 success = (rename(filename_tmp, filename) == 0);
4351 boolean CreateUserLevelSet(char *level_subdir, char *level_name,
4352 char *level_author, int num_levels,
4353 boolean use_artwork_set)
4355 LevelDirTree *level_info;
4360 // create user level sub-directory, if needed
4361 createDirectory(getUserLevelDir(level_subdir), "user level", PERMS_PRIVATE);
4363 filename = getPath2(getUserLevelDir(level_subdir), LEVELINFO_FILENAME);
4365 if (!(file = fopen(filename, MODE_WRITE)))
4367 Warn("cannot write level info file '%s'", filename);
4374 level_info = newTreeInfo();
4376 // always start with reliable default values
4377 setTreeInfoToDefaults(level_info, TREE_TYPE_LEVEL_DIR);
4379 setString(&level_info->name, level_name);
4380 setString(&level_info->author, level_author);
4381 level_info->levels = num_levels;
4382 level_info->first_level = 1;
4383 level_info->sort_priority = LEVELCLASS_PRIVATE_START;
4384 level_info->readonly = FALSE;
4386 if (use_artwork_set)
4388 level_info->graphics_set =
4389 getStringCopy(getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_GRAPHICS));
4390 level_info->sounds_set =
4391 getStringCopy(getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_SOUNDS));
4392 level_info->music_set =
4393 getStringCopy(getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_MUSIC));
4396 token_value_position = TOKEN_VALUE_POSITION_SHORT;
4398 fprintFileHeader(file, LEVELINFO_FILENAME);
4401 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
4403 if (i == LEVELINFO_TOKEN_NAME ||
4404 i == LEVELINFO_TOKEN_AUTHOR ||
4405 i == LEVELINFO_TOKEN_LEVELS ||
4406 i == LEVELINFO_TOKEN_FIRST_LEVEL ||
4407 i == LEVELINFO_TOKEN_SORT_PRIORITY ||
4408 i == LEVELINFO_TOKEN_READONLY ||
4409 (use_artwork_set && (i == LEVELINFO_TOKEN_GRAPHICS_SET ||
4410 i == LEVELINFO_TOKEN_SOUNDS_SET ||
4411 i == LEVELINFO_TOKEN_MUSIC_SET)))
4412 fprintf(file, "%s\n", getSetupLine(levelinfo_tokens, "", i));
4414 // just to make things nicer :)
4415 if (i == LEVELINFO_TOKEN_AUTHOR ||
4416 i == LEVELINFO_TOKEN_FIRST_LEVEL ||
4417 (use_artwork_set && i == LEVELINFO_TOKEN_READONLY))
4418 fprintf(file, "\n");
4421 token_value_position = TOKEN_VALUE_POSITION_DEFAULT;
4425 SetFilePermissions(filename, PERMS_PRIVATE);
4427 freeTreeInfo(level_info);
4433 static void SaveUserLevelInfo(void)
4435 CreateUserLevelSet(getLoginName(), getLoginName(), getRealName(), 100, FALSE);
4438 char *getSetupValue(int type, void *value)
4440 static char value_string[MAX_LINE_LEN];
4448 strcpy(value_string, (*(boolean *)value ? "true" : "false"));
4452 strcpy(value_string, (*(boolean *)value ? "on" : "off"));
4456 strcpy(value_string, (*(int *)value == AUTO ? "auto" :
4457 *(int *)value == FALSE ? "off" : "on"));
4461 strcpy(value_string, (*(boolean *)value ? "yes" : "no"));
4464 case TYPE_YES_NO_AUTO:
4465 strcpy(value_string, (*(int *)value == AUTO ? "auto" :
4466 *(int *)value == FALSE ? "no" : "yes"));
4470 strcpy(value_string, (*(boolean *)value ? "AGA" : "ECS"));
4474 strcpy(value_string, getKeyNameFromKey(*(Key *)value));
4478 strcpy(value_string, getX11KeyNameFromKey(*(Key *)value));
4482 sprintf(value_string, "%d", *(int *)value);
4486 if (*(char **)value == NULL)
4489 strcpy(value_string, *(char **)value);
4493 sprintf(value_string, "player_%d", *(int *)value + 1);
4497 value_string[0] = '\0';
4501 if (type & TYPE_GHOSTED)
4502 strcpy(value_string, "n/a");
4504 return value_string;
4507 char *getSetupLine(struct TokenInfo *token_info, char *prefix, int token_nr)
4511 static char token_string[MAX_LINE_LEN];
4512 int token_type = token_info[token_nr].type;
4513 void *setup_value = token_info[token_nr].value;
4514 char *token_text = token_info[token_nr].text;
4515 char *value_string = getSetupValue(token_type, setup_value);
4517 // build complete token string
4518 sprintf(token_string, "%s%s", prefix, token_text);
4520 // build setup entry line
4521 line = getFormattedSetupEntry(token_string, value_string);
4523 if (token_type == TYPE_KEY_X11)
4525 Key key = *(Key *)setup_value;
4526 char *keyname = getKeyNameFromKey(key);
4528 // add comment, if useful
4529 if (!strEqual(keyname, "(undefined)") &&
4530 !strEqual(keyname, "(unknown)"))
4532 // add at least one whitespace
4534 for (i = strlen(line); i < token_comment_position; i++)
4538 strcat(line, keyname);
4545 static void InitLastPlayedLevels_ParentNode(void)
4547 LevelDirTree **leveldir_top = &leveldir_first->node_group->next;
4548 LevelDirTree *leveldir_new = NULL;
4550 // check if parent node for last played levels already exists
4551 if (strEqual((*leveldir_top)->identifier, TOKEN_STR_LAST_LEVEL_SERIES))
4554 leveldir_new = newTreeInfo();
4556 setTreeInfoToDefaultsFromParent(leveldir_new, leveldir_first);
4558 leveldir_new->level_group = TRUE;
4560 setString(&leveldir_new->identifier, TOKEN_STR_LAST_LEVEL_SERIES);
4561 setString(&leveldir_new->name, "<< (last played level sets)");
4563 pushTreeInfo(leveldir_top, leveldir_new);
4565 // create node to link back to current level directory
4566 createParentTreeInfoNode(leveldir_new);
4569 void UpdateLastPlayedLevels_TreeInfo(void)
4571 char **last_level_series = setup.level_setup.last_level_series;
4572 boolean reset_leveldir_current = FALSE;
4573 LevelDirTree *leveldir_last;
4574 TreeInfo **node_new = NULL;
4577 if (last_level_series[0] == NULL)
4580 InitLastPlayedLevels_ParentNode();
4582 // check if current level set is from "last played" sub-tree to be rebuilt
4583 reset_leveldir_current = strEqual(leveldir_current->node_parent->identifier,
4584 TOKEN_STR_LAST_LEVEL_SERIES);
4586 leveldir_last = getTreeInfoFromIdentifierExt(leveldir_first,
4587 TOKEN_STR_LAST_LEVEL_SERIES,
4589 if (leveldir_last == NULL)
4592 node_new = &leveldir_last->node_group->next;
4594 freeTreeInfo(*node_new);
4596 for (i = 0; last_level_series[i] != NULL; i++)
4598 LevelDirTree *node_last = getTreeInfoFromIdentifier(leveldir_first,
4599 last_level_series[i]);
4601 *node_new = getTreeInfoCopy(node_last); // copy complete node
4603 (*node_new)->node_top = &leveldir_first; // correct top node link
4604 (*node_new)->node_parent = leveldir_last; // correct parent node link
4606 (*node_new)->node_group = NULL;
4607 (*node_new)->next = NULL;
4609 (*node_new)->cl_first = -1; // force setting tree cursor
4611 node_new = &((*node_new)->next);
4614 if (reset_leveldir_current)
4615 leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
4616 last_level_series[0]);
4619 static void UpdateLastPlayedLevels_List(void)
4621 char **last_level_series = setup.level_setup.last_level_series;
4622 int pos = MAX_LEVELDIR_HISTORY - 1;
4625 // search for potentially already existing entry in list of level sets
4626 for (i = 0; last_level_series[i] != NULL; i++)
4627 if (strEqual(last_level_series[i], leveldir_current->identifier))
4630 // move list of level sets one entry down (using potentially free entry)
4631 for (i = pos; i > 0; i--)
4632 setString(&last_level_series[i], last_level_series[i - 1]);
4634 // put last played level set at top position
4635 setString(&last_level_series[0], leveldir_current->identifier);
4638 void LoadLevelSetup_LastSeries(void)
4640 // --------------------------------------------------------------------------
4641 // ~/.<program>/levelsetup.conf
4642 // --------------------------------------------------------------------------
4644 char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
4645 SetupFileHash *level_setup_hash = NULL;
4649 // always start with reliable default values
4650 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
4652 // start with empty history of last played level sets
4653 setString(&setup.level_setup.last_level_series[0], NULL);
4655 if (!strEqual(DEFAULT_LEVELSET, UNDEFINED_LEVELSET))
4657 leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
4659 if (leveldir_current == NULL)
4660 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
4663 if ((level_setup_hash = loadSetupFileHash(filename)))
4665 char *last_level_series =
4666 getHashEntry(level_setup_hash, TOKEN_STR_LAST_LEVEL_SERIES);
4668 leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
4670 if (leveldir_current == NULL)
4671 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
4673 for (i = 0; i < MAX_LEVELDIR_HISTORY; i++)
4675 char token[strlen(TOKEN_STR_LAST_LEVEL_SERIES) + 10];
4676 LevelDirTree *leveldir_last;
4678 sprintf(token, "%s.%03d", TOKEN_STR_LAST_LEVEL_SERIES, i);
4680 last_level_series = getHashEntry(level_setup_hash, token);
4682 leveldir_last = getTreeInfoFromIdentifier(leveldir_first,
4684 if (leveldir_last != NULL)
4685 setString(&setup.level_setup.last_level_series[pos++],
4689 setString(&setup.level_setup.last_level_series[pos], NULL);
4691 freeSetupFileHash(level_setup_hash);
4695 Debug("setup", "using default setup values");
4701 static void SaveLevelSetup_LastSeries_Ext(boolean deactivate_last_level_series)
4703 // --------------------------------------------------------------------------
4704 // ~/.<program>/levelsetup.conf
4705 // --------------------------------------------------------------------------
4707 // check if the current level directory structure is available at this point
4708 if (leveldir_current == NULL)
4711 char **last_level_series = setup.level_setup.last_level_series;
4712 char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
4716 InitUserDataDirectory();
4718 UpdateLastPlayedLevels_List();
4720 if (!(file = fopen(filename, MODE_WRITE)))
4722 Warn("cannot write setup file '%s'", filename);
4729 fprintFileHeader(file, LEVELSETUP_FILENAME);
4731 if (deactivate_last_level_series)
4732 fprintf(file, "# %s\n# ", "the following level set may have caused a problem and was deactivated");
4734 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_LAST_LEVEL_SERIES,
4735 leveldir_current->identifier));
4737 for (i = 0; last_level_series[i] != NULL; i++)
4739 char token[strlen(TOKEN_STR_LAST_LEVEL_SERIES) + 10];
4741 sprintf(token, "%s.%03d", TOKEN_STR_LAST_LEVEL_SERIES, i);
4743 fprintf(file, "%s\n", getFormattedSetupEntry(token, last_level_series[i]));
4748 SetFilePermissions(filename, PERMS_PRIVATE);
4753 void SaveLevelSetup_LastSeries(void)
4755 SaveLevelSetup_LastSeries_Ext(FALSE);
4758 void SaveLevelSetup_LastSeries_Deactivate(void)
4760 SaveLevelSetup_LastSeries_Ext(TRUE);
4763 static void checkSeriesInfo(void)
4765 static char *level_directory = NULL;
4768 DirectoryEntry *dir_entry;
4771 checked_free(level_directory);
4773 // check for more levels besides the 'levels' field of 'levelinfo.conf'
4775 level_directory = getPath2((leveldir_current->in_user_dir ?
4776 getUserLevelDir(NULL) :
4777 options.level_directory),
4778 leveldir_current->fullpath);
4780 if ((dir = openDirectory(level_directory)) == NULL)
4782 Warn("cannot read level directory '%s'", level_directory);
4788 while ((dir_entry = readDirectory(dir)) != NULL) // loop all entries
4790 if (strlen(dir_entry->basename) > 4 &&
4791 dir_entry->basename[3] == '.' &&
4792 strEqual(&dir_entry->basename[4], LEVELFILE_EXTENSION))
4794 char levelnum_str[4];
4797 strncpy(levelnum_str, dir_entry->basename, 3);
4798 levelnum_str[3] = '\0';
4800 levelnum_value = atoi(levelnum_str);
4802 if (levelnum_value < leveldir_current->first_level)
4804 Warn("additional level %d found", levelnum_value);
4806 leveldir_current->first_level = levelnum_value;
4808 else if (levelnum_value > leveldir_current->last_level)
4810 Warn("additional level %d found", levelnum_value);
4812 leveldir_current->last_level = levelnum_value;
4818 closeDirectory(dir);
4821 void LoadLevelSetup_SeriesInfo(void)
4824 SetupFileHash *level_setup_hash = NULL;
4825 char *level_subdir = leveldir_current->subdir;
4828 // always start with reliable default values
4829 level_nr = leveldir_current->first_level;
4831 for (i = 0; i < MAX_LEVELS; i++)
4833 LevelStats_setPlayed(i, 0);
4834 LevelStats_setSolved(i, 0);
4839 // --------------------------------------------------------------------------
4840 // ~/.<program>/levelsetup/<level series>/levelsetup.conf
4841 // --------------------------------------------------------------------------
4843 level_subdir = leveldir_current->subdir;
4845 filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
4847 if ((level_setup_hash = loadSetupFileHash(filename)))
4851 // get last played level in this level set
4853 token_value = getHashEntry(level_setup_hash, TOKEN_STR_LAST_PLAYED_LEVEL);
4857 level_nr = atoi(token_value);
4859 if (level_nr < leveldir_current->first_level)
4860 level_nr = leveldir_current->first_level;
4861 if (level_nr > leveldir_current->last_level)
4862 level_nr = leveldir_current->last_level;
4865 // get handicap level in this level set
4867 token_value = getHashEntry(level_setup_hash, TOKEN_STR_HANDICAP_LEVEL);
4871 int level_nr = atoi(token_value);
4873 if (level_nr < leveldir_current->first_level)
4874 level_nr = leveldir_current->first_level;
4875 if (level_nr > leveldir_current->last_level + 1)
4876 level_nr = leveldir_current->last_level;
4878 if (leveldir_current->user_defined || !leveldir_current->handicap)
4879 level_nr = leveldir_current->last_level;
4881 leveldir_current->handicap_level = level_nr;
4884 // get number of played and solved levels in this level set
4886 BEGIN_HASH_ITERATION(level_setup_hash, itr)
4888 char *token = HASH_ITERATION_TOKEN(itr);
4889 char *value = HASH_ITERATION_VALUE(itr);
4891 if (strlen(token) == 3 &&
4892 token[0] >= '0' && token[0] <= '9' &&
4893 token[1] >= '0' && token[1] <= '9' &&
4894 token[2] >= '0' && token[2] <= '9')
4896 int level_nr = atoi(token);
4899 LevelStats_setPlayed(level_nr, atoi(value)); // read 1st column
4901 value = strchr(value, ' ');
4904 LevelStats_setSolved(level_nr, atoi(value)); // read 2nd column
4907 END_HASH_ITERATION(hash, itr)
4909 freeSetupFileHash(level_setup_hash);
4913 Debug("setup", "using default setup values");
4919 void SaveLevelSetup_SeriesInfo(void)
4922 char *level_subdir = leveldir_current->subdir;
4923 char *level_nr_str = int2str(level_nr, 0);
4924 char *handicap_level_str = int2str(leveldir_current->handicap_level, 0);
4928 // --------------------------------------------------------------------------
4929 // ~/.<program>/levelsetup/<level series>/levelsetup.conf
4930 // --------------------------------------------------------------------------
4932 InitLevelSetupDirectory(level_subdir);
4934 filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
4936 if (!(file = fopen(filename, MODE_WRITE)))
4938 Warn("cannot write setup file '%s'", filename);
4945 fprintFileHeader(file, LEVELSETUP_FILENAME);
4947 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_PLAYED_LEVEL,
4949 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_HANDICAP_LEVEL,
4950 handicap_level_str));
4952 for (i = leveldir_current->first_level; i <= leveldir_current->last_level;
4955 if (LevelStats_getPlayed(i) > 0 ||
4956 LevelStats_getSolved(i) > 0)
4961 sprintf(token, "%03d", i);
4962 sprintf(value, "%d %d", LevelStats_getPlayed(i), LevelStats_getSolved(i));
4964 fprintf(file, "%s\n", getFormattedSetupEntry(token, value));
4970 SetFilePermissions(filename, PERMS_PRIVATE);
4975 int LevelStats_getPlayed(int nr)
4977 return (nr >= 0 && nr < MAX_LEVELS ? level_stats[nr].played : 0);
4980 int LevelStats_getSolved(int nr)
4982 return (nr >= 0 && nr < MAX_LEVELS ? level_stats[nr].solved : 0);
4985 void LevelStats_setPlayed(int nr, int value)
4987 if (nr >= 0 && nr < MAX_LEVELS)
4988 level_stats[nr].played = value;
4991 void LevelStats_setSolved(int nr, int value)
4993 if (nr >= 0 && nr < MAX_LEVELS)
4994 level_stats[nr].solved = value;
4997 void LevelStats_incPlayed(int nr)
4999 if (nr >= 0 && nr < MAX_LEVELS)
5000 level_stats[nr].played++;
5003 void LevelStats_incSolved(int nr)
5005 if (nr >= 0 && nr < MAX_LEVELS)
5006 level_stats[nr].solved++;
5009 void LoadUserSetup(void)
5011 // --------------------------------------------------------------------------
5012 // ~/.<program>/usersetup.conf
5013 // --------------------------------------------------------------------------
5015 char *filename = getPath2(getMainUserGameDataDir(), USERSETUP_FILENAME);
5016 SetupFileHash *user_setup_hash = NULL;
5018 // always start with reliable default values
5021 if ((user_setup_hash = loadSetupFileHash(filename)))
5025 // get last selected user number
5026 token_value = getHashEntry(user_setup_hash, TOKEN_STR_LAST_USER);
5029 user.nr = MIN(MAX(0, atoi(token_value)), MAX_PLAYER_NAMES - 1);
5031 freeSetupFileHash(user_setup_hash);
5035 Debug("setup", "using default setup values");
5041 void SaveUserSetup(void)
5043 // --------------------------------------------------------------------------
5044 // ~/.<program>/usersetup.conf
5045 // --------------------------------------------------------------------------
5047 char *filename = getPath2(getMainUserGameDataDir(), USERSETUP_FILENAME);
5050 InitMainUserDataDirectory();
5052 if (!(file = fopen(filename, MODE_WRITE)))
5054 Warn("cannot write setup file '%s'", filename);
5061 fprintFileHeader(file, USERSETUP_FILENAME);
5063 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_USER,
5067 SetFilePermissions(filename, PERMS_PRIVATE);