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->level_group != entry2->level_group)
2881 compare_result = (entry1->level_group ? -1 : +1);
2882 else if (entry1->sort_priority == entry2->sort_priority)
2883 compare_result = strcasecmp(entry1->name_sorting, entry2->name_sorting);
2884 else if (class_sorting1 == class_sorting2)
2885 compare_result = entry1->sort_priority - entry2->sort_priority;
2887 compare_result = class_sorting1 - class_sorting2;
2889 return compare_result;
2892 static TreeInfo *createParentTreeInfoNode(TreeInfo *node_parent)
2896 if (node_parent == NULL)
2899 ti_new = newTreeInfo();
2900 setTreeInfoToDefaults(ti_new, node_parent->type);
2902 ti_new->node_parent = node_parent;
2903 ti_new->parent_link = TRUE;
2905 setString(&ti_new->identifier, node_parent->identifier);
2906 setString(&ti_new->name, BACKLINK_TEXT_PARENT);
2907 setString(&ti_new->name_sorting, ti_new->name);
2909 setString(&ti_new->subdir, STRING_PARENT_DIRECTORY);
2910 setString(&ti_new->fullpath, node_parent->fullpath);
2912 ti_new->sort_priority = node_parent->sort_priority;
2913 ti_new->latest_engine = node_parent->latest_engine;
2915 setString(&ti_new->class_desc, getLevelClassDescription(ti_new));
2917 pushTreeInfo(&node_parent->node_group, ti_new);
2922 static TreeInfo *createTopTreeInfoNode(TreeInfo *node_first)
2924 if (node_first == NULL)
2927 TreeInfo *ti_new = newTreeInfo();
2928 int type = node_first->type;
2930 setTreeInfoToDefaults(ti_new, type);
2932 ti_new->node_parent = NULL;
2933 ti_new->parent_link = FALSE;
2935 setString(&ti_new->identifier, node_first->identifier);
2936 setString(&ti_new->name, TREE_INFOTEXT(type));
2937 setString(&ti_new->name_sorting, ti_new->name);
2939 setString(&ti_new->subdir, STRING_TOP_DIRECTORY);
2940 setString(&ti_new->fullpath, ".");
2942 ti_new->sort_priority = node_first->sort_priority;;
2943 ti_new->latest_engine = node_first->latest_engine;
2945 setString(&ti_new->class_desc, TREE_INFOTEXT(type));
2947 ti_new->node_group = node_first;
2948 ti_new->level_group = TRUE;
2950 TreeInfo *ti_new2 = createParentTreeInfoNode(ti_new);
2952 setString(&ti_new2->name, TREE_BACKLINK_TEXT(type));
2953 setString(&ti_new2->name_sorting, ti_new2->name);
2958 static void setTreeInfoParentNodes(TreeInfo *node, TreeInfo *node_parent)
2962 if (node->node_group)
2963 setTreeInfoParentNodes(node->node_group, node);
2965 node->node_parent = node_parent;
2972 // ----------------------------------------------------------------------------
2973 // functions for handling level and custom artwork info cache
2974 // ----------------------------------------------------------------------------
2976 static void LoadArtworkInfoCache(void)
2978 InitCacheDirectory();
2980 if (artworkinfo_cache_old == NULL)
2982 char *filename = getPath2(getCacheDir(), ARTWORKINFO_CACHE_FILE);
2984 // try to load artwork info hash from already existing cache file
2985 artworkinfo_cache_old = loadSetupFileHash(filename);
2987 // if no artwork info cache file was found, start with empty hash
2988 if (artworkinfo_cache_old == NULL)
2989 artworkinfo_cache_old = newSetupFileHash();
2994 if (artworkinfo_cache_new == NULL)
2995 artworkinfo_cache_new = newSetupFileHash();
2997 update_artworkinfo_cache = FALSE;
3000 static void SaveArtworkInfoCache(void)
3002 if (!update_artworkinfo_cache)
3005 char *filename = getPath2(getCacheDir(), ARTWORKINFO_CACHE_FILE);
3007 InitCacheDirectory();
3009 saveSetupFileHash(artworkinfo_cache_new, filename);
3014 static char *getCacheTokenPrefix(char *prefix1, char *prefix2)
3016 static char *prefix = NULL;
3018 checked_free(prefix);
3020 prefix = getStringCat2WithSeparator(prefix1, prefix2, ".");
3025 // (identical to above function, but separate string buffer needed -- nasty)
3026 static char *getCacheToken(char *prefix, char *suffix)
3028 static char *token = NULL;
3030 checked_free(token);
3032 token = getStringCat2WithSeparator(prefix, suffix, ".");
3037 static char *getFileTimestampString(char *filename)
3039 return getStringCopy(i_to_a(getFileTimestampEpochSeconds(filename)));
3042 static boolean modifiedFileTimestamp(char *filename, char *timestamp_string)
3044 struct stat file_status;
3046 if (timestamp_string == NULL)
3049 if (!fileExists(filename)) // file does not exist
3050 return (atoi(timestamp_string) != 0);
3052 if (stat(filename, &file_status) != 0) // cannot stat file
3055 return (file_status.st_mtime != atoi(timestamp_string));
3058 static TreeInfo *getArtworkInfoCacheEntry(LevelDirTree *level_node, int type)
3060 char *identifier = level_node->subdir;
3061 char *type_string = ARTWORK_DIRECTORY(type);
3062 char *token_prefix = getCacheTokenPrefix(type_string, identifier);
3063 char *token_main = getCacheToken(token_prefix, "CACHED");
3064 char *cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
3065 boolean cached = (cache_entry != NULL && strEqual(cache_entry, "true"));
3066 TreeInfo *artwork_info = NULL;
3068 if (!use_artworkinfo_cache)
3071 if (optional_tokens_hash == NULL)
3075 // create hash from list of optional tokens (for quick access)
3076 optional_tokens_hash = newSetupFileHash();
3077 for (i = 0; optional_tokens[i] != NULL; i++)
3078 setHashEntry(optional_tokens_hash, optional_tokens[i], "");
3085 artwork_info = newTreeInfo();
3086 setTreeInfoToDefaults(artwork_info, type);
3088 // set all structure fields according to the token/value pairs
3089 ldi = *artwork_info;
3090 for (i = 0; artworkinfo_tokens[i].type != -1; i++)
3092 char *token_suffix = artworkinfo_tokens[i].text;
3093 char *token = getCacheToken(token_prefix, token_suffix);
3094 char *value = getHashEntry(artworkinfo_cache_old, token);
3096 (getHashEntry(optional_tokens_hash, token_suffix) != NULL);
3098 setSetupInfo(artworkinfo_tokens, i, value);
3100 // check if cache entry for this item is mandatory, but missing
3101 if (value == NULL && !optional)
3103 Warn("missing cache entry '%s'", token);
3109 *artwork_info = ldi;
3114 char *filename_levelinfo = getPath2(getLevelDirFromTreeInfo(level_node),
3115 LEVELINFO_FILENAME);
3116 char *filename_artworkinfo = getPath2(getSetupArtworkDir(artwork_info),
3117 ARTWORKINFO_FILENAME(type));
3119 // check if corresponding "levelinfo.conf" file has changed
3120 token_main = getCacheToken(token_prefix, "TIMESTAMP_LEVELINFO");
3121 cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
3123 if (modifiedFileTimestamp(filename_levelinfo, cache_entry))
3126 // check if corresponding "<artworkinfo>.conf" file has changed
3127 token_main = getCacheToken(token_prefix, "TIMESTAMP_ARTWORKINFO");
3128 cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
3130 if (modifiedFileTimestamp(filename_artworkinfo, cache_entry))
3133 checked_free(filename_levelinfo);
3134 checked_free(filename_artworkinfo);
3137 if (!cached && artwork_info != NULL)
3139 freeTreeInfo(artwork_info);
3144 return artwork_info;
3147 static void setArtworkInfoCacheEntry(TreeInfo *artwork_info,
3148 LevelDirTree *level_node, int type)
3150 char *identifier = level_node->subdir;
3151 char *type_string = ARTWORK_DIRECTORY(type);
3152 char *token_prefix = getCacheTokenPrefix(type_string, identifier);
3153 char *token_main = getCacheToken(token_prefix, "CACHED");
3154 boolean set_cache_timestamps = TRUE;
3157 setHashEntry(artworkinfo_cache_new, token_main, "true");
3159 if (set_cache_timestamps)
3161 char *filename_levelinfo = getPath2(getLevelDirFromTreeInfo(level_node),
3162 LEVELINFO_FILENAME);
3163 char *filename_artworkinfo = getPath2(getSetupArtworkDir(artwork_info),
3164 ARTWORKINFO_FILENAME(type));
3165 char *timestamp_levelinfo = getFileTimestampString(filename_levelinfo);
3166 char *timestamp_artworkinfo = getFileTimestampString(filename_artworkinfo);
3168 token_main = getCacheToken(token_prefix, "TIMESTAMP_LEVELINFO");
3169 setHashEntry(artworkinfo_cache_new, token_main, timestamp_levelinfo);
3171 token_main = getCacheToken(token_prefix, "TIMESTAMP_ARTWORKINFO");
3172 setHashEntry(artworkinfo_cache_new, token_main, timestamp_artworkinfo);
3174 checked_free(filename_levelinfo);
3175 checked_free(filename_artworkinfo);
3176 checked_free(timestamp_levelinfo);
3177 checked_free(timestamp_artworkinfo);
3180 ldi = *artwork_info;
3181 for (i = 0; artworkinfo_tokens[i].type != -1; i++)
3183 char *token = getCacheToken(token_prefix, artworkinfo_tokens[i].text);
3184 char *value = getSetupValue(artworkinfo_tokens[i].type,
3185 artworkinfo_tokens[i].value);
3187 setHashEntry(artworkinfo_cache_new, token, value);
3192 // ----------------------------------------------------------------------------
3193 // functions for loading level info and custom artwork info
3194 // ----------------------------------------------------------------------------
3196 int GetZipFileTreeType(char *zip_filename)
3198 static char *top_dir_path = NULL;
3199 static char *top_dir_conf_filename[NUM_BASE_TREE_TYPES] = { NULL };
3200 static char *conf_basename[NUM_BASE_TREE_TYPES] =
3202 GRAPHICSINFO_FILENAME,
3203 SOUNDSINFO_FILENAME,
3209 checked_free(top_dir_path);
3210 top_dir_path = NULL;
3212 for (j = 0; j < NUM_BASE_TREE_TYPES; j++)
3214 checked_free(top_dir_conf_filename[j]);
3215 top_dir_conf_filename[j] = NULL;
3218 char **zip_entries = zip_list(zip_filename);
3220 // check if zip file successfully opened
3221 if (zip_entries == NULL || zip_entries[0] == NULL)
3222 return TREE_TYPE_UNDEFINED;
3224 // first zip file entry is expected to be top level directory
3225 char *top_dir = zip_entries[0];
3227 // check if valid top level directory found in zip file
3228 if (!strSuffix(top_dir, "/"))
3229 return TREE_TYPE_UNDEFINED;
3231 // get filenames of valid configuration files in top level directory
3232 for (j = 0; j < NUM_BASE_TREE_TYPES; j++)
3233 top_dir_conf_filename[j] = getStringCat2(top_dir, conf_basename[j]);
3235 int tree_type = TREE_TYPE_UNDEFINED;
3238 while (zip_entries[e] != NULL)
3240 // check if every zip file entry is below top level directory
3241 if (!strPrefix(zip_entries[e], top_dir))
3242 return TREE_TYPE_UNDEFINED;
3244 // check if this zip file entry is a valid configuration filename
3245 for (j = 0; j < NUM_BASE_TREE_TYPES; j++)
3247 if (strEqual(zip_entries[e], top_dir_conf_filename[j]))
3249 // only exactly one valid configuration file allowed
3250 if (tree_type != TREE_TYPE_UNDEFINED)
3251 return TREE_TYPE_UNDEFINED;
3263 static boolean CheckZipFileForDirectory(char *zip_filename, char *directory,
3266 static char *top_dir_path = NULL;
3267 static char *top_dir_conf_filename = NULL;
3269 checked_free(top_dir_path);
3270 checked_free(top_dir_conf_filename);
3272 top_dir_path = NULL;
3273 top_dir_conf_filename = NULL;
3275 char *conf_basename = (tree_type == TREE_TYPE_LEVEL_DIR ? LEVELINFO_FILENAME :
3276 ARTWORKINFO_FILENAME(tree_type));
3278 // check if valid configuration filename determined
3279 if (conf_basename == NULL || strEqual(conf_basename, ""))
3282 char **zip_entries = zip_list(zip_filename);
3284 // check if zip file successfully opened
3285 if (zip_entries == NULL || zip_entries[0] == NULL)
3288 // first zip file entry is expected to be top level directory
3289 char *top_dir = zip_entries[0];
3291 // check if valid top level directory found in zip file
3292 if (!strSuffix(top_dir, "/"))
3295 // get path of extracted top level directory
3296 top_dir_path = getPath2(directory, top_dir);
3298 // remove trailing directory separator from top level directory path
3299 // (required to be able to check for file and directory in next step)
3300 top_dir_path[strlen(top_dir_path) - 1] = '\0';
3302 // check if zip file's top level directory already exists in target directory
3303 if (fileExists(top_dir_path)) // (checks for file and directory)
3306 // get filename of configuration file in top level directory
3307 top_dir_conf_filename = getStringCat2(top_dir, conf_basename);
3309 boolean found_top_dir_conf_filename = FALSE;
3312 while (zip_entries[i] != NULL)
3314 // check if every zip file entry is below top level directory
3315 if (!strPrefix(zip_entries[i], top_dir))
3318 // check if this zip file entry is the configuration filename
3319 if (strEqual(zip_entries[i], top_dir_conf_filename))
3320 found_top_dir_conf_filename = TRUE;
3325 // check if valid configuration filename was found in zip file
3326 if (!found_top_dir_conf_filename)
3332 char *ExtractZipFileIntoDirectory(char *zip_filename, char *directory,
3335 boolean zip_file_valid = CheckZipFileForDirectory(zip_filename, directory,
3338 if (!zip_file_valid)
3340 Warn("zip file '%s' rejected!", zip_filename);
3345 char **zip_entries = zip_extract(zip_filename, directory);
3347 if (zip_entries == NULL)
3349 Warn("zip file '%s' could not be extracted!", zip_filename);
3354 Info("zip file '%s' successfully extracted!", zip_filename);
3356 // first zip file entry contains top level directory
3357 char *top_dir = zip_entries[0];
3359 // remove trailing directory separator from top level directory
3360 top_dir[strlen(top_dir) - 1] = '\0';
3365 static void ProcessZipFilesInDirectory(char *directory, int tree_type)
3368 DirectoryEntry *dir_entry;
3370 if ((dir = openDirectory(directory)) == NULL)
3372 // display error if directory is main "options.graphics_directory" etc.
3373 if (tree_type == TREE_TYPE_LEVEL_DIR ||
3374 directory == OPTIONS_ARTWORK_DIRECTORY(tree_type))
3375 Warn("cannot read directory '%s'", directory);
3380 while ((dir_entry = readDirectory(dir)) != NULL) // loop all entries
3382 // skip non-zip files (and also directories with zip extension)
3383 if (!strSuffixLower(dir_entry->basename, ".zip") || dir_entry->is_directory)
3386 char *zip_filename = getPath2(directory, dir_entry->basename);
3387 char *zip_filename_extracted = getStringCat2(zip_filename, ".extracted");
3388 char *zip_filename_rejected = getStringCat2(zip_filename, ".rejected");
3390 // check if zip file hasn't already been extracted or rejected
3391 if (!fileExists(zip_filename_extracted) &&
3392 !fileExists(zip_filename_rejected))
3394 char *top_dir = ExtractZipFileIntoDirectory(zip_filename, directory,
3396 char *marker_filename = (top_dir != NULL ? zip_filename_extracted :
3397 zip_filename_rejected);
3400 // create empty file to mark zip file as extracted or rejected
3401 if ((marker_file = fopen(marker_filename, MODE_WRITE)))
3402 fclose(marker_file);
3405 free(zip_filename_extracted);
3406 free(zip_filename_rejected);
3410 closeDirectory(dir);
3413 // forward declaration for recursive call by "LoadLevelInfoFromLevelDir()"
3414 static void LoadLevelInfoFromLevelDir(TreeInfo **, TreeInfo *, char *);
3416 static boolean LoadLevelInfoFromLevelConf(TreeInfo **node_first,
3417 TreeInfo *node_parent,
3418 char *level_directory,
3419 char *directory_name)
3421 char *directory_path = getPath2(level_directory, directory_name);
3422 char *filename = getPath2(directory_path, LEVELINFO_FILENAME);
3423 SetupFileHash *setup_file_hash;
3424 LevelDirTree *leveldir_new = NULL;
3427 // unless debugging, silently ignore directories without "levelinfo.conf"
3428 if (!options.debug && !fileExists(filename))
3430 free(directory_path);
3436 setup_file_hash = loadSetupFileHash(filename);
3438 if (setup_file_hash == NULL)
3440 #if DEBUG_NO_CONFIG_FILE
3441 Debug("setup", "ignoring level directory '%s'", directory_path);
3444 free(directory_path);
3450 leveldir_new = newTreeInfo();
3453 setTreeInfoToDefaultsFromParent(leveldir_new, node_parent);
3455 setTreeInfoToDefaults(leveldir_new, TREE_TYPE_LEVEL_DIR);
3457 leveldir_new->subdir = getStringCopy(directory_name);
3459 // set all structure fields according to the token/value pairs
3460 ldi = *leveldir_new;
3461 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
3462 setSetupInfo(levelinfo_tokens, i,
3463 getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
3464 *leveldir_new = ldi;
3466 if (strEqual(leveldir_new->name, ANONYMOUS_NAME))
3467 setString(&leveldir_new->name, leveldir_new->subdir);
3469 if (leveldir_new->identifier == NULL)
3470 leveldir_new->identifier = getStringCopy(leveldir_new->subdir);
3472 if (leveldir_new->name_sorting == NULL)
3473 leveldir_new->name_sorting = getStringCopy(leveldir_new->name);
3475 if (node_parent == NULL) // top level group
3477 leveldir_new->basepath = getStringCopy(level_directory);
3478 leveldir_new->fullpath = getStringCopy(leveldir_new->subdir);
3480 else // sub level group
3482 leveldir_new->basepath = getStringCopy(node_parent->basepath);
3483 leveldir_new->fullpath = getPath2(node_parent->fullpath, directory_name);
3486 leveldir_new->last_level =
3487 leveldir_new->first_level + leveldir_new->levels - 1;
3489 leveldir_new->in_user_dir =
3490 (!strEqual(leveldir_new->basepath, options.level_directory));
3492 // adjust some settings if user's private level directory was detected
3493 if (leveldir_new->sort_priority == LEVELCLASS_UNDEFINED &&
3494 leveldir_new->in_user_dir &&
3495 (strEqual(leveldir_new->subdir, getLoginName()) ||
3496 strEqual(leveldir_new->name, getLoginName()) ||
3497 strEqual(leveldir_new->author, getRealName())))
3499 leveldir_new->sort_priority = LEVELCLASS_PRIVATE_START;
3500 leveldir_new->readonly = FALSE;
3503 leveldir_new->user_defined =
3504 (leveldir_new->in_user_dir && IS_LEVELCLASS_PRIVATE(leveldir_new));
3506 leveldir_new->color = LEVELCOLOR(leveldir_new);
3508 setString(&leveldir_new->class_desc, getLevelClassDescription(leveldir_new));
3510 leveldir_new->handicap_level = // set handicap to default value
3511 (leveldir_new->user_defined || !leveldir_new->handicap ?
3512 leveldir_new->last_level : leveldir_new->first_level);
3514 DrawInitText(leveldir_new->name, 150, FC_YELLOW);
3516 pushTreeInfo(node_first, leveldir_new);
3518 freeSetupFileHash(setup_file_hash);
3520 if (leveldir_new->level_group)
3522 // create node to link back to current level directory
3523 createParentTreeInfoNode(leveldir_new);
3525 // recursively step into sub-directory and look for more level series
3526 LoadLevelInfoFromLevelDir(&leveldir_new->node_group,
3527 leveldir_new, directory_path);
3530 free(directory_path);
3536 static void LoadLevelInfoFromLevelDir(TreeInfo **node_first,
3537 TreeInfo *node_parent,
3538 char *level_directory)
3540 // ---------- 1st stage: process any level set zip files ----------
3542 ProcessZipFilesInDirectory(level_directory, TREE_TYPE_LEVEL_DIR);
3544 // ---------- 2nd stage: check for level set directories ----------
3547 DirectoryEntry *dir_entry;
3548 boolean valid_entry_found = FALSE;
3550 if ((dir = openDirectory(level_directory)) == NULL)
3552 Warn("cannot read level directory '%s'", level_directory);
3557 while ((dir_entry = readDirectory(dir)) != NULL) // loop all entries
3559 char *directory_name = dir_entry->basename;
3560 char *directory_path = getPath2(level_directory, directory_name);
3562 // skip entries for current and parent directory
3563 if (strEqual(directory_name, ".") ||
3564 strEqual(directory_name, ".."))
3566 free(directory_path);
3571 // find out if directory entry is itself a directory
3572 if (!dir_entry->is_directory) // not a directory
3574 free(directory_path);
3579 free(directory_path);
3581 if (strEqual(directory_name, GRAPHICS_DIRECTORY) ||
3582 strEqual(directory_name, SOUNDS_DIRECTORY) ||
3583 strEqual(directory_name, MUSIC_DIRECTORY))
3586 valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
3591 closeDirectory(dir);
3593 // special case: top level directory may directly contain "levelinfo.conf"
3594 if (node_parent == NULL && !valid_entry_found)
3596 // check if this directory directly contains a file "levelinfo.conf"
3597 valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
3598 level_directory, ".");
3601 if (!valid_entry_found)
3602 Warn("cannot find any valid level series in directory '%s'",
3606 boolean AdjustGraphicsForEMC(void)
3608 boolean settings_changed = FALSE;
3610 settings_changed |= adjustTreeGraphicsForEMC(leveldir_first_all);
3611 settings_changed |= adjustTreeGraphicsForEMC(leveldir_first);
3613 return settings_changed;
3616 boolean AdjustSoundsForEMC(void)
3618 boolean settings_changed = FALSE;
3620 settings_changed |= adjustTreeSoundsForEMC(leveldir_first_all);
3621 settings_changed |= adjustTreeSoundsForEMC(leveldir_first);
3623 return settings_changed;
3626 void LoadLevelInfo(void)
3628 InitUserLevelDirectory(getLoginName());
3630 DrawInitText("Loading level series", 120, FC_GREEN);
3632 LoadLevelInfoFromLevelDir(&leveldir_first, NULL, options.level_directory);
3633 LoadLevelInfoFromLevelDir(&leveldir_first, NULL, getUserLevelDir(NULL));
3635 leveldir_first = createTopTreeInfoNode(leveldir_first);
3637 /* after loading all level set information, clone the level directory tree
3638 and remove all level sets without levels (these may still contain artwork
3639 to be offered in the setup menu as "custom artwork", and are therefore
3640 checked for existing artwork in the function "LoadLevelArtworkInfo()") */
3641 leveldir_first_all = leveldir_first;
3642 cloneTree(&leveldir_first, leveldir_first_all, TRUE);
3644 AdjustGraphicsForEMC();
3645 AdjustSoundsForEMC();
3647 // before sorting, the first entries will be from the user directory
3648 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
3650 if (leveldir_first == NULL)
3651 Fail("cannot find any valid level series in any directory");
3653 sortTreeInfo(&leveldir_first);
3655 #if ENABLE_UNUSED_CODE
3656 dumpTreeInfo(leveldir_first, 0);
3660 static boolean LoadArtworkInfoFromArtworkConf(TreeInfo **node_first,
3661 TreeInfo *node_parent,
3662 char *base_directory,
3663 char *directory_name, int type)
3665 char *directory_path = getPath2(base_directory, directory_name);
3666 char *filename = getPath2(directory_path, ARTWORKINFO_FILENAME(type));
3667 SetupFileHash *setup_file_hash = NULL;
3668 TreeInfo *artwork_new = NULL;
3671 if (fileExists(filename))
3672 setup_file_hash = loadSetupFileHash(filename);
3674 if (setup_file_hash == NULL) // no config file -- look for artwork files
3677 DirectoryEntry *dir_entry;
3678 boolean valid_file_found = FALSE;
3680 if ((dir = openDirectory(directory_path)) != NULL)
3682 while ((dir_entry = readDirectory(dir)) != NULL)
3684 if (FileIsArtworkType(dir_entry->filename, type))
3686 valid_file_found = TRUE;
3692 closeDirectory(dir);
3695 if (!valid_file_found)
3697 #if DEBUG_NO_CONFIG_FILE
3698 if (!strEqual(directory_name, "."))
3699 Debug("setup", "ignoring artwork directory '%s'", directory_path);
3702 free(directory_path);
3709 artwork_new = newTreeInfo();
3712 setTreeInfoToDefaultsFromParent(artwork_new, node_parent);
3714 setTreeInfoToDefaults(artwork_new, type);
3716 artwork_new->subdir = getStringCopy(directory_name);
3718 if (setup_file_hash) // (before defining ".color" and ".class_desc")
3720 // set all structure fields according to the token/value pairs
3722 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
3723 setSetupInfo(levelinfo_tokens, i,
3724 getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
3727 if (strEqual(artwork_new->name, ANONYMOUS_NAME))
3728 setString(&artwork_new->name, artwork_new->subdir);
3730 if (artwork_new->identifier == NULL)
3731 artwork_new->identifier = getStringCopy(artwork_new->subdir);
3733 if (artwork_new->name_sorting == NULL)
3734 artwork_new->name_sorting = getStringCopy(artwork_new->name);
3737 if (node_parent == NULL) // top level group
3739 artwork_new->basepath = getStringCopy(base_directory);
3740 artwork_new->fullpath = getStringCopy(artwork_new->subdir);
3742 else // sub level group
3744 artwork_new->basepath = getStringCopy(node_parent->basepath);
3745 artwork_new->fullpath = getPath2(node_parent->fullpath, directory_name);
3748 artwork_new->in_user_dir =
3749 (!strEqual(artwork_new->basepath, OPTIONS_ARTWORK_DIRECTORY(type)));
3751 // (may use ".sort_priority" from "setup_file_hash" above)
3752 artwork_new->color = ARTWORKCOLOR(artwork_new);
3754 setString(&artwork_new->class_desc, getLevelClassDescription(artwork_new));
3756 if (setup_file_hash == NULL) // (after determining ".user_defined")
3758 if (strEqual(artwork_new->subdir, "."))
3760 if (artwork_new->user_defined)
3762 setString(&artwork_new->identifier, "private");
3763 artwork_new->sort_priority = ARTWORKCLASS_PRIVATE;
3767 setString(&artwork_new->identifier, "classic");
3768 artwork_new->sort_priority = ARTWORKCLASS_CLASSICS;
3771 // set to new values after changing ".sort_priority"
3772 artwork_new->color = ARTWORKCOLOR(artwork_new);
3774 setString(&artwork_new->class_desc,
3775 getLevelClassDescription(artwork_new));
3779 setString(&artwork_new->identifier, artwork_new->subdir);
3782 setString(&artwork_new->name, artwork_new->identifier);
3783 setString(&artwork_new->name_sorting, artwork_new->name);
3786 pushTreeInfo(node_first, artwork_new);
3788 freeSetupFileHash(setup_file_hash);
3790 free(directory_path);
3796 static void LoadArtworkInfoFromArtworkDir(TreeInfo **node_first,
3797 TreeInfo *node_parent,
3798 char *base_directory, int type)
3800 // ---------- 1st stage: process any artwork set zip files ----------
3802 ProcessZipFilesInDirectory(base_directory, type);
3804 // ---------- 2nd stage: check for artwork set directories ----------
3807 DirectoryEntry *dir_entry;
3808 boolean valid_entry_found = FALSE;
3810 if ((dir = openDirectory(base_directory)) == NULL)
3812 // display error if directory is main "options.graphics_directory" etc.
3813 if (base_directory == OPTIONS_ARTWORK_DIRECTORY(type))
3814 Warn("cannot read directory '%s'", base_directory);
3819 while ((dir_entry = readDirectory(dir)) != NULL) // loop all entries
3821 char *directory_name = dir_entry->basename;
3822 char *directory_path = getPath2(base_directory, directory_name);
3824 // skip directory entries for current and parent directory
3825 if (strEqual(directory_name, ".") ||
3826 strEqual(directory_name, ".."))
3828 free(directory_path);
3833 // skip directory entries which are not a directory
3834 if (!dir_entry->is_directory) // not a directory
3836 free(directory_path);
3841 free(directory_path);
3843 // check if this directory contains artwork with or without config file
3844 valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
3846 directory_name, type);
3849 closeDirectory(dir);
3851 // check if this directory directly contains artwork itself
3852 valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
3853 base_directory, ".",
3855 if (!valid_entry_found)
3856 Warn("cannot find any valid artwork in directory '%s'", base_directory);
3859 static TreeInfo *getDummyArtworkInfo(int type)
3861 // this is only needed when there is completely no artwork available
3862 TreeInfo *artwork_new = newTreeInfo();
3864 setTreeInfoToDefaults(artwork_new, type);
3866 setString(&artwork_new->subdir, UNDEFINED_FILENAME);
3867 setString(&artwork_new->fullpath, UNDEFINED_FILENAME);
3868 setString(&artwork_new->basepath, UNDEFINED_FILENAME);
3870 setString(&artwork_new->identifier, UNDEFINED_FILENAME);
3871 setString(&artwork_new->name, UNDEFINED_FILENAME);
3872 setString(&artwork_new->name_sorting, UNDEFINED_FILENAME);
3877 void SetCurrentArtwork(int type)
3879 ArtworkDirTree **current_ptr = ARTWORK_CURRENT_PTR(artwork, type);
3880 ArtworkDirTree *first_node = ARTWORK_FIRST_NODE(artwork, type);
3881 char *setup_set = SETUP_ARTWORK_SET(setup, type);
3882 char *default_subdir = ARTWORK_DEFAULT_SUBDIR(type);
3884 // set current artwork to artwork configured in setup menu
3885 *current_ptr = getTreeInfoFromIdentifier(first_node, setup_set);
3887 // if not found, set current artwork to default artwork
3888 if (*current_ptr == NULL)
3889 *current_ptr = getTreeInfoFromIdentifier(first_node, default_subdir);
3891 // if not found, set current artwork to first artwork in tree
3892 if (*current_ptr == NULL)
3893 *current_ptr = getFirstValidTreeInfoEntry(first_node);
3896 void ChangeCurrentArtworkIfNeeded(int type)
3898 char *current_identifier = ARTWORK_CURRENT_IDENTIFIER(artwork, type);
3899 char *setup_set = SETUP_ARTWORK_SET(setup, type);
3901 if (!strEqual(current_identifier, setup_set))
3902 SetCurrentArtwork(type);
3905 void LoadArtworkInfo(void)
3907 LoadArtworkInfoCache();
3909 DrawInitText("Looking for custom artwork", 120, FC_GREEN);
3911 LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
3912 options.graphics_directory,
3913 TREE_TYPE_GRAPHICS_DIR);
3914 LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
3915 getUserGraphicsDir(),
3916 TREE_TYPE_GRAPHICS_DIR);
3918 LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
3919 options.sounds_directory,
3920 TREE_TYPE_SOUNDS_DIR);
3921 LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
3923 TREE_TYPE_SOUNDS_DIR);
3925 LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
3926 options.music_directory,
3927 TREE_TYPE_MUSIC_DIR);
3928 LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
3930 TREE_TYPE_MUSIC_DIR);
3932 if (artwork.gfx_first == NULL)
3933 artwork.gfx_first = getDummyArtworkInfo(TREE_TYPE_GRAPHICS_DIR);
3934 if (artwork.snd_first == NULL)
3935 artwork.snd_first = getDummyArtworkInfo(TREE_TYPE_SOUNDS_DIR);
3936 if (artwork.mus_first == NULL)
3937 artwork.mus_first = getDummyArtworkInfo(TREE_TYPE_MUSIC_DIR);
3939 // before sorting, the first entries will be from the user directory
3940 SetCurrentArtwork(ARTWORK_TYPE_GRAPHICS);
3941 SetCurrentArtwork(ARTWORK_TYPE_SOUNDS);
3942 SetCurrentArtwork(ARTWORK_TYPE_MUSIC);
3944 artwork.gfx_current_identifier = artwork.gfx_current->identifier;
3945 artwork.snd_current_identifier = artwork.snd_current->identifier;
3946 artwork.mus_current_identifier = artwork.mus_current->identifier;
3948 #if ENABLE_UNUSED_CODE
3949 Debug("setup:LoadArtworkInfo", "graphics set == %s",
3950 artwork.gfx_current_identifier);
3951 Debug("setup:LoadArtworkInfo", "sounds set == %s",
3952 artwork.snd_current_identifier);
3953 Debug("setup:LoadArtworkInfo", "music set == %s",
3954 artwork.mus_current_identifier);
3957 sortTreeInfo(&artwork.gfx_first);
3958 sortTreeInfo(&artwork.snd_first);
3959 sortTreeInfo(&artwork.mus_first);
3961 #if ENABLE_UNUSED_CODE
3962 dumpTreeInfo(artwork.gfx_first, 0);
3963 dumpTreeInfo(artwork.snd_first, 0);
3964 dumpTreeInfo(artwork.mus_first, 0);
3968 static void MoveArtworkInfoIntoSubTree(ArtworkDirTree **artwork_node)
3970 ArtworkDirTree *artwork_new = newTreeInfo();
3971 char *top_node_name = "standalone artwork";
3973 setTreeInfoToDefaults(artwork_new, (*artwork_node)->type);
3975 artwork_new->level_group = TRUE;
3977 setString(&artwork_new->identifier, top_node_name);
3978 setString(&artwork_new->name, top_node_name);
3979 setString(&artwork_new->name_sorting, top_node_name);
3981 // create node to link back to current custom artwork directory
3982 createParentTreeInfoNode(artwork_new);
3984 // move existing custom artwork tree into newly created sub-tree
3985 artwork_new->node_group->next = *artwork_node;
3987 // change custom artwork tree to contain only newly created node
3988 *artwork_node = artwork_new;
3991 static void LoadArtworkInfoFromLevelInfoExt(ArtworkDirTree **artwork_node,
3992 ArtworkDirTree *node_parent,
3993 LevelDirTree *level_node,
3994 boolean empty_level_set_mode)
3996 int type = (*artwork_node)->type;
3998 // recursively check all level directories for artwork sub-directories
4002 boolean empty_level_set = (level_node->levels == 0);
4004 // check all tree entries for artwork, but skip parent link entries
4005 if (!level_node->parent_link && empty_level_set == empty_level_set_mode)
4007 TreeInfo *artwork_new = getArtworkInfoCacheEntry(level_node, type);
4008 boolean cached = (artwork_new != NULL);
4012 pushTreeInfo(artwork_node, artwork_new);
4016 TreeInfo *topnode_last = *artwork_node;
4017 char *path = getPath2(getLevelDirFromTreeInfo(level_node),
4018 ARTWORK_DIRECTORY(type));
4020 LoadArtworkInfoFromArtworkDir(artwork_node, NULL, path, type);
4022 if (topnode_last != *artwork_node) // check for newly added node
4024 artwork_new = *artwork_node;
4026 setString(&artwork_new->identifier, level_node->subdir);
4027 setString(&artwork_new->name, level_node->name);
4028 setString(&artwork_new->name_sorting, level_node->name_sorting);
4030 artwork_new->sort_priority = level_node->sort_priority;
4031 artwork_new->color = LEVELCOLOR(artwork_new);
4033 update_artworkinfo_cache = TRUE;
4039 // insert artwork info (from old cache or filesystem) into new cache
4040 if (artwork_new != NULL)
4041 setArtworkInfoCacheEntry(artwork_new, level_node, type);
4044 DrawInitText(level_node->name, 150, FC_YELLOW);
4046 if (level_node->node_group != NULL)
4048 TreeInfo *artwork_new = newTreeInfo();
4051 setTreeInfoToDefaultsFromParent(artwork_new, node_parent);
4053 setTreeInfoToDefaults(artwork_new, type);
4055 artwork_new->level_group = TRUE;
4057 setString(&artwork_new->identifier, level_node->subdir);
4059 if (node_parent == NULL) // check for top tree node
4061 char *top_node_name = (empty_level_set_mode ?
4062 "artwork for certain level sets" :
4063 "artwork included in level sets");
4065 setString(&artwork_new->name, top_node_name);
4066 setString(&artwork_new->name_sorting, top_node_name);
4070 setString(&artwork_new->name, level_node->name);
4071 setString(&artwork_new->name_sorting, level_node->name_sorting);
4074 pushTreeInfo(artwork_node, artwork_new);
4076 // create node to link back to current custom artwork directory
4077 createParentTreeInfoNode(artwork_new);
4079 // recursively step into sub-directory and look for more custom artwork
4080 LoadArtworkInfoFromLevelInfoExt(&artwork_new->node_group, artwork_new,
4081 level_node->node_group,
4082 empty_level_set_mode);
4084 // if sub-tree has no custom artwork at all, remove it
4085 if (artwork_new->node_group->next == NULL)
4086 removeTreeInfo(artwork_node);
4089 level_node = level_node->next;
4093 static void LoadArtworkInfoFromLevelInfo(ArtworkDirTree **artwork_node)
4095 // move peviously loaded artwork tree into separate sub-tree
4096 MoveArtworkInfoIntoSubTree(artwork_node);
4098 // load artwork from level sets into separate sub-trees
4099 LoadArtworkInfoFromLevelInfoExt(artwork_node, NULL, leveldir_first_all, TRUE);
4100 LoadArtworkInfoFromLevelInfoExt(artwork_node, NULL, leveldir_first_all, FALSE);
4102 // add top tree node over all three separate sub-trees
4103 *artwork_node = createTopTreeInfoNode(*artwork_node);
4105 // set all parent links (back links) in complete artwork tree
4106 setTreeInfoParentNodes(*artwork_node, NULL);
4109 void LoadLevelArtworkInfo(void)
4111 print_timestamp_init("LoadLevelArtworkInfo");
4113 DrawInitText("Looking for custom level artwork", 120, FC_GREEN);
4115 print_timestamp_time("DrawTimeText");
4117 LoadArtworkInfoFromLevelInfo(&artwork.gfx_first);
4118 print_timestamp_time("LoadArtworkInfoFromLevelInfo (gfx)");
4119 LoadArtworkInfoFromLevelInfo(&artwork.snd_first);
4120 print_timestamp_time("LoadArtworkInfoFromLevelInfo (snd)");
4121 LoadArtworkInfoFromLevelInfo(&artwork.mus_first);
4122 print_timestamp_time("LoadArtworkInfoFromLevelInfo (mus)");
4124 SaveArtworkInfoCache();
4126 print_timestamp_time("SaveArtworkInfoCache");
4128 // needed for reloading level artwork not known at ealier stage
4129 ChangeCurrentArtworkIfNeeded(ARTWORK_TYPE_GRAPHICS);
4130 ChangeCurrentArtworkIfNeeded(ARTWORK_TYPE_SOUNDS);
4131 ChangeCurrentArtworkIfNeeded(ARTWORK_TYPE_MUSIC);
4133 print_timestamp_time("getTreeInfoFromIdentifier");
4135 sortTreeInfo(&artwork.gfx_first);
4136 sortTreeInfo(&artwork.snd_first);
4137 sortTreeInfo(&artwork.mus_first);
4139 print_timestamp_time("sortTreeInfo");
4141 #if ENABLE_UNUSED_CODE
4142 dumpTreeInfo(artwork.gfx_first, 0);
4143 dumpTreeInfo(artwork.snd_first, 0);
4144 dumpTreeInfo(artwork.mus_first, 0);
4147 print_timestamp_done("LoadLevelArtworkInfo");
4150 static boolean AddTreeSetToTreeInfoExt(TreeInfo *tree_node_old, char *tree_dir,
4151 char *tree_subdir_new, int type)
4153 if (tree_node_old == NULL)
4155 if (type == TREE_TYPE_LEVEL_DIR)
4157 // get level info tree node of personal user level set
4158 tree_node_old = getTreeInfoFromIdentifier(leveldir_first, getLoginName());
4160 // this may happen if "setup.internal.create_user_levelset" is FALSE
4161 // or if file "levelinfo.conf" is missing in personal user level set
4162 if (tree_node_old == NULL)
4163 tree_node_old = leveldir_first->node_group;
4167 // get artwork info tree node of first artwork set
4168 tree_node_old = ARTWORK_FIRST_NODE(artwork, type);
4172 if (tree_dir == NULL)
4173 tree_dir = TREE_USERDIR(type);
4175 if (tree_node_old == NULL ||
4177 tree_subdir_new == NULL) // should not happen
4180 int draw_deactivation_mask = GetDrawDeactivationMask();
4182 // override draw deactivation mask (temporarily disable drawing)
4183 SetDrawDeactivationMask(REDRAW_ALL);
4185 if (type == TREE_TYPE_LEVEL_DIR)
4187 // load new level set config and add it next to first user level set
4188 LoadLevelInfoFromLevelConf(&tree_node_old->next,
4189 tree_node_old->node_parent,
4190 tree_dir, tree_subdir_new);
4194 // load new artwork set config and add it next to first artwork set
4195 LoadArtworkInfoFromArtworkConf(&tree_node_old->next,
4196 tree_node_old->node_parent,
4197 tree_dir, tree_subdir_new, type);
4200 // set draw deactivation mask to previous value
4201 SetDrawDeactivationMask(draw_deactivation_mask);
4203 // get first node of level or artwork info tree
4204 TreeInfo **tree_node_first = TREE_FIRST_NODE_PTR(type);
4206 // get tree info node of newly added level or artwork set
4207 TreeInfo *tree_node_new = getTreeInfoFromIdentifier(*tree_node_first,
4210 if (tree_node_new == NULL) // should not happen
4213 // correct top link and parent node link of newly created tree node
4214 tree_node_new->node_top = tree_node_old->node_top;
4215 tree_node_new->node_parent = tree_node_old->node_parent;
4217 // sort tree info to adjust position of newly added tree set
4218 sortTreeInfo(tree_node_first);
4223 void AddTreeSetToTreeInfo(TreeInfo *tree_node, char *tree_dir,
4224 char *tree_subdir_new, int type)
4226 if (!AddTreeSetToTreeInfoExt(tree_node, tree_dir, tree_subdir_new, type))
4227 Fail("internal tree info structure corrupted -- aborting");
4230 void AddUserLevelSetToLevelInfo(char *level_subdir_new)
4232 AddTreeSetToTreeInfo(NULL, NULL, level_subdir_new, TREE_TYPE_LEVEL_DIR);
4235 char *getArtworkIdentifierForUserLevelSet(int type)
4237 char *classic_artwork_set = getClassicArtworkSet(type);
4239 // check for custom artwork configured in "levelinfo.conf"
4240 char *leveldir_artwork_set =
4241 *LEVELDIR_ARTWORK_SET_PTR(leveldir_current, type);
4242 boolean has_leveldir_artwork_set =
4243 (leveldir_artwork_set != NULL && !strEqual(leveldir_artwork_set,
4244 classic_artwork_set));
4246 // check for custom artwork in sub-directory "graphics" etc.
4247 TreeInfo *artwork_first_node = ARTWORK_FIRST_NODE(artwork, type);
4248 char *leveldir_identifier = leveldir_current->identifier;
4249 boolean has_artwork_subdir =
4250 (getTreeInfoFromIdentifier(artwork_first_node,
4251 leveldir_identifier) != NULL);
4253 return (has_leveldir_artwork_set ? leveldir_artwork_set :
4254 has_artwork_subdir ? leveldir_identifier :
4255 classic_artwork_set);
4258 TreeInfo *getArtworkTreeInfoForUserLevelSet(int type)
4260 char *artwork_set = getArtworkIdentifierForUserLevelSet(type);
4261 TreeInfo *artwork_first_node = ARTWORK_FIRST_NODE(artwork, type);
4262 TreeInfo *ti = getTreeInfoFromIdentifier(artwork_first_node, artwork_set);
4266 ti = getTreeInfoFromIdentifier(artwork_first_node,
4267 ARTWORK_DEFAULT_SUBDIR(type));
4269 Fail("cannot find default graphics -- should not happen");
4275 boolean checkIfCustomArtworkExistsForCurrentLevelSet(void)
4277 char *graphics_set =
4278 getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_GRAPHICS);
4280 getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_SOUNDS);
4282 getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_MUSIC);
4284 return (!strEqual(graphics_set, GFX_CLASSIC_SUBDIR) ||
4285 !strEqual(sounds_set, SND_CLASSIC_SUBDIR) ||
4286 !strEqual(music_set, MUS_CLASSIC_SUBDIR));
4289 boolean UpdateUserLevelSet(char *level_subdir, char *level_name,
4290 char *level_author, int num_levels)
4292 char *filename = getPath2(getUserLevelDir(level_subdir), LEVELINFO_FILENAME);
4293 char *filename_tmp = getStringCat2(filename, ".tmp");
4295 FILE *file_tmp = NULL;
4296 char line[MAX_LINE_LEN];
4297 boolean success = FALSE;
4298 LevelDirTree *leveldir = getTreeInfoFromIdentifier(leveldir_first,
4300 // update values in level directory tree
4302 if (level_name != NULL)
4303 setString(&leveldir->name, level_name);
4305 if (level_author != NULL)
4306 setString(&leveldir->author, level_author);
4308 if (num_levels != -1)
4309 leveldir->levels = num_levels;
4311 // update values that depend on other values
4313 setString(&leveldir->name_sorting, leveldir->name);
4315 leveldir->last_level = leveldir->first_level + leveldir->levels - 1;
4317 // sort order of level sets may have changed
4318 sortTreeInfo(&leveldir_first);
4320 if ((file = fopen(filename, MODE_READ)) &&
4321 (file_tmp = fopen(filename_tmp, MODE_WRITE)))
4323 while (fgets(line, MAX_LINE_LEN, file))
4325 if (strPrefix(line, "name:") && level_name != NULL)
4326 fprintf(file_tmp, "%-32s%s\n", "name:", level_name);
4327 else if (strPrefix(line, "author:") && level_author != NULL)
4328 fprintf(file_tmp, "%-32s%s\n", "author:", level_author);
4329 else if (strPrefix(line, "levels:") && num_levels != -1)
4330 fprintf(file_tmp, "%-32s%d\n", "levels:", num_levels);
4332 fputs(line, file_tmp);
4345 success = (rename(filename_tmp, filename) == 0);
4353 boolean CreateUserLevelSet(char *level_subdir, char *level_name,
4354 char *level_author, int num_levels,
4355 boolean use_artwork_set)
4357 LevelDirTree *level_info;
4362 // create user level sub-directory, if needed
4363 createDirectory(getUserLevelDir(level_subdir), "user level", PERMS_PRIVATE);
4365 filename = getPath2(getUserLevelDir(level_subdir), LEVELINFO_FILENAME);
4367 if (!(file = fopen(filename, MODE_WRITE)))
4369 Warn("cannot write level info file '%s'", filename);
4376 level_info = newTreeInfo();
4378 // always start with reliable default values
4379 setTreeInfoToDefaults(level_info, TREE_TYPE_LEVEL_DIR);
4381 setString(&level_info->name, level_name);
4382 setString(&level_info->author, level_author);
4383 level_info->levels = num_levels;
4384 level_info->first_level = 1;
4385 level_info->sort_priority = LEVELCLASS_PRIVATE_START;
4386 level_info->readonly = FALSE;
4388 if (use_artwork_set)
4390 level_info->graphics_set =
4391 getStringCopy(getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_GRAPHICS));
4392 level_info->sounds_set =
4393 getStringCopy(getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_SOUNDS));
4394 level_info->music_set =
4395 getStringCopy(getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_MUSIC));
4398 token_value_position = TOKEN_VALUE_POSITION_SHORT;
4400 fprintFileHeader(file, LEVELINFO_FILENAME);
4403 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
4405 if (i == LEVELINFO_TOKEN_NAME ||
4406 i == LEVELINFO_TOKEN_AUTHOR ||
4407 i == LEVELINFO_TOKEN_LEVELS ||
4408 i == LEVELINFO_TOKEN_FIRST_LEVEL ||
4409 i == LEVELINFO_TOKEN_SORT_PRIORITY ||
4410 i == LEVELINFO_TOKEN_READONLY ||
4411 (use_artwork_set && (i == LEVELINFO_TOKEN_GRAPHICS_SET ||
4412 i == LEVELINFO_TOKEN_SOUNDS_SET ||
4413 i == LEVELINFO_TOKEN_MUSIC_SET)))
4414 fprintf(file, "%s\n", getSetupLine(levelinfo_tokens, "", i));
4416 // just to make things nicer :)
4417 if (i == LEVELINFO_TOKEN_AUTHOR ||
4418 i == LEVELINFO_TOKEN_FIRST_LEVEL ||
4419 (use_artwork_set && i == LEVELINFO_TOKEN_READONLY))
4420 fprintf(file, "\n");
4423 token_value_position = TOKEN_VALUE_POSITION_DEFAULT;
4427 SetFilePermissions(filename, PERMS_PRIVATE);
4429 freeTreeInfo(level_info);
4435 static void SaveUserLevelInfo(void)
4437 CreateUserLevelSet(getLoginName(), getLoginName(), getRealName(), 100, FALSE);
4440 char *getSetupValue(int type, void *value)
4442 static char value_string[MAX_LINE_LEN];
4450 strcpy(value_string, (*(boolean *)value ? "true" : "false"));
4454 strcpy(value_string, (*(boolean *)value ? "on" : "off"));
4458 strcpy(value_string, (*(int *)value == AUTO ? "auto" :
4459 *(int *)value == FALSE ? "off" : "on"));
4463 strcpy(value_string, (*(boolean *)value ? "yes" : "no"));
4466 case TYPE_YES_NO_AUTO:
4467 strcpy(value_string, (*(int *)value == AUTO ? "auto" :
4468 *(int *)value == FALSE ? "no" : "yes"));
4472 strcpy(value_string, (*(boolean *)value ? "AGA" : "ECS"));
4476 strcpy(value_string, getKeyNameFromKey(*(Key *)value));
4480 strcpy(value_string, getX11KeyNameFromKey(*(Key *)value));
4484 sprintf(value_string, "%d", *(int *)value);
4488 if (*(char **)value == NULL)
4491 strcpy(value_string, *(char **)value);
4495 sprintf(value_string, "player_%d", *(int *)value + 1);
4499 value_string[0] = '\0';
4503 if (type & TYPE_GHOSTED)
4504 strcpy(value_string, "n/a");
4506 return value_string;
4509 char *getSetupLine(struct TokenInfo *token_info, char *prefix, int token_nr)
4513 static char token_string[MAX_LINE_LEN];
4514 int token_type = token_info[token_nr].type;
4515 void *setup_value = token_info[token_nr].value;
4516 char *token_text = token_info[token_nr].text;
4517 char *value_string = getSetupValue(token_type, setup_value);
4519 // build complete token string
4520 sprintf(token_string, "%s%s", prefix, token_text);
4522 // build setup entry line
4523 line = getFormattedSetupEntry(token_string, value_string);
4525 if (token_type == TYPE_KEY_X11)
4527 Key key = *(Key *)setup_value;
4528 char *keyname = getKeyNameFromKey(key);
4530 // add comment, if useful
4531 if (!strEqual(keyname, "(undefined)") &&
4532 !strEqual(keyname, "(unknown)"))
4534 // add at least one whitespace
4536 for (i = strlen(line); i < token_comment_position; i++)
4540 strcat(line, keyname);
4547 static void InitLastPlayedLevels_ParentNode(void)
4549 LevelDirTree **leveldir_top = &leveldir_first->node_group->next;
4550 LevelDirTree *leveldir_new = NULL;
4552 // check if parent node for last played levels already exists
4553 if (strEqual((*leveldir_top)->identifier, TOKEN_STR_LAST_LEVEL_SERIES))
4556 leveldir_new = newTreeInfo();
4558 setTreeInfoToDefaultsFromParent(leveldir_new, leveldir_first);
4560 leveldir_new->level_group = TRUE;
4562 setString(&leveldir_new->identifier, TOKEN_STR_LAST_LEVEL_SERIES);
4563 setString(&leveldir_new->name, "<< (last played level sets)");
4565 pushTreeInfo(leveldir_top, leveldir_new);
4567 // create node to link back to current level directory
4568 createParentTreeInfoNode(leveldir_new);
4571 void UpdateLastPlayedLevels_TreeInfo(void)
4573 char **last_level_series = setup.level_setup.last_level_series;
4574 boolean reset_leveldir_current = FALSE;
4575 LevelDirTree *leveldir_last;
4576 TreeInfo **node_new = NULL;
4579 if (last_level_series[0] == NULL)
4582 InitLastPlayedLevels_ParentNode();
4584 // check if current level set is from "last played" sub-tree to be rebuilt
4585 reset_leveldir_current = strEqual(leveldir_current->node_parent->identifier,
4586 TOKEN_STR_LAST_LEVEL_SERIES);
4588 leveldir_last = getTreeInfoFromIdentifierExt(leveldir_first,
4589 TOKEN_STR_LAST_LEVEL_SERIES,
4591 if (leveldir_last == NULL)
4594 node_new = &leveldir_last->node_group->next;
4596 freeTreeInfo(*node_new);
4598 for (i = 0; last_level_series[i] != NULL; i++)
4600 LevelDirTree *node_last = getTreeInfoFromIdentifier(leveldir_first,
4601 last_level_series[i]);
4603 *node_new = getTreeInfoCopy(node_last); // copy complete node
4605 (*node_new)->node_top = &leveldir_first; // correct top node link
4606 (*node_new)->node_parent = leveldir_last; // correct parent node link
4608 (*node_new)->node_group = NULL;
4609 (*node_new)->next = NULL;
4611 (*node_new)->cl_first = -1; // force setting tree cursor
4613 node_new = &((*node_new)->next);
4616 if (reset_leveldir_current)
4617 leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
4618 last_level_series[0]);
4621 static void UpdateLastPlayedLevels_List(void)
4623 char **last_level_series = setup.level_setup.last_level_series;
4624 int pos = MAX_LEVELDIR_HISTORY - 1;
4627 // search for potentially already existing entry in list of level sets
4628 for (i = 0; last_level_series[i] != NULL; i++)
4629 if (strEqual(last_level_series[i], leveldir_current->identifier))
4632 // move list of level sets one entry down (using potentially free entry)
4633 for (i = pos; i > 0; i--)
4634 setString(&last_level_series[i], last_level_series[i - 1]);
4636 // put last played level set at top position
4637 setString(&last_level_series[0], leveldir_current->identifier);
4640 void LoadLevelSetup_LastSeries(void)
4642 // --------------------------------------------------------------------------
4643 // ~/.<program>/levelsetup.conf
4644 // --------------------------------------------------------------------------
4646 char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
4647 SetupFileHash *level_setup_hash = NULL;
4651 // always start with reliable default values
4652 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
4654 // start with empty history of last played level sets
4655 setString(&setup.level_setup.last_level_series[0], NULL);
4657 if (!strEqual(DEFAULT_LEVELSET, UNDEFINED_LEVELSET))
4659 leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
4661 if (leveldir_current == NULL)
4662 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
4665 if ((level_setup_hash = loadSetupFileHash(filename)))
4667 char *last_level_series =
4668 getHashEntry(level_setup_hash, TOKEN_STR_LAST_LEVEL_SERIES);
4670 leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
4672 if (leveldir_current == NULL)
4673 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
4675 for (i = 0; i < MAX_LEVELDIR_HISTORY; i++)
4677 char token[strlen(TOKEN_STR_LAST_LEVEL_SERIES) + 10];
4678 LevelDirTree *leveldir_last;
4680 sprintf(token, "%s.%03d", TOKEN_STR_LAST_LEVEL_SERIES, i);
4682 last_level_series = getHashEntry(level_setup_hash, token);
4684 leveldir_last = getTreeInfoFromIdentifier(leveldir_first,
4686 if (leveldir_last != NULL)
4687 setString(&setup.level_setup.last_level_series[pos++],
4691 setString(&setup.level_setup.last_level_series[pos], NULL);
4693 freeSetupFileHash(level_setup_hash);
4697 Debug("setup", "using default setup values");
4703 static void SaveLevelSetup_LastSeries_Ext(boolean deactivate_last_level_series)
4705 // --------------------------------------------------------------------------
4706 // ~/.<program>/levelsetup.conf
4707 // --------------------------------------------------------------------------
4709 // check if the current level directory structure is available at this point
4710 if (leveldir_current == NULL)
4713 char **last_level_series = setup.level_setup.last_level_series;
4714 char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
4718 InitUserDataDirectory();
4720 UpdateLastPlayedLevels_List();
4722 if (!(file = fopen(filename, MODE_WRITE)))
4724 Warn("cannot write setup file '%s'", filename);
4731 fprintFileHeader(file, LEVELSETUP_FILENAME);
4733 if (deactivate_last_level_series)
4734 fprintf(file, "# %s\n# ", "the following level set may have caused a problem and was deactivated");
4736 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_LAST_LEVEL_SERIES,
4737 leveldir_current->identifier));
4739 for (i = 0; last_level_series[i] != NULL; i++)
4741 char token[strlen(TOKEN_STR_LAST_LEVEL_SERIES) + 10];
4743 sprintf(token, "%s.%03d", TOKEN_STR_LAST_LEVEL_SERIES, i);
4745 fprintf(file, "%s\n", getFormattedSetupEntry(token, last_level_series[i]));
4750 SetFilePermissions(filename, PERMS_PRIVATE);
4755 void SaveLevelSetup_LastSeries(void)
4757 SaveLevelSetup_LastSeries_Ext(FALSE);
4760 void SaveLevelSetup_LastSeries_Deactivate(void)
4762 SaveLevelSetup_LastSeries_Ext(TRUE);
4765 static void checkSeriesInfo(void)
4767 static char *level_directory = NULL;
4770 DirectoryEntry *dir_entry;
4773 checked_free(level_directory);
4775 // check for more levels besides the 'levels' field of 'levelinfo.conf'
4777 level_directory = getPath2((leveldir_current->in_user_dir ?
4778 getUserLevelDir(NULL) :
4779 options.level_directory),
4780 leveldir_current->fullpath);
4782 if ((dir = openDirectory(level_directory)) == NULL)
4784 Warn("cannot read level directory '%s'", level_directory);
4790 while ((dir_entry = readDirectory(dir)) != NULL) // loop all entries
4792 if (strlen(dir_entry->basename) > 4 &&
4793 dir_entry->basename[3] == '.' &&
4794 strEqual(&dir_entry->basename[4], LEVELFILE_EXTENSION))
4796 char levelnum_str[4];
4799 strncpy(levelnum_str, dir_entry->basename, 3);
4800 levelnum_str[3] = '\0';
4802 levelnum_value = atoi(levelnum_str);
4804 if (levelnum_value < leveldir_current->first_level)
4806 Warn("additional level %d found", levelnum_value);
4808 leveldir_current->first_level = levelnum_value;
4810 else if (levelnum_value > leveldir_current->last_level)
4812 Warn("additional level %d found", levelnum_value);
4814 leveldir_current->last_level = levelnum_value;
4820 closeDirectory(dir);
4823 void LoadLevelSetup_SeriesInfo(void)
4826 SetupFileHash *level_setup_hash = NULL;
4827 char *level_subdir = leveldir_current->subdir;
4830 // always start with reliable default values
4831 level_nr = leveldir_current->first_level;
4833 for (i = 0; i < MAX_LEVELS; i++)
4835 LevelStats_setPlayed(i, 0);
4836 LevelStats_setSolved(i, 0);
4841 // --------------------------------------------------------------------------
4842 // ~/.<program>/levelsetup/<level series>/levelsetup.conf
4843 // --------------------------------------------------------------------------
4845 level_subdir = leveldir_current->subdir;
4847 filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
4849 if ((level_setup_hash = loadSetupFileHash(filename)))
4853 // get last played level in this level set
4855 token_value = getHashEntry(level_setup_hash, TOKEN_STR_LAST_PLAYED_LEVEL);
4859 level_nr = atoi(token_value);
4861 if (level_nr < leveldir_current->first_level)
4862 level_nr = leveldir_current->first_level;
4863 if (level_nr > leveldir_current->last_level)
4864 level_nr = leveldir_current->last_level;
4867 // get handicap level in this level set
4869 token_value = getHashEntry(level_setup_hash, TOKEN_STR_HANDICAP_LEVEL);
4873 int level_nr = atoi(token_value);
4875 if (level_nr < leveldir_current->first_level)
4876 level_nr = leveldir_current->first_level;
4877 if (level_nr > leveldir_current->last_level + 1)
4878 level_nr = leveldir_current->last_level;
4880 if (leveldir_current->user_defined || !leveldir_current->handicap)
4881 level_nr = leveldir_current->last_level;
4883 leveldir_current->handicap_level = level_nr;
4886 // get number of played and solved levels in this level set
4888 BEGIN_HASH_ITERATION(level_setup_hash, itr)
4890 char *token = HASH_ITERATION_TOKEN(itr);
4891 char *value = HASH_ITERATION_VALUE(itr);
4893 if (strlen(token) == 3 &&
4894 token[0] >= '0' && token[0] <= '9' &&
4895 token[1] >= '0' && token[1] <= '9' &&
4896 token[2] >= '0' && token[2] <= '9')
4898 int level_nr = atoi(token);
4901 LevelStats_setPlayed(level_nr, atoi(value)); // read 1st column
4903 value = strchr(value, ' ');
4906 LevelStats_setSolved(level_nr, atoi(value)); // read 2nd column
4909 END_HASH_ITERATION(hash, itr)
4911 freeSetupFileHash(level_setup_hash);
4915 Debug("setup", "using default setup values");
4921 void SaveLevelSetup_SeriesInfo(void)
4924 char *level_subdir = leveldir_current->subdir;
4925 char *level_nr_str = int2str(level_nr, 0);
4926 char *handicap_level_str = int2str(leveldir_current->handicap_level, 0);
4930 // --------------------------------------------------------------------------
4931 // ~/.<program>/levelsetup/<level series>/levelsetup.conf
4932 // --------------------------------------------------------------------------
4934 InitLevelSetupDirectory(level_subdir);
4936 filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
4938 if (!(file = fopen(filename, MODE_WRITE)))
4940 Warn("cannot write setup file '%s'", filename);
4947 fprintFileHeader(file, LEVELSETUP_FILENAME);
4949 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_PLAYED_LEVEL,
4951 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_HANDICAP_LEVEL,
4952 handicap_level_str));
4954 for (i = leveldir_current->first_level; i <= leveldir_current->last_level;
4957 if (LevelStats_getPlayed(i) > 0 ||
4958 LevelStats_getSolved(i) > 0)
4963 sprintf(token, "%03d", i);
4964 sprintf(value, "%d %d", LevelStats_getPlayed(i), LevelStats_getSolved(i));
4966 fprintf(file, "%s\n", getFormattedSetupEntry(token, value));
4972 SetFilePermissions(filename, PERMS_PRIVATE);
4977 int LevelStats_getPlayed(int nr)
4979 return (nr >= 0 && nr < MAX_LEVELS ? level_stats[nr].played : 0);
4982 int LevelStats_getSolved(int nr)
4984 return (nr >= 0 && nr < MAX_LEVELS ? level_stats[nr].solved : 0);
4987 void LevelStats_setPlayed(int nr, int value)
4989 if (nr >= 0 && nr < MAX_LEVELS)
4990 level_stats[nr].played = value;
4993 void LevelStats_setSolved(int nr, int value)
4995 if (nr >= 0 && nr < MAX_LEVELS)
4996 level_stats[nr].solved = value;
4999 void LevelStats_incPlayed(int nr)
5001 if (nr >= 0 && nr < MAX_LEVELS)
5002 level_stats[nr].played++;
5005 void LevelStats_incSolved(int nr)
5007 if (nr >= 0 && nr < MAX_LEVELS)
5008 level_stats[nr].solved++;
5011 void LoadUserSetup(void)
5013 // --------------------------------------------------------------------------
5014 // ~/.<program>/usersetup.conf
5015 // --------------------------------------------------------------------------
5017 char *filename = getPath2(getMainUserGameDataDir(), USERSETUP_FILENAME);
5018 SetupFileHash *user_setup_hash = NULL;
5020 // always start with reliable default values
5023 if ((user_setup_hash = loadSetupFileHash(filename)))
5027 // get last selected user number
5028 token_value = getHashEntry(user_setup_hash, TOKEN_STR_LAST_USER);
5031 user.nr = MIN(MAX(0, atoi(token_value)), MAX_PLAYER_NAMES - 1);
5033 freeSetupFileHash(user_setup_hash);
5037 Debug("setup", "using default setup values");
5043 void SaveUserSetup(void)
5045 // --------------------------------------------------------------------------
5046 // ~/.<program>/usersetup.conf
5047 // --------------------------------------------------------------------------
5049 char *filename = getPath2(getMainUserGameDataDir(), USERSETUP_FILENAME);
5052 InitMainUserDataDirectory();
5054 if (!(file = fopen(filename, MODE_WRITE)))
5056 Warn("cannot write setup file '%s'", filename);
5063 fprintFileHeader(file, USERSETUP_FILENAME);
5065 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_USER,
5069 SetFilePermissions(filename, PERMS_PRIVATE);