1 // ============================================================================
2 // Artsoft Retro-Game Library
3 // ----------------------------------------------------------------------------
4 // (c) 1995-2014 by Artsoft Entertainment
7 // https://www.artsoft.org/
8 // ----------------------------------------------------------------------------
10 // ============================================================================
12 #include <sys/types.h>
26 #include "zip/miniunz.h"
29 #define ENABLE_UNUSED_CODE FALSE // for currently unused functions
30 #define DEBUG_NO_CONFIG_FILE FALSE // for extra-verbose debug output
32 #define NUM_LEVELCLASS_DESC 8
34 static char *levelclass_desc[NUM_LEVELCLASS_DESC] =
47 #define LEVELCOLOR(n) (IS_LEVELCLASS_TUTORIAL(n) ? FC_BLUE : \
48 IS_LEVELCLASS_CLASSICS(n) ? FC_RED : \
49 IS_LEVELCLASS_BD(n) ? FC_YELLOW : \
50 IS_LEVELCLASS_EM(n) ? FC_YELLOW : \
51 IS_LEVELCLASS_SP(n) ? FC_YELLOW : \
52 IS_LEVELCLASS_DX(n) ? FC_YELLOW : \
53 IS_LEVELCLASS_SB(n) ? FC_YELLOW : \
54 IS_LEVELCLASS_CONTRIB(n) ? FC_GREEN : \
55 IS_LEVELCLASS_PRIVATE(n) ? FC_RED : \
58 #define LEVELSORTING(n) (IS_LEVELCLASS_TUTORIAL(n) ? 0 : \
59 IS_LEVELCLASS_CLASSICS(n) ? 1 : \
60 IS_LEVELCLASS_BD(n) ? 2 : \
61 IS_LEVELCLASS_EM(n) ? 3 : \
62 IS_LEVELCLASS_SP(n) ? 4 : \
63 IS_LEVELCLASS_DX(n) ? 5 : \
64 IS_LEVELCLASS_SB(n) ? 6 : \
65 IS_LEVELCLASS_CONTRIB(n) ? 7 : \
66 IS_LEVELCLASS_PRIVATE(n) ? 8 : \
69 #define ARTWORKCOLOR(n) (IS_ARTWORKCLASS_CLASSICS(n) ? FC_RED : \
70 IS_ARTWORKCLASS_CONTRIB(n) ? FC_GREEN : \
71 IS_ARTWORKCLASS_PRIVATE(n) ? FC_RED : \
72 IS_ARTWORKCLASS_LEVEL(n) ? FC_YELLOW : \
75 #define ARTWORKSORTING(n) (IS_ARTWORKCLASS_CLASSICS(n) ? 0 : \
76 IS_ARTWORKCLASS_LEVEL(n) ? 1 : \
77 IS_ARTWORKCLASS_CONTRIB(n) ? 2 : \
78 IS_ARTWORKCLASS_PRIVATE(n) ? 3 : \
81 #define TOKEN_VALUE_POSITION_SHORT 32
82 #define TOKEN_VALUE_POSITION_DEFAULT 40
83 #define TOKEN_COMMENT_POSITION_DEFAULT 60
85 #define MAX_COOKIE_LEN 256
88 static void setTreeInfoToDefaults(TreeInfo *, int);
89 static TreeInfo *getTreeInfoCopy(TreeInfo *ti);
90 static int compareTreeInfoEntries(const void *, const void *);
92 static int token_value_position = TOKEN_VALUE_POSITION_DEFAULT;
93 static int token_comment_position = TOKEN_COMMENT_POSITION_DEFAULT;
95 static SetupFileHash *artworkinfo_cache_old = NULL;
96 static SetupFileHash *artworkinfo_cache_new = NULL;
97 static SetupFileHash *optional_tokens_hash = NULL;
98 static boolean use_artworkinfo_cache = TRUE;
99 static boolean update_artworkinfo_cache = FALSE;
102 // ----------------------------------------------------------------------------
104 // ----------------------------------------------------------------------------
106 static char *getLevelClassDescription(TreeInfo *ti)
108 int position = ti->sort_priority / 100;
110 if (position >= 0 && position < NUM_LEVELCLASS_DESC)
111 return levelclass_desc[position];
113 return "Unknown Level Class";
116 static char *getScoreDir(char *level_subdir)
118 static char *score_dir = NULL;
119 static char *score_level_dir = NULL;
120 char *score_subdir = SCORES_DIRECTORY;
122 if (score_dir == NULL)
124 if (program.global_scores)
125 score_dir = getPath2(getCommonDataDir(), score_subdir);
127 score_dir = getPath2(getMainUserGameDataDir(), score_subdir);
130 if (level_subdir != NULL)
132 checked_free(score_level_dir);
134 score_level_dir = getPath2(score_dir, level_subdir);
136 return score_level_dir;
142 static char *getUserSubdir(int nr)
144 static char user_subdir[16] = { 0 };
146 sprintf(user_subdir, "%03d", nr);
151 static char *getUserDir(int nr)
153 static char *user_dir = NULL;
154 char *main_data_dir = getMainUserGameDataDir();
155 char *users_subdir = USERS_DIRECTORY;
156 char *user_subdir = getUserSubdir(nr);
158 checked_free(user_dir);
161 user_dir = getPath3(main_data_dir, users_subdir, user_subdir);
163 user_dir = getPath2(main_data_dir, users_subdir);
168 static char *getLevelSetupDir(char *level_subdir)
170 static char *levelsetup_dir = NULL;
171 char *data_dir = getUserGameDataDir();
172 char *levelsetup_subdir = LEVELSETUP_DIRECTORY;
174 checked_free(levelsetup_dir);
176 if (level_subdir != NULL)
177 levelsetup_dir = getPath3(data_dir, levelsetup_subdir, level_subdir);
179 levelsetup_dir = getPath2(data_dir, levelsetup_subdir);
181 return levelsetup_dir;
184 static char *getCacheDir(void)
186 static char *cache_dir = NULL;
188 if (cache_dir == NULL)
189 cache_dir = getPath2(getMainUserGameDataDir(), CACHE_DIRECTORY);
194 static char *getNetworkDir(void)
196 static char *network_dir = NULL;
198 if (network_dir == NULL)
199 network_dir = getPath2(getMainUserGameDataDir(), NETWORK_DIRECTORY);
204 char *getLevelDirFromTreeInfo(TreeInfo *node)
206 static char *level_dir = NULL;
209 return options.level_directory;
211 checked_free(level_dir);
213 level_dir = getPath2((node->in_user_dir ? getUserLevelDir(NULL) :
214 options.level_directory), node->fullpath);
219 char *getUserLevelDir(char *level_subdir)
221 static char *userlevel_dir = NULL;
222 char *data_dir = getMainUserGameDataDir();
223 char *userlevel_subdir = LEVELS_DIRECTORY;
225 checked_free(userlevel_dir);
227 if (level_subdir != NULL)
228 userlevel_dir = getPath3(data_dir, userlevel_subdir, level_subdir);
230 userlevel_dir = getPath2(data_dir, userlevel_subdir);
232 return userlevel_dir;
235 char *getNetworkLevelDir(char *level_subdir)
237 static char *network_level_dir = NULL;
238 char *data_dir = getNetworkDir();
239 char *networklevel_subdir = LEVELS_DIRECTORY;
241 checked_free(network_level_dir);
243 if (level_subdir != NULL)
244 network_level_dir = getPath3(data_dir, networklevel_subdir, level_subdir);
246 network_level_dir = getPath2(data_dir, networklevel_subdir);
248 return network_level_dir;
251 char *getCurrentLevelDir(void)
253 return getLevelDirFromTreeInfo(leveldir_current);
256 char *getNewUserLevelSubdir(void)
258 static char *new_level_subdir = NULL;
259 char *subdir_prefix = getLoginName();
260 char subdir_suffix[10];
261 int max_suffix_number = 1000;
264 while (++i < max_suffix_number)
266 sprintf(subdir_suffix, "_%d", i);
268 checked_free(new_level_subdir);
269 new_level_subdir = getStringCat2(subdir_prefix, subdir_suffix);
271 if (!directoryExists(getUserLevelDir(new_level_subdir)))
275 return new_level_subdir;
278 static char *getTapeDir(char *level_subdir)
280 static char *tape_dir = NULL;
281 char *data_dir = getUserGameDataDir();
282 char *tape_subdir = TAPES_DIRECTORY;
284 checked_free(tape_dir);
286 if (level_subdir != NULL)
287 tape_dir = getPath3(data_dir, tape_subdir, level_subdir);
289 tape_dir = getPath2(data_dir, tape_subdir);
294 static char *getSolutionTapeDir(void)
296 static char *tape_dir = NULL;
297 char *data_dir = getCurrentLevelDir();
298 char *tape_subdir = TAPES_DIRECTORY;
300 checked_free(tape_dir);
302 tape_dir = getPath2(data_dir, tape_subdir);
307 static char *getDefaultGraphicsDir(char *graphics_subdir)
309 static char *graphics_dir = NULL;
311 if (graphics_subdir == NULL)
312 return options.graphics_directory;
314 checked_free(graphics_dir);
316 graphics_dir = getPath2(options.graphics_directory, graphics_subdir);
321 static char *getDefaultSoundsDir(char *sounds_subdir)
323 static char *sounds_dir = NULL;
325 if (sounds_subdir == NULL)
326 return options.sounds_directory;
328 checked_free(sounds_dir);
330 sounds_dir = getPath2(options.sounds_directory, sounds_subdir);
335 static char *getDefaultMusicDir(char *music_subdir)
337 static char *music_dir = NULL;
339 if (music_subdir == NULL)
340 return options.music_directory;
342 checked_free(music_dir);
344 music_dir = getPath2(options.music_directory, music_subdir);
349 static char *getClassicArtworkSet(int type)
351 return (type == TREE_TYPE_GRAPHICS_DIR ? GFX_CLASSIC_SUBDIR :
352 type == TREE_TYPE_SOUNDS_DIR ? SND_CLASSIC_SUBDIR :
353 type == TREE_TYPE_MUSIC_DIR ? MUS_CLASSIC_SUBDIR : "");
356 static char *getClassicArtworkDir(int type)
358 return (type == TREE_TYPE_GRAPHICS_DIR ?
359 getDefaultGraphicsDir(GFX_CLASSIC_SUBDIR) :
360 type == TREE_TYPE_SOUNDS_DIR ?
361 getDefaultSoundsDir(SND_CLASSIC_SUBDIR) :
362 type == TREE_TYPE_MUSIC_DIR ?
363 getDefaultMusicDir(MUS_CLASSIC_SUBDIR) : "");
366 char *getUserGraphicsDir(void)
368 static char *usergraphics_dir = NULL;
370 if (usergraphics_dir == NULL)
371 usergraphics_dir = getPath2(getMainUserGameDataDir(), GRAPHICS_DIRECTORY);
373 return usergraphics_dir;
376 char *getUserSoundsDir(void)
378 static char *usersounds_dir = NULL;
380 if (usersounds_dir == NULL)
381 usersounds_dir = getPath2(getMainUserGameDataDir(), SOUNDS_DIRECTORY);
383 return usersounds_dir;
386 char *getUserMusicDir(void)
388 static char *usermusic_dir = NULL;
390 if (usermusic_dir == NULL)
391 usermusic_dir = getPath2(getMainUserGameDataDir(), MUSIC_DIRECTORY);
393 return usermusic_dir;
396 static char *getSetupArtworkDir(TreeInfo *ti)
398 static char *artwork_dir = NULL;
403 checked_free(artwork_dir);
405 artwork_dir = getPath2(ti->basepath, ti->fullpath);
410 char *setLevelArtworkDir(TreeInfo *ti)
412 char **artwork_path_ptr, **artwork_set_ptr;
413 TreeInfo *level_artwork;
415 if (ti == NULL || leveldir_current == NULL)
418 artwork_path_ptr = LEVELDIR_ARTWORK_PATH_PTR(leveldir_current, ti->type);
419 artwork_set_ptr = LEVELDIR_ARTWORK_SET_PTR( leveldir_current, ti->type);
421 checked_free(*artwork_path_ptr);
423 if ((level_artwork = getTreeInfoFromIdentifier(ti, *artwork_set_ptr)))
425 *artwork_path_ptr = getStringCopy(getSetupArtworkDir(level_artwork));
430 No (or non-existing) artwork configured in "levelinfo.conf". This would
431 normally result in using the artwork configured in the setup menu. But
432 if an artwork subdirectory exists (which might contain custom artwork
433 or an artwork configuration file), this level artwork must be treated
434 as relative to the default "classic" artwork, not to the artwork that
435 is currently configured in the setup menu.
437 Update: For "special" versions of R'n'D (like "R'n'D jue"), do not use
438 the "default" artwork (which would be "jue0" for "R'n'D jue"), but use
439 the real "classic" artwork from the original R'n'D (like "gfx_classic").
442 char *dir = getPath2(getCurrentLevelDir(), ARTWORK_DIRECTORY(ti->type));
444 checked_free(*artwork_set_ptr);
446 if (directoryExists(dir))
448 *artwork_path_ptr = getStringCopy(getClassicArtworkDir(ti->type));
449 *artwork_set_ptr = getStringCopy(getClassicArtworkSet(ti->type));
453 *artwork_path_ptr = getStringCopy(UNDEFINED_FILENAME);
454 *artwork_set_ptr = NULL;
460 return *artwork_set_ptr;
463 static char *getLevelArtworkSet(int type)
465 if (leveldir_current == NULL)
468 return LEVELDIR_ARTWORK_SET(leveldir_current, type);
471 static char *getLevelArtworkDir(int type)
473 if (leveldir_current == NULL)
474 return UNDEFINED_FILENAME;
476 return LEVELDIR_ARTWORK_PATH(leveldir_current, type);
479 char *getProgramMainDataPath(char *command_filename, char *base_path)
481 // check if the program's main data base directory is configured
482 if (!strEqual(base_path, "."))
483 return getStringCopy(base_path);
485 /* if the program is configured to start from current directory (default),
486 determine program package directory from program binary (some versions
487 of KDE/Konqueror and Mac OS X (especially "Mavericks") apparently do not
488 set the current working directory to the program package directory) */
489 char *main_data_path = getBasePath(command_filename);
491 #if defined(PLATFORM_MACOSX)
492 if (strSuffix(main_data_path, MAC_APP_BINARY_SUBDIR))
494 char *main_data_path_old = main_data_path;
496 // cut relative path to Mac OS X application binary directory from path
497 main_data_path[strlen(main_data_path) -
498 strlen(MAC_APP_BINARY_SUBDIR)] = '\0';
500 // cut trailing path separator from path (but not if path is root directory)
501 if (strSuffix(main_data_path, "/") && !strEqual(main_data_path, "/"))
502 main_data_path[strlen(main_data_path) - 1] = '\0';
504 // replace empty path with current directory
505 if (strEqual(main_data_path, ""))
506 main_data_path = ".";
508 // add relative path to Mac OS X application resources directory to path
509 main_data_path = getPath2(main_data_path, MAC_APP_FILES_SUBDIR);
511 free(main_data_path_old);
515 return main_data_path;
518 char *getProgramConfigFilename(char *command_filename)
520 static char *config_filename_1 = NULL;
521 static char *config_filename_2 = NULL;
522 static char *config_filename_3 = NULL;
523 static boolean initialized = FALSE;
527 char *command_filename_1 = getStringCopy(command_filename);
529 // strip trailing executable suffix from command filename
530 if (strSuffix(command_filename_1, ".exe"))
531 command_filename_1[strlen(command_filename_1) - 4] = '\0';
533 char *ro_base_path = getProgramMainDataPath(command_filename, RO_BASE_PATH);
534 char *conf_directory = getPath2(ro_base_path, CONF_DIRECTORY);
536 char *command_basepath = getBasePath(command_filename);
537 char *command_basename = getBaseNameNoSuffix(command_filename);
538 char *command_filename_2 = getPath2(command_basepath, command_basename);
540 config_filename_1 = getStringCat2(command_filename_1, ".conf");
541 config_filename_2 = getStringCat2(command_filename_2, ".conf");
542 config_filename_3 = getPath2(conf_directory, SETUP_FILENAME);
544 checked_free(ro_base_path);
545 checked_free(conf_directory);
547 checked_free(command_basepath);
548 checked_free(command_basename);
550 checked_free(command_filename_1);
551 checked_free(command_filename_2);
556 // 1st try: look for config file that exactly matches the binary filename
557 if (fileExists(config_filename_1))
558 return config_filename_1;
560 // 2nd try: look for config file that matches binary filename without suffix
561 if (fileExists(config_filename_2))
562 return config_filename_2;
564 // 3rd try: return setup config filename in global program config directory
565 return config_filename_3;
568 char *getTapeFilename(int nr)
570 static char *filename = NULL;
571 char basename[MAX_FILENAME_LEN];
573 checked_free(filename);
575 sprintf(basename, "%03d.%s", nr, TAPEFILE_EXTENSION);
576 filename = getPath2(getTapeDir(leveldir_current->subdir), basename);
581 char *getSolutionTapeFilename(int nr)
583 static char *filename = NULL;
584 char basename[MAX_FILENAME_LEN];
586 checked_free(filename);
588 sprintf(basename, "%03d.%s", nr, TAPEFILE_EXTENSION);
589 filename = getPath2(getSolutionTapeDir(), basename);
591 if (!fileExists(filename))
593 static char *filename_sln = NULL;
595 checked_free(filename_sln);
597 sprintf(basename, "%03d.sln", nr);
598 filename_sln = getPath2(getSolutionTapeDir(), basename);
600 if (fileExists(filename_sln))
607 char *getScoreFilename(int nr)
609 static char *filename = NULL;
610 char basename[MAX_FILENAME_LEN];
612 checked_free(filename);
614 sprintf(basename, "%03d.%s", nr, SCOREFILE_EXTENSION);
616 // used instead of "leveldir_current->subdir" (for network games)
617 filename = getPath2(getScoreDir(levelset.identifier), basename);
622 char *getSetupFilename(void)
624 static char *filename = NULL;
626 checked_free(filename);
628 filename = getPath2(getSetupDir(), SETUP_FILENAME);
633 char *getDefaultSetupFilename(void)
635 return program.config_filename;
638 char *getEditorSetupFilename(void)
640 static char *filename = NULL;
642 checked_free(filename);
643 filename = getPath2(getCurrentLevelDir(), EDITORSETUP_FILENAME);
645 if (fileExists(filename))
648 checked_free(filename);
649 filename = getPath2(getSetupDir(), EDITORSETUP_FILENAME);
654 char *getHelpAnimFilename(void)
656 static char *filename = NULL;
658 checked_free(filename);
660 filename = getPath2(getCurrentLevelDir(), HELPANIM_FILENAME);
665 char *getHelpTextFilename(void)
667 static char *filename = NULL;
669 checked_free(filename);
671 filename = getPath2(getCurrentLevelDir(), HELPTEXT_FILENAME);
676 char *getLevelSetInfoFilename(void)
678 static char *filename = NULL;
693 for (i = 0; basenames[i] != NULL; i++)
695 checked_free(filename);
696 filename = getPath2(getCurrentLevelDir(), basenames[i]);
698 if (fileExists(filename))
705 static char *getLevelSetTitleMessageBasename(int nr, boolean initial)
707 static char basename[32];
709 sprintf(basename, "%s_%d.txt",
710 (initial ? "titlemessage_initial" : "titlemessage"), nr + 1);
715 char *getLevelSetTitleMessageFilename(int nr, boolean initial)
717 static char *filename = NULL;
719 boolean skip_setup_artwork = FALSE;
721 checked_free(filename);
723 basename = getLevelSetTitleMessageBasename(nr, initial);
725 if (!gfx.override_level_graphics)
727 // 1st try: look for special artwork in current level series directory
728 filename = getPath3(getCurrentLevelDir(), GRAPHICS_DIRECTORY, basename);
729 if (fileExists(filename))
734 // 2nd try: look for message file in current level set directory
735 filename = getPath2(getCurrentLevelDir(), basename);
736 if (fileExists(filename))
741 // check if there is special artwork configured in level series config
742 if (getLevelArtworkSet(ARTWORK_TYPE_GRAPHICS) != NULL)
744 // 3rd try: look for special artwork configured in level series config
745 filename = getPath2(getLevelArtworkDir(ARTWORK_TYPE_GRAPHICS), basename);
746 if (fileExists(filename))
751 // take missing artwork configured in level set config from default
752 skip_setup_artwork = TRUE;
756 if (!skip_setup_artwork)
758 // 4th try: look for special artwork in configured artwork directory
759 filename = getPath2(getSetupArtworkDir(artwork.gfx_current), basename);
760 if (fileExists(filename))
766 // 5th try: look for default artwork in new default artwork directory
767 filename = getPath2(getDefaultGraphicsDir(GFX_DEFAULT_SUBDIR), basename);
768 if (fileExists(filename))
773 // 6th try: look for default artwork in old default artwork directory
774 filename = getPath2(options.graphics_directory, basename);
775 if (fileExists(filename))
778 return NULL; // cannot find specified artwork file anywhere
781 static char *getCorrectedArtworkBasename(char *basename)
786 char *getCustomImageFilename(char *basename)
788 static char *filename = NULL;
789 boolean skip_setup_artwork = FALSE;
791 checked_free(filename);
793 basename = getCorrectedArtworkBasename(basename);
795 if (!gfx.override_level_graphics)
797 // 1st try: look for special artwork in current level series directory
798 filename = getImg3(getCurrentLevelDir(), GRAPHICS_DIRECTORY, basename);
799 if (fileExists(filename))
804 // check if there is special artwork configured in level series config
805 if (getLevelArtworkSet(ARTWORK_TYPE_GRAPHICS) != NULL)
807 // 2nd try: look for special artwork configured in level series config
808 filename = getImg2(getLevelArtworkDir(ARTWORK_TYPE_GRAPHICS), basename);
809 if (fileExists(filename))
814 // take missing artwork configured in level set config from default
815 skip_setup_artwork = TRUE;
819 if (!skip_setup_artwork)
821 // 3rd try: look for special artwork in configured artwork directory
822 filename = getImg2(getSetupArtworkDir(artwork.gfx_current), basename);
823 if (fileExists(filename))
829 // 4th try: look for default artwork in new default artwork directory
830 filename = getImg2(getDefaultGraphicsDir(GFX_DEFAULT_SUBDIR), basename);
831 if (fileExists(filename))
836 // 5th try: look for default artwork in old default artwork directory
837 filename = getImg2(options.graphics_directory, basename);
838 if (fileExists(filename))
841 if (!strEqual(GFX_FALLBACK_FILENAME, UNDEFINED_FILENAME))
845 Warn("cannot find artwork file '%s' (using fallback)", basename);
847 // 6th try: look for fallback artwork in old default artwork directory
848 // (needed to prevent errors when trying to access unused artwork files)
849 filename = getImg2(options.graphics_directory, GFX_FALLBACK_FILENAME);
850 if (fileExists(filename))
854 return NULL; // cannot find specified artwork file anywhere
857 char *getCustomSoundFilename(char *basename)
859 static char *filename = NULL;
860 boolean skip_setup_artwork = FALSE;
862 checked_free(filename);
864 basename = getCorrectedArtworkBasename(basename);
866 if (!gfx.override_level_sounds)
868 // 1st try: look for special artwork in current level series directory
869 filename = getPath3(getCurrentLevelDir(), SOUNDS_DIRECTORY, basename);
870 if (fileExists(filename))
875 // check if there is special artwork configured in level series config
876 if (getLevelArtworkSet(ARTWORK_TYPE_SOUNDS) != NULL)
878 // 2nd try: look for special artwork configured in level series config
879 filename = getPath2(getLevelArtworkDir(TREE_TYPE_SOUNDS_DIR), basename);
880 if (fileExists(filename))
885 // take missing artwork configured in level set config from default
886 skip_setup_artwork = TRUE;
890 if (!skip_setup_artwork)
892 // 3rd try: look for special artwork in configured artwork directory
893 filename = getPath2(getSetupArtworkDir(artwork.snd_current), basename);
894 if (fileExists(filename))
900 // 4th try: look for default artwork in new default artwork directory
901 filename = getPath2(getDefaultSoundsDir(SND_DEFAULT_SUBDIR), basename);
902 if (fileExists(filename))
907 // 5th try: look for default artwork in old default artwork directory
908 filename = getPath2(options.sounds_directory, basename);
909 if (fileExists(filename))
912 if (!strEqual(SND_FALLBACK_FILENAME, UNDEFINED_FILENAME))
916 Warn("cannot find artwork file '%s' (using fallback)", basename);
918 // 6th try: look for fallback artwork in old default artwork directory
919 // (needed to prevent errors when trying to access unused artwork files)
920 filename = getPath2(options.sounds_directory, SND_FALLBACK_FILENAME);
921 if (fileExists(filename))
925 return NULL; // cannot find specified artwork file anywhere
928 char *getCustomMusicFilename(char *basename)
930 static char *filename = NULL;
931 boolean skip_setup_artwork = FALSE;
933 checked_free(filename);
935 basename = getCorrectedArtworkBasename(basename);
937 if (!gfx.override_level_music)
939 // 1st try: look for special artwork in current level series directory
940 filename = getPath3(getCurrentLevelDir(), MUSIC_DIRECTORY, basename);
941 if (fileExists(filename))
946 // check if there is special artwork configured in level series config
947 if (getLevelArtworkSet(ARTWORK_TYPE_MUSIC) != NULL)
949 // 2nd try: look for special artwork configured in level series config
950 filename = getPath2(getLevelArtworkDir(TREE_TYPE_MUSIC_DIR), basename);
951 if (fileExists(filename))
956 // take missing artwork configured in level set config from default
957 skip_setup_artwork = TRUE;
961 if (!skip_setup_artwork)
963 // 3rd try: look for special artwork in configured artwork directory
964 filename = getPath2(getSetupArtworkDir(artwork.mus_current), basename);
965 if (fileExists(filename))
971 // 4th try: look for default artwork in new default artwork directory
972 filename = getPath2(getDefaultMusicDir(MUS_DEFAULT_SUBDIR), basename);
973 if (fileExists(filename))
978 // 5th try: look for default artwork in old default artwork directory
979 filename = getPath2(options.music_directory, basename);
980 if (fileExists(filename))
983 if (!strEqual(MUS_FALLBACK_FILENAME, UNDEFINED_FILENAME))
987 Warn("cannot find artwork file '%s' (using fallback)", basename);
989 // 6th try: look for fallback artwork in old default artwork directory
990 // (needed to prevent errors when trying to access unused artwork files)
991 filename = getPath2(options.music_directory, MUS_FALLBACK_FILENAME);
992 if (fileExists(filename))
996 return NULL; // cannot find specified artwork file anywhere
999 char *getCustomArtworkFilename(char *basename, int type)
1001 if (type == ARTWORK_TYPE_GRAPHICS)
1002 return getCustomImageFilename(basename);
1003 else if (type == ARTWORK_TYPE_SOUNDS)
1004 return getCustomSoundFilename(basename);
1005 else if (type == ARTWORK_TYPE_MUSIC)
1006 return getCustomMusicFilename(basename);
1008 return UNDEFINED_FILENAME;
1011 char *getCustomArtworkConfigFilename(int type)
1013 return getCustomArtworkFilename(ARTWORKINFO_FILENAME(type), type);
1016 char *getCustomArtworkLevelConfigFilename(int type)
1018 static char *filename = NULL;
1020 checked_free(filename);
1022 filename = getPath2(getLevelArtworkDir(type), ARTWORKINFO_FILENAME(type));
1027 char *getCustomMusicDirectory(void)
1029 static char *directory = NULL;
1030 boolean skip_setup_artwork = FALSE;
1032 checked_free(directory);
1034 if (!gfx.override_level_music)
1036 // 1st try: look for special artwork in current level series directory
1037 directory = getPath2(getCurrentLevelDir(), MUSIC_DIRECTORY);
1038 if (directoryExists(directory))
1043 // check if there is special artwork configured in level series config
1044 if (getLevelArtworkSet(ARTWORK_TYPE_MUSIC) != NULL)
1046 // 2nd try: look for special artwork configured in level series config
1047 directory = getStringCopy(getLevelArtworkDir(TREE_TYPE_MUSIC_DIR));
1048 if (directoryExists(directory))
1053 // take missing artwork configured in level set config from default
1054 skip_setup_artwork = TRUE;
1058 if (!skip_setup_artwork)
1060 // 3rd try: look for special artwork in configured artwork directory
1061 directory = getStringCopy(getSetupArtworkDir(artwork.mus_current));
1062 if (directoryExists(directory))
1068 // 4th try: look for default artwork in new default artwork directory
1069 directory = getStringCopy(getDefaultMusicDir(MUS_DEFAULT_SUBDIR));
1070 if (directoryExists(directory))
1075 // 5th try: look for default artwork in old default artwork directory
1076 directory = getStringCopy(options.music_directory);
1077 if (directoryExists(directory))
1080 return NULL; // cannot find specified artwork file anywhere
1083 void InitTapeDirectory(char *level_subdir)
1085 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
1086 createDirectory(getTapeDir(NULL), "main tape", PERMS_PRIVATE);
1087 createDirectory(getTapeDir(level_subdir), "level tape", PERMS_PRIVATE);
1090 void InitScoreDirectory(char *level_subdir)
1092 int permissions = (program.global_scores ? PERMS_PUBLIC : PERMS_PRIVATE);
1094 if (program.global_scores)
1095 createDirectory(getCommonDataDir(), "common data", permissions);
1097 createDirectory(getMainUserGameDataDir(), "main user data", permissions);
1099 createDirectory(getScoreDir(NULL), "main score", permissions);
1100 createDirectory(getScoreDir(level_subdir), "level score", permissions);
1103 static void SaveUserLevelInfo(void);
1105 void InitUserLevelDirectory(char *level_subdir)
1107 if (!directoryExists(getUserLevelDir(level_subdir)))
1109 createDirectory(getMainUserGameDataDir(), "main user data", PERMS_PRIVATE);
1110 createDirectory(getUserLevelDir(NULL), "main user level", PERMS_PRIVATE);
1111 createDirectory(getUserLevelDir(level_subdir), "user level", PERMS_PRIVATE);
1113 if (setup.internal.create_user_levelset)
1114 SaveUserLevelInfo();
1118 void InitNetworkLevelDirectory(char *level_subdir)
1120 if (!directoryExists(getNetworkLevelDir(level_subdir)))
1122 createDirectory(getMainUserGameDataDir(), "main user data", PERMS_PRIVATE);
1123 createDirectory(getNetworkDir(), "network data", PERMS_PRIVATE);
1124 createDirectory(getNetworkLevelDir(NULL), "main network level", PERMS_PRIVATE);
1125 createDirectory(getNetworkLevelDir(level_subdir), "network level", PERMS_PRIVATE);
1129 void InitLevelSetupDirectory(char *level_subdir)
1131 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
1132 createDirectory(getLevelSetupDir(NULL), "main level setup", PERMS_PRIVATE);
1133 createDirectory(getLevelSetupDir(level_subdir), "level setup", PERMS_PRIVATE);
1136 static void InitCacheDirectory(void)
1138 createDirectory(getMainUserGameDataDir(), "main user data", PERMS_PRIVATE);
1139 createDirectory(getCacheDir(), "cache data", PERMS_PRIVATE);
1143 // ----------------------------------------------------------------------------
1144 // some functions to handle lists of level and artwork directories
1145 // ----------------------------------------------------------------------------
1147 TreeInfo *newTreeInfo(void)
1149 return checked_calloc(sizeof(TreeInfo));
1152 TreeInfo *newTreeInfo_setDefaults(int type)
1154 TreeInfo *ti = newTreeInfo();
1156 setTreeInfoToDefaults(ti, type);
1161 void pushTreeInfo(TreeInfo **node_first, TreeInfo *node_new)
1163 node_new->next = *node_first;
1164 *node_first = node_new;
1167 void removeTreeInfo(TreeInfo **node_first)
1169 TreeInfo *node_old = *node_first;
1171 *node_first = node_old->next;
1172 node_old->next = NULL;
1174 freeTreeInfo(node_old);
1177 int numTreeInfo(TreeInfo *node)
1190 boolean validLevelSeries(TreeInfo *node)
1192 return (node != NULL && !node->node_group && !node->parent_link);
1195 TreeInfo *getFirstValidTreeInfoEntry(TreeInfo *node)
1200 if (node->node_group) // enter level group (step down into tree)
1201 return getFirstValidTreeInfoEntry(node->node_group);
1202 else if (node->parent_link) // skip start entry of level group
1204 if (node->next) // get first real level series entry
1205 return getFirstValidTreeInfoEntry(node->next);
1206 else // leave empty level group and go on
1207 return getFirstValidTreeInfoEntry(node->node_parent->next);
1209 else // this seems to be a regular level series
1213 TreeInfo *getTreeInfoFirstGroupEntry(TreeInfo *node)
1218 if (node->node_parent == NULL) // top level group
1219 return *node->node_top;
1220 else // sub level group
1221 return node->node_parent->node_group;
1224 int numTreeInfoInGroup(TreeInfo *node)
1226 return numTreeInfo(getTreeInfoFirstGroupEntry(node));
1229 int getPosFromTreeInfo(TreeInfo *node)
1231 TreeInfo *node_cmp = getTreeInfoFirstGroupEntry(node);
1236 if (node_cmp == node)
1240 node_cmp = node_cmp->next;
1246 TreeInfo *getTreeInfoFromPos(TreeInfo *node, int pos)
1248 TreeInfo *node_default = node;
1260 return node_default;
1263 static TreeInfo *getTreeInfoFromIdentifierExt(TreeInfo *node, char *identifier,
1264 boolean include_node_groups)
1266 if (identifier == NULL)
1271 if (node->node_group)
1273 if (include_node_groups && strEqual(identifier, node->identifier))
1276 TreeInfo *node_group = getTreeInfoFromIdentifierExt(node->node_group,
1278 include_node_groups);
1282 else if (!node->parent_link)
1284 if (strEqual(identifier, node->identifier))
1294 TreeInfo *getTreeInfoFromIdentifier(TreeInfo *node, char *identifier)
1296 return getTreeInfoFromIdentifierExt(node, identifier, FALSE);
1299 static TreeInfo *cloneTreeNode(TreeInfo **node_top, TreeInfo *node_parent,
1300 TreeInfo *node, boolean skip_sets_without_levels)
1307 if (!node->parent_link && !node->level_group &&
1308 skip_sets_without_levels && node->levels == 0)
1309 return cloneTreeNode(node_top, node_parent, node->next,
1310 skip_sets_without_levels);
1312 node_new = getTreeInfoCopy(node); // copy complete node
1314 node_new->node_top = node_top; // correct top node link
1315 node_new->node_parent = node_parent; // correct parent node link
1317 if (node->level_group)
1318 node_new->node_group = cloneTreeNode(node_top, node_new, node->node_group,
1319 skip_sets_without_levels);
1321 node_new->next = cloneTreeNode(node_top, node_parent, node->next,
1322 skip_sets_without_levels);
1327 static void cloneTree(TreeInfo **ti_new, TreeInfo *ti, boolean skip_empty_sets)
1329 TreeInfo *ti_cloned = cloneTreeNode(ti_new, NULL, ti, skip_empty_sets);
1331 *ti_new = ti_cloned;
1334 static boolean adjustTreeGraphicsForEMC(TreeInfo *node)
1336 boolean settings_changed = FALSE;
1340 boolean want_ecs = (setup.prefer_aga_graphics == FALSE);
1341 boolean want_aga = (setup.prefer_aga_graphics == TRUE);
1342 boolean has_only_ecs = (!node->graphics_set && !node->graphics_set_aga);
1343 boolean has_only_aga = (!node->graphics_set && !node->graphics_set_ecs);
1344 char *graphics_set = NULL;
1346 if (node->graphics_set_ecs && (want_ecs || has_only_ecs))
1347 graphics_set = node->graphics_set_ecs;
1349 if (node->graphics_set_aga && (want_aga || has_only_aga))
1350 graphics_set = node->graphics_set_aga;
1352 if (graphics_set && !strEqual(node->graphics_set, graphics_set))
1354 setString(&node->graphics_set, graphics_set);
1355 settings_changed = TRUE;
1358 if (node->node_group != NULL)
1359 settings_changed |= adjustTreeGraphicsForEMC(node->node_group);
1364 return settings_changed;
1367 static boolean adjustTreeSoundsForEMC(TreeInfo *node)
1369 boolean settings_changed = FALSE;
1373 boolean want_default = (setup.prefer_lowpass_sounds == FALSE);
1374 boolean want_lowpass = (setup.prefer_lowpass_sounds == TRUE);
1375 boolean has_only_default = (!node->sounds_set && !node->sounds_set_lowpass);
1376 boolean has_only_lowpass = (!node->sounds_set && !node->sounds_set_default);
1377 char *sounds_set = NULL;
1379 if (node->sounds_set_default && (want_default || has_only_default))
1380 sounds_set = node->sounds_set_default;
1382 if (node->sounds_set_lowpass && (want_lowpass || has_only_lowpass))
1383 sounds_set = node->sounds_set_lowpass;
1385 if (sounds_set && !strEqual(node->sounds_set, sounds_set))
1387 setString(&node->sounds_set, sounds_set);
1388 settings_changed = TRUE;
1391 if (node->node_group != NULL)
1392 settings_changed |= adjustTreeSoundsForEMC(node->node_group);
1397 return settings_changed;
1400 void dumpTreeInfo(TreeInfo *node, int depth)
1402 char bullet_list[] = { '-', '*', 'o' };
1406 Debug("tree", "Dumping TreeInfo:");
1410 char bullet = bullet_list[depth % ARRAY_SIZE(bullet_list)];
1412 for (i = 0; i < depth * 2; i++)
1413 DebugContinued("", " ");
1415 DebugContinued("tree", "%c '%s' ['%s] [PARENT: '%s'] %s\n",
1416 bullet, node->name, node->identifier,
1417 (node->node_parent ? node->node_parent->identifier : "-"),
1418 (node->node_group ? "[GROUP]" : ""));
1421 // use for dumping artwork info tree
1422 Debug("tree", "subdir == '%s' ['%s', '%s'] [%d])",
1423 node->subdir, node->fullpath, node->basepath, node->in_user_dir);
1426 if (node->node_group != NULL)
1427 dumpTreeInfo(node->node_group, depth + 1);
1433 void sortTreeInfoBySortFunction(TreeInfo **node_first,
1434 int (*compare_function)(const void *,
1437 int num_nodes = numTreeInfo(*node_first);
1438 TreeInfo **sort_array;
1439 TreeInfo *node = *node_first;
1445 // allocate array for sorting structure pointers
1446 sort_array = checked_calloc(num_nodes * sizeof(TreeInfo *));
1448 // writing structure pointers to sorting array
1449 while (i < num_nodes && node) // double boundary check...
1451 sort_array[i] = node;
1457 // sorting the structure pointers in the sorting array
1458 qsort(sort_array, num_nodes, sizeof(TreeInfo *),
1461 // update the linkage of list elements with the sorted node array
1462 for (i = 0; i < num_nodes - 1; i++)
1463 sort_array[i]->next = sort_array[i + 1];
1464 sort_array[num_nodes - 1]->next = NULL;
1466 // update the linkage of the main list anchor pointer
1467 *node_first = sort_array[0];
1471 // now recursively sort the level group structures
1475 if (node->node_group != NULL)
1476 sortTreeInfoBySortFunction(&node->node_group, compare_function);
1482 void sortTreeInfo(TreeInfo **node_first)
1484 sortTreeInfoBySortFunction(node_first, compareTreeInfoEntries);
1488 // ============================================================================
1489 // some stuff from "files.c"
1490 // ============================================================================
1492 #if defined(PLATFORM_WIN32)
1494 #define S_IRGRP S_IRUSR
1497 #define S_IROTH S_IRUSR
1500 #define S_IWGRP S_IWUSR
1503 #define S_IWOTH S_IWUSR
1506 #define S_IXGRP S_IXUSR
1509 #define S_IXOTH S_IXUSR
1512 #define S_IRWXG (S_IRGRP | S_IWGRP | S_IXGRP)
1517 #endif // PLATFORM_WIN32
1519 // file permissions for newly written files
1520 #define MODE_R_ALL (S_IRUSR | S_IRGRP | S_IROTH)
1521 #define MODE_W_ALL (S_IWUSR | S_IWGRP | S_IWOTH)
1522 #define MODE_X_ALL (S_IXUSR | S_IXGRP | S_IXOTH)
1524 #define MODE_W_PRIVATE (S_IWUSR)
1525 #define MODE_W_PUBLIC_FILE (S_IWUSR | S_IWGRP)
1526 #define MODE_W_PUBLIC_DIR (S_IWUSR | S_IWGRP | S_ISGID)
1528 #define DIR_PERMS_PRIVATE (MODE_R_ALL | MODE_X_ALL | MODE_W_PRIVATE)
1529 #define DIR_PERMS_PUBLIC (MODE_R_ALL | MODE_X_ALL | MODE_W_PUBLIC_DIR)
1530 #define DIR_PERMS_PUBLIC_ALL (MODE_R_ALL | MODE_X_ALL | MODE_W_ALL)
1532 #define FILE_PERMS_PRIVATE (MODE_R_ALL | MODE_W_PRIVATE)
1533 #define FILE_PERMS_PUBLIC (MODE_R_ALL | MODE_W_PUBLIC_FILE)
1534 #define FILE_PERMS_PUBLIC_ALL (MODE_R_ALL | MODE_W_ALL)
1537 char *getHomeDir(void)
1539 static char *dir = NULL;
1541 #if defined(PLATFORM_WIN32)
1544 dir = checked_malloc(MAX_PATH + 1);
1546 if (!SUCCEEDED(SHGetFolderPath(NULL, CSIDL_PERSONAL, NULL, 0, dir)))
1549 #elif defined(PLATFORM_UNIX)
1552 if ((dir = getenv("HOME")) == NULL)
1554 dir = getUnixHomeDir();
1557 dir = getStringCopy(dir);
1569 char *getCommonDataDir(void)
1571 static char *common_data_dir = NULL;
1573 #if defined(PLATFORM_WIN32)
1574 if (common_data_dir == NULL)
1576 char *dir = checked_malloc(MAX_PATH + 1);
1578 if (SUCCEEDED(SHGetFolderPath(NULL, CSIDL_COMMON_DOCUMENTS, NULL, 0, dir))
1579 && !strEqual(dir, "")) // empty for Windows 95/98
1580 common_data_dir = getPath2(dir, program.userdata_subdir);
1582 common_data_dir = options.rw_base_directory;
1585 if (common_data_dir == NULL)
1586 common_data_dir = options.rw_base_directory;
1589 return common_data_dir;
1592 char *getPersonalDataDir(void)
1594 static char *personal_data_dir = NULL;
1596 #if defined(PLATFORM_MACOSX)
1597 if (personal_data_dir == NULL)
1598 personal_data_dir = getPath2(getHomeDir(), "Documents");
1600 if (personal_data_dir == NULL)
1601 personal_data_dir = getHomeDir();
1604 return personal_data_dir;
1607 char *getMainUserGameDataDir(void)
1609 static char *main_user_data_dir = NULL;
1611 #if defined(PLATFORM_ANDROID)
1612 if (main_user_data_dir == NULL)
1613 main_user_data_dir = (char *)(SDL_AndroidGetExternalStorageState() &
1614 SDL_ANDROID_EXTERNAL_STORAGE_WRITE ?
1615 SDL_AndroidGetExternalStoragePath() :
1616 SDL_AndroidGetInternalStoragePath());
1618 if (main_user_data_dir == NULL)
1619 main_user_data_dir = getPath2(getPersonalDataDir(),
1620 program.userdata_subdir);
1623 return main_user_data_dir;
1626 char *getUserGameDataDir(void)
1629 return getMainUserGameDataDir();
1631 return getUserDir(user.nr);
1634 char *getSetupDir(void)
1636 return getUserGameDataDir();
1639 static mode_t posix_umask(mode_t mask)
1641 #if defined(PLATFORM_UNIX)
1648 static int posix_mkdir(const char *pathname, mode_t mode)
1650 #if defined(PLATFORM_WIN32)
1651 return mkdir(pathname);
1653 return mkdir(pathname, mode);
1657 static boolean posix_process_running_setgid(void)
1659 #if defined(PLATFORM_UNIX)
1660 return (getgid() != getegid());
1666 void createDirectory(char *dir, char *text, int permission_class)
1668 if (directoryExists(dir))
1671 // leave "other" permissions in umask untouched, but ensure group parts
1672 // of USERDATA_DIR_MODE are not masked
1673 mode_t dir_mode = (permission_class == PERMS_PRIVATE ?
1674 DIR_PERMS_PRIVATE : DIR_PERMS_PUBLIC);
1675 mode_t last_umask = posix_umask(0);
1676 mode_t group_umask = ~(dir_mode & S_IRWXG);
1677 int running_setgid = posix_process_running_setgid();
1679 if (permission_class == PERMS_PUBLIC)
1681 // if we're setgid, protect files against "other"
1682 // else keep umask(0) to make the dir world-writable
1685 posix_umask(last_umask & group_umask);
1687 dir_mode = DIR_PERMS_PUBLIC_ALL;
1690 if (posix_mkdir(dir, dir_mode) != 0)
1691 Warn("cannot create %s directory '%s': %s", text, dir, strerror(errno));
1693 if (permission_class == PERMS_PUBLIC && !running_setgid)
1694 chmod(dir, dir_mode);
1696 posix_umask(last_umask); // restore previous umask
1699 void InitMainUserDataDirectory(void)
1701 createDirectory(getMainUserGameDataDir(), "main user data", PERMS_PRIVATE);
1704 void InitUserDataDirectory(void)
1706 createDirectory(getMainUserGameDataDir(), "main user data", PERMS_PRIVATE);
1710 createDirectory(getUserDir(-1), "users", PERMS_PRIVATE);
1711 createDirectory(getUserDir(user.nr), "user data", PERMS_PRIVATE);
1715 void SetFilePermissions(char *filename, int permission_class)
1717 int running_setgid = posix_process_running_setgid();
1718 int perms = (permission_class == PERMS_PRIVATE ?
1719 FILE_PERMS_PRIVATE : FILE_PERMS_PUBLIC);
1721 if (permission_class == PERMS_PUBLIC && !running_setgid)
1722 perms = FILE_PERMS_PUBLIC_ALL;
1724 chmod(filename, perms);
1727 char *getCookie(char *file_type)
1729 static char cookie[MAX_COOKIE_LEN + 1];
1731 if (strlen(program.cookie_prefix) + 1 +
1732 strlen(file_type) + strlen("_FILE_VERSION_x.x") > MAX_COOKIE_LEN)
1733 return "[COOKIE ERROR]"; // should never happen
1735 sprintf(cookie, "%s_%s_FILE_VERSION_%d.%d",
1736 program.cookie_prefix, file_type,
1737 program.version_super, program.version_major);
1742 void fprintFileHeader(FILE *file, char *basename)
1744 char *prefix = "# ";
1747 fprintf_line_with_prefix(file, prefix, sep1, 77);
1748 fprintf(file, "%s%s\n", prefix, basename);
1749 fprintf_line_with_prefix(file, prefix, sep1, 77);
1750 fprintf(file, "\n");
1753 int getFileVersionFromCookieString(const char *cookie)
1755 const char *ptr_cookie1, *ptr_cookie2;
1756 const char *pattern1 = "_FILE_VERSION_";
1757 const char *pattern2 = "?.?";
1758 const int len_cookie = strlen(cookie);
1759 const int len_pattern1 = strlen(pattern1);
1760 const int len_pattern2 = strlen(pattern2);
1761 const int len_pattern = len_pattern1 + len_pattern2;
1762 int version_super, version_major;
1764 if (len_cookie <= len_pattern)
1767 ptr_cookie1 = &cookie[len_cookie - len_pattern];
1768 ptr_cookie2 = &cookie[len_cookie - len_pattern2];
1770 if (strncmp(ptr_cookie1, pattern1, len_pattern1) != 0)
1773 if (ptr_cookie2[0] < '0' || ptr_cookie2[0] > '9' ||
1774 ptr_cookie2[1] != '.' ||
1775 ptr_cookie2[2] < '0' || ptr_cookie2[2] > '9')
1778 version_super = ptr_cookie2[0] - '0';
1779 version_major = ptr_cookie2[2] - '0';
1781 return VERSION_IDENT(version_super, version_major, 0, 0);
1784 boolean checkCookieString(const char *cookie, const char *template)
1786 const char *pattern = "_FILE_VERSION_?.?";
1787 const int len_cookie = strlen(cookie);
1788 const int len_template = strlen(template);
1789 const int len_pattern = strlen(pattern);
1791 if (len_cookie != len_template)
1794 if (strncmp(cookie, template, len_cookie - len_pattern) != 0)
1801 // ----------------------------------------------------------------------------
1802 // setup file list and hash handling functions
1803 // ----------------------------------------------------------------------------
1805 char *getFormattedSetupEntry(char *token, char *value)
1808 static char entry[MAX_LINE_LEN];
1810 // if value is an empty string, just return token without value
1814 // start with the token and some spaces to format output line
1815 sprintf(entry, "%s:", token);
1816 for (i = strlen(entry); i < token_value_position; i++)
1819 // continue with the token's value
1820 strcat(entry, value);
1825 SetupFileList *newSetupFileList(char *token, char *value)
1827 SetupFileList *new = checked_malloc(sizeof(SetupFileList));
1829 new->token = getStringCopy(token);
1830 new->value = getStringCopy(value);
1837 void freeSetupFileList(SetupFileList *list)
1842 checked_free(list->token);
1843 checked_free(list->value);
1846 freeSetupFileList(list->next);
1851 char *getListEntry(SetupFileList *list, char *token)
1856 if (strEqual(list->token, token))
1859 return getListEntry(list->next, token);
1862 SetupFileList *setListEntry(SetupFileList *list, char *token, char *value)
1867 if (strEqual(list->token, token))
1869 checked_free(list->value);
1871 list->value = getStringCopy(value);
1875 else if (list->next == NULL)
1876 return (list->next = newSetupFileList(token, value));
1878 return setListEntry(list->next, token, value);
1881 SetupFileList *addListEntry(SetupFileList *list, char *token, char *value)
1886 if (list->next == NULL)
1887 return (list->next = newSetupFileList(token, value));
1889 return addListEntry(list->next, token, value);
1892 #if ENABLE_UNUSED_CODE
1894 static void printSetupFileList(SetupFileList *list)
1899 Debug("setup:printSetupFileList", "token: '%s'", list->token);
1900 Debug("setup:printSetupFileList", "value: '%s'", list->value);
1902 printSetupFileList(list->next);
1908 DEFINE_HASHTABLE_INSERT(insert_hash_entry, char, char);
1909 DEFINE_HASHTABLE_SEARCH(search_hash_entry, char, char);
1910 DEFINE_HASHTABLE_CHANGE(change_hash_entry, char, char);
1911 DEFINE_HASHTABLE_REMOVE(remove_hash_entry, char, char);
1913 #define insert_hash_entry hashtable_insert
1914 #define search_hash_entry hashtable_search
1915 #define change_hash_entry hashtable_change
1916 #define remove_hash_entry hashtable_remove
1919 unsigned int get_hash_from_key(void *key)
1924 This algorithm (k=33) was first reported by Dan Bernstein many years ago in
1925 'comp.lang.c'. Another version of this algorithm (now favored by Bernstein)
1926 uses XOR: hash(i) = hash(i - 1) * 33 ^ str[i]; the magic of number 33 (why
1927 it works better than many other constants, prime or not) has never been
1928 adequately explained.
1930 If you just want to have a good hash function, and cannot wait, djb2
1931 is one of the best string hash functions i know. It has excellent
1932 distribution and speed on many different sets of keys and table sizes.
1933 You are not likely to do better with one of the "well known" functions
1934 such as PJW, K&R, etc.
1936 Ozan (oz) Yigit [http://www.cs.yorku.ca/~oz/hash.html]
1939 char *str = (char *)key;
1940 unsigned int hash = 5381;
1943 while ((c = *str++))
1944 hash = ((hash << 5) + hash) + c; // hash * 33 + c
1949 static int keys_are_equal(void *key1, void *key2)
1951 return (strEqual((char *)key1, (char *)key2));
1954 SetupFileHash *newSetupFileHash(void)
1956 SetupFileHash *new_hash =
1957 create_hashtable(16, 0.75, get_hash_from_key, keys_are_equal);
1959 if (new_hash == NULL)
1960 Fail("create_hashtable() failed -- out of memory");
1965 void freeSetupFileHash(SetupFileHash *hash)
1970 hashtable_destroy(hash, 1); // 1 == also free values stored in hash
1973 char *getHashEntry(SetupFileHash *hash, char *token)
1978 return search_hash_entry(hash, token);
1981 void setHashEntry(SetupFileHash *hash, char *token, char *value)
1988 value_copy = getStringCopy(value);
1990 // change value; if it does not exist, insert it as new
1991 if (!change_hash_entry(hash, token, value_copy))
1992 if (!insert_hash_entry(hash, getStringCopy(token), value_copy))
1993 Fail("cannot insert into hash -- aborting");
1996 char *removeHashEntry(SetupFileHash *hash, char *token)
2001 return remove_hash_entry(hash, token);
2004 #if ENABLE_UNUSED_CODE
2006 static void printSetupFileHash(SetupFileHash *hash)
2008 BEGIN_HASH_ITERATION(hash, itr)
2010 Debug("setup:printSetupFileHash", "token: '%s'", HASH_ITERATION_TOKEN(itr));
2011 Debug("setup:printSetupFileHash", "value: '%s'", HASH_ITERATION_VALUE(itr));
2013 END_HASH_ITERATION(hash, itr)
2018 #define ALLOW_TOKEN_VALUE_SEPARATOR_BEING_WHITESPACE 1
2019 #define CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING 0
2020 #define CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH 0
2022 static boolean token_value_separator_found = FALSE;
2023 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2024 static boolean token_value_separator_warning = FALSE;
2026 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2027 static boolean token_already_exists_warning = FALSE;
2030 static boolean getTokenValueFromSetupLineExt(char *line,
2031 char **token_ptr, char **value_ptr,
2032 char *filename, char *line_raw,
2034 boolean separator_required)
2036 static char line_copy[MAX_LINE_LEN + 1], line_raw_copy[MAX_LINE_LEN + 1];
2037 char *token, *value, *line_ptr;
2039 // when externally invoked via ReadTokenValueFromLine(), copy line buffers
2040 if (line_raw == NULL)
2042 strncpy(line_copy, line, MAX_LINE_LEN);
2043 line_copy[MAX_LINE_LEN] = '\0';
2046 strcpy(line_raw_copy, line_copy);
2047 line_raw = line_raw_copy;
2050 // cut trailing comment from input line
2051 for (line_ptr = line; *line_ptr; line_ptr++)
2053 if (*line_ptr == '#')
2060 // cut trailing whitespaces from input line
2061 for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
2062 if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
2065 // ignore empty lines
2069 // cut leading whitespaces from token
2070 for (token = line; *token; token++)
2071 if (*token != ' ' && *token != '\t')
2074 // start with empty value as reliable default
2077 token_value_separator_found = FALSE;
2079 // find end of token to determine start of value
2080 for (line_ptr = token; *line_ptr; line_ptr++)
2082 // first look for an explicit token/value separator, like ':' or '='
2083 if (*line_ptr == ':' || *line_ptr == '=')
2085 *line_ptr = '\0'; // terminate token string
2086 value = line_ptr + 1; // set beginning of value
2088 token_value_separator_found = TRUE;
2094 #if ALLOW_TOKEN_VALUE_SEPARATOR_BEING_WHITESPACE
2095 // fallback: if no token/value separator found, also allow whitespaces
2096 if (!token_value_separator_found && !separator_required)
2098 for (line_ptr = token; *line_ptr; line_ptr++)
2100 if (*line_ptr == ' ' || *line_ptr == '\t')
2102 *line_ptr = '\0'; // terminate token string
2103 value = line_ptr + 1; // set beginning of value
2105 token_value_separator_found = TRUE;
2111 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2112 if (token_value_separator_found)
2114 if (!token_value_separator_warning)
2116 Debug("setup", "---");
2118 if (filename != NULL)
2120 Debug("setup", "missing token/value separator(s) in config file:");
2121 Debug("setup", "- config file: '%s'", filename);
2125 Debug("setup", "missing token/value separator(s):");
2128 token_value_separator_warning = TRUE;
2131 if (filename != NULL)
2132 Debug("setup", "- line %d: '%s'", line_nr, line_raw);
2134 Debug("setup", "- line: '%s'", line_raw);
2140 // cut trailing whitespaces from token
2141 for (line_ptr = &token[strlen(token)]; line_ptr >= token; line_ptr--)
2142 if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
2145 // cut leading whitespaces from value
2146 for (; *value; value++)
2147 if (*value != ' ' && *value != '\t')
2156 boolean getTokenValueFromSetupLine(char *line, char **token, char **value)
2158 // while the internal (old) interface does not require a token/value
2159 // separator (for downwards compatibility with existing files which
2160 // don't use them), it is mandatory for the external (new) interface
2162 return getTokenValueFromSetupLineExt(line, token, value, NULL, NULL, 0, TRUE);
2165 static boolean loadSetupFileData(void *setup_file_data, char *filename,
2166 boolean top_recursion_level, boolean is_hash)
2168 static SetupFileHash *include_filename_hash = NULL;
2169 char line[MAX_LINE_LEN], line_raw[MAX_LINE_LEN], previous_line[MAX_LINE_LEN];
2170 char *token, *value, *line_ptr;
2171 void *insert_ptr = NULL;
2172 boolean read_continued_line = FALSE;
2174 int line_nr = 0, token_count = 0, include_count = 0;
2176 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2177 token_value_separator_warning = FALSE;
2180 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2181 token_already_exists_warning = FALSE;
2184 if (!(file = openFile(filename, MODE_READ)))
2186 #if DEBUG_NO_CONFIG_FILE
2187 Debug("setup", "cannot open configuration file '%s'", filename);
2193 // use "insert pointer" to store list end for constant insertion complexity
2195 insert_ptr = setup_file_data;
2197 // on top invocation, create hash to mark included files (to prevent loops)
2198 if (top_recursion_level)
2199 include_filename_hash = newSetupFileHash();
2201 // mark this file as already included (to prevent including it again)
2202 setHashEntry(include_filename_hash, getBaseNamePtr(filename), "true");
2204 while (!checkEndOfFile(file))
2206 // read next line of input file
2207 if (!getStringFromFile(file, line, MAX_LINE_LEN))
2210 // check if line was completely read and is terminated by line break
2211 if (strlen(line) > 0 && line[strlen(line) - 1] == '\n')
2214 // cut trailing line break (this can be newline and/or carriage return)
2215 for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
2216 if ((*line_ptr == '\n' || *line_ptr == '\r') && *(line_ptr + 1) == '\0')
2219 // copy raw input line for later use (mainly debugging output)
2220 strcpy(line_raw, line);
2222 if (read_continued_line)
2224 // append new line to existing line, if there is enough space
2225 if (strlen(previous_line) + strlen(line_ptr) < MAX_LINE_LEN)
2226 strcat(previous_line, line_ptr);
2228 strcpy(line, previous_line); // copy storage buffer to line
2230 read_continued_line = FALSE;
2233 // if the last character is '\', continue at next line
2234 if (strlen(line) > 0 && line[strlen(line) - 1] == '\\')
2236 line[strlen(line) - 1] = '\0'; // cut off trailing backslash
2237 strcpy(previous_line, line); // copy line to storage buffer
2239 read_continued_line = TRUE;
2244 if (!getTokenValueFromSetupLineExt(line, &token, &value, filename,
2245 line_raw, line_nr, FALSE))
2250 if (strEqual(token, "include"))
2252 if (getHashEntry(include_filename_hash, value) == NULL)
2254 char *basepath = getBasePath(filename);
2255 char *basename = getBaseName(value);
2256 char *filename_include = getPath2(basepath, basename);
2258 loadSetupFileData(setup_file_data, filename_include, FALSE, is_hash);
2262 free(filename_include);
2268 Warn("ignoring already processed file '%s'", value);
2275 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2277 getHashEntry((SetupFileHash *)setup_file_data, token);
2279 if (old_value != NULL)
2281 if (!token_already_exists_warning)
2283 Debug("setup", "---");
2284 Debug("setup", "duplicate token(s) found in config file:");
2285 Debug("setup", "- config file: '%s'", filename);
2287 token_already_exists_warning = TRUE;
2290 Debug("setup", "- token: '%s' (in line %d)", token, line_nr);
2291 Debug("setup", " old value: '%s'", old_value);
2292 Debug("setup", " new value: '%s'", value);
2296 setHashEntry((SetupFileHash *)setup_file_data, token, value);
2300 insert_ptr = addListEntry((SetupFileList *)insert_ptr, token, value);
2310 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2311 if (token_value_separator_warning)
2312 Debug("setup", "---");
2315 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2316 if (token_already_exists_warning)
2317 Debug("setup", "---");
2320 if (token_count == 0 && include_count == 0)
2321 Warn("configuration file '%s' is empty", filename);
2323 if (top_recursion_level)
2324 freeSetupFileHash(include_filename_hash);
2329 static int compareSetupFileData(const void *object1, const void *object2)
2331 const struct ConfigInfo *entry1 = (struct ConfigInfo *)object1;
2332 const struct ConfigInfo *entry2 = (struct ConfigInfo *)object2;
2334 return strcmp(entry1->token, entry2->token);
2337 static void saveSetupFileHash(SetupFileHash *hash, char *filename)
2339 int item_count = hashtable_count(hash);
2340 int item_size = sizeof(struct ConfigInfo);
2341 struct ConfigInfo *sort_array = checked_malloc(item_count * item_size);
2345 // copy string pointers from hash to array
2346 BEGIN_HASH_ITERATION(hash, itr)
2348 sort_array[i].token = HASH_ITERATION_TOKEN(itr);
2349 sort_array[i].value = HASH_ITERATION_VALUE(itr);
2353 if (i > item_count) // should never happen
2356 END_HASH_ITERATION(hash, itr)
2358 // sort string pointers from hash in array
2359 qsort(sort_array, item_count, item_size, compareSetupFileData);
2361 if (!(file = fopen(filename, MODE_WRITE)))
2363 Warn("cannot write configuration file '%s'", filename);
2368 for (i = 0; i < item_count; i++)
2369 fprintf(file, "%s\n", getFormattedSetupEntry(sort_array[i].token,
2370 sort_array[i].value));
2373 checked_free(sort_array);
2376 SetupFileList *loadSetupFileList(char *filename)
2378 SetupFileList *setup_file_list = newSetupFileList("", "");
2379 SetupFileList *first_valid_list_entry;
2381 if (!loadSetupFileData(setup_file_list, filename, TRUE, FALSE))
2383 freeSetupFileList(setup_file_list);
2388 first_valid_list_entry = setup_file_list->next;
2390 // free empty list header
2391 setup_file_list->next = NULL;
2392 freeSetupFileList(setup_file_list);
2394 return first_valid_list_entry;
2397 SetupFileHash *loadSetupFileHash(char *filename)
2399 SetupFileHash *setup_file_hash = newSetupFileHash();
2401 if (!loadSetupFileData(setup_file_hash, filename, TRUE, TRUE))
2403 freeSetupFileHash(setup_file_hash);
2408 return setup_file_hash;
2412 // ============================================================================
2414 // ============================================================================
2416 #define TOKEN_STR_LAST_LEVEL_SERIES "last_level_series"
2417 #define TOKEN_STR_LAST_PLAYED_LEVEL "last_played_level"
2418 #define TOKEN_STR_HANDICAP_LEVEL "handicap_level"
2419 #define TOKEN_STR_LAST_USER "last_user"
2421 // level directory info
2422 #define LEVELINFO_TOKEN_IDENTIFIER 0
2423 #define LEVELINFO_TOKEN_NAME 1
2424 #define LEVELINFO_TOKEN_NAME_SORTING 2
2425 #define LEVELINFO_TOKEN_AUTHOR 3
2426 #define LEVELINFO_TOKEN_YEAR 4
2427 #define LEVELINFO_TOKEN_PROGRAM_TITLE 5
2428 #define LEVELINFO_TOKEN_PROGRAM_COPYRIGHT 6
2429 #define LEVELINFO_TOKEN_PROGRAM_COMPANY 7
2430 #define LEVELINFO_TOKEN_IMPORTED_FROM 8
2431 #define LEVELINFO_TOKEN_IMPORTED_BY 9
2432 #define LEVELINFO_TOKEN_TESTED_BY 10
2433 #define LEVELINFO_TOKEN_LEVELS 11
2434 #define LEVELINFO_TOKEN_FIRST_LEVEL 12
2435 #define LEVELINFO_TOKEN_SORT_PRIORITY 13
2436 #define LEVELINFO_TOKEN_LATEST_ENGINE 14
2437 #define LEVELINFO_TOKEN_LEVEL_GROUP 15
2438 #define LEVELINFO_TOKEN_READONLY 16
2439 #define LEVELINFO_TOKEN_GRAPHICS_SET_ECS 17
2440 #define LEVELINFO_TOKEN_GRAPHICS_SET_AGA 18
2441 #define LEVELINFO_TOKEN_GRAPHICS_SET 19
2442 #define LEVELINFO_TOKEN_SOUNDS_SET_DEFAULT 20
2443 #define LEVELINFO_TOKEN_SOUNDS_SET_LOWPASS 21
2444 #define LEVELINFO_TOKEN_SOUNDS_SET 22
2445 #define LEVELINFO_TOKEN_MUSIC_SET 23
2446 #define LEVELINFO_TOKEN_FILENAME 24
2447 #define LEVELINFO_TOKEN_FILETYPE 25
2448 #define LEVELINFO_TOKEN_SPECIAL_FLAGS 26
2449 #define LEVELINFO_TOKEN_HANDICAP 27
2450 #define LEVELINFO_TOKEN_SKIP_LEVELS 28
2451 #define LEVELINFO_TOKEN_USE_EMC_TILES 29
2453 #define NUM_LEVELINFO_TOKENS 30
2455 static LevelDirTree ldi;
2457 static struct TokenInfo levelinfo_tokens[] =
2459 // level directory info
2460 { TYPE_STRING, &ldi.identifier, "identifier" },
2461 { TYPE_STRING, &ldi.name, "name" },
2462 { TYPE_STRING, &ldi.name_sorting, "name_sorting" },
2463 { TYPE_STRING, &ldi.author, "author" },
2464 { TYPE_STRING, &ldi.year, "year" },
2465 { TYPE_STRING, &ldi.program_title, "program_title" },
2466 { TYPE_STRING, &ldi.program_copyright, "program_copyright" },
2467 { TYPE_STRING, &ldi.program_company, "program_company" },
2468 { TYPE_STRING, &ldi.imported_from, "imported_from" },
2469 { TYPE_STRING, &ldi.imported_by, "imported_by" },
2470 { TYPE_STRING, &ldi.tested_by, "tested_by" },
2471 { TYPE_INTEGER, &ldi.levels, "levels" },
2472 { TYPE_INTEGER, &ldi.first_level, "first_level" },
2473 { TYPE_INTEGER, &ldi.sort_priority, "sort_priority" },
2474 { TYPE_BOOLEAN, &ldi.latest_engine, "latest_engine" },
2475 { TYPE_BOOLEAN, &ldi.level_group, "level_group" },
2476 { TYPE_BOOLEAN, &ldi.readonly, "readonly" },
2477 { TYPE_STRING, &ldi.graphics_set_ecs, "graphics_set.ecs" },
2478 { TYPE_STRING, &ldi.graphics_set_aga, "graphics_set.aga" },
2479 { TYPE_STRING, &ldi.graphics_set, "graphics_set" },
2480 { TYPE_STRING, &ldi.sounds_set_default,"sounds_set.default" },
2481 { TYPE_STRING, &ldi.sounds_set_lowpass,"sounds_set.lowpass" },
2482 { TYPE_STRING, &ldi.sounds_set, "sounds_set" },
2483 { TYPE_STRING, &ldi.music_set, "music_set" },
2484 { TYPE_STRING, &ldi.level_filename, "filename" },
2485 { TYPE_STRING, &ldi.level_filetype, "filetype" },
2486 { TYPE_STRING, &ldi.special_flags, "special_flags" },
2487 { TYPE_BOOLEAN, &ldi.handicap, "handicap" },
2488 { TYPE_BOOLEAN, &ldi.skip_levels, "skip_levels" },
2489 { TYPE_BOOLEAN, &ldi.use_emc_tiles, "use_emc_tiles" }
2492 static struct TokenInfo artworkinfo_tokens[] =
2494 // artwork directory info
2495 { TYPE_STRING, &ldi.identifier, "identifier" },
2496 { TYPE_STRING, &ldi.subdir, "subdir" },
2497 { TYPE_STRING, &ldi.name, "name" },
2498 { TYPE_STRING, &ldi.name_sorting, "name_sorting" },
2499 { TYPE_STRING, &ldi.author, "author" },
2500 { TYPE_STRING, &ldi.program_title, "program_title" },
2501 { TYPE_STRING, &ldi.program_copyright, "program_copyright" },
2502 { TYPE_STRING, &ldi.program_company, "program_company" },
2503 { TYPE_INTEGER, &ldi.sort_priority, "sort_priority" },
2504 { TYPE_STRING, &ldi.basepath, "basepath" },
2505 { TYPE_STRING, &ldi.fullpath, "fullpath" },
2506 { TYPE_BOOLEAN, &ldi.in_user_dir, "in_user_dir" },
2507 { TYPE_INTEGER, &ldi.color, "color" },
2508 { TYPE_STRING, &ldi.class_desc, "class_desc" },
2513 static char *optional_tokens[] =
2516 "program_copyright",
2522 static void setTreeInfoToDefaults(TreeInfo *ti, int type)
2526 ti->node_top = (ti->type == TREE_TYPE_LEVEL_DIR ? &leveldir_first :
2527 ti->type == TREE_TYPE_GRAPHICS_DIR ? &artwork.gfx_first :
2528 ti->type == TREE_TYPE_SOUNDS_DIR ? &artwork.snd_first :
2529 ti->type == TREE_TYPE_MUSIC_DIR ? &artwork.mus_first :
2532 ti->node_parent = NULL;
2533 ti->node_group = NULL;
2540 ti->fullpath = NULL;
2541 ti->basepath = NULL;
2542 ti->identifier = NULL;
2543 ti->name = getStringCopy(ANONYMOUS_NAME);
2544 ti->name_sorting = NULL;
2545 ti->author = getStringCopy(ANONYMOUS_NAME);
2548 ti->program_title = NULL;
2549 ti->program_copyright = NULL;
2550 ti->program_company = NULL;
2552 ti->sort_priority = LEVELCLASS_UNDEFINED; // default: least priority
2553 ti->latest_engine = FALSE; // default: get from level
2554 ti->parent_link = FALSE;
2555 ti->in_user_dir = FALSE;
2556 ti->user_defined = FALSE;
2558 ti->class_desc = NULL;
2560 ti->infotext = getStringCopy(TREE_INFOTEXT(ti->type));
2562 if (ti->type == TREE_TYPE_LEVEL_DIR)
2564 ti->imported_from = NULL;
2565 ti->imported_by = NULL;
2566 ti->tested_by = NULL;
2568 ti->graphics_set_ecs = NULL;
2569 ti->graphics_set_aga = NULL;
2570 ti->graphics_set = NULL;
2571 ti->sounds_set_default = NULL;
2572 ti->sounds_set_lowpass = NULL;
2573 ti->sounds_set = NULL;
2574 ti->music_set = NULL;
2575 ti->graphics_path = getStringCopy(UNDEFINED_FILENAME);
2576 ti->sounds_path = getStringCopy(UNDEFINED_FILENAME);
2577 ti->music_path = getStringCopy(UNDEFINED_FILENAME);
2579 ti->level_filename = NULL;
2580 ti->level_filetype = NULL;
2582 ti->special_flags = NULL;
2585 ti->first_level = 0;
2587 ti->level_group = FALSE;
2588 ti->handicap_level = 0;
2589 ti->readonly = TRUE;
2590 ti->handicap = TRUE;
2591 ti->skip_levels = FALSE;
2593 ti->use_emc_tiles = FALSE;
2597 static void setTreeInfoToDefaultsFromParent(TreeInfo *ti, TreeInfo *parent)
2601 Warn("setTreeInfoToDefaultsFromParent(): parent == NULL");
2603 setTreeInfoToDefaults(ti, TREE_TYPE_UNDEFINED);
2608 // copy all values from the parent structure
2610 ti->type = parent->type;
2612 ti->node_top = parent->node_top;
2613 ti->node_parent = parent;
2614 ti->node_group = NULL;
2621 ti->fullpath = NULL;
2622 ti->basepath = NULL;
2623 ti->identifier = NULL;
2624 ti->name = getStringCopy(ANONYMOUS_NAME);
2625 ti->name_sorting = NULL;
2626 ti->author = getStringCopy(parent->author);
2627 ti->year = getStringCopy(parent->year);
2629 ti->program_title = getStringCopy(parent->program_title);
2630 ti->program_copyright = getStringCopy(parent->program_copyright);
2631 ti->program_company = getStringCopy(parent->program_company);
2633 ti->sort_priority = parent->sort_priority;
2634 ti->latest_engine = parent->latest_engine;
2635 ti->parent_link = FALSE;
2636 ti->in_user_dir = parent->in_user_dir;
2637 ti->user_defined = parent->user_defined;
2638 ti->color = parent->color;
2639 ti->class_desc = getStringCopy(parent->class_desc);
2641 ti->infotext = getStringCopy(parent->infotext);
2643 if (ti->type == TREE_TYPE_LEVEL_DIR)
2645 ti->imported_from = getStringCopy(parent->imported_from);
2646 ti->imported_by = getStringCopy(parent->imported_by);
2647 ti->tested_by = getStringCopy(parent->tested_by);
2649 ti->graphics_set_ecs = getStringCopy(parent->graphics_set_ecs);
2650 ti->graphics_set_aga = getStringCopy(parent->graphics_set_aga);
2651 ti->graphics_set = getStringCopy(parent->graphics_set);
2652 ti->sounds_set_default = getStringCopy(parent->sounds_set_default);
2653 ti->sounds_set_lowpass = getStringCopy(parent->sounds_set_lowpass);
2654 ti->sounds_set = getStringCopy(parent->sounds_set);
2655 ti->music_set = getStringCopy(parent->music_set);
2656 ti->graphics_path = getStringCopy(UNDEFINED_FILENAME);
2657 ti->sounds_path = getStringCopy(UNDEFINED_FILENAME);
2658 ti->music_path = getStringCopy(UNDEFINED_FILENAME);
2660 ti->level_filename = getStringCopy(parent->level_filename);
2661 ti->level_filetype = getStringCopy(parent->level_filetype);
2663 ti->special_flags = getStringCopy(parent->special_flags);
2665 ti->levels = parent->levels;
2666 ti->first_level = parent->first_level;
2667 ti->last_level = parent->last_level;
2668 ti->level_group = FALSE;
2669 ti->handicap_level = parent->handicap_level;
2670 ti->readonly = parent->readonly;
2671 ti->handicap = parent->handicap;
2672 ti->skip_levels = parent->skip_levels;
2674 ti->use_emc_tiles = parent->use_emc_tiles;
2678 static TreeInfo *getTreeInfoCopy(TreeInfo *ti)
2680 TreeInfo *ti_copy = newTreeInfo();
2682 // copy all values from the original structure
2684 ti_copy->type = ti->type;
2686 ti_copy->node_top = ti->node_top;
2687 ti_copy->node_parent = ti->node_parent;
2688 ti_copy->node_group = ti->node_group;
2689 ti_copy->next = ti->next;
2691 ti_copy->cl_first = ti->cl_first;
2692 ti_copy->cl_cursor = ti->cl_cursor;
2694 ti_copy->subdir = getStringCopy(ti->subdir);
2695 ti_copy->fullpath = getStringCopy(ti->fullpath);
2696 ti_copy->basepath = getStringCopy(ti->basepath);
2697 ti_copy->identifier = getStringCopy(ti->identifier);
2698 ti_copy->name = getStringCopy(ti->name);
2699 ti_copy->name_sorting = getStringCopy(ti->name_sorting);
2700 ti_copy->author = getStringCopy(ti->author);
2701 ti_copy->year = getStringCopy(ti->year);
2703 ti_copy->program_title = getStringCopy(ti->program_title);
2704 ti_copy->program_copyright = getStringCopy(ti->program_copyright);
2705 ti_copy->program_company = getStringCopy(ti->program_company);
2707 ti_copy->imported_from = getStringCopy(ti->imported_from);
2708 ti_copy->imported_by = getStringCopy(ti->imported_by);
2709 ti_copy->tested_by = getStringCopy(ti->tested_by);
2711 ti_copy->graphics_set_ecs = getStringCopy(ti->graphics_set_ecs);
2712 ti_copy->graphics_set_aga = getStringCopy(ti->graphics_set_aga);
2713 ti_copy->graphics_set = getStringCopy(ti->graphics_set);
2714 ti_copy->sounds_set_default = getStringCopy(ti->sounds_set_default);
2715 ti_copy->sounds_set_lowpass = getStringCopy(ti->sounds_set_lowpass);
2716 ti_copy->sounds_set = getStringCopy(ti->sounds_set);
2717 ti_copy->music_set = getStringCopy(ti->music_set);
2718 ti_copy->graphics_path = getStringCopy(ti->graphics_path);
2719 ti_copy->sounds_path = getStringCopy(ti->sounds_path);
2720 ti_copy->music_path = getStringCopy(ti->music_path);
2722 ti_copy->level_filename = getStringCopy(ti->level_filename);
2723 ti_copy->level_filetype = getStringCopy(ti->level_filetype);
2725 ti_copy->special_flags = getStringCopy(ti->special_flags);
2727 ti_copy->levels = ti->levels;
2728 ti_copy->first_level = ti->first_level;
2729 ti_copy->last_level = ti->last_level;
2730 ti_copy->sort_priority = ti->sort_priority;
2732 ti_copy->latest_engine = ti->latest_engine;
2734 ti_copy->level_group = ti->level_group;
2735 ti_copy->parent_link = ti->parent_link;
2736 ti_copy->in_user_dir = ti->in_user_dir;
2737 ti_copy->user_defined = ti->user_defined;
2738 ti_copy->readonly = ti->readonly;
2739 ti_copy->handicap = ti->handicap;
2740 ti_copy->skip_levels = ti->skip_levels;
2742 ti_copy->use_emc_tiles = ti->use_emc_tiles;
2744 ti_copy->color = ti->color;
2745 ti_copy->class_desc = getStringCopy(ti->class_desc);
2746 ti_copy->handicap_level = ti->handicap_level;
2748 ti_copy->infotext = getStringCopy(ti->infotext);
2753 void freeTreeInfo(TreeInfo *ti)
2758 checked_free(ti->subdir);
2759 checked_free(ti->fullpath);
2760 checked_free(ti->basepath);
2761 checked_free(ti->identifier);
2763 checked_free(ti->name);
2764 checked_free(ti->name_sorting);
2765 checked_free(ti->author);
2766 checked_free(ti->year);
2768 checked_free(ti->program_title);
2769 checked_free(ti->program_copyright);
2770 checked_free(ti->program_company);
2772 checked_free(ti->class_desc);
2774 checked_free(ti->infotext);
2776 if (ti->type == TREE_TYPE_LEVEL_DIR)
2778 checked_free(ti->imported_from);
2779 checked_free(ti->imported_by);
2780 checked_free(ti->tested_by);
2782 checked_free(ti->graphics_set_ecs);
2783 checked_free(ti->graphics_set_aga);
2784 checked_free(ti->graphics_set);
2785 checked_free(ti->sounds_set_default);
2786 checked_free(ti->sounds_set_lowpass);
2787 checked_free(ti->sounds_set);
2788 checked_free(ti->music_set);
2790 checked_free(ti->graphics_path);
2791 checked_free(ti->sounds_path);
2792 checked_free(ti->music_path);
2794 checked_free(ti->level_filename);
2795 checked_free(ti->level_filetype);
2797 checked_free(ti->special_flags);
2800 // recursively free child node
2802 freeTreeInfo(ti->node_group);
2804 // recursively free next node
2806 freeTreeInfo(ti->next);
2811 void setSetupInfo(struct TokenInfo *token_info,
2812 int token_nr, char *token_value)
2814 int token_type = token_info[token_nr].type;
2815 void *setup_value = token_info[token_nr].value;
2817 if (token_value == NULL)
2820 // set setup field to corresponding token value
2825 *(boolean *)setup_value = get_boolean_from_string(token_value);
2829 *(int *)setup_value = get_switch3_from_string(token_value);
2833 *(Key *)setup_value = getKeyFromKeyName(token_value);
2837 *(Key *)setup_value = getKeyFromX11KeyName(token_value);
2841 *(int *)setup_value = get_integer_from_string(token_value);
2845 checked_free(*(char **)setup_value);
2846 *(char **)setup_value = getStringCopy(token_value);
2850 *(int *)setup_value = get_player_nr_from_string(token_value);
2858 static int compareTreeInfoEntries(const void *object1, const void *object2)
2860 const TreeInfo *entry1 = *((TreeInfo **)object1);
2861 const TreeInfo *entry2 = *((TreeInfo **)object2);
2862 int class_sorting1 = 0, class_sorting2 = 0;
2865 if (entry1->type == TREE_TYPE_LEVEL_DIR)
2867 class_sorting1 = LEVELSORTING(entry1);
2868 class_sorting2 = LEVELSORTING(entry2);
2870 else if (entry1->type == TREE_TYPE_GRAPHICS_DIR ||
2871 entry1->type == TREE_TYPE_SOUNDS_DIR ||
2872 entry1->type == TREE_TYPE_MUSIC_DIR)
2874 class_sorting1 = ARTWORKSORTING(entry1);
2875 class_sorting2 = ARTWORKSORTING(entry2);
2878 if (entry1->parent_link || entry2->parent_link)
2879 compare_result = (entry1->parent_link ? -1 : +1);
2880 else if (entry1->sort_priority == entry2->sort_priority)
2882 char *name1 = getStringToLower(entry1->name_sorting);
2883 char *name2 = getStringToLower(entry2->name_sorting);
2885 compare_result = strcmp(name1, name2);
2890 else if (class_sorting1 == class_sorting2)
2891 compare_result = entry1->sort_priority - entry2->sort_priority;
2893 compare_result = class_sorting1 - class_sorting2;
2895 return compare_result;
2898 static TreeInfo *createParentTreeInfoNode(TreeInfo *node_parent)
2902 if (node_parent == NULL)
2905 ti_new = newTreeInfo();
2906 setTreeInfoToDefaults(ti_new, node_parent->type);
2908 ti_new->node_parent = node_parent;
2909 ti_new->parent_link = TRUE;
2911 setString(&ti_new->identifier, node_parent->identifier);
2912 setString(&ti_new->name, BACKLINK_TEXT_PARENT);
2913 setString(&ti_new->name_sorting, ti_new->name);
2915 setString(&ti_new->subdir, STRING_PARENT_DIRECTORY);
2916 setString(&ti_new->fullpath, node_parent->fullpath);
2918 ti_new->sort_priority = node_parent->sort_priority;
2919 ti_new->latest_engine = node_parent->latest_engine;
2921 setString(&ti_new->class_desc, getLevelClassDescription(ti_new));
2923 pushTreeInfo(&node_parent->node_group, ti_new);
2928 static TreeInfo *createTopTreeInfoNode(TreeInfo *node_first)
2930 if (node_first == NULL)
2933 TreeInfo *ti_new = newTreeInfo();
2934 int type = node_first->type;
2936 setTreeInfoToDefaults(ti_new, type);
2938 ti_new->node_parent = NULL;
2939 ti_new->parent_link = FALSE;
2941 setString(&ti_new->identifier, node_first->identifier);
2942 setString(&ti_new->name, TREE_INFOTEXT(type));
2943 setString(&ti_new->name_sorting, ti_new->name);
2945 setString(&ti_new->subdir, STRING_TOP_DIRECTORY);
2946 setString(&ti_new->fullpath, ".");
2948 ti_new->sort_priority = node_first->sort_priority;;
2949 ti_new->latest_engine = node_first->latest_engine;
2951 setString(&ti_new->class_desc, TREE_INFOTEXT(type));
2953 ti_new->node_group = node_first;
2954 ti_new->level_group = TRUE;
2956 TreeInfo *ti_new2 = createParentTreeInfoNode(ti_new);
2958 setString(&ti_new2->name, TREE_BACKLINK_TEXT(type));
2959 setString(&ti_new2->name_sorting, ti_new2->name);
2964 static void setTreeInfoParentNodes(TreeInfo *node, TreeInfo *node_parent)
2968 if (node->node_group)
2969 setTreeInfoParentNodes(node->node_group, node);
2971 node->node_parent = node_parent;
2978 // ----------------------------------------------------------------------------
2979 // functions for handling level and custom artwork info cache
2980 // ----------------------------------------------------------------------------
2982 static void LoadArtworkInfoCache(void)
2984 InitCacheDirectory();
2986 if (artworkinfo_cache_old == NULL)
2988 char *filename = getPath2(getCacheDir(), ARTWORKINFO_CACHE_FILE);
2990 // try to load artwork info hash from already existing cache file
2991 artworkinfo_cache_old = loadSetupFileHash(filename);
2993 // if no artwork info cache file was found, start with empty hash
2994 if (artworkinfo_cache_old == NULL)
2995 artworkinfo_cache_old = newSetupFileHash();
3000 if (artworkinfo_cache_new == NULL)
3001 artworkinfo_cache_new = newSetupFileHash();
3003 update_artworkinfo_cache = FALSE;
3006 static void SaveArtworkInfoCache(void)
3008 if (!update_artworkinfo_cache)
3011 char *filename = getPath2(getCacheDir(), ARTWORKINFO_CACHE_FILE);
3013 InitCacheDirectory();
3015 saveSetupFileHash(artworkinfo_cache_new, filename);
3020 static char *getCacheTokenPrefix(char *prefix1, char *prefix2)
3022 static char *prefix = NULL;
3024 checked_free(prefix);
3026 prefix = getStringCat2WithSeparator(prefix1, prefix2, ".");
3031 // (identical to above function, but separate string buffer needed -- nasty)
3032 static char *getCacheToken(char *prefix, char *suffix)
3034 static char *token = NULL;
3036 checked_free(token);
3038 token = getStringCat2WithSeparator(prefix, suffix, ".");
3043 static char *getFileTimestampString(char *filename)
3045 return getStringCopy(i_to_a(getFileTimestampEpochSeconds(filename)));
3048 static boolean modifiedFileTimestamp(char *filename, char *timestamp_string)
3050 struct stat file_status;
3052 if (timestamp_string == NULL)
3055 if (!fileExists(filename)) // file does not exist
3056 return (atoi(timestamp_string) != 0);
3058 if (stat(filename, &file_status) != 0) // cannot stat file
3061 return (file_status.st_mtime != atoi(timestamp_string));
3064 static TreeInfo *getArtworkInfoCacheEntry(LevelDirTree *level_node, int type)
3066 char *identifier = level_node->subdir;
3067 char *type_string = ARTWORK_DIRECTORY(type);
3068 char *token_prefix = getCacheTokenPrefix(type_string, identifier);
3069 char *token_main = getCacheToken(token_prefix, "CACHED");
3070 char *cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
3071 boolean cached = (cache_entry != NULL && strEqual(cache_entry, "true"));
3072 TreeInfo *artwork_info = NULL;
3074 if (!use_artworkinfo_cache)
3077 if (optional_tokens_hash == NULL)
3081 // create hash from list of optional tokens (for quick access)
3082 optional_tokens_hash = newSetupFileHash();
3083 for (i = 0; optional_tokens[i] != NULL; i++)
3084 setHashEntry(optional_tokens_hash, optional_tokens[i], "");
3091 artwork_info = newTreeInfo();
3092 setTreeInfoToDefaults(artwork_info, type);
3094 // set all structure fields according to the token/value pairs
3095 ldi = *artwork_info;
3096 for (i = 0; artworkinfo_tokens[i].type != -1; i++)
3098 char *token_suffix = artworkinfo_tokens[i].text;
3099 char *token = getCacheToken(token_prefix, token_suffix);
3100 char *value = getHashEntry(artworkinfo_cache_old, token);
3102 (getHashEntry(optional_tokens_hash, token_suffix) != NULL);
3104 setSetupInfo(artworkinfo_tokens, i, value);
3106 // check if cache entry for this item is mandatory, but missing
3107 if (value == NULL && !optional)
3109 Warn("missing cache entry '%s'", token);
3115 *artwork_info = ldi;
3120 char *filename_levelinfo = getPath2(getLevelDirFromTreeInfo(level_node),
3121 LEVELINFO_FILENAME);
3122 char *filename_artworkinfo = getPath2(getSetupArtworkDir(artwork_info),
3123 ARTWORKINFO_FILENAME(type));
3125 // check if corresponding "levelinfo.conf" file has changed
3126 token_main = getCacheToken(token_prefix, "TIMESTAMP_LEVELINFO");
3127 cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
3129 if (modifiedFileTimestamp(filename_levelinfo, cache_entry))
3132 // check if corresponding "<artworkinfo>.conf" file has changed
3133 token_main = getCacheToken(token_prefix, "TIMESTAMP_ARTWORKINFO");
3134 cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
3136 if (modifiedFileTimestamp(filename_artworkinfo, cache_entry))
3139 checked_free(filename_levelinfo);
3140 checked_free(filename_artworkinfo);
3143 if (!cached && artwork_info != NULL)
3145 freeTreeInfo(artwork_info);
3150 return artwork_info;
3153 static void setArtworkInfoCacheEntry(TreeInfo *artwork_info,
3154 LevelDirTree *level_node, int type)
3156 char *identifier = level_node->subdir;
3157 char *type_string = ARTWORK_DIRECTORY(type);
3158 char *token_prefix = getCacheTokenPrefix(type_string, identifier);
3159 char *token_main = getCacheToken(token_prefix, "CACHED");
3160 boolean set_cache_timestamps = TRUE;
3163 setHashEntry(artworkinfo_cache_new, token_main, "true");
3165 if (set_cache_timestamps)
3167 char *filename_levelinfo = getPath2(getLevelDirFromTreeInfo(level_node),
3168 LEVELINFO_FILENAME);
3169 char *filename_artworkinfo = getPath2(getSetupArtworkDir(artwork_info),
3170 ARTWORKINFO_FILENAME(type));
3171 char *timestamp_levelinfo = getFileTimestampString(filename_levelinfo);
3172 char *timestamp_artworkinfo = getFileTimestampString(filename_artworkinfo);
3174 token_main = getCacheToken(token_prefix, "TIMESTAMP_LEVELINFO");
3175 setHashEntry(artworkinfo_cache_new, token_main, timestamp_levelinfo);
3177 token_main = getCacheToken(token_prefix, "TIMESTAMP_ARTWORKINFO");
3178 setHashEntry(artworkinfo_cache_new, token_main, timestamp_artworkinfo);
3180 checked_free(filename_levelinfo);
3181 checked_free(filename_artworkinfo);
3182 checked_free(timestamp_levelinfo);
3183 checked_free(timestamp_artworkinfo);
3186 ldi = *artwork_info;
3187 for (i = 0; artworkinfo_tokens[i].type != -1; i++)
3189 char *token = getCacheToken(token_prefix, artworkinfo_tokens[i].text);
3190 char *value = getSetupValue(artworkinfo_tokens[i].type,
3191 artworkinfo_tokens[i].value);
3193 setHashEntry(artworkinfo_cache_new, token, value);
3198 // ----------------------------------------------------------------------------
3199 // functions for loading level info and custom artwork info
3200 // ----------------------------------------------------------------------------
3202 int GetZipFileTreeType(char *zip_filename)
3204 static char *top_dir_path = NULL;
3205 static char *top_dir_conf_filename[NUM_BASE_TREE_TYPES] = { NULL };
3206 static char *conf_basename[NUM_BASE_TREE_TYPES] =
3208 GRAPHICSINFO_FILENAME,
3209 SOUNDSINFO_FILENAME,
3215 checked_free(top_dir_path);
3216 top_dir_path = NULL;
3218 for (j = 0; j < NUM_BASE_TREE_TYPES; j++)
3220 checked_free(top_dir_conf_filename[j]);
3221 top_dir_conf_filename[j] = NULL;
3224 char **zip_entries = zip_list(zip_filename);
3226 // check if zip file successfully opened
3227 if (zip_entries == NULL || zip_entries[0] == NULL)
3228 return TREE_TYPE_UNDEFINED;
3230 // first zip file entry is expected to be top level directory
3231 char *top_dir = zip_entries[0];
3233 // check if valid top level directory found in zip file
3234 if (!strSuffix(top_dir, "/"))
3235 return TREE_TYPE_UNDEFINED;
3237 // get filenames of valid configuration files in top level directory
3238 for (j = 0; j < NUM_BASE_TREE_TYPES; j++)
3239 top_dir_conf_filename[j] = getStringCat2(top_dir, conf_basename[j]);
3241 int tree_type = TREE_TYPE_UNDEFINED;
3244 while (zip_entries[e] != NULL)
3246 // check if every zip file entry is below top level directory
3247 if (!strPrefix(zip_entries[e], top_dir))
3248 return TREE_TYPE_UNDEFINED;
3250 // check if this zip file entry is a valid configuration filename
3251 for (j = 0; j < NUM_BASE_TREE_TYPES; j++)
3253 if (strEqual(zip_entries[e], top_dir_conf_filename[j]))
3255 // only exactly one valid configuration file allowed
3256 if (tree_type != TREE_TYPE_UNDEFINED)
3257 return TREE_TYPE_UNDEFINED;
3269 static boolean CheckZipFileForDirectory(char *zip_filename, char *directory,
3272 static char *top_dir_path = NULL;
3273 static char *top_dir_conf_filename = NULL;
3275 checked_free(top_dir_path);
3276 checked_free(top_dir_conf_filename);
3278 top_dir_path = NULL;
3279 top_dir_conf_filename = NULL;
3281 char *conf_basename = (tree_type == TREE_TYPE_LEVEL_DIR ? LEVELINFO_FILENAME :
3282 ARTWORKINFO_FILENAME(tree_type));
3284 // check if valid configuration filename determined
3285 if (conf_basename == NULL || strEqual(conf_basename, ""))
3288 char **zip_entries = zip_list(zip_filename);
3290 // check if zip file successfully opened
3291 if (zip_entries == NULL || zip_entries[0] == NULL)
3294 // first zip file entry is expected to be top level directory
3295 char *top_dir = zip_entries[0];
3297 // check if valid top level directory found in zip file
3298 if (!strSuffix(top_dir, "/"))
3301 // get path of extracted top level directory
3302 top_dir_path = getPath2(directory, top_dir);
3304 // remove trailing directory separator from top level directory path
3305 // (required to be able to check for file and directory in next step)
3306 top_dir_path[strlen(top_dir_path) - 1] = '\0';
3308 // check if zip file's top level directory already exists in target directory
3309 if (fileExists(top_dir_path)) // (checks for file and directory)
3312 // get filename of configuration file in top level directory
3313 top_dir_conf_filename = getStringCat2(top_dir, conf_basename);
3315 boolean found_top_dir_conf_filename = FALSE;
3318 while (zip_entries[i] != NULL)
3320 // check if every zip file entry is below top level directory
3321 if (!strPrefix(zip_entries[i], top_dir))
3324 // check if this zip file entry is the configuration filename
3325 if (strEqual(zip_entries[i], top_dir_conf_filename))
3326 found_top_dir_conf_filename = TRUE;
3331 // check if valid configuration filename was found in zip file
3332 if (!found_top_dir_conf_filename)
3338 char *ExtractZipFileIntoDirectory(char *zip_filename, char *directory,
3341 boolean zip_file_valid = CheckZipFileForDirectory(zip_filename, directory,
3344 if (!zip_file_valid)
3346 Warn("zip file '%s' rejected!", zip_filename);
3351 char **zip_entries = zip_extract(zip_filename, directory);
3353 if (zip_entries == NULL)
3355 Warn("zip file '%s' could not be extracted!", zip_filename);
3360 Info("zip file '%s' successfully extracted!", zip_filename);
3362 // first zip file entry contains top level directory
3363 char *top_dir = zip_entries[0];
3365 // remove trailing directory separator from top level directory
3366 top_dir[strlen(top_dir) - 1] = '\0';
3371 static void ProcessZipFilesInDirectory(char *directory, int tree_type)
3374 DirectoryEntry *dir_entry;
3376 if ((dir = openDirectory(directory)) == NULL)
3378 // display error if directory is main "options.graphics_directory" etc.
3379 if (tree_type == TREE_TYPE_LEVEL_DIR ||
3380 directory == OPTIONS_ARTWORK_DIRECTORY(tree_type))
3381 Warn("cannot read directory '%s'", directory);
3386 while ((dir_entry = readDirectory(dir)) != NULL) // loop all entries
3388 // skip non-zip files (and also directories with zip extension)
3389 if (!strSuffixLower(dir_entry->basename, ".zip") || dir_entry->is_directory)
3392 char *zip_filename = getPath2(directory, dir_entry->basename);
3393 char *zip_filename_extracted = getStringCat2(zip_filename, ".extracted");
3394 char *zip_filename_rejected = getStringCat2(zip_filename, ".rejected");
3396 // check if zip file hasn't already been extracted or rejected
3397 if (!fileExists(zip_filename_extracted) &&
3398 !fileExists(zip_filename_rejected))
3400 char *top_dir = ExtractZipFileIntoDirectory(zip_filename, directory,
3402 char *marker_filename = (top_dir != NULL ? zip_filename_extracted :
3403 zip_filename_rejected);
3406 // create empty file to mark zip file as extracted or rejected
3407 if ((marker_file = fopen(marker_filename, MODE_WRITE)))
3408 fclose(marker_file);
3411 free(zip_filename_extracted);
3412 free(zip_filename_rejected);
3416 closeDirectory(dir);
3419 // forward declaration for recursive call by "LoadLevelInfoFromLevelDir()"
3420 static void LoadLevelInfoFromLevelDir(TreeInfo **, TreeInfo *, char *);
3422 static boolean LoadLevelInfoFromLevelConf(TreeInfo **node_first,
3423 TreeInfo *node_parent,
3424 char *level_directory,
3425 char *directory_name)
3427 char *directory_path = getPath2(level_directory, directory_name);
3428 char *filename = getPath2(directory_path, LEVELINFO_FILENAME);
3429 SetupFileHash *setup_file_hash;
3430 LevelDirTree *leveldir_new = NULL;
3433 // unless debugging, silently ignore directories without "levelinfo.conf"
3434 if (!options.debug && !fileExists(filename))
3436 free(directory_path);
3442 setup_file_hash = loadSetupFileHash(filename);
3444 if (setup_file_hash == NULL)
3446 #if DEBUG_NO_CONFIG_FILE
3447 Debug("setup", "ignoring level directory '%s'", directory_path);
3450 free(directory_path);
3456 leveldir_new = newTreeInfo();
3459 setTreeInfoToDefaultsFromParent(leveldir_new, node_parent);
3461 setTreeInfoToDefaults(leveldir_new, TREE_TYPE_LEVEL_DIR);
3463 leveldir_new->subdir = getStringCopy(directory_name);
3465 // set all structure fields according to the token/value pairs
3466 ldi = *leveldir_new;
3467 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
3468 setSetupInfo(levelinfo_tokens, i,
3469 getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
3470 *leveldir_new = ldi;
3472 if (strEqual(leveldir_new->name, ANONYMOUS_NAME))
3473 setString(&leveldir_new->name, leveldir_new->subdir);
3475 if (leveldir_new->identifier == NULL)
3476 leveldir_new->identifier = getStringCopy(leveldir_new->subdir);
3478 if (leveldir_new->name_sorting == NULL)
3479 leveldir_new->name_sorting = getStringCopy(leveldir_new->name);
3481 if (node_parent == NULL) // top level group
3483 leveldir_new->basepath = getStringCopy(level_directory);
3484 leveldir_new->fullpath = getStringCopy(leveldir_new->subdir);
3486 else // sub level group
3488 leveldir_new->basepath = getStringCopy(node_parent->basepath);
3489 leveldir_new->fullpath = getPath2(node_parent->fullpath, directory_name);
3492 leveldir_new->last_level =
3493 leveldir_new->first_level + leveldir_new->levels - 1;
3495 leveldir_new->in_user_dir =
3496 (!strEqual(leveldir_new->basepath, options.level_directory));
3498 // adjust some settings if user's private level directory was detected
3499 if (leveldir_new->sort_priority == LEVELCLASS_UNDEFINED &&
3500 leveldir_new->in_user_dir &&
3501 (strEqual(leveldir_new->subdir, getLoginName()) ||
3502 strEqual(leveldir_new->name, getLoginName()) ||
3503 strEqual(leveldir_new->author, getRealName())))
3505 leveldir_new->sort_priority = LEVELCLASS_PRIVATE_START;
3506 leveldir_new->readonly = FALSE;
3509 leveldir_new->user_defined =
3510 (leveldir_new->in_user_dir && IS_LEVELCLASS_PRIVATE(leveldir_new));
3512 leveldir_new->color = LEVELCOLOR(leveldir_new);
3514 setString(&leveldir_new->class_desc, getLevelClassDescription(leveldir_new));
3516 leveldir_new->handicap_level = // set handicap to default value
3517 (leveldir_new->user_defined || !leveldir_new->handicap ?
3518 leveldir_new->last_level : leveldir_new->first_level);
3520 DrawInitText(leveldir_new->name, 150, FC_YELLOW);
3522 pushTreeInfo(node_first, leveldir_new);
3524 freeSetupFileHash(setup_file_hash);
3526 if (leveldir_new->level_group)
3528 // create node to link back to current level directory
3529 createParentTreeInfoNode(leveldir_new);
3531 // recursively step into sub-directory and look for more level series
3532 LoadLevelInfoFromLevelDir(&leveldir_new->node_group,
3533 leveldir_new, directory_path);
3536 free(directory_path);
3542 static void LoadLevelInfoFromLevelDir(TreeInfo **node_first,
3543 TreeInfo *node_parent,
3544 char *level_directory)
3546 // ---------- 1st stage: process any level set zip files ----------
3548 ProcessZipFilesInDirectory(level_directory, TREE_TYPE_LEVEL_DIR);
3550 // ---------- 2nd stage: check for level set directories ----------
3553 DirectoryEntry *dir_entry;
3554 boolean valid_entry_found = FALSE;
3556 if ((dir = openDirectory(level_directory)) == NULL)
3558 Warn("cannot read level directory '%s'", level_directory);
3563 while ((dir_entry = readDirectory(dir)) != NULL) // loop all entries
3565 char *directory_name = dir_entry->basename;
3566 char *directory_path = getPath2(level_directory, directory_name);
3568 // skip entries for current and parent directory
3569 if (strEqual(directory_name, ".") ||
3570 strEqual(directory_name, ".."))
3572 free(directory_path);
3577 // find out if directory entry is itself a directory
3578 if (!dir_entry->is_directory) // not a directory
3580 free(directory_path);
3585 free(directory_path);
3587 if (strEqual(directory_name, GRAPHICS_DIRECTORY) ||
3588 strEqual(directory_name, SOUNDS_DIRECTORY) ||
3589 strEqual(directory_name, MUSIC_DIRECTORY))
3592 valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
3597 closeDirectory(dir);
3599 // special case: top level directory may directly contain "levelinfo.conf"
3600 if (node_parent == NULL && !valid_entry_found)
3602 // check if this directory directly contains a file "levelinfo.conf"
3603 valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
3604 level_directory, ".");
3607 if (!valid_entry_found)
3608 Warn("cannot find any valid level series in directory '%s'",
3612 boolean AdjustGraphicsForEMC(void)
3614 boolean settings_changed = FALSE;
3616 settings_changed |= adjustTreeGraphicsForEMC(leveldir_first_all);
3617 settings_changed |= adjustTreeGraphicsForEMC(leveldir_first);
3619 return settings_changed;
3622 boolean AdjustSoundsForEMC(void)
3624 boolean settings_changed = FALSE;
3626 settings_changed |= adjustTreeSoundsForEMC(leveldir_first_all);
3627 settings_changed |= adjustTreeSoundsForEMC(leveldir_first);
3629 return settings_changed;
3632 void LoadLevelInfo(void)
3634 InitUserLevelDirectory(getLoginName());
3636 DrawInitText("Loading level series", 120, FC_GREEN);
3638 LoadLevelInfoFromLevelDir(&leveldir_first, NULL, options.level_directory);
3639 LoadLevelInfoFromLevelDir(&leveldir_first, NULL, getUserLevelDir(NULL));
3641 leveldir_first = createTopTreeInfoNode(leveldir_first);
3643 /* after loading all level set information, clone the level directory tree
3644 and remove all level sets without levels (these may still contain artwork
3645 to be offered in the setup menu as "custom artwork", and are therefore
3646 checked for existing artwork in the function "LoadLevelArtworkInfo()") */
3647 leveldir_first_all = leveldir_first;
3648 cloneTree(&leveldir_first, leveldir_first_all, TRUE);
3650 AdjustGraphicsForEMC();
3651 AdjustSoundsForEMC();
3653 // before sorting, the first entries will be from the user directory
3654 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
3656 if (leveldir_first == NULL)
3657 Fail("cannot find any valid level series in any directory");
3659 sortTreeInfo(&leveldir_first);
3661 #if ENABLE_UNUSED_CODE
3662 dumpTreeInfo(leveldir_first, 0);
3666 static boolean LoadArtworkInfoFromArtworkConf(TreeInfo **node_first,
3667 TreeInfo *node_parent,
3668 char *base_directory,
3669 char *directory_name, int type)
3671 char *directory_path = getPath2(base_directory, directory_name);
3672 char *filename = getPath2(directory_path, ARTWORKINFO_FILENAME(type));
3673 SetupFileHash *setup_file_hash = NULL;
3674 TreeInfo *artwork_new = NULL;
3677 if (fileExists(filename))
3678 setup_file_hash = loadSetupFileHash(filename);
3680 if (setup_file_hash == NULL) // no config file -- look for artwork files
3683 DirectoryEntry *dir_entry;
3684 boolean valid_file_found = FALSE;
3686 if ((dir = openDirectory(directory_path)) != NULL)
3688 while ((dir_entry = readDirectory(dir)) != NULL)
3690 if (FileIsArtworkType(dir_entry->filename, type))
3692 valid_file_found = TRUE;
3698 closeDirectory(dir);
3701 if (!valid_file_found)
3703 #if DEBUG_NO_CONFIG_FILE
3704 if (!strEqual(directory_name, "."))
3705 Debug("setup", "ignoring artwork directory '%s'", directory_path);
3708 free(directory_path);
3715 artwork_new = newTreeInfo();
3718 setTreeInfoToDefaultsFromParent(artwork_new, node_parent);
3720 setTreeInfoToDefaults(artwork_new, type);
3722 artwork_new->subdir = getStringCopy(directory_name);
3724 if (setup_file_hash) // (before defining ".color" and ".class_desc")
3726 // set all structure fields according to the token/value pairs
3728 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
3729 setSetupInfo(levelinfo_tokens, i,
3730 getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
3733 if (strEqual(artwork_new->name, ANONYMOUS_NAME))
3734 setString(&artwork_new->name, artwork_new->subdir);
3736 if (artwork_new->identifier == NULL)
3737 artwork_new->identifier = getStringCopy(artwork_new->subdir);
3739 if (artwork_new->name_sorting == NULL)
3740 artwork_new->name_sorting = getStringCopy(artwork_new->name);
3743 if (node_parent == NULL) // top level group
3745 artwork_new->basepath = getStringCopy(base_directory);
3746 artwork_new->fullpath = getStringCopy(artwork_new->subdir);
3748 else // sub level group
3750 artwork_new->basepath = getStringCopy(node_parent->basepath);
3751 artwork_new->fullpath = getPath2(node_parent->fullpath, directory_name);
3754 artwork_new->in_user_dir =
3755 (!strEqual(artwork_new->basepath, OPTIONS_ARTWORK_DIRECTORY(type)));
3757 // (may use ".sort_priority" from "setup_file_hash" above)
3758 artwork_new->color = ARTWORKCOLOR(artwork_new);
3760 setString(&artwork_new->class_desc, getLevelClassDescription(artwork_new));
3762 if (setup_file_hash == NULL) // (after determining ".user_defined")
3764 if (strEqual(artwork_new->subdir, "."))
3766 if (artwork_new->user_defined)
3768 setString(&artwork_new->identifier, "private");
3769 artwork_new->sort_priority = ARTWORKCLASS_PRIVATE;
3773 setString(&artwork_new->identifier, "classic");
3774 artwork_new->sort_priority = ARTWORKCLASS_CLASSICS;
3777 // set to new values after changing ".sort_priority"
3778 artwork_new->color = ARTWORKCOLOR(artwork_new);
3780 setString(&artwork_new->class_desc,
3781 getLevelClassDescription(artwork_new));
3785 setString(&artwork_new->identifier, artwork_new->subdir);
3788 setString(&artwork_new->name, artwork_new->identifier);
3789 setString(&artwork_new->name_sorting, artwork_new->name);
3792 pushTreeInfo(node_first, artwork_new);
3794 freeSetupFileHash(setup_file_hash);
3796 free(directory_path);
3802 static void LoadArtworkInfoFromArtworkDir(TreeInfo **node_first,
3803 TreeInfo *node_parent,
3804 char *base_directory, int type)
3806 // ---------- 1st stage: process any artwork set zip files ----------
3808 ProcessZipFilesInDirectory(base_directory, type);
3810 // ---------- 2nd stage: check for artwork set directories ----------
3813 DirectoryEntry *dir_entry;
3814 boolean valid_entry_found = FALSE;
3816 if ((dir = openDirectory(base_directory)) == NULL)
3818 // display error if directory is main "options.graphics_directory" etc.
3819 if (base_directory == OPTIONS_ARTWORK_DIRECTORY(type))
3820 Warn("cannot read directory '%s'", base_directory);
3825 while ((dir_entry = readDirectory(dir)) != NULL) // loop all entries
3827 char *directory_name = dir_entry->basename;
3828 char *directory_path = getPath2(base_directory, directory_name);
3830 // skip directory entries for current and parent directory
3831 if (strEqual(directory_name, ".") ||
3832 strEqual(directory_name, ".."))
3834 free(directory_path);
3839 // skip directory entries which are not a directory
3840 if (!dir_entry->is_directory) // not a directory
3842 free(directory_path);
3847 free(directory_path);
3849 // check if this directory contains artwork with or without config file
3850 valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
3852 directory_name, type);
3855 closeDirectory(dir);
3857 // check if this directory directly contains artwork itself
3858 valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
3859 base_directory, ".",
3861 if (!valid_entry_found)
3862 Warn("cannot find any valid artwork in directory '%s'", base_directory);
3865 static TreeInfo *getDummyArtworkInfo(int type)
3867 // this is only needed when there is completely no artwork available
3868 TreeInfo *artwork_new = newTreeInfo();
3870 setTreeInfoToDefaults(artwork_new, type);
3872 setString(&artwork_new->subdir, UNDEFINED_FILENAME);
3873 setString(&artwork_new->fullpath, UNDEFINED_FILENAME);
3874 setString(&artwork_new->basepath, UNDEFINED_FILENAME);
3876 setString(&artwork_new->identifier, UNDEFINED_FILENAME);
3877 setString(&artwork_new->name, UNDEFINED_FILENAME);
3878 setString(&artwork_new->name_sorting, UNDEFINED_FILENAME);
3883 void SetCurrentArtwork(int type)
3885 ArtworkDirTree **current_ptr = ARTWORK_CURRENT_PTR(artwork, type);
3886 ArtworkDirTree *first_node = ARTWORK_FIRST_NODE(artwork, type);
3887 char *setup_set = SETUP_ARTWORK_SET(setup, type);
3888 char *default_subdir = ARTWORK_DEFAULT_SUBDIR(type);
3890 // set current artwork to artwork configured in setup menu
3891 *current_ptr = getTreeInfoFromIdentifier(first_node, setup_set);
3893 // if not found, set current artwork to default artwork
3894 if (*current_ptr == NULL)
3895 *current_ptr = getTreeInfoFromIdentifier(first_node, default_subdir);
3897 // if not found, set current artwork to first artwork in tree
3898 if (*current_ptr == NULL)
3899 *current_ptr = getFirstValidTreeInfoEntry(first_node);
3902 void ChangeCurrentArtworkIfNeeded(int type)
3904 char *current_identifier = ARTWORK_CURRENT_IDENTIFIER(artwork, type);
3905 char *setup_set = SETUP_ARTWORK_SET(setup, type);
3907 if (!strEqual(current_identifier, setup_set))
3908 SetCurrentArtwork(type);
3911 void LoadArtworkInfo(void)
3913 LoadArtworkInfoCache();
3915 DrawInitText("Looking for custom artwork", 120, FC_GREEN);
3917 LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
3918 options.graphics_directory,
3919 TREE_TYPE_GRAPHICS_DIR);
3920 LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
3921 getUserGraphicsDir(),
3922 TREE_TYPE_GRAPHICS_DIR);
3924 LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
3925 options.sounds_directory,
3926 TREE_TYPE_SOUNDS_DIR);
3927 LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
3929 TREE_TYPE_SOUNDS_DIR);
3931 LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
3932 options.music_directory,
3933 TREE_TYPE_MUSIC_DIR);
3934 LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
3936 TREE_TYPE_MUSIC_DIR);
3938 if (artwork.gfx_first == NULL)
3939 artwork.gfx_first = getDummyArtworkInfo(TREE_TYPE_GRAPHICS_DIR);
3940 if (artwork.snd_first == NULL)
3941 artwork.snd_first = getDummyArtworkInfo(TREE_TYPE_SOUNDS_DIR);
3942 if (artwork.mus_first == NULL)
3943 artwork.mus_first = getDummyArtworkInfo(TREE_TYPE_MUSIC_DIR);
3945 // before sorting, the first entries will be from the user directory
3946 SetCurrentArtwork(ARTWORK_TYPE_GRAPHICS);
3947 SetCurrentArtwork(ARTWORK_TYPE_SOUNDS);
3948 SetCurrentArtwork(ARTWORK_TYPE_MUSIC);
3950 artwork.gfx_current_identifier = artwork.gfx_current->identifier;
3951 artwork.snd_current_identifier = artwork.snd_current->identifier;
3952 artwork.mus_current_identifier = artwork.mus_current->identifier;
3954 #if ENABLE_UNUSED_CODE
3955 Debug("setup:LoadArtworkInfo", "graphics set == %s",
3956 artwork.gfx_current_identifier);
3957 Debug("setup:LoadArtworkInfo", "sounds set == %s",
3958 artwork.snd_current_identifier);
3959 Debug("setup:LoadArtworkInfo", "music set == %s",
3960 artwork.mus_current_identifier);
3963 sortTreeInfo(&artwork.gfx_first);
3964 sortTreeInfo(&artwork.snd_first);
3965 sortTreeInfo(&artwork.mus_first);
3967 #if ENABLE_UNUSED_CODE
3968 dumpTreeInfo(artwork.gfx_first, 0);
3969 dumpTreeInfo(artwork.snd_first, 0);
3970 dumpTreeInfo(artwork.mus_first, 0);
3974 static void MoveArtworkInfoIntoSubTree(ArtworkDirTree **artwork_node)
3976 ArtworkDirTree *artwork_new = newTreeInfo();
3977 char *top_node_name = "standalone artwork";
3979 setTreeInfoToDefaults(artwork_new, (*artwork_node)->type);
3981 artwork_new->level_group = TRUE;
3983 setString(&artwork_new->identifier, top_node_name);
3984 setString(&artwork_new->name, top_node_name);
3985 setString(&artwork_new->name_sorting, top_node_name);
3987 // create node to link back to current custom artwork directory
3988 createParentTreeInfoNode(artwork_new);
3990 // move existing custom artwork tree into newly created sub-tree
3991 artwork_new->node_group->next = *artwork_node;
3993 // change custom artwork tree to contain only newly created node
3994 *artwork_node = artwork_new;
3997 static void LoadArtworkInfoFromLevelInfoExt(ArtworkDirTree **artwork_node,
3998 ArtworkDirTree *node_parent,
3999 LevelDirTree *level_node,
4000 boolean empty_level_set_mode)
4002 int type = (*artwork_node)->type;
4004 // recursively check all level directories for artwork sub-directories
4008 boolean empty_level_set = (level_node->levels == 0);
4010 // check all tree entries for artwork, but skip parent link entries
4011 if (!level_node->parent_link && empty_level_set == empty_level_set_mode)
4013 TreeInfo *artwork_new = getArtworkInfoCacheEntry(level_node, type);
4014 boolean cached = (artwork_new != NULL);
4018 pushTreeInfo(artwork_node, artwork_new);
4022 TreeInfo *topnode_last = *artwork_node;
4023 char *path = getPath2(getLevelDirFromTreeInfo(level_node),
4024 ARTWORK_DIRECTORY(type));
4026 LoadArtworkInfoFromArtworkDir(artwork_node, NULL, path, type);
4028 if (topnode_last != *artwork_node) // check for newly added node
4030 artwork_new = *artwork_node;
4032 setString(&artwork_new->identifier, level_node->subdir);
4033 setString(&artwork_new->name, level_node->name);
4034 setString(&artwork_new->name_sorting, level_node->name_sorting);
4036 artwork_new->sort_priority = level_node->sort_priority;
4037 artwork_new->color = LEVELCOLOR(artwork_new);
4039 update_artworkinfo_cache = TRUE;
4045 // insert artwork info (from old cache or filesystem) into new cache
4046 if (artwork_new != NULL)
4047 setArtworkInfoCacheEntry(artwork_new, level_node, type);
4050 DrawInitText(level_node->name, 150, FC_YELLOW);
4052 if (level_node->node_group != NULL)
4054 TreeInfo *artwork_new = newTreeInfo();
4057 setTreeInfoToDefaultsFromParent(artwork_new, node_parent);
4059 setTreeInfoToDefaults(artwork_new, type);
4061 artwork_new->level_group = TRUE;
4063 setString(&artwork_new->identifier, level_node->subdir);
4065 if (node_parent == NULL) // check for top tree node
4067 char *top_node_name = (empty_level_set_mode ?
4068 "artwork for certain level sets" :
4069 "artwork included in level sets");
4071 setString(&artwork_new->name, top_node_name);
4072 setString(&artwork_new->name_sorting, top_node_name);
4076 setString(&artwork_new->name, level_node->name);
4077 setString(&artwork_new->name_sorting, level_node->name_sorting);
4080 pushTreeInfo(artwork_node, artwork_new);
4082 // create node to link back to current custom artwork directory
4083 createParentTreeInfoNode(artwork_new);
4085 // recursively step into sub-directory and look for more custom artwork
4086 LoadArtworkInfoFromLevelInfoExt(&artwork_new->node_group, artwork_new,
4087 level_node->node_group,
4088 empty_level_set_mode);
4090 // if sub-tree has no custom artwork at all, remove it
4091 if (artwork_new->node_group->next == NULL)
4092 removeTreeInfo(artwork_node);
4095 level_node = level_node->next;
4099 static void LoadArtworkInfoFromLevelInfo(ArtworkDirTree **artwork_node)
4101 // move peviously loaded artwork tree into separate sub-tree
4102 MoveArtworkInfoIntoSubTree(artwork_node);
4104 // load artwork from level sets into separate sub-trees
4105 LoadArtworkInfoFromLevelInfoExt(artwork_node, NULL, leveldir_first_all, TRUE);
4106 LoadArtworkInfoFromLevelInfoExt(artwork_node, NULL, leveldir_first_all, FALSE);
4108 // add top tree node over all three separate sub-trees
4109 *artwork_node = createTopTreeInfoNode(*artwork_node);
4111 // set all parent links (back links) in complete artwork tree
4112 setTreeInfoParentNodes(*artwork_node, NULL);
4115 void LoadLevelArtworkInfo(void)
4117 print_timestamp_init("LoadLevelArtworkInfo");
4119 DrawInitText("Looking for custom level artwork", 120, FC_GREEN);
4121 print_timestamp_time("DrawTimeText");
4123 LoadArtworkInfoFromLevelInfo(&artwork.gfx_first);
4124 print_timestamp_time("LoadArtworkInfoFromLevelInfo (gfx)");
4125 LoadArtworkInfoFromLevelInfo(&artwork.snd_first);
4126 print_timestamp_time("LoadArtworkInfoFromLevelInfo (snd)");
4127 LoadArtworkInfoFromLevelInfo(&artwork.mus_first);
4128 print_timestamp_time("LoadArtworkInfoFromLevelInfo (mus)");
4130 SaveArtworkInfoCache();
4132 print_timestamp_time("SaveArtworkInfoCache");
4134 // needed for reloading level artwork not known at ealier stage
4135 ChangeCurrentArtworkIfNeeded(ARTWORK_TYPE_GRAPHICS);
4136 ChangeCurrentArtworkIfNeeded(ARTWORK_TYPE_SOUNDS);
4137 ChangeCurrentArtworkIfNeeded(ARTWORK_TYPE_MUSIC);
4139 print_timestamp_time("getTreeInfoFromIdentifier");
4141 sortTreeInfo(&artwork.gfx_first);
4142 sortTreeInfo(&artwork.snd_first);
4143 sortTreeInfo(&artwork.mus_first);
4145 print_timestamp_time("sortTreeInfo");
4147 #if ENABLE_UNUSED_CODE
4148 dumpTreeInfo(artwork.gfx_first, 0);
4149 dumpTreeInfo(artwork.snd_first, 0);
4150 dumpTreeInfo(artwork.mus_first, 0);
4153 print_timestamp_done("LoadLevelArtworkInfo");
4156 static boolean AddTreeSetToTreeInfoExt(TreeInfo *tree_node_old, char *tree_dir,
4157 char *tree_subdir_new, int type)
4159 if (tree_node_old == NULL)
4161 if (type == TREE_TYPE_LEVEL_DIR)
4163 // get level info tree node of personal user level set
4164 tree_node_old = getTreeInfoFromIdentifier(leveldir_first, getLoginName());
4166 // this may happen if "setup.internal.create_user_levelset" is FALSE
4167 // or if file "levelinfo.conf" is missing in personal user level set
4168 if (tree_node_old == NULL)
4169 tree_node_old = leveldir_first->node_group;
4173 // get artwork info tree node of first artwork set
4174 tree_node_old = ARTWORK_FIRST_NODE(artwork, type);
4178 if (tree_dir == NULL)
4179 tree_dir = TREE_USERDIR(type);
4181 if (tree_node_old == NULL ||
4183 tree_subdir_new == NULL) // should not happen
4186 int draw_deactivation_mask = GetDrawDeactivationMask();
4188 // override draw deactivation mask (temporarily disable drawing)
4189 SetDrawDeactivationMask(REDRAW_ALL);
4191 if (type == TREE_TYPE_LEVEL_DIR)
4193 // load new level set config and add it next to first user level set
4194 LoadLevelInfoFromLevelConf(&tree_node_old->next,
4195 tree_node_old->node_parent,
4196 tree_dir, tree_subdir_new);
4200 // load new artwork set config and add it next to first artwork set
4201 LoadArtworkInfoFromArtworkConf(&tree_node_old->next,
4202 tree_node_old->node_parent,
4203 tree_dir, tree_subdir_new, type);
4206 // set draw deactivation mask to previous value
4207 SetDrawDeactivationMask(draw_deactivation_mask);
4209 // get first node of level or artwork info tree
4210 TreeInfo **tree_node_first = TREE_FIRST_NODE_PTR(type);
4212 // get tree info node of newly added level or artwork set
4213 TreeInfo *tree_node_new = getTreeInfoFromIdentifier(*tree_node_first,
4216 if (tree_node_new == NULL) // should not happen
4219 // correct top link and parent node link of newly created tree node
4220 tree_node_new->node_top = tree_node_old->node_top;
4221 tree_node_new->node_parent = tree_node_old->node_parent;
4223 // sort tree info to adjust position of newly added tree set
4224 sortTreeInfo(tree_node_first);
4229 void AddTreeSetToTreeInfo(TreeInfo *tree_node, char *tree_dir,
4230 char *tree_subdir_new, int type)
4232 if (!AddTreeSetToTreeInfoExt(tree_node, tree_dir, tree_subdir_new, type))
4233 Fail("internal tree info structure corrupted -- aborting");
4236 void AddUserLevelSetToLevelInfo(char *level_subdir_new)
4238 AddTreeSetToTreeInfo(NULL, NULL, level_subdir_new, TREE_TYPE_LEVEL_DIR);
4241 char *getArtworkIdentifierForUserLevelSet(int type)
4243 char *classic_artwork_set = getClassicArtworkSet(type);
4245 // check for custom artwork configured in "levelinfo.conf"
4246 char *leveldir_artwork_set =
4247 *LEVELDIR_ARTWORK_SET_PTR(leveldir_current, type);
4248 boolean has_leveldir_artwork_set =
4249 (leveldir_artwork_set != NULL && !strEqual(leveldir_artwork_set,
4250 classic_artwork_set));
4252 // check for custom artwork in sub-directory "graphics" etc.
4253 TreeInfo *artwork_first_node = ARTWORK_FIRST_NODE(artwork, type);
4254 char *leveldir_identifier = leveldir_current->identifier;
4255 boolean has_artwork_subdir =
4256 (getTreeInfoFromIdentifier(artwork_first_node,
4257 leveldir_identifier) != NULL);
4259 return (has_leveldir_artwork_set ? leveldir_artwork_set :
4260 has_artwork_subdir ? leveldir_identifier :
4261 classic_artwork_set);
4264 TreeInfo *getArtworkTreeInfoForUserLevelSet(int type)
4266 char *artwork_set = getArtworkIdentifierForUserLevelSet(type);
4267 TreeInfo *artwork_first_node = ARTWORK_FIRST_NODE(artwork, type);
4268 TreeInfo *ti = getTreeInfoFromIdentifier(artwork_first_node, artwork_set);
4272 ti = getTreeInfoFromIdentifier(artwork_first_node,
4273 ARTWORK_DEFAULT_SUBDIR(type));
4275 Fail("cannot find default graphics -- should not happen");
4281 boolean checkIfCustomArtworkExistsForCurrentLevelSet(void)
4283 char *graphics_set =
4284 getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_GRAPHICS);
4286 getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_SOUNDS);
4288 getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_MUSIC);
4290 return (!strEqual(graphics_set, GFX_CLASSIC_SUBDIR) ||
4291 !strEqual(sounds_set, SND_CLASSIC_SUBDIR) ||
4292 !strEqual(music_set, MUS_CLASSIC_SUBDIR));
4295 boolean UpdateUserLevelSet(char *level_subdir, char *level_name,
4296 char *level_author, int num_levels)
4298 char *filename = getPath2(getUserLevelDir(level_subdir), LEVELINFO_FILENAME);
4299 char *filename_tmp = getStringCat2(filename, ".tmp");
4301 FILE *file_tmp = NULL;
4302 char line[MAX_LINE_LEN];
4303 boolean success = FALSE;
4304 LevelDirTree *leveldir = getTreeInfoFromIdentifier(leveldir_first,
4306 // update values in level directory tree
4308 if (level_name != NULL)
4309 setString(&leveldir->name, level_name);
4311 if (level_author != NULL)
4312 setString(&leveldir->author, level_author);
4314 if (num_levels != -1)
4315 leveldir->levels = num_levels;
4317 // update values that depend on other values
4319 setString(&leveldir->name_sorting, leveldir->name);
4321 leveldir->last_level = leveldir->first_level + leveldir->levels - 1;
4323 // sort order of level sets may have changed
4324 sortTreeInfo(&leveldir_first);
4326 if ((file = fopen(filename, MODE_READ)) &&
4327 (file_tmp = fopen(filename_tmp, MODE_WRITE)))
4329 while (fgets(line, MAX_LINE_LEN, file))
4331 if (strPrefix(line, "name:") && level_name != NULL)
4332 fprintf(file_tmp, "%-32s%s\n", "name:", level_name);
4333 else if (strPrefix(line, "author:") && level_author != NULL)
4334 fprintf(file_tmp, "%-32s%s\n", "author:", level_author);
4335 else if (strPrefix(line, "levels:") && num_levels != -1)
4336 fprintf(file_tmp, "%-32s%d\n", "levels:", num_levels);
4338 fputs(line, file_tmp);
4351 success = (rename(filename_tmp, filename) == 0);
4359 boolean CreateUserLevelSet(char *level_subdir, char *level_name,
4360 char *level_author, int num_levels,
4361 boolean use_artwork_set)
4363 LevelDirTree *level_info;
4368 // create user level sub-directory, if needed
4369 createDirectory(getUserLevelDir(level_subdir), "user level", PERMS_PRIVATE);
4371 filename = getPath2(getUserLevelDir(level_subdir), LEVELINFO_FILENAME);
4373 if (!(file = fopen(filename, MODE_WRITE)))
4375 Warn("cannot write level info file '%s'", filename);
4382 level_info = newTreeInfo();
4384 // always start with reliable default values
4385 setTreeInfoToDefaults(level_info, TREE_TYPE_LEVEL_DIR);
4387 setString(&level_info->name, level_name);
4388 setString(&level_info->author, level_author);
4389 level_info->levels = num_levels;
4390 level_info->first_level = 1;
4391 level_info->sort_priority = LEVELCLASS_PRIVATE_START;
4392 level_info->readonly = FALSE;
4394 if (use_artwork_set)
4396 level_info->graphics_set =
4397 getStringCopy(getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_GRAPHICS));
4398 level_info->sounds_set =
4399 getStringCopy(getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_SOUNDS));
4400 level_info->music_set =
4401 getStringCopy(getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_MUSIC));
4404 token_value_position = TOKEN_VALUE_POSITION_SHORT;
4406 fprintFileHeader(file, LEVELINFO_FILENAME);
4409 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
4411 if (i == LEVELINFO_TOKEN_NAME ||
4412 i == LEVELINFO_TOKEN_AUTHOR ||
4413 i == LEVELINFO_TOKEN_LEVELS ||
4414 i == LEVELINFO_TOKEN_FIRST_LEVEL ||
4415 i == LEVELINFO_TOKEN_SORT_PRIORITY ||
4416 i == LEVELINFO_TOKEN_READONLY ||
4417 (use_artwork_set && (i == LEVELINFO_TOKEN_GRAPHICS_SET ||
4418 i == LEVELINFO_TOKEN_SOUNDS_SET ||
4419 i == LEVELINFO_TOKEN_MUSIC_SET)))
4420 fprintf(file, "%s\n", getSetupLine(levelinfo_tokens, "", i));
4422 // just to make things nicer :)
4423 if (i == LEVELINFO_TOKEN_AUTHOR ||
4424 i == LEVELINFO_TOKEN_FIRST_LEVEL ||
4425 (use_artwork_set && i == LEVELINFO_TOKEN_READONLY))
4426 fprintf(file, "\n");
4429 token_value_position = TOKEN_VALUE_POSITION_DEFAULT;
4433 SetFilePermissions(filename, PERMS_PRIVATE);
4435 freeTreeInfo(level_info);
4441 static void SaveUserLevelInfo(void)
4443 CreateUserLevelSet(getLoginName(), getLoginName(), getRealName(), 100, FALSE);
4446 char *getSetupValue(int type, void *value)
4448 static char value_string[MAX_LINE_LEN];
4456 strcpy(value_string, (*(boolean *)value ? "true" : "false"));
4460 strcpy(value_string, (*(boolean *)value ? "on" : "off"));
4464 strcpy(value_string, (*(int *)value == AUTO ? "auto" :
4465 *(int *)value == FALSE ? "off" : "on"));
4469 strcpy(value_string, (*(boolean *)value ? "yes" : "no"));
4472 case TYPE_YES_NO_AUTO:
4473 strcpy(value_string, (*(int *)value == AUTO ? "auto" :
4474 *(int *)value == FALSE ? "no" : "yes"));
4478 strcpy(value_string, (*(boolean *)value ? "AGA" : "ECS"));
4482 strcpy(value_string, getKeyNameFromKey(*(Key *)value));
4486 strcpy(value_string, getX11KeyNameFromKey(*(Key *)value));
4490 sprintf(value_string, "%d", *(int *)value);
4494 if (*(char **)value == NULL)
4497 strcpy(value_string, *(char **)value);
4501 sprintf(value_string, "player_%d", *(int *)value + 1);
4505 value_string[0] = '\0';
4509 if (type & TYPE_GHOSTED)
4510 strcpy(value_string, "n/a");
4512 return value_string;
4515 char *getSetupLine(struct TokenInfo *token_info, char *prefix, int token_nr)
4519 static char token_string[MAX_LINE_LEN];
4520 int token_type = token_info[token_nr].type;
4521 void *setup_value = token_info[token_nr].value;
4522 char *token_text = token_info[token_nr].text;
4523 char *value_string = getSetupValue(token_type, setup_value);
4525 // build complete token string
4526 sprintf(token_string, "%s%s", prefix, token_text);
4528 // build setup entry line
4529 line = getFormattedSetupEntry(token_string, value_string);
4531 if (token_type == TYPE_KEY_X11)
4533 Key key = *(Key *)setup_value;
4534 char *keyname = getKeyNameFromKey(key);
4536 // add comment, if useful
4537 if (!strEqual(keyname, "(undefined)") &&
4538 !strEqual(keyname, "(unknown)"))
4540 // add at least one whitespace
4542 for (i = strlen(line); i < token_comment_position; i++)
4546 strcat(line, keyname);
4553 static void InitLastPlayedLevels_ParentNode(void)
4555 LevelDirTree **leveldir_top = &leveldir_first->node_group->next;
4556 LevelDirTree *leveldir_new = NULL;
4558 // check if parent node for last played levels already exists
4559 if (strEqual((*leveldir_top)->identifier, TOKEN_STR_LAST_LEVEL_SERIES))
4562 leveldir_new = newTreeInfo();
4564 setTreeInfoToDefaultsFromParent(leveldir_new, leveldir_first);
4566 leveldir_new->level_group = TRUE;
4568 setString(&leveldir_new->identifier, TOKEN_STR_LAST_LEVEL_SERIES);
4569 setString(&leveldir_new->name, "<< (last played level sets)");
4571 pushTreeInfo(leveldir_top, leveldir_new);
4573 // create node to link back to current level directory
4574 createParentTreeInfoNode(leveldir_new);
4577 void UpdateLastPlayedLevels_TreeInfo(void)
4579 char **last_level_series = setup.level_setup.last_level_series;
4580 boolean reset_leveldir_current = FALSE;
4581 LevelDirTree *leveldir_last;
4582 TreeInfo **node_new = NULL;
4585 if (last_level_series[0] == NULL)
4588 InitLastPlayedLevels_ParentNode();
4590 // check if current level set is from "last played" sub-tree to be rebuilt
4591 reset_leveldir_current = strEqual(leveldir_current->node_parent->identifier,
4592 TOKEN_STR_LAST_LEVEL_SERIES);
4594 leveldir_last = getTreeInfoFromIdentifierExt(leveldir_first,
4595 TOKEN_STR_LAST_LEVEL_SERIES,
4597 if (leveldir_last == NULL)
4600 node_new = &leveldir_last->node_group->next;
4602 freeTreeInfo(*node_new);
4604 for (i = 0; last_level_series[i] != NULL; i++)
4606 LevelDirTree *node_last = getTreeInfoFromIdentifier(leveldir_first,
4607 last_level_series[i]);
4609 *node_new = getTreeInfoCopy(node_last); // copy complete node
4611 (*node_new)->node_top = &leveldir_first; // correct top node link
4612 (*node_new)->node_parent = leveldir_last; // correct parent node link
4614 (*node_new)->node_group = NULL;
4615 (*node_new)->next = NULL;
4617 (*node_new)->cl_first = -1; // force setting tree cursor
4619 node_new = &((*node_new)->next);
4622 if (reset_leveldir_current)
4623 leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
4624 last_level_series[0]);
4627 static void UpdateLastPlayedLevels_List(void)
4629 char **last_level_series = setup.level_setup.last_level_series;
4630 int pos = MAX_LEVELDIR_HISTORY - 1;
4633 // search for potentially already existing entry in list of level sets
4634 for (i = 0; last_level_series[i] != NULL; i++)
4635 if (strEqual(last_level_series[i], leveldir_current->identifier))
4638 // move list of level sets one entry down (using potentially free entry)
4639 for (i = pos; i > 0; i--)
4640 setString(&last_level_series[i], last_level_series[i - 1]);
4642 // put last played level set at top position
4643 setString(&last_level_series[0], leveldir_current->identifier);
4646 void LoadLevelSetup_LastSeries(void)
4648 // --------------------------------------------------------------------------
4649 // ~/.<program>/levelsetup.conf
4650 // --------------------------------------------------------------------------
4652 char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
4653 SetupFileHash *level_setup_hash = NULL;
4657 // always start with reliable default values
4658 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
4660 // start with empty history of last played level sets
4661 setString(&setup.level_setup.last_level_series[0], NULL);
4663 if (!strEqual(DEFAULT_LEVELSET, UNDEFINED_LEVELSET))
4665 leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
4667 if (leveldir_current == NULL)
4668 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
4671 if ((level_setup_hash = loadSetupFileHash(filename)))
4673 char *last_level_series =
4674 getHashEntry(level_setup_hash, TOKEN_STR_LAST_LEVEL_SERIES);
4676 leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
4678 if (leveldir_current == NULL)
4679 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
4681 for (i = 0; i < MAX_LEVELDIR_HISTORY; i++)
4683 char token[strlen(TOKEN_STR_LAST_LEVEL_SERIES) + 10];
4684 LevelDirTree *leveldir_last;
4686 sprintf(token, "%s.%03d", TOKEN_STR_LAST_LEVEL_SERIES, i);
4688 last_level_series = getHashEntry(level_setup_hash, token);
4690 leveldir_last = getTreeInfoFromIdentifier(leveldir_first,
4692 if (leveldir_last != NULL)
4693 setString(&setup.level_setup.last_level_series[pos++],
4697 setString(&setup.level_setup.last_level_series[pos], NULL);
4699 freeSetupFileHash(level_setup_hash);
4703 Debug("setup", "using default setup values");
4709 static void SaveLevelSetup_LastSeries_Ext(boolean deactivate_last_level_series)
4711 // --------------------------------------------------------------------------
4712 // ~/.<program>/levelsetup.conf
4713 // --------------------------------------------------------------------------
4715 // check if the current level directory structure is available at this point
4716 if (leveldir_current == NULL)
4719 char **last_level_series = setup.level_setup.last_level_series;
4720 char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
4724 InitUserDataDirectory();
4726 UpdateLastPlayedLevels_List();
4728 if (!(file = fopen(filename, MODE_WRITE)))
4730 Warn("cannot write setup file '%s'", filename);
4737 fprintFileHeader(file, LEVELSETUP_FILENAME);
4739 if (deactivate_last_level_series)
4740 fprintf(file, "# %s\n# ", "the following level set may have caused a problem and was deactivated");
4742 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_LAST_LEVEL_SERIES,
4743 leveldir_current->identifier));
4745 for (i = 0; last_level_series[i] != NULL; i++)
4747 char token[strlen(TOKEN_STR_LAST_LEVEL_SERIES) + 10];
4749 sprintf(token, "%s.%03d", TOKEN_STR_LAST_LEVEL_SERIES, i);
4751 fprintf(file, "%s\n", getFormattedSetupEntry(token, last_level_series[i]));
4756 SetFilePermissions(filename, PERMS_PRIVATE);
4761 void SaveLevelSetup_LastSeries(void)
4763 SaveLevelSetup_LastSeries_Ext(FALSE);
4766 void SaveLevelSetup_LastSeries_Deactivate(void)
4768 SaveLevelSetup_LastSeries_Ext(TRUE);
4771 static void checkSeriesInfo(void)
4773 static char *level_directory = NULL;
4776 DirectoryEntry *dir_entry;
4779 checked_free(level_directory);
4781 // check for more levels besides the 'levels' field of 'levelinfo.conf'
4783 level_directory = getPath2((leveldir_current->in_user_dir ?
4784 getUserLevelDir(NULL) :
4785 options.level_directory),
4786 leveldir_current->fullpath);
4788 if ((dir = openDirectory(level_directory)) == NULL)
4790 Warn("cannot read level directory '%s'", level_directory);
4796 while ((dir_entry = readDirectory(dir)) != NULL) // loop all entries
4798 if (strlen(dir_entry->basename) > 4 &&
4799 dir_entry->basename[3] == '.' &&
4800 strEqual(&dir_entry->basename[4], LEVELFILE_EXTENSION))
4802 char levelnum_str[4];
4805 strncpy(levelnum_str, dir_entry->basename, 3);
4806 levelnum_str[3] = '\0';
4808 levelnum_value = atoi(levelnum_str);
4810 if (levelnum_value < leveldir_current->first_level)
4812 Warn("additional level %d found", levelnum_value);
4814 leveldir_current->first_level = levelnum_value;
4816 else if (levelnum_value > leveldir_current->last_level)
4818 Warn("additional level %d found", levelnum_value);
4820 leveldir_current->last_level = levelnum_value;
4826 closeDirectory(dir);
4829 void LoadLevelSetup_SeriesInfo(void)
4832 SetupFileHash *level_setup_hash = NULL;
4833 char *level_subdir = leveldir_current->subdir;
4836 // always start with reliable default values
4837 level_nr = leveldir_current->first_level;
4839 for (i = 0; i < MAX_LEVELS; i++)
4841 LevelStats_setPlayed(i, 0);
4842 LevelStats_setSolved(i, 0);
4847 // --------------------------------------------------------------------------
4848 // ~/.<program>/levelsetup/<level series>/levelsetup.conf
4849 // --------------------------------------------------------------------------
4851 level_subdir = leveldir_current->subdir;
4853 filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
4855 if ((level_setup_hash = loadSetupFileHash(filename)))
4859 // get last played level in this level set
4861 token_value = getHashEntry(level_setup_hash, TOKEN_STR_LAST_PLAYED_LEVEL);
4865 level_nr = atoi(token_value);
4867 if (level_nr < leveldir_current->first_level)
4868 level_nr = leveldir_current->first_level;
4869 if (level_nr > leveldir_current->last_level)
4870 level_nr = leveldir_current->last_level;
4873 // get handicap level in this level set
4875 token_value = getHashEntry(level_setup_hash, TOKEN_STR_HANDICAP_LEVEL);
4879 int level_nr = atoi(token_value);
4881 if (level_nr < leveldir_current->first_level)
4882 level_nr = leveldir_current->first_level;
4883 if (level_nr > leveldir_current->last_level + 1)
4884 level_nr = leveldir_current->last_level;
4886 if (leveldir_current->user_defined || !leveldir_current->handicap)
4887 level_nr = leveldir_current->last_level;
4889 leveldir_current->handicap_level = level_nr;
4892 // get number of played and solved levels in this level set
4894 BEGIN_HASH_ITERATION(level_setup_hash, itr)
4896 char *token = HASH_ITERATION_TOKEN(itr);
4897 char *value = HASH_ITERATION_VALUE(itr);
4899 if (strlen(token) == 3 &&
4900 token[0] >= '0' && token[0] <= '9' &&
4901 token[1] >= '0' && token[1] <= '9' &&
4902 token[2] >= '0' && token[2] <= '9')
4904 int level_nr = atoi(token);
4907 LevelStats_setPlayed(level_nr, atoi(value)); // read 1st column
4909 value = strchr(value, ' ');
4912 LevelStats_setSolved(level_nr, atoi(value)); // read 2nd column
4915 END_HASH_ITERATION(hash, itr)
4917 freeSetupFileHash(level_setup_hash);
4921 Debug("setup", "using default setup values");
4927 void SaveLevelSetup_SeriesInfo(void)
4930 char *level_subdir = leveldir_current->subdir;
4931 char *level_nr_str = int2str(level_nr, 0);
4932 char *handicap_level_str = int2str(leveldir_current->handicap_level, 0);
4936 // --------------------------------------------------------------------------
4937 // ~/.<program>/levelsetup/<level series>/levelsetup.conf
4938 // --------------------------------------------------------------------------
4940 InitLevelSetupDirectory(level_subdir);
4942 filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
4944 if (!(file = fopen(filename, MODE_WRITE)))
4946 Warn("cannot write setup file '%s'", filename);
4953 fprintFileHeader(file, LEVELSETUP_FILENAME);
4955 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_PLAYED_LEVEL,
4957 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_HANDICAP_LEVEL,
4958 handicap_level_str));
4960 for (i = leveldir_current->first_level; i <= leveldir_current->last_level;
4963 if (LevelStats_getPlayed(i) > 0 ||
4964 LevelStats_getSolved(i) > 0)
4969 sprintf(token, "%03d", i);
4970 sprintf(value, "%d %d", LevelStats_getPlayed(i), LevelStats_getSolved(i));
4972 fprintf(file, "%s\n", getFormattedSetupEntry(token, value));
4978 SetFilePermissions(filename, PERMS_PRIVATE);
4983 int LevelStats_getPlayed(int nr)
4985 return (nr >= 0 && nr < MAX_LEVELS ? level_stats[nr].played : 0);
4988 int LevelStats_getSolved(int nr)
4990 return (nr >= 0 && nr < MAX_LEVELS ? level_stats[nr].solved : 0);
4993 void LevelStats_setPlayed(int nr, int value)
4995 if (nr >= 0 && nr < MAX_LEVELS)
4996 level_stats[nr].played = value;
4999 void LevelStats_setSolved(int nr, int value)
5001 if (nr >= 0 && nr < MAX_LEVELS)
5002 level_stats[nr].solved = value;
5005 void LevelStats_incPlayed(int nr)
5007 if (nr >= 0 && nr < MAX_LEVELS)
5008 level_stats[nr].played++;
5011 void LevelStats_incSolved(int nr)
5013 if (nr >= 0 && nr < MAX_LEVELS)
5014 level_stats[nr].solved++;
5017 void LoadUserSetup(void)
5019 // --------------------------------------------------------------------------
5020 // ~/.<program>/usersetup.conf
5021 // --------------------------------------------------------------------------
5023 char *filename = getPath2(getMainUserGameDataDir(), USERSETUP_FILENAME);
5024 SetupFileHash *user_setup_hash = NULL;
5026 // always start with reliable default values
5029 if ((user_setup_hash = loadSetupFileHash(filename)))
5033 // get last selected user number
5034 token_value = getHashEntry(user_setup_hash, TOKEN_STR_LAST_USER);
5037 user.nr = MIN(MAX(0, atoi(token_value)), MAX_PLAYER_NAMES - 1);
5039 freeSetupFileHash(user_setup_hash);
5043 Debug("setup", "using default setup values");
5049 void SaveUserSetup(void)
5051 // --------------------------------------------------------------------------
5052 // ~/.<program>/usersetup.conf
5053 // --------------------------------------------------------------------------
5055 char *filename = getPath2(getMainUserGameDataDir(), USERSETUP_FILENAME);
5058 InitMainUserDataDirectory();
5060 if (!(file = fopen(filename, MODE_WRITE)))
5062 Warn("cannot write setup file '%s'", filename);
5069 fprintFileHeader(file, USERSETUP_FILENAME);
5071 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_USER,
5075 SetFilePermissions(filename, PERMS_PRIVATE);