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] =
46 #define TOKEN_VALUE_POSITION_SHORT 32
47 #define TOKEN_VALUE_POSITION_DEFAULT 40
48 #define TOKEN_COMMENT_POSITION_DEFAULT 60
50 #define MAX_COOKIE_LEN 256
52 #define TREE_NODE_TYPE_DEFAULT 0
53 #define TREE_NODE_TYPE_PARENT 1
54 #define TREE_NODE_TYPE_GROUP 2
55 #define TREE_NODE_TYPE_COPY 3
57 #define TREE_NODE_TYPE(ti) (ti->node_group ? TREE_NODE_TYPE_GROUP : \
58 ti->parent_link ? TREE_NODE_TYPE_PARENT : \
59 ti->is_copy ? TREE_NODE_TYPE_COPY : \
60 TREE_NODE_TYPE_DEFAULT)
63 static void setTreeInfoToDefaults(TreeInfo *, int);
64 static TreeInfo *getTreeInfoCopy(TreeInfo *ti);
65 static int compareTreeInfoEntries(const void *, const void *);
67 static int token_value_position = TOKEN_VALUE_POSITION_DEFAULT;
68 static int token_comment_position = TOKEN_COMMENT_POSITION_DEFAULT;
70 static SetupFileHash *artworkinfo_cache_old = NULL;
71 static SetupFileHash *artworkinfo_cache_new = NULL;
72 static SetupFileHash *optional_tokens_hash = NULL;
73 static boolean use_artworkinfo_cache = TRUE;
74 static boolean update_artworkinfo_cache = FALSE;
77 // ----------------------------------------------------------------------------
79 // ----------------------------------------------------------------------------
81 static char *getLevelClassDescription(TreeInfo *ti)
83 int position = ti->sort_priority / 100;
85 if (position >= 0 && position < NUM_LEVELCLASS_DESC)
86 return levelclass_desc[position];
88 return "Unknown Level Class";
91 static char *getCacheDir(void)
93 static char *cache_dir = NULL;
95 if (cache_dir == NULL)
96 cache_dir = getPath2(getMainUserGameDataDir(), CACHE_DIRECTORY);
101 static char *getScoreDir(char *level_subdir)
103 static char *score_dir = NULL;
104 static char *score_level_dir = NULL;
105 char *score_subdir = SCORES_DIRECTORY;
107 if (score_dir == NULL)
109 if (program.global_scores)
110 score_dir = getPath2(getCommonDataDir(), score_subdir);
112 score_dir = getPath2(getMainUserGameDataDir(), score_subdir);
115 if (level_subdir != NULL)
117 checked_free(score_level_dir);
119 score_level_dir = getPath2(score_dir, level_subdir);
121 return score_level_dir;
127 static char *getScoreCacheDir(char *level_subdir)
129 static char *score_dir = NULL;
130 static char *score_level_dir = NULL;
131 char *score_subdir = SCORES_DIRECTORY;
133 if (score_dir == NULL)
134 score_dir = getPath2(getCacheDir(), score_subdir);
136 if (level_subdir != NULL)
138 checked_free(score_level_dir);
140 score_level_dir = getPath2(score_dir, level_subdir);
142 return score_level_dir;
148 static char *getScoreTapeDir(char *level_subdir, int nr)
150 static char *score_tape_dir = NULL;
151 char tape_subdir[MAX_FILENAME_LEN];
153 checked_free(score_tape_dir);
155 sprintf(tape_subdir, "%03d", nr);
156 score_tape_dir = getPath2(getScoreDir(level_subdir), tape_subdir);
158 return score_tape_dir;
161 static char *getUserSubdir(int nr)
163 static char user_subdir[16] = { 0 };
165 sprintf(user_subdir, "%03d", nr);
170 static char *getUserDir(int nr)
172 static char *user_dir = NULL;
173 char *main_data_dir = getMainUserGameDataDir();
174 char *users_subdir = USERS_DIRECTORY;
175 char *user_subdir = getUserSubdir(nr);
177 checked_free(user_dir);
180 user_dir = getPath3(main_data_dir, users_subdir, user_subdir);
182 user_dir = getPath2(main_data_dir, users_subdir);
187 static char *getLevelSetupDir(char *level_subdir)
189 static char *levelsetup_dir = NULL;
190 char *data_dir = getUserGameDataDir();
191 char *levelsetup_subdir = LEVELSETUP_DIRECTORY;
193 checked_free(levelsetup_dir);
195 if (level_subdir != NULL)
196 levelsetup_dir = getPath3(data_dir, levelsetup_subdir, level_subdir);
198 levelsetup_dir = getPath2(data_dir, levelsetup_subdir);
200 return levelsetup_dir;
203 static char *getNetworkDir(void)
205 static char *network_dir = NULL;
207 if (network_dir == NULL)
208 network_dir = getPath2(getMainUserGameDataDir(), NETWORK_DIRECTORY);
213 char *getLevelDirFromTreeInfo(TreeInfo *node)
215 static char *level_dir = NULL;
218 return options.level_directory;
220 checked_free(level_dir);
222 level_dir = getPath2((node->in_user_dir ? getUserLevelDir(NULL) :
223 options.level_directory), node->fullpath);
228 char *getUserLevelDir(char *level_subdir)
230 static char *userlevel_dir = NULL;
231 char *data_dir = getMainUserGameDataDir();
232 char *userlevel_subdir = LEVELS_DIRECTORY;
234 checked_free(userlevel_dir);
236 if (level_subdir != NULL)
237 userlevel_dir = getPath3(data_dir, userlevel_subdir, level_subdir);
239 userlevel_dir = getPath2(data_dir, userlevel_subdir);
241 return userlevel_dir;
244 char *getNetworkLevelDir(char *level_subdir)
246 static char *network_level_dir = NULL;
247 char *data_dir = getNetworkDir();
248 char *networklevel_subdir = LEVELS_DIRECTORY;
250 checked_free(network_level_dir);
252 if (level_subdir != NULL)
253 network_level_dir = getPath3(data_dir, networklevel_subdir, level_subdir);
255 network_level_dir = getPath2(data_dir, networklevel_subdir);
257 return network_level_dir;
260 char *getCurrentLevelDir(void)
262 return getLevelDirFromTreeInfo(leveldir_current);
265 char *getNewUserLevelSubdir(void)
267 static char *new_level_subdir = NULL;
268 char *subdir_prefix = getLoginName();
269 char subdir_suffix[10];
270 int max_suffix_number = 1000;
273 while (++i < max_suffix_number)
275 sprintf(subdir_suffix, "_%d", i);
277 checked_free(new_level_subdir);
278 new_level_subdir = getStringCat2(subdir_prefix, subdir_suffix);
280 if (!directoryExists(getUserLevelDir(new_level_subdir)))
284 return new_level_subdir;
287 static char *getTapeDir(char *level_subdir)
289 static char *tape_dir = NULL;
290 char *data_dir = getUserGameDataDir();
291 char *tape_subdir = TAPES_DIRECTORY;
293 checked_free(tape_dir);
295 if (level_subdir != NULL)
296 tape_dir = getPath3(data_dir, tape_subdir, level_subdir);
298 tape_dir = getPath2(data_dir, tape_subdir);
303 static char *getSolutionTapeDir(void)
305 static char *tape_dir = NULL;
306 char *data_dir = getCurrentLevelDir();
307 char *tape_subdir = TAPES_DIRECTORY;
309 checked_free(tape_dir);
311 tape_dir = getPath2(data_dir, tape_subdir);
316 static char *getDefaultGraphicsDir(char *graphics_subdir)
318 static char *graphics_dir = NULL;
320 if (graphics_subdir == NULL)
321 return options.graphics_directory;
323 checked_free(graphics_dir);
325 graphics_dir = getPath2(options.graphics_directory, graphics_subdir);
330 static char *getDefaultSoundsDir(char *sounds_subdir)
332 static char *sounds_dir = NULL;
334 if (sounds_subdir == NULL)
335 return options.sounds_directory;
337 checked_free(sounds_dir);
339 sounds_dir = getPath2(options.sounds_directory, sounds_subdir);
344 static char *getDefaultMusicDir(char *music_subdir)
346 static char *music_dir = NULL;
348 if (music_subdir == NULL)
349 return options.music_directory;
351 checked_free(music_dir);
353 music_dir = getPath2(options.music_directory, music_subdir);
358 static char *getClassicArtworkSet(int type)
360 return (type == TREE_TYPE_GRAPHICS_DIR ? GFX_CLASSIC_SUBDIR :
361 type == TREE_TYPE_SOUNDS_DIR ? SND_CLASSIC_SUBDIR :
362 type == TREE_TYPE_MUSIC_DIR ? MUS_CLASSIC_SUBDIR : "");
365 static char *getClassicArtworkDir(int type)
367 return (type == TREE_TYPE_GRAPHICS_DIR ?
368 getDefaultGraphicsDir(GFX_CLASSIC_SUBDIR) :
369 type == TREE_TYPE_SOUNDS_DIR ?
370 getDefaultSoundsDir(SND_CLASSIC_SUBDIR) :
371 type == TREE_TYPE_MUSIC_DIR ?
372 getDefaultMusicDir(MUS_CLASSIC_SUBDIR) : "");
375 char *getUserGraphicsDir(void)
377 static char *usergraphics_dir = NULL;
379 if (usergraphics_dir == NULL)
380 usergraphics_dir = getPath2(getMainUserGameDataDir(), GRAPHICS_DIRECTORY);
382 return usergraphics_dir;
385 char *getUserSoundsDir(void)
387 static char *usersounds_dir = NULL;
389 if (usersounds_dir == NULL)
390 usersounds_dir = getPath2(getMainUserGameDataDir(), SOUNDS_DIRECTORY);
392 return usersounds_dir;
395 char *getUserMusicDir(void)
397 static char *usermusic_dir = NULL;
399 if (usermusic_dir == NULL)
400 usermusic_dir = getPath2(getMainUserGameDataDir(), MUSIC_DIRECTORY);
402 return usermusic_dir;
405 static char *getSetupArtworkDir(TreeInfo *ti)
407 static char *artwork_dir = NULL;
412 checked_free(artwork_dir);
414 artwork_dir = getPath2(ti->basepath, ti->fullpath);
419 char *setLevelArtworkDir(TreeInfo *ti)
421 char **artwork_path_ptr, **artwork_set_ptr;
422 TreeInfo *level_artwork;
424 if (ti == NULL || leveldir_current == NULL)
427 artwork_path_ptr = LEVELDIR_ARTWORK_PATH_PTR(leveldir_current, ti->type);
428 artwork_set_ptr = LEVELDIR_ARTWORK_SET_PTR( leveldir_current, ti->type);
430 checked_free(*artwork_path_ptr);
432 if ((level_artwork = getTreeInfoFromIdentifier(ti, *artwork_set_ptr)))
434 *artwork_path_ptr = getStringCopy(getSetupArtworkDir(level_artwork));
439 No (or non-existing) artwork configured in "levelinfo.conf". This would
440 normally result in using the artwork configured in the setup menu. But
441 if an artwork subdirectory exists (which might contain custom artwork
442 or an artwork configuration file), this level artwork must be treated
443 as relative to the default "classic" artwork, not to the artwork that
444 is currently configured in the setup menu.
446 Update: For "special" versions of R'n'D (like "R'n'D jue"), do not use
447 the "default" artwork (which would be "jue0" for "R'n'D jue"), but use
448 the real "classic" artwork from the original R'n'D (like "gfx_classic").
451 char *dir = getPath2(getCurrentLevelDir(), ARTWORK_DIRECTORY(ti->type));
453 checked_free(*artwork_set_ptr);
455 if (directoryExists(dir))
457 *artwork_path_ptr = getStringCopy(getClassicArtworkDir(ti->type));
458 *artwork_set_ptr = getStringCopy(getClassicArtworkSet(ti->type));
462 *artwork_path_ptr = getStringCopy(UNDEFINED_FILENAME);
463 *artwork_set_ptr = NULL;
469 return *artwork_set_ptr;
472 static char *getLevelArtworkSet(int type)
474 if (leveldir_current == NULL)
477 return LEVELDIR_ARTWORK_SET(leveldir_current, type);
480 static char *getLevelArtworkDir(int type)
482 if (leveldir_current == NULL)
483 return UNDEFINED_FILENAME;
485 return LEVELDIR_ARTWORK_PATH(leveldir_current, type);
488 char *getProgramMainDataPath(char *command_filename, char *base_path)
490 // check if the program's main data base directory is configured
491 if (!strEqual(base_path, "."))
492 return getStringCopy(base_path);
494 /* if the program is configured to start from current directory (default),
495 determine program package directory from program binary (some versions
496 of KDE/Konqueror and Mac OS X (especially "Mavericks") apparently do not
497 set the current working directory to the program package directory) */
498 char *main_data_path = getBasePath(command_filename);
500 #if defined(PLATFORM_MACOSX)
501 if (strSuffix(main_data_path, MAC_APP_BINARY_SUBDIR))
503 char *main_data_path_old = main_data_path;
505 // cut relative path to Mac OS X application binary directory from path
506 main_data_path[strlen(main_data_path) -
507 strlen(MAC_APP_BINARY_SUBDIR)] = '\0';
509 // cut trailing path separator from path (but not if path is root directory)
510 if (strSuffix(main_data_path, "/") && !strEqual(main_data_path, "/"))
511 main_data_path[strlen(main_data_path) - 1] = '\0';
513 // replace empty path with current directory
514 if (strEqual(main_data_path, ""))
515 main_data_path = ".";
517 // add relative path to Mac OS X application resources directory to path
518 main_data_path = getPath2(main_data_path, MAC_APP_FILES_SUBDIR);
520 free(main_data_path_old);
524 return main_data_path;
527 char *getProgramConfigFilename(char *command_filename)
529 static char *config_filename_1 = NULL;
530 static char *config_filename_2 = NULL;
531 static char *config_filename_3 = NULL;
532 static boolean initialized = FALSE;
536 char *command_filename_1 = getStringCopy(command_filename);
538 // strip trailing executable suffix from command filename
539 if (strSuffix(command_filename_1, ".exe"))
540 command_filename_1[strlen(command_filename_1) - 4] = '\0';
542 char *ro_base_path = getProgramMainDataPath(command_filename, RO_BASE_PATH);
543 char *conf_directory = getPath2(ro_base_path, CONF_DIRECTORY);
545 char *command_basepath = getBasePath(command_filename);
546 char *command_basename = getBaseNameNoSuffix(command_filename);
547 char *command_filename_2 = getPath2(command_basepath, command_basename);
549 config_filename_1 = getStringCat2(command_filename_1, ".conf");
550 config_filename_2 = getStringCat2(command_filename_2, ".conf");
551 config_filename_3 = getPath2(conf_directory, SETUP_FILENAME);
553 checked_free(ro_base_path);
554 checked_free(conf_directory);
556 checked_free(command_basepath);
557 checked_free(command_basename);
559 checked_free(command_filename_1);
560 checked_free(command_filename_2);
565 // 1st try: look for config file that exactly matches the binary filename
566 if (fileExists(config_filename_1))
567 return config_filename_1;
569 // 2nd try: look for config file that matches binary filename without suffix
570 if (fileExists(config_filename_2))
571 return config_filename_2;
573 // 3rd try: return setup config filename in global program config directory
574 return config_filename_3;
577 char *getTapeFilename(int nr)
579 static char *filename = NULL;
580 char basename[MAX_FILENAME_LEN];
582 checked_free(filename);
584 sprintf(basename, "%03d.%s", nr, TAPEFILE_EXTENSION);
585 filename = getPath2(getTapeDir(leveldir_current->subdir), basename);
590 char *getSolutionTapeFilename(int nr)
592 static char *filename = NULL;
593 char basename[MAX_FILENAME_LEN];
595 checked_free(filename);
597 sprintf(basename, "%03d.%s", nr, TAPEFILE_EXTENSION);
598 filename = getPath2(getSolutionTapeDir(), basename);
600 if (!fileExists(filename))
602 static char *filename_sln = NULL;
604 checked_free(filename_sln);
606 sprintf(basename, "%03d.sln", nr);
607 filename_sln = getPath2(getSolutionTapeDir(), basename);
609 if (fileExists(filename_sln))
616 char *getScoreFilename(int nr)
618 static char *filename = NULL;
619 char basename[MAX_FILENAME_LEN];
621 checked_free(filename);
623 sprintf(basename, "%03d.%s", nr, SCOREFILE_EXTENSION);
625 // used instead of "leveldir_current->subdir" (for network games)
626 filename = getPath2(getScoreDir(levelset.identifier), basename);
631 char *getScoreCacheFilename(int nr)
633 static char *filename = NULL;
634 char basename[MAX_FILENAME_LEN];
636 checked_free(filename);
638 sprintf(basename, "%03d.%s", nr, SCOREFILE_EXTENSION);
640 // used instead of "leveldir_current->subdir" (for network games)
641 filename = getPath2(getScoreCacheDir(levelset.identifier), basename);
646 char *getScoreTapeBasename(char *name)
648 static char basename[MAX_FILENAME_LEN];
649 char basename_raw[MAX_FILENAME_LEN];
652 sprintf(timestamp, "%s", getCurrentTimestamp());
653 sprintf(basename_raw, "%s-%s", timestamp, name);
654 sprintf(basename, "%s-%08x", timestamp, get_hash_from_key(basename_raw));
659 char *getScoreTapeFilename(char *basename_no_ext, int nr)
661 static char *filename = NULL;
662 char basename[MAX_FILENAME_LEN];
664 checked_free(filename);
666 sprintf(basename, "%s.%s", basename_no_ext, TAPEFILE_EXTENSION);
668 // used instead of "leveldir_current->subdir" (for network games)
669 filename = getPath2(getScoreTapeDir(levelset.identifier, nr), basename);
674 char *getSetupFilename(void)
676 static char *filename = NULL;
678 checked_free(filename);
680 filename = getPath2(getSetupDir(), SETUP_FILENAME);
685 char *getDefaultSetupFilename(void)
687 return program.config_filename;
690 char *getEditorSetupFilename(void)
692 static char *filename = NULL;
694 checked_free(filename);
695 filename = getPath2(getCurrentLevelDir(), EDITORSETUP_FILENAME);
697 if (fileExists(filename))
700 checked_free(filename);
701 filename = getPath2(getSetupDir(), EDITORSETUP_FILENAME);
706 char *getHelpAnimFilename(void)
708 static char *filename = NULL;
710 checked_free(filename);
712 filename = getPath2(getCurrentLevelDir(), HELPANIM_FILENAME);
717 char *getHelpTextFilename(void)
719 static char *filename = NULL;
721 checked_free(filename);
723 filename = getPath2(getCurrentLevelDir(), HELPTEXT_FILENAME);
728 char *getLevelSetInfoFilename(void)
730 static char *filename = NULL;
745 for (i = 0; basenames[i] != NULL; i++)
747 checked_free(filename);
748 filename = getPath2(getCurrentLevelDir(), basenames[i]);
750 if (fileExists(filename))
757 static char *getLevelSetTitleMessageBasename(int nr, boolean initial)
759 static char basename[32];
761 sprintf(basename, "%s_%d.txt",
762 (initial ? "titlemessage_initial" : "titlemessage"), nr + 1);
767 char *getLevelSetTitleMessageFilename(int nr, boolean initial)
769 static char *filename = NULL;
771 boolean skip_setup_artwork = FALSE;
773 checked_free(filename);
775 basename = getLevelSetTitleMessageBasename(nr, initial);
777 if (!gfx.override_level_graphics)
779 // 1st try: look for special artwork in current level series directory
780 filename = getPath3(getCurrentLevelDir(), GRAPHICS_DIRECTORY, basename);
781 if (fileExists(filename))
786 // 2nd try: look for message file in current level set directory
787 filename = getPath2(getCurrentLevelDir(), basename);
788 if (fileExists(filename))
793 // check if there is special artwork configured in level series config
794 if (getLevelArtworkSet(ARTWORK_TYPE_GRAPHICS) != NULL)
796 // 3rd try: look for special artwork configured in level series config
797 filename = getPath2(getLevelArtworkDir(ARTWORK_TYPE_GRAPHICS), basename);
798 if (fileExists(filename))
803 // take missing artwork configured in level set config from default
804 skip_setup_artwork = TRUE;
808 if (!skip_setup_artwork)
810 // 4th try: look for special artwork in configured artwork directory
811 filename = getPath2(getSetupArtworkDir(artwork.gfx_current), basename);
812 if (fileExists(filename))
818 // 5th try: look for default artwork in new default artwork directory
819 filename = getPath2(getDefaultGraphicsDir(GFX_DEFAULT_SUBDIR), basename);
820 if (fileExists(filename))
825 // 6th try: look for default artwork in old default artwork directory
826 filename = getPath2(options.graphics_directory, basename);
827 if (fileExists(filename))
830 return NULL; // cannot find specified artwork file anywhere
833 static char *getCorrectedArtworkBasename(char *basename)
838 char *getCustomImageFilename(char *basename)
840 static char *filename = NULL;
841 boolean skip_setup_artwork = FALSE;
843 checked_free(filename);
845 basename = getCorrectedArtworkBasename(basename);
847 if (!gfx.override_level_graphics)
849 // 1st try: look for special artwork in current level series directory
850 filename = getImg3(getCurrentLevelDir(), GRAPHICS_DIRECTORY, basename);
851 if (fileExists(filename))
856 // check if there is special artwork configured in level series config
857 if (getLevelArtworkSet(ARTWORK_TYPE_GRAPHICS) != NULL)
859 // 2nd try: look for special artwork configured in level series config
860 filename = getImg2(getLevelArtworkDir(ARTWORK_TYPE_GRAPHICS), basename);
861 if (fileExists(filename))
866 // take missing artwork configured in level set config from default
867 skip_setup_artwork = TRUE;
871 if (!skip_setup_artwork)
873 // 3rd try: look for special artwork in configured artwork directory
874 filename = getImg2(getSetupArtworkDir(artwork.gfx_current), basename);
875 if (fileExists(filename))
881 // 4th try: look for default artwork in new default artwork directory
882 filename = getImg2(getDefaultGraphicsDir(GFX_DEFAULT_SUBDIR), basename);
883 if (fileExists(filename))
888 // 5th try: look for default artwork in old default artwork directory
889 filename = getImg2(options.graphics_directory, basename);
890 if (fileExists(filename))
893 if (!strEqual(GFX_FALLBACK_FILENAME, UNDEFINED_FILENAME))
897 Warn("cannot find artwork file '%s' (using fallback)", basename);
899 // 6th try: look for fallback artwork in old default artwork directory
900 // (needed to prevent errors when trying to access unused artwork files)
901 filename = getImg2(options.graphics_directory, GFX_FALLBACK_FILENAME);
902 if (fileExists(filename))
906 return NULL; // cannot find specified artwork file anywhere
909 char *getCustomSoundFilename(char *basename)
911 static char *filename = NULL;
912 boolean skip_setup_artwork = FALSE;
914 checked_free(filename);
916 basename = getCorrectedArtworkBasename(basename);
918 if (!gfx.override_level_sounds)
920 // 1st try: look for special artwork in current level series directory
921 filename = getPath3(getCurrentLevelDir(), SOUNDS_DIRECTORY, basename);
922 if (fileExists(filename))
927 // check if there is special artwork configured in level series config
928 if (getLevelArtworkSet(ARTWORK_TYPE_SOUNDS) != NULL)
930 // 2nd try: look for special artwork configured in level series config
931 filename = getPath2(getLevelArtworkDir(TREE_TYPE_SOUNDS_DIR), basename);
932 if (fileExists(filename))
937 // take missing artwork configured in level set config from default
938 skip_setup_artwork = TRUE;
942 if (!skip_setup_artwork)
944 // 3rd try: look for special artwork in configured artwork directory
945 filename = getPath2(getSetupArtworkDir(artwork.snd_current), basename);
946 if (fileExists(filename))
952 // 4th try: look for default artwork in new default artwork directory
953 filename = getPath2(getDefaultSoundsDir(SND_DEFAULT_SUBDIR), basename);
954 if (fileExists(filename))
959 // 5th try: look for default artwork in old default artwork directory
960 filename = getPath2(options.sounds_directory, basename);
961 if (fileExists(filename))
964 if (!strEqual(SND_FALLBACK_FILENAME, UNDEFINED_FILENAME))
968 Warn("cannot find artwork file '%s' (using fallback)", basename);
970 // 6th try: look for fallback artwork in old default artwork directory
971 // (needed to prevent errors when trying to access unused artwork files)
972 filename = getPath2(options.sounds_directory, SND_FALLBACK_FILENAME);
973 if (fileExists(filename))
977 return NULL; // cannot find specified artwork file anywhere
980 char *getCustomMusicFilename(char *basename)
982 static char *filename = NULL;
983 boolean skip_setup_artwork = FALSE;
985 checked_free(filename);
987 basename = getCorrectedArtworkBasename(basename);
989 if (!gfx.override_level_music)
991 // 1st try: look for special artwork in current level series directory
992 filename = getPath3(getCurrentLevelDir(), MUSIC_DIRECTORY, basename);
993 if (fileExists(filename))
998 // check if there is special artwork configured in level series config
999 if (getLevelArtworkSet(ARTWORK_TYPE_MUSIC) != NULL)
1001 // 2nd try: look for special artwork configured in level series config
1002 filename = getPath2(getLevelArtworkDir(TREE_TYPE_MUSIC_DIR), basename);
1003 if (fileExists(filename))
1008 // take missing artwork configured in level set config from default
1009 skip_setup_artwork = TRUE;
1013 if (!skip_setup_artwork)
1015 // 3rd try: look for special artwork in configured artwork directory
1016 filename = getPath2(getSetupArtworkDir(artwork.mus_current), basename);
1017 if (fileExists(filename))
1023 // 4th try: look for default artwork in new default artwork directory
1024 filename = getPath2(getDefaultMusicDir(MUS_DEFAULT_SUBDIR), basename);
1025 if (fileExists(filename))
1030 // 5th try: look for default artwork in old default artwork directory
1031 filename = getPath2(options.music_directory, basename);
1032 if (fileExists(filename))
1035 if (!strEqual(MUS_FALLBACK_FILENAME, UNDEFINED_FILENAME))
1039 Warn("cannot find artwork file '%s' (using fallback)", basename);
1041 // 6th try: look for fallback artwork in old default artwork directory
1042 // (needed to prevent errors when trying to access unused artwork files)
1043 filename = getPath2(options.music_directory, MUS_FALLBACK_FILENAME);
1044 if (fileExists(filename))
1048 return NULL; // cannot find specified artwork file anywhere
1051 char *getCustomArtworkFilename(char *basename, int type)
1053 if (type == ARTWORK_TYPE_GRAPHICS)
1054 return getCustomImageFilename(basename);
1055 else if (type == ARTWORK_TYPE_SOUNDS)
1056 return getCustomSoundFilename(basename);
1057 else if (type == ARTWORK_TYPE_MUSIC)
1058 return getCustomMusicFilename(basename);
1060 return UNDEFINED_FILENAME;
1063 char *getCustomArtworkConfigFilename(int type)
1065 return getCustomArtworkFilename(ARTWORKINFO_FILENAME(type), type);
1068 char *getCustomArtworkLevelConfigFilename(int type)
1070 static char *filename = NULL;
1072 checked_free(filename);
1074 filename = getPath2(getLevelArtworkDir(type), ARTWORKINFO_FILENAME(type));
1079 char *getCustomMusicDirectory(void)
1081 static char *directory = NULL;
1082 boolean skip_setup_artwork = FALSE;
1084 checked_free(directory);
1086 if (!gfx.override_level_music)
1088 // 1st try: look for special artwork in current level series directory
1089 directory = getPath2(getCurrentLevelDir(), MUSIC_DIRECTORY);
1090 if (directoryExists(directory))
1095 // check if there is special artwork configured in level series config
1096 if (getLevelArtworkSet(ARTWORK_TYPE_MUSIC) != NULL)
1098 // 2nd try: look for special artwork configured in level series config
1099 directory = getStringCopy(getLevelArtworkDir(TREE_TYPE_MUSIC_DIR));
1100 if (directoryExists(directory))
1105 // take missing artwork configured in level set config from default
1106 skip_setup_artwork = TRUE;
1110 if (!skip_setup_artwork)
1112 // 3rd try: look for special artwork in configured artwork directory
1113 directory = getStringCopy(getSetupArtworkDir(artwork.mus_current));
1114 if (directoryExists(directory))
1120 // 4th try: look for default artwork in new default artwork directory
1121 directory = getStringCopy(getDefaultMusicDir(MUS_DEFAULT_SUBDIR));
1122 if (directoryExists(directory))
1127 // 5th try: look for default artwork in old default artwork directory
1128 directory = getStringCopy(options.music_directory);
1129 if (directoryExists(directory))
1132 return NULL; // cannot find specified artwork file anywhere
1135 void InitTapeDirectory(char *level_subdir)
1137 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
1138 createDirectory(getTapeDir(NULL), "main tape", PERMS_PRIVATE);
1139 createDirectory(getTapeDir(level_subdir), "level tape", PERMS_PRIVATE);
1142 void InitScoreDirectory(char *level_subdir)
1144 int permissions = (program.global_scores ? PERMS_PUBLIC : PERMS_PRIVATE);
1146 if (program.global_scores)
1147 createDirectory(getCommonDataDir(), "common data", permissions);
1149 createDirectory(getMainUserGameDataDir(), "main user data", permissions);
1151 createDirectory(getScoreDir(NULL), "main score", permissions);
1152 createDirectory(getScoreDir(level_subdir), "level score", permissions);
1155 void InitScoreCacheDirectory(char *level_subdir)
1157 createDirectory(getMainUserGameDataDir(), "main user data", PERMS_PRIVATE);
1158 createDirectory(getCacheDir(), "cache data", PERMS_PRIVATE);
1159 createDirectory(getScoreCacheDir(NULL), "main score", PERMS_PRIVATE);
1160 createDirectory(getScoreCacheDir(level_subdir), "level score", PERMS_PRIVATE);
1163 void InitScoreTapeDirectory(char *level_subdir, int nr)
1165 int permissions = (program.global_scores ? PERMS_PUBLIC : PERMS_PRIVATE);
1167 InitScoreDirectory(level_subdir);
1169 createDirectory(getScoreTapeDir(level_subdir, nr), "score tape", permissions);
1172 static void SaveUserLevelInfo(void);
1174 void InitUserLevelDirectory(char *level_subdir)
1176 if (!directoryExists(getUserLevelDir(level_subdir)))
1178 createDirectory(getMainUserGameDataDir(), "main user data", PERMS_PRIVATE);
1179 createDirectory(getUserLevelDir(NULL), "main user level", PERMS_PRIVATE);
1180 createDirectory(getUserLevelDir(level_subdir), "user level", PERMS_PRIVATE);
1182 if (setup.internal.create_user_levelset)
1183 SaveUserLevelInfo();
1187 void InitNetworkLevelDirectory(char *level_subdir)
1189 if (!directoryExists(getNetworkLevelDir(level_subdir)))
1191 createDirectory(getMainUserGameDataDir(), "main user data", PERMS_PRIVATE);
1192 createDirectory(getNetworkDir(), "network data", PERMS_PRIVATE);
1193 createDirectory(getNetworkLevelDir(NULL), "main network level", PERMS_PRIVATE);
1194 createDirectory(getNetworkLevelDir(level_subdir), "network level", PERMS_PRIVATE);
1198 void InitLevelSetupDirectory(char *level_subdir)
1200 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
1201 createDirectory(getLevelSetupDir(NULL), "main level setup", PERMS_PRIVATE);
1202 createDirectory(getLevelSetupDir(level_subdir), "level setup", PERMS_PRIVATE);
1205 static void InitCacheDirectory(void)
1207 createDirectory(getMainUserGameDataDir(), "main user data", PERMS_PRIVATE);
1208 createDirectory(getCacheDir(), "cache data", PERMS_PRIVATE);
1212 // ----------------------------------------------------------------------------
1213 // some functions to handle lists of level and artwork directories
1214 // ----------------------------------------------------------------------------
1216 TreeInfo *newTreeInfo(void)
1218 return checked_calloc(sizeof(TreeInfo));
1221 TreeInfo *newTreeInfo_setDefaults(int type)
1223 TreeInfo *ti = newTreeInfo();
1225 setTreeInfoToDefaults(ti, type);
1230 void pushTreeInfo(TreeInfo **node_first, TreeInfo *node_new)
1232 node_new->next = *node_first;
1233 *node_first = node_new;
1236 void removeTreeInfo(TreeInfo **node_first)
1238 TreeInfo *node_old = *node_first;
1240 *node_first = node_old->next;
1241 node_old->next = NULL;
1243 freeTreeInfo(node_old);
1246 int numTreeInfo(TreeInfo *node)
1259 boolean validLevelSeries(TreeInfo *node)
1261 // in a number of cases, tree node is no valid level set
1262 if (node == NULL || node->node_group || node->parent_link || node->is_copy)
1268 TreeInfo *getValidLevelSeries(TreeInfo *node, TreeInfo *default_node)
1270 if (validLevelSeries(node))
1272 else if (node->is_copy)
1273 return getTreeInfoFromIdentifier(leveldir_first, node->identifier);
1275 return getFirstValidTreeInfoEntry(default_node);
1278 TreeInfo *getFirstValidTreeInfoEntry(TreeInfo *node)
1283 if (node->node_group) // enter level group (step down into tree)
1284 return getFirstValidTreeInfoEntry(node->node_group);
1285 else if (node->parent_link) // skip start entry of level group
1287 if (node->next) // get first real level series entry
1288 return getFirstValidTreeInfoEntry(node->next);
1289 else // leave empty level group and go on
1290 return getFirstValidTreeInfoEntry(node->node_parent->next);
1292 else // this seems to be a regular level series
1296 TreeInfo *getTreeInfoFirstGroupEntry(TreeInfo *node)
1301 if (node->node_parent == NULL) // top level group
1302 return *node->node_top;
1303 else // sub level group
1304 return node->node_parent->node_group;
1307 int numTreeInfoInGroup(TreeInfo *node)
1309 return numTreeInfo(getTreeInfoFirstGroupEntry(node));
1312 int getPosFromTreeInfo(TreeInfo *node)
1314 TreeInfo *node_cmp = getTreeInfoFirstGroupEntry(node);
1319 if (node_cmp == node)
1323 node_cmp = node_cmp->next;
1329 TreeInfo *getTreeInfoFromPos(TreeInfo *node, int pos)
1331 TreeInfo *node_default = node;
1343 return node_default;
1346 static TreeInfo *getTreeInfoFromIdentifierExt(TreeInfo *node, char *identifier,
1347 int node_type_wanted)
1349 if (identifier == NULL)
1354 if (TREE_NODE_TYPE(node) == node_type_wanted &&
1355 strEqual(identifier, node->identifier))
1358 if (node->node_group)
1360 TreeInfo *node_group = getTreeInfoFromIdentifierExt(node->node_group,
1373 TreeInfo *getTreeInfoFromIdentifier(TreeInfo *node, char *identifier)
1375 return getTreeInfoFromIdentifierExt(node, identifier, TREE_NODE_TYPE_DEFAULT);
1378 static TreeInfo *cloneTreeNode(TreeInfo **node_top, TreeInfo *node_parent,
1379 TreeInfo *node, boolean skip_sets_without_levels)
1386 if (!node->parent_link && !node->level_group &&
1387 skip_sets_without_levels && node->levels == 0)
1388 return cloneTreeNode(node_top, node_parent, node->next,
1389 skip_sets_without_levels);
1391 node_new = getTreeInfoCopy(node); // copy complete node
1393 node_new->node_top = node_top; // correct top node link
1394 node_new->node_parent = node_parent; // correct parent node link
1396 if (node->level_group)
1397 node_new->node_group = cloneTreeNode(node_top, node_new, node->node_group,
1398 skip_sets_without_levels);
1400 node_new->next = cloneTreeNode(node_top, node_parent, node->next,
1401 skip_sets_without_levels);
1406 static void cloneTree(TreeInfo **ti_new, TreeInfo *ti, boolean skip_empty_sets)
1408 TreeInfo *ti_cloned = cloneTreeNode(ti_new, NULL, ti, skip_empty_sets);
1410 *ti_new = ti_cloned;
1413 static boolean adjustTreeGraphicsForEMC(TreeInfo *node)
1415 boolean settings_changed = FALSE;
1419 boolean want_ecs = (setup.prefer_aga_graphics == FALSE);
1420 boolean want_aga = (setup.prefer_aga_graphics == TRUE);
1421 boolean has_only_ecs = (!node->graphics_set && !node->graphics_set_aga);
1422 boolean has_only_aga = (!node->graphics_set && !node->graphics_set_ecs);
1423 char *graphics_set = NULL;
1425 if (node->graphics_set_ecs && (want_ecs || has_only_ecs))
1426 graphics_set = node->graphics_set_ecs;
1428 if (node->graphics_set_aga && (want_aga || has_only_aga))
1429 graphics_set = node->graphics_set_aga;
1431 if (graphics_set && !strEqual(node->graphics_set, graphics_set))
1433 setString(&node->graphics_set, graphics_set);
1434 settings_changed = TRUE;
1437 if (node->node_group != NULL)
1438 settings_changed |= adjustTreeGraphicsForEMC(node->node_group);
1443 return settings_changed;
1446 static boolean adjustTreeSoundsForEMC(TreeInfo *node)
1448 boolean settings_changed = FALSE;
1452 boolean want_default = (setup.prefer_lowpass_sounds == FALSE);
1453 boolean want_lowpass = (setup.prefer_lowpass_sounds == TRUE);
1454 boolean has_only_default = (!node->sounds_set && !node->sounds_set_lowpass);
1455 boolean has_only_lowpass = (!node->sounds_set && !node->sounds_set_default);
1456 char *sounds_set = NULL;
1458 if (node->sounds_set_default && (want_default || has_only_default))
1459 sounds_set = node->sounds_set_default;
1461 if (node->sounds_set_lowpass && (want_lowpass || has_only_lowpass))
1462 sounds_set = node->sounds_set_lowpass;
1464 if (sounds_set && !strEqual(node->sounds_set, sounds_set))
1466 setString(&node->sounds_set, sounds_set);
1467 settings_changed = TRUE;
1470 if (node->node_group != NULL)
1471 settings_changed |= adjustTreeSoundsForEMC(node->node_group);
1476 return settings_changed;
1479 void dumpTreeInfo(TreeInfo *node, int depth)
1481 char bullet_list[] = { '-', '*', 'o' };
1485 Debug("tree", "Dumping TreeInfo:");
1489 char bullet = bullet_list[depth % ARRAY_SIZE(bullet_list)];
1491 for (i = 0; i < depth * 2; i++)
1492 DebugContinued("", " ");
1494 DebugContinued("tree", "%c '%s' ['%s] [PARENT: '%s'] %s\n",
1495 bullet, node->name, node->identifier,
1496 (node->node_parent ? node->node_parent->identifier : "-"),
1497 (node->node_group ? "[GROUP]" : ""));
1500 // use for dumping artwork info tree
1501 Debug("tree", "subdir == '%s' ['%s', '%s'] [%d])",
1502 node->subdir, node->fullpath, node->basepath, node->in_user_dir);
1505 if (node->node_group != NULL)
1506 dumpTreeInfo(node->node_group, depth + 1);
1512 void sortTreeInfoBySortFunction(TreeInfo **node_first,
1513 int (*compare_function)(const void *,
1516 int num_nodes = numTreeInfo(*node_first);
1517 TreeInfo **sort_array;
1518 TreeInfo *node = *node_first;
1524 // allocate array for sorting structure pointers
1525 sort_array = checked_calloc(num_nodes * sizeof(TreeInfo *));
1527 // writing structure pointers to sorting array
1528 while (i < num_nodes && node) // double boundary check...
1530 sort_array[i] = node;
1536 // sorting the structure pointers in the sorting array
1537 qsort(sort_array, num_nodes, sizeof(TreeInfo *),
1540 // update the linkage of list elements with the sorted node array
1541 for (i = 0; i < num_nodes - 1; i++)
1542 sort_array[i]->next = sort_array[i + 1];
1543 sort_array[num_nodes - 1]->next = NULL;
1545 // update the linkage of the main list anchor pointer
1546 *node_first = sort_array[0];
1550 // now recursively sort the level group structures
1554 if (node->node_group != NULL)
1555 sortTreeInfoBySortFunction(&node->node_group, compare_function);
1561 void sortTreeInfo(TreeInfo **node_first)
1563 sortTreeInfoBySortFunction(node_first, compareTreeInfoEntries);
1567 // ============================================================================
1568 // some stuff from "files.c"
1569 // ============================================================================
1571 #if defined(PLATFORM_WIN32)
1573 #define S_IRGRP S_IRUSR
1576 #define S_IROTH S_IRUSR
1579 #define S_IWGRP S_IWUSR
1582 #define S_IWOTH S_IWUSR
1585 #define S_IXGRP S_IXUSR
1588 #define S_IXOTH S_IXUSR
1591 #define S_IRWXG (S_IRGRP | S_IWGRP | S_IXGRP)
1596 #endif // PLATFORM_WIN32
1598 // file permissions for newly written files
1599 #define MODE_R_ALL (S_IRUSR | S_IRGRP | S_IROTH)
1600 #define MODE_W_ALL (S_IWUSR | S_IWGRP | S_IWOTH)
1601 #define MODE_X_ALL (S_IXUSR | S_IXGRP | S_IXOTH)
1603 #define MODE_W_PRIVATE (S_IWUSR)
1604 #define MODE_W_PUBLIC_FILE (S_IWUSR | S_IWGRP)
1605 #define MODE_W_PUBLIC_DIR (S_IWUSR | S_IWGRP | S_ISGID)
1607 #define DIR_PERMS_PRIVATE (MODE_R_ALL | MODE_X_ALL | MODE_W_PRIVATE)
1608 #define DIR_PERMS_PUBLIC (MODE_R_ALL | MODE_X_ALL | MODE_W_PUBLIC_DIR)
1609 #define DIR_PERMS_PUBLIC_ALL (MODE_R_ALL | MODE_X_ALL | MODE_W_ALL)
1611 #define FILE_PERMS_PRIVATE (MODE_R_ALL | MODE_W_PRIVATE)
1612 #define FILE_PERMS_PUBLIC (MODE_R_ALL | MODE_W_PUBLIC_FILE)
1613 #define FILE_PERMS_PUBLIC_ALL (MODE_R_ALL | MODE_W_ALL)
1616 char *getHomeDir(void)
1618 static char *dir = NULL;
1620 #if defined(PLATFORM_WIN32)
1623 dir = checked_malloc(MAX_PATH + 1);
1625 if (!SUCCEEDED(SHGetFolderPath(NULL, CSIDL_PERSONAL, NULL, 0, dir)))
1628 #elif defined(PLATFORM_EMSCRIPTEN)
1629 dir = "/persistent";
1630 #elif defined(PLATFORM_UNIX)
1633 if ((dir = getenv("HOME")) == NULL)
1635 dir = getUnixHomeDir();
1638 dir = getStringCopy(dir);
1650 char *getCommonDataDir(void)
1652 static char *common_data_dir = NULL;
1654 #if defined(PLATFORM_WIN32)
1655 if (common_data_dir == NULL)
1657 char *dir = checked_malloc(MAX_PATH + 1);
1659 if (SUCCEEDED(SHGetFolderPath(NULL, CSIDL_COMMON_DOCUMENTS, NULL, 0, dir))
1660 && !strEqual(dir, "")) // empty for Windows 95/98
1661 common_data_dir = getPath2(dir, program.userdata_subdir);
1663 common_data_dir = options.rw_base_directory;
1666 if (common_data_dir == NULL)
1667 common_data_dir = options.rw_base_directory;
1670 return common_data_dir;
1673 char *getPersonalDataDir(void)
1675 static char *personal_data_dir = NULL;
1677 #if defined(PLATFORM_MACOSX)
1678 if (personal_data_dir == NULL)
1679 personal_data_dir = getPath2(getHomeDir(), "Documents");
1681 if (personal_data_dir == NULL)
1682 personal_data_dir = getHomeDir();
1685 return personal_data_dir;
1688 char *getMainUserGameDataDir(void)
1690 static char *main_user_data_dir = NULL;
1692 #if defined(PLATFORM_ANDROID)
1693 if (main_user_data_dir == NULL)
1694 main_user_data_dir = (char *)(SDL_AndroidGetExternalStorageState() &
1695 SDL_ANDROID_EXTERNAL_STORAGE_WRITE ?
1696 SDL_AndroidGetExternalStoragePath() :
1697 SDL_AndroidGetInternalStoragePath());
1699 if (main_user_data_dir == NULL)
1700 main_user_data_dir = getPath2(getPersonalDataDir(),
1701 program.userdata_subdir);
1704 return main_user_data_dir;
1707 char *getUserGameDataDir(void)
1710 return getMainUserGameDataDir();
1712 return getUserDir(user.nr);
1715 char *getSetupDir(void)
1717 return getUserGameDataDir();
1720 static mode_t posix_umask(mode_t mask)
1722 #if defined(PLATFORM_UNIX)
1729 static int posix_mkdir(const char *pathname, mode_t mode)
1731 #if defined(PLATFORM_WIN32)
1732 return mkdir(pathname);
1734 return mkdir(pathname, mode);
1738 static boolean posix_process_running_setgid(void)
1740 #if defined(PLATFORM_UNIX)
1741 return (getgid() != getegid());
1747 void createDirectory(char *dir, char *text, int permission_class)
1749 if (directoryExists(dir))
1752 // leave "other" permissions in umask untouched, but ensure group parts
1753 // of USERDATA_DIR_MODE are not masked
1754 mode_t dir_mode = (permission_class == PERMS_PRIVATE ?
1755 DIR_PERMS_PRIVATE : DIR_PERMS_PUBLIC);
1756 mode_t last_umask = posix_umask(0);
1757 mode_t group_umask = ~(dir_mode & S_IRWXG);
1758 int running_setgid = posix_process_running_setgid();
1760 if (permission_class == PERMS_PUBLIC)
1762 // if we're setgid, protect files against "other"
1763 // else keep umask(0) to make the dir world-writable
1766 posix_umask(last_umask & group_umask);
1768 dir_mode = DIR_PERMS_PUBLIC_ALL;
1771 if (posix_mkdir(dir, dir_mode) != 0)
1772 Warn("cannot create %s directory '%s': %s", text, dir, strerror(errno));
1774 if (permission_class == PERMS_PUBLIC && !running_setgid)
1775 chmod(dir, dir_mode);
1777 posix_umask(last_umask); // restore previous umask
1780 void InitMainUserDataDirectory(void)
1782 createDirectory(getMainUserGameDataDir(), "main user data", PERMS_PRIVATE);
1785 void InitUserDataDirectory(void)
1787 createDirectory(getMainUserGameDataDir(), "main user data", PERMS_PRIVATE);
1791 createDirectory(getUserDir(-1), "users", PERMS_PRIVATE);
1792 createDirectory(getUserDir(user.nr), "user data", PERMS_PRIVATE);
1796 void SetFilePermissions(char *filename, int permission_class)
1798 int running_setgid = posix_process_running_setgid();
1799 int perms = (permission_class == PERMS_PRIVATE ?
1800 FILE_PERMS_PRIVATE : FILE_PERMS_PUBLIC);
1802 if (permission_class == PERMS_PUBLIC && !running_setgid)
1803 perms = FILE_PERMS_PUBLIC_ALL;
1805 chmod(filename, perms);
1808 char *getCookie(char *file_type)
1810 static char cookie[MAX_COOKIE_LEN + 1];
1812 if (strlen(program.cookie_prefix) + 1 +
1813 strlen(file_type) + strlen("_FILE_VERSION_x.x") > MAX_COOKIE_LEN)
1814 return "[COOKIE ERROR]"; // should never happen
1816 sprintf(cookie, "%s_%s_FILE_VERSION_%d.%d",
1817 program.cookie_prefix, file_type,
1818 program.version_super, program.version_major);
1823 void fprintFileHeader(FILE *file, char *basename)
1825 char *prefix = "# ";
1828 fprintf_line_with_prefix(file, prefix, sep1, 77);
1829 fprintf(file, "%s%s\n", prefix, basename);
1830 fprintf_line_with_prefix(file, prefix, sep1, 77);
1831 fprintf(file, "\n");
1834 int getFileVersionFromCookieString(const char *cookie)
1836 const char *ptr_cookie1, *ptr_cookie2;
1837 const char *pattern1 = "_FILE_VERSION_";
1838 const char *pattern2 = "?.?";
1839 const int len_cookie = strlen(cookie);
1840 const int len_pattern1 = strlen(pattern1);
1841 const int len_pattern2 = strlen(pattern2);
1842 const int len_pattern = len_pattern1 + len_pattern2;
1843 int version_super, version_major;
1845 if (len_cookie <= len_pattern)
1848 ptr_cookie1 = &cookie[len_cookie - len_pattern];
1849 ptr_cookie2 = &cookie[len_cookie - len_pattern2];
1851 if (strncmp(ptr_cookie1, pattern1, len_pattern1) != 0)
1854 if (ptr_cookie2[0] < '0' || ptr_cookie2[0] > '9' ||
1855 ptr_cookie2[1] != '.' ||
1856 ptr_cookie2[2] < '0' || ptr_cookie2[2] > '9')
1859 version_super = ptr_cookie2[0] - '0';
1860 version_major = ptr_cookie2[2] - '0';
1862 return VERSION_IDENT(version_super, version_major, 0, 0);
1865 boolean checkCookieString(const char *cookie, const char *template)
1867 const char *pattern = "_FILE_VERSION_?.?";
1868 const int len_cookie = strlen(cookie);
1869 const int len_template = strlen(template);
1870 const int len_pattern = strlen(pattern);
1872 if (len_cookie != len_template)
1875 if (strncmp(cookie, template, len_cookie - len_pattern) != 0)
1882 // ----------------------------------------------------------------------------
1883 // setup file list and hash handling functions
1884 // ----------------------------------------------------------------------------
1886 char *getFormattedSetupEntry(char *token, char *value)
1889 static char entry[MAX_LINE_LEN];
1891 // if value is an empty string, just return token without value
1895 // start with the token and some spaces to format output line
1896 sprintf(entry, "%s:", token);
1897 for (i = strlen(entry); i < token_value_position; i++)
1900 // continue with the token's value
1901 strcat(entry, value);
1906 SetupFileList *newSetupFileList(char *token, char *value)
1908 SetupFileList *new = checked_malloc(sizeof(SetupFileList));
1910 new->token = getStringCopy(token);
1911 new->value = getStringCopy(value);
1918 void freeSetupFileList(SetupFileList *list)
1923 checked_free(list->token);
1924 checked_free(list->value);
1927 freeSetupFileList(list->next);
1932 char *getListEntry(SetupFileList *list, char *token)
1937 if (strEqual(list->token, token))
1940 return getListEntry(list->next, token);
1943 SetupFileList *setListEntry(SetupFileList *list, char *token, char *value)
1948 if (strEqual(list->token, token))
1950 checked_free(list->value);
1952 list->value = getStringCopy(value);
1956 else if (list->next == NULL)
1957 return (list->next = newSetupFileList(token, value));
1959 return setListEntry(list->next, token, value);
1962 SetupFileList *addListEntry(SetupFileList *list, char *token, char *value)
1967 if (list->next == NULL)
1968 return (list->next = newSetupFileList(token, value));
1970 return addListEntry(list->next, token, value);
1973 #if ENABLE_UNUSED_CODE
1975 static void printSetupFileList(SetupFileList *list)
1980 Debug("setup:printSetupFileList", "token: '%s'", list->token);
1981 Debug("setup:printSetupFileList", "value: '%s'", list->value);
1983 printSetupFileList(list->next);
1989 DEFINE_HASHTABLE_INSERT(insert_hash_entry, char, char);
1990 DEFINE_HASHTABLE_SEARCH(search_hash_entry, char, char);
1991 DEFINE_HASHTABLE_CHANGE(change_hash_entry, char, char);
1992 DEFINE_HASHTABLE_REMOVE(remove_hash_entry, char, char);
1994 #define insert_hash_entry hashtable_insert
1995 #define search_hash_entry hashtable_search
1996 #define change_hash_entry hashtable_change
1997 #define remove_hash_entry hashtable_remove
2000 unsigned int get_hash_from_key(void *key)
2005 This algorithm (k=33) was first reported by Dan Bernstein many years ago in
2006 'comp.lang.c'. Another version of this algorithm (now favored by Bernstein)
2007 uses XOR: hash(i) = hash(i - 1) * 33 ^ str[i]; the magic of number 33 (why
2008 it works better than many other constants, prime or not) has never been
2009 adequately explained.
2011 If you just want to have a good hash function, and cannot wait, djb2
2012 is one of the best string hash functions i know. It has excellent
2013 distribution and speed on many different sets of keys and table sizes.
2014 You are not likely to do better with one of the "well known" functions
2015 such as PJW, K&R, etc.
2017 Ozan (oz) Yigit [http://www.cs.yorku.ca/~oz/hash.html]
2020 char *str = (char *)key;
2021 unsigned int hash = 5381;
2024 while ((c = *str++))
2025 hash = ((hash << 5) + hash) + c; // hash * 33 + c
2030 static int keys_are_equal(void *key1, void *key2)
2032 return (strEqual((char *)key1, (char *)key2));
2035 SetupFileHash *newSetupFileHash(void)
2037 SetupFileHash *new_hash =
2038 create_hashtable(16, 0.75, get_hash_from_key, keys_are_equal);
2040 if (new_hash == NULL)
2041 Fail("create_hashtable() failed -- out of memory");
2046 void freeSetupFileHash(SetupFileHash *hash)
2051 hashtable_destroy(hash, 1); // 1 == also free values stored in hash
2054 char *getHashEntry(SetupFileHash *hash, char *token)
2059 return search_hash_entry(hash, token);
2062 void setHashEntry(SetupFileHash *hash, char *token, char *value)
2069 value_copy = getStringCopy(value);
2071 // change value; if it does not exist, insert it as new
2072 if (!change_hash_entry(hash, token, value_copy))
2073 if (!insert_hash_entry(hash, getStringCopy(token), value_copy))
2074 Fail("cannot insert into hash -- aborting");
2077 char *removeHashEntry(SetupFileHash *hash, char *token)
2082 return remove_hash_entry(hash, token);
2085 #if ENABLE_UNUSED_CODE
2087 static void printSetupFileHash(SetupFileHash *hash)
2089 BEGIN_HASH_ITERATION(hash, itr)
2091 Debug("setup:printSetupFileHash", "token: '%s'", HASH_ITERATION_TOKEN(itr));
2092 Debug("setup:printSetupFileHash", "value: '%s'", HASH_ITERATION_VALUE(itr));
2094 END_HASH_ITERATION(hash, itr)
2099 #define ALLOW_TOKEN_VALUE_SEPARATOR_BEING_WHITESPACE 1
2100 #define CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING 0
2101 #define CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH 0
2103 static boolean token_value_separator_found = FALSE;
2104 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2105 static boolean token_value_separator_warning = FALSE;
2107 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2108 static boolean token_already_exists_warning = FALSE;
2111 static boolean getTokenValueFromSetupLineExt(char *line,
2112 char **token_ptr, char **value_ptr,
2113 char *filename, char *line_raw,
2115 boolean separator_required)
2117 static char line_copy[MAX_LINE_LEN + 1], line_raw_copy[MAX_LINE_LEN + 1];
2118 char *token, *value, *line_ptr;
2120 // when externally invoked via ReadTokenValueFromLine(), copy line buffers
2121 if (line_raw == NULL)
2123 strncpy(line_copy, line, MAX_LINE_LEN);
2124 line_copy[MAX_LINE_LEN] = '\0';
2127 strcpy(line_raw_copy, line_copy);
2128 line_raw = line_raw_copy;
2131 // cut trailing comment from input line
2132 for (line_ptr = line; *line_ptr; line_ptr++)
2134 if (*line_ptr == '#')
2141 // cut trailing whitespaces from input line
2142 for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
2143 if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
2146 // ignore empty lines
2150 // cut leading whitespaces from token
2151 for (token = line; *token; token++)
2152 if (*token != ' ' && *token != '\t')
2155 // start with empty value as reliable default
2158 token_value_separator_found = FALSE;
2160 // find end of token to determine start of value
2161 for (line_ptr = token; *line_ptr; line_ptr++)
2163 // first look for an explicit token/value separator, like ':' or '='
2164 if (*line_ptr == ':' || *line_ptr == '=')
2166 *line_ptr = '\0'; // terminate token string
2167 value = line_ptr + 1; // set beginning of value
2169 token_value_separator_found = TRUE;
2175 #if ALLOW_TOKEN_VALUE_SEPARATOR_BEING_WHITESPACE
2176 // fallback: if no token/value separator found, also allow whitespaces
2177 if (!token_value_separator_found && !separator_required)
2179 for (line_ptr = token; *line_ptr; line_ptr++)
2181 if (*line_ptr == ' ' || *line_ptr == '\t')
2183 *line_ptr = '\0'; // terminate token string
2184 value = line_ptr + 1; // set beginning of value
2186 token_value_separator_found = TRUE;
2192 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2193 if (token_value_separator_found)
2195 if (!token_value_separator_warning)
2197 Debug("setup", "---");
2199 if (filename != NULL)
2201 Debug("setup", "missing token/value separator(s) in config file:");
2202 Debug("setup", "- config file: '%s'", filename);
2206 Debug("setup", "missing token/value separator(s):");
2209 token_value_separator_warning = TRUE;
2212 if (filename != NULL)
2213 Debug("setup", "- line %d: '%s'", line_nr, line_raw);
2215 Debug("setup", "- line: '%s'", line_raw);
2221 // cut trailing whitespaces from token
2222 for (line_ptr = &token[strlen(token)]; line_ptr >= token; line_ptr--)
2223 if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
2226 // cut leading whitespaces from value
2227 for (; *value; value++)
2228 if (*value != ' ' && *value != '\t')
2237 boolean getTokenValueFromSetupLine(char *line, char **token, char **value)
2239 // while the internal (old) interface does not require a token/value
2240 // separator (for downwards compatibility with existing files which
2241 // don't use them), it is mandatory for the external (new) interface
2243 return getTokenValueFromSetupLineExt(line, token, value, NULL, NULL, 0, TRUE);
2246 static boolean loadSetupFileData(void *setup_file_data, char *filename,
2247 boolean top_recursion_level, boolean is_hash)
2249 static SetupFileHash *include_filename_hash = NULL;
2250 char line[MAX_LINE_LEN], line_raw[MAX_LINE_LEN], previous_line[MAX_LINE_LEN];
2251 char *token, *value, *line_ptr;
2252 void *insert_ptr = NULL;
2253 boolean read_continued_line = FALSE;
2255 int line_nr = 0, token_count = 0, include_count = 0;
2257 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2258 token_value_separator_warning = FALSE;
2261 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2262 token_already_exists_warning = FALSE;
2265 if (!(file = openFile(filename, MODE_READ)))
2267 #if DEBUG_NO_CONFIG_FILE
2268 Debug("setup", "cannot open configuration file '%s'", filename);
2274 // use "insert pointer" to store list end for constant insertion complexity
2276 insert_ptr = setup_file_data;
2278 // on top invocation, create hash to mark included files (to prevent loops)
2279 if (top_recursion_level)
2280 include_filename_hash = newSetupFileHash();
2282 // mark this file as already included (to prevent including it again)
2283 setHashEntry(include_filename_hash, getBaseNamePtr(filename), "true");
2285 while (!checkEndOfFile(file))
2287 // read next line of input file
2288 if (!getStringFromFile(file, line, MAX_LINE_LEN))
2291 // check if line was completely read and is terminated by line break
2292 if (strlen(line) > 0 && line[strlen(line) - 1] == '\n')
2295 // cut trailing line break (this can be newline and/or carriage return)
2296 for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
2297 if ((*line_ptr == '\n' || *line_ptr == '\r') && *(line_ptr + 1) == '\0')
2300 // copy raw input line for later use (mainly debugging output)
2301 strcpy(line_raw, line);
2303 if (read_continued_line)
2305 // append new line to existing line, if there is enough space
2306 if (strlen(previous_line) + strlen(line_ptr) < MAX_LINE_LEN)
2307 strcat(previous_line, line_ptr);
2309 strcpy(line, previous_line); // copy storage buffer to line
2311 read_continued_line = FALSE;
2314 // if the last character is '\', continue at next line
2315 if (strlen(line) > 0 && line[strlen(line) - 1] == '\\')
2317 line[strlen(line) - 1] = '\0'; // cut off trailing backslash
2318 strcpy(previous_line, line); // copy line to storage buffer
2320 read_continued_line = TRUE;
2325 if (!getTokenValueFromSetupLineExt(line, &token, &value, filename,
2326 line_raw, line_nr, FALSE))
2331 if (strEqual(token, "include"))
2333 if (getHashEntry(include_filename_hash, value) == NULL)
2335 char *basepath = getBasePath(filename);
2336 char *basename = getBaseName(value);
2337 char *filename_include = getPath2(basepath, basename);
2339 loadSetupFileData(setup_file_data, filename_include, FALSE, is_hash);
2343 free(filename_include);
2349 Warn("ignoring already processed file '%s'", value);
2356 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2358 getHashEntry((SetupFileHash *)setup_file_data, token);
2360 if (old_value != NULL)
2362 if (!token_already_exists_warning)
2364 Debug("setup", "---");
2365 Debug("setup", "duplicate token(s) found in config file:");
2366 Debug("setup", "- config file: '%s'", filename);
2368 token_already_exists_warning = TRUE;
2371 Debug("setup", "- token: '%s' (in line %d)", token, line_nr);
2372 Debug("setup", " old value: '%s'", old_value);
2373 Debug("setup", " new value: '%s'", value);
2377 setHashEntry((SetupFileHash *)setup_file_data, token, value);
2381 insert_ptr = addListEntry((SetupFileList *)insert_ptr, token, value);
2391 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2392 if (token_value_separator_warning)
2393 Debug("setup", "---");
2396 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2397 if (token_already_exists_warning)
2398 Debug("setup", "---");
2401 if (token_count == 0 && include_count == 0)
2402 Warn("configuration file '%s' is empty", filename);
2404 if (top_recursion_level)
2405 freeSetupFileHash(include_filename_hash);
2410 static int compareSetupFileData(const void *object1, const void *object2)
2412 const struct ConfigInfo *entry1 = (struct ConfigInfo *)object1;
2413 const struct ConfigInfo *entry2 = (struct ConfigInfo *)object2;
2415 return strcmp(entry1->token, entry2->token);
2418 static void saveSetupFileHash(SetupFileHash *hash, char *filename)
2420 int item_count = hashtable_count(hash);
2421 int item_size = sizeof(struct ConfigInfo);
2422 struct ConfigInfo *sort_array = checked_malloc(item_count * item_size);
2426 // copy string pointers from hash to array
2427 BEGIN_HASH_ITERATION(hash, itr)
2429 sort_array[i].token = HASH_ITERATION_TOKEN(itr);
2430 sort_array[i].value = HASH_ITERATION_VALUE(itr);
2434 if (i > item_count) // should never happen
2437 END_HASH_ITERATION(hash, itr)
2439 // sort string pointers from hash in array
2440 qsort(sort_array, item_count, item_size, compareSetupFileData);
2442 if (!(file = fopen(filename, MODE_WRITE)))
2444 Warn("cannot write configuration file '%s'", filename);
2449 fprintf(file, "%s\n\n", getFormattedSetupEntry("program.version",
2450 program.version_string));
2451 for (i = 0; i < item_count; i++)
2452 fprintf(file, "%s\n", getFormattedSetupEntry(sort_array[i].token,
2453 sort_array[i].value));
2456 checked_free(sort_array);
2459 SetupFileList *loadSetupFileList(char *filename)
2461 SetupFileList *setup_file_list = newSetupFileList("", "");
2462 SetupFileList *first_valid_list_entry;
2464 if (!loadSetupFileData(setup_file_list, filename, TRUE, FALSE))
2466 freeSetupFileList(setup_file_list);
2471 first_valid_list_entry = setup_file_list->next;
2473 // free empty list header
2474 setup_file_list->next = NULL;
2475 freeSetupFileList(setup_file_list);
2477 return first_valid_list_entry;
2480 SetupFileHash *loadSetupFileHash(char *filename)
2482 SetupFileHash *setup_file_hash = newSetupFileHash();
2484 if (!loadSetupFileData(setup_file_hash, filename, TRUE, TRUE))
2486 freeSetupFileHash(setup_file_hash);
2491 return setup_file_hash;
2495 // ============================================================================
2497 // ============================================================================
2499 #define TOKEN_STR_LAST_LEVEL_SERIES "last_level_series"
2500 #define TOKEN_STR_LAST_PLAYED_LEVEL "last_played_level"
2501 #define TOKEN_STR_HANDICAP_LEVEL "handicap_level"
2502 #define TOKEN_STR_LAST_USER "last_user"
2504 // level directory info
2505 #define LEVELINFO_TOKEN_IDENTIFIER 0
2506 #define LEVELINFO_TOKEN_NAME 1
2507 #define LEVELINFO_TOKEN_NAME_SORTING 2
2508 #define LEVELINFO_TOKEN_AUTHOR 3
2509 #define LEVELINFO_TOKEN_YEAR 4
2510 #define LEVELINFO_TOKEN_PROGRAM_TITLE 5
2511 #define LEVELINFO_TOKEN_PROGRAM_COPYRIGHT 6
2512 #define LEVELINFO_TOKEN_PROGRAM_COMPANY 7
2513 #define LEVELINFO_TOKEN_IMPORTED_FROM 8
2514 #define LEVELINFO_TOKEN_IMPORTED_BY 9
2515 #define LEVELINFO_TOKEN_TESTED_BY 10
2516 #define LEVELINFO_TOKEN_LEVELS 11
2517 #define LEVELINFO_TOKEN_FIRST_LEVEL 12
2518 #define LEVELINFO_TOKEN_SORT_PRIORITY 13
2519 #define LEVELINFO_TOKEN_LATEST_ENGINE 14
2520 #define LEVELINFO_TOKEN_LEVEL_GROUP 15
2521 #define LEVELINFO_TOKEN_READONLY 16
2522 #define LEVELINFO_TOKEN_GRAPHICS_SET_ECS 17
2523 #define LEVELINFO_TOKEN_GRAPHICS_SET_AGA 18
2524 #define LEVELINFO_TOKEN_GRAPHICS_SET 19
2525 #define LEVELINFO_TOKEN_SOUNDS_SET_DEFAULT 20
2526 #define LEVELINFO_TOKEN_SOUNDS_SET_LOWPASS 21
2527 #define LEVELINFO_TOKEN_SOUNDS_SET 22
2528 #define LEVELINFO_TOKEN_MUSIC_SET 23
2529 #define LEVELINFO_TOKEN_FILENAME 24
2530 #define LEVELINFO_TOKEN_FILETYPE 25
2531 #define LEVELINFO_TOKEN_SPECIAL_FLAGS 26
2532 #define LEVELINFO_TOKEN_HANDICAP 27
2533 #define LEVELINFO_TOKEN_SKIP_LEVELS 28
2534 #define LEVELINFO_TOKEN_USE_EMC_TILES 29
2536 #define NUM_LEVELINFO_TOKENS 30
2538 static LevelDirTree ldi;
2540 static struct TokenInfo levelinfo_tokens[] =
2542 // level directory info
2543 { TYPE_STRING, &ldi.identifier, "identifier" },
2544 { TYPE_STRING, &ldi.name, "name" },
2545 { TYPE_STRING, &ldi.name_sorting, "name_sorting" },
2546 { TYPE_STRING, &ldi.author, "author" },
2547 { TYPE_STRING, &ldi.year, "year" },
2548 { TYPE_STRING, &ldi.program_title, "program_title" },
2549 { TYPE_STRING, &ldi.program_copyright, "program_copyright" },
2550 { TYPE_STRING, &ldi.program_company, "program_company" },
2551 { TYPE_STRING, &ldi.imported_from, "imported_from" },
2552 { TYPE_STRING, &ldi.imported_by, "imported_by" },
2553 { TYPE_STRING, &ldi.tested_by, "tested_by" },
2554 { TYPE_INTEGER, &ldi.levels, "levels" },
2555 { TYPE_INTEGER, &ldi.first_level, "first_level" },
2556 { TYPE_INTEGER, &ldi.sort_priority, "sort_priority" },
2557 { TYPE_BOOLEAN, &ldi.latest_engine, "latest_engine" },
2558 { TYPE_BOOLEAN, &ldi.level_group, "level_group" },
2559 { TYPE_BOOLEAN, &ldi.readonly, "readonly" },
2560 { TYPE_STRING, &ldi.graphics_set_ecs, "graphics_set.ecs" },
2561 { TYPE_STRING, &ldi.graphics_set_aga, "graphics_set.aga" },
2562 { TYPE_STRING, &ldi.graphics_set, "graphics_set" },
2563 { TYPE_STRING, &ldi.sounds_set_default,"sounds_set.default" },
2564 { TYPE_STRING, &ldi.sounds_set_lowpass,"sounds_set.lowpass" },
2565 { TYPE_STRING, &ldi.sounds_set, "sounds_set" },
2566 { TYPE_STRING, &ldi.music_set, "music_set" },
2567 { TYPE_STRING, &ldi.level_filename, "filename" },
2568 { TYPE_STRING, &ldi.level_filetype, "filetype" },
2569 { TYPE_STRING, &ldi.special_flags, "special_flags" },
2570 { TYPE_BOOLEAN, &ldi.handicap, "handicap" },
2571 { TYPE_BOOLEAN, &ldi.skip_levels, "skip_levels" },
2572 { TYPE_BOOLEAN, &ldi.use_emc_tiles, "use_emc_tiles" }
2575 static struct TokenInfo artworkinfo_tokens[] =
2577 // artwork directory info
2578 { TYPE_STRING, &ldi.identifier, "identifier" },
2579 { TYPE_STRING, &ldi.subdir, "subdir" },
2580 { TYPE_STRING, &ldi.name, "name" },
2581 { TYPE_STRING, &ldi.name_sorting, "name_sorting" },
2582 { TYPE_STRING, &ldi.author, "author" },
2583 { TYPE_STRING, &ldi.program_title, "program_title" },
2584 { TYPE_STRING, &ldi.program_copyright, "program_copyright" },
2585 { TYPE_STRING, &ldi.program_company, "program_company" },
2586 { TYPE_INTEGER, &ldi.sort_priority, "sort_priority" },
2587 { TYPE_STRING, &ldi.basepath, "basepath" },
2588 { TYPE_STRING, &ldi.fullpath, "fullpath" },
2589 { TYPE_BOOLEAN, &ldi.in_user_dir, "in_user_dir" },
2590 { TYPE_STRING, &ldi.class_desc, "class_desc" },
2595 static char *optional_tokens[] =
2598 "program_copyright",
2604 static void setTreeInfoToDefaults(TreeInfo *ti, int type)
2608 ti->node_top = (ti->type == TREE_TYPE_LEVEL_DIR ? &leveldir_first :
2609 ti->type == TREE_TYPE_GRAPHICS_DIR ? &artwork.gfx_first :
2610 ti->type == TREE_TYPE_SOUNDS_DIR ? &artwork.snd_first :
2611 ti->type == TREE_TYPE_MUSIC_DIR ? &artwork.mus_first :
2614 ti->node_parent = NULL;
2615 ti->node_group = NULL;
2622 ti->fullpath = NULL;
2623 ti->basepath = NULL;
2624 ti->identifier = NULL;
2625 ti->name = getStringCopy(ANONYMOUS_NAME);
2626 ti->name_sorting = NULL;
2627 ti->author = getStringCopy(ANONYMOUS_NAME);
2630 ti->program_title = NULL;
2631 ti->program_copyright = NULL;
2632 ti->program_company = NULL;
2634 ti->sort_priority = LEVELCLASS_UNDEFINED; // default: least priority
2635 ti->latest_engine = FALSE; // default: get from level
2636 ti->parent_link = FALSE;
2637 ti->is_copy = FALSE;
2638 ti->in_user_dir = FALSE;
2639 ti->user_defined = FALSE;
2641 ti->class_desc = NULL;
2643 ti->infotext = getStringCopy(TREE_INFOTEXT(ti->type));
2645 if (ti->type == TREE_TYPE_LEVEL_DIR)
2647 ti->imported_from = NULL;
2648 ti->imported_by = NULL;
2649 ti->tested_by = NULL;
2651 ti->graphics_set_ecs = NULL;
2652 ti->graphics_set_aga = NULL;
2653 ti->graphics_set = NULL;
2654 ti->sounds_set_default = NULL;
2655 ti->sounds_set_lowpass = NULL;
2656 ti->sounds_set = NULL;
2657 ti->music_set = NULL;
2658 ti->graphics_path = getStringCopy(UNDEFINED_FILENAME);
2659 ti->sounds_path = getStringCopy(UNDEFINED_FILENAME);
2660 ti->music_path = getStringCopy(UNDEFINED_FILENAME);
2662 ti->level_filename = NULL;
2663 ti->level_filetype = NULL;
2665 ti->special_flags = NULL;
2668 ti->first_level = 0;
2670 ti->level_group = FALSE;
2671 ti->handicap_level = 0;
2672 ti->readonly = TRUE;
2673 ti->handicap = TRUE;
2674 ti->skip_levels = FALSE;
2676 ti->use_emc_tiles = FALSE;
2680 static void setTreeInfoToDefaultsFromParent(TreeInfo *ti, TreeInfo *parent)
2684 Warn("setTreeInfoToDefaultsFromParent(): parent == NULL");
2686 setTreeInfoToDefaults(ti, TREE_TYPE_UNDEFINED);
2691 // copy all values from the parent structure
2693 ti->type = parent->type;
2695 ti->node_top = parent->node_top;
2696 ti->node_parent = parent;
2697 ti->node_group = NULL;
2704 ti->fullpath = NULL;
2705 ti->basepath = NULL;
2706 ti->identifier = NULL;
2707 ti->name = getStringCopy(ANONYMOUS_NAME);
2708 ti->name_sorting = NULL;
2709 ti->author = getStringCopy(parent->author);
2710 ti->year = getStringCopy(parent->year);
2712 ti->program_title = getStringCopy(parent->program_title);
2713 ti->program_copyright = getStringCopy(parent->program_copyright);
2714 ti->program_company = getStringCopy(parent->program_company);
2716 ti->sort_priority = parent->sort_priority;
2717 ti->latest_engine = parent->latest_engine;
2718 ti->parent_link = FALSE;
2719 ti->is_copy = FALSE;
2720 ti->in_user_dir = parent->in_user_dir;
2721 ti->user_defined = parent->user_defined;
2722 ti->color = parent->color;
2723 ti->class_desc = getStringCopy(parent->class_desc);
2725 ti->infotext = getStringCopy(parent->infotext);
2727 if (ti->type == TREE_TYPE_LEVEL_DIR)
2729 ti->imported_from = getStringCopy(parent->imported_from);
2730 ti->imported_by = getStringCopy(parent->imported_by);
2731 ti->tested_by = getStringCopy(parent->tested_by);
2733 ti->graphics_set_ecs = getStringCopy(parent->graphics_set_ecs);
2734 ti->graphics_set_aga = getStringCopy(parent->graphics_set_aga);
2735 ti->graphics_set = getStringCopy(parent->graphics_set);
2736 ti->sounds_set_default = getStringCopy(parent->sounds_set_default);
2737 ti->sounds_set_lowpass = getStringCopy(parent->sounds_set_lowpass);
2738 ti->sounds_set = getStringCopy(parent->sounds_set);
2739 ti->music_set = getStringCopy(parent->music_set);
2740 ti->graphics_path = getStringCopy(UNDEFINED_FILENAME);
2741 ti->sounds_path = getStringCopy(UNDEFINED_FILENAME);
2742 ti->music_path = getStringCopy(UNDEFINED_FILENAME);
2744 ti->level_filename = getStringCopy(parent->level_filename);
2745 ti->level_filetype = getStringCopy(parent->level_filetype);
2747 ti->special_flags = getStringCopy(parent->special_flags);
2749 ti->levels = parent->levels;
2750 ti->first_level = parent->first_level;
2751 ti->last_level = parent->last_level;
2752 ti->level_group = FALSE;
2753 ti->handicap_level = parent->handicap_level;
2754 ti->readonly = parent->readonly;
2755 ti->handicap = parent->handicap;
2756 ti->skip_levels = parent->skip_levels;
2758 ti->use_emc_tiles = parent->use_emc_tiles;
2762 static TreeInfo *getTreeInfoCopy(TreeInfo *ti)
2764 TreeInfo *ti_copy = newTreeInfo();
2766 // copy all values from the original structure
2768 ti_copy->type = ti->type;
2770 ti_copy->node_top = ti->node_top;
2771 ti_copy->node_parent = ti->node_parent;
2772 ti_copy->node_group = ti->node_group;
2773 ti_copy->next = ti->next;
2775 ti_copy->cl_first = ti->cl_first;
2776 ti_copy->cl_cursor = ti->cl_cursor;
2778 ti_copy->subdir = getStringCopy(ti->subdir);
2779 ti_copy->fullpath = getStringCopy(ti->fullpath);
2780 ti_copy->basepath = getStringCopy(ti->basepath);
2781 ti_copy->identifier = getStringCopy(ti->identifier);
2782 ti_copy->name = getStringCopy(ti->name);
2783 ti_copy->name_sorting = getStringCopy(ti->name_sorting);
2784 ti_copy->author = getStringCopy(ti->author);
2785 ti_copy->year = getStringCopy(ti->year);
2787 ti_copy->program_title = getStringCopy(ti->program_title);
2788 ti_copy->program_copyright = getStringCopy(ti->program_copyright);
2789 ti_copy->program_company = getStringCopy(ti->program_company);
2791 ti_copy->imported_from = getStringCopy(ti->imported_from);
2792 ti_copy->imported_by = getStringCopy(ti->imported_by);
2793 ti_copy->tested_by = getStringCopy(ti->tested_by);
2795 ti_copy->graphics_set_ecs = getStringCopy(ti->graphics_set_ecs);
2796 ti_copy->graphics_set_aga = getStringCopy(ti->graphics_set_aga);
2797 ti_copy->graphics_set = getStringCopy(ti->graphics_set);
2798 ti_copy->sounds_set_default = getStringCopy(ti->sounds_set_default);
2799 ti_copy->sounds_set_lowpass = getStringCopy(ti->sounds_set_lowpass);
2800 ti_copy->sounds_set = getStringCopy(ti->sounds_set);
2801 ti_copy->music_set = getStringCopy(ti->music_set);
2802 ti_copy->graphics_path = getStringCopy(ti->graphics_path);
2803 ti_copy->sounds_path = getStringCopy(ti->sounds_path);
2804 ti_copy->music_path = getStringCopy(ti->music_path);
2806 ti_copy->level_filename = getStringCopy(ti->level_filename);
2807 ti_copy->level_filetype = getStringCopy(ti->level_filetype);
2809 ti_copy->special_flags = getStringCopy(ti->special_flags);
2811 ti_copy->levels = ti->levels;
2812 ti_copy->first_level = ti->first_level;
2813 ti_copy->last_level = ti->last_level;
2814 ti_copy->sort_priority = ti->sort_priority;
2816 ti_copy->latest_engine = ti->latest_engine;
2818 ti_copy->level_group = ti->level_group;
2819 ti_copy->parent_link = ti->parent_link;
2820 ti_copy->is_copy = ti->is_copy;
2821 ti_copy->in_user_dir = ti->in_user_dir;
2822 ti_copy->user_defined = ti->user_defined;
2823 ti_copy->readonly = ti->readonly;
2824 ti_copy->handicap = ti->handicap;
2825 ti_copy->skip_levels = ti->skip_levels;
2827 ti_copy->use_emc_tiles = ti->use_emc_tiles;
2829 ti_copy->color = ti->color;
2830 ti_copy->class_desc = getStringCopy(ti->class_desc);
2831 ti_copy->handicap_level = ti->handicap_level;
2833 ti_copy->infotext = getStringCopy(ti->infotext);
2838 void freeTreeInfo(TreeInfo *ti)
2843 checked_free(ti->subdir);
2844 checked_free(ti->fullpath);
2845 checked_free(ti->basepath);
2846 checked_free(ti->identifier);
2848 checked_free(ti->name);
2849 checked_free(ti->name_sorting);
2850 checked_free(ti->author);
2851 checked_free(ti->year);
2853 checked_free(ti->program_title);
2854 checked_free(ti->program_copyright);
2855 checked_free(ti->program_company);
2857 checked_free(ti->class_desc);
2859 checked_free(ti->infotext);
2861 if (ti->type == TREE_TYPE_LEVEL_DIR)
2863 checked_free(ti->imported_from);
2864 checked_free(ti->imported_by);
2865 checked_free(ti->tested_by);
2867 checked_free(ti->graphics_set_ecs);
2868 checked_free(ti->graphics_set_aga);
2869 checked_free(ti->graphics_set);
2870 checked_free(ti->sounds_set_default);
2871 checked_free(ti->sounds_set_lowpass);
2872 checked_free(ti->sounds_set);
2873 checked_free(ti->music_set);
2875 checked_free(ti->graphics_path);
2876 checked_free(ti->sounds_path);
2877 checked_free(ti->music_path);
2879 checked_free(ti->level_filename);
2880 checked_free(ti->level_filetype);
2882 checked_free(ti->special_flags);
2885 // recursively free child node
2887 freeTreeInfo(ti->node_group);
2889 // recursively free next node
2891 freeTreeInfo(ti->next);
2896 void setSetupInfo(struct TokenInfo *token_info,
2897 int token_nr, char *token_value)
2899 int token_type = token_info[token_nr].type;
2900 void *setup_value = token_info[token_nr].value;
2902 if (token_value == NULL)
2905 // set setup field to corresponding token value
2910 *(boolean *)setup_value = get_boolean_from_string(token_value);
2914 *(int *)setup_value = get_switch3_from_string(token_value);
2918 *(Key *)setup_value = getKeyFromKeyName(token_value);
2922 *(Key *)setup_value = getKeyFromX11KeyName(token_value);
2926 *(int *)setup_value = get_integer_from_string(token_value);
2930 checked_free(*(char **)setup_value);
2931 *(char **)setup_value = getStringCopy(token_value);
2935 *(int *)setup_value = get_player_nr_from_string(token_value);
2943 static int compareTreeInfoEntries(const void *object1, const void *object2)
2945 const TreeInfo *entry1 = *((TreeInfo **)object1);
2946 const TreeInfo *entry2 = *((TreeInfo **)object2);
2947 int tree_sorting1 = TREE_SORTING(entry1);
2948 int tree_sorting2 = TREE_SORTING(entry2);
2950 if (tree_sorting1 != tree_sorting2)
2951 return (tree_sorting1 - tree_sorting2);
2953 return strcasecmp(entry1->name_sorting, entry2->name_sorting);
2956 static TreeInfo *createParentTreeInfoNode(TreeInfo *node_parent)
2960 if (node_parent == NULL)
2963 ti_new = newTreeInfo();
2964 setTreeInfoToDefaults(ti_new, node_parent->type);
2966 ti_new->node_parent = node_parent;
2967 ti_new->parent_link = TRUE;
2969 setString(&ti_new->identifier, node_parent->identifier);
2970 setString(&ti_new->name, BACKLINK_TEXT_PARENT);
2971 setString(&ti_new->name_sorting, ti_new->name);
2973 setString(&ti_new->subdir, STRING_PARENT_DIRECTORY);
2974 setString(&ti_new->fullpath, node_parent->fullpath);
2976 ti_new->sort_priority = LEVELCLASS_PARENT;
2977 ti_new->latest_engine = node_parent->latest_engine;
2979 setString(&ti_new->class_desc, getLevelClassDescription(ti_new));
2981 pushTreeInfo(&node_parent->node_group, ti_new);
2986 static TreeInfo *createTopTreeInfoNode(TreeInfo *node_first)
2988 if (node_first == NULL)
2991 TreeInfo *ti_new = newTreeInfo();
2992 int type = node_first->type;
2994 setTreeInfoToDefaults(ti_new, type);
2996 ti_new->node_parent = NULL;
2997 ti_new->parent_link = FALSE;
2999 setString(&ti_new->identifier, "top_tree_node");
3000 setString(&ti_new->name, TREE_INFOTEXT(type));
3001 setString(&ti_new->name_sorting, ti_new->name);
3003 setString(&ti_new->subdir, STRING_TOP_DIRECTORY);
3004 setString(&ti_new->fullpath, ".");
3006 ti_new->sort_priority = LEVELCLASS_TOP;
3007 ti_new->latest_engine = node_first->latest_engine;
3009 setString(&ti_new->class_desc, TREE_INFOTEXT(type));
3011 ti_new->node_group = node_first;
3012 ti_new->level_group = TRUE;
3014 TreeInfo *ti_new2 = createParentTreeInfoNode(ti_new);
3016 setString(&ti_new2->name, TREE_BACKLINK_TEXT(type));
3017 setString(&ti_new2->name_sorting, ti_new2->name);
3022 static void setTreeInfoParentNodes(TreeInfo *node, TreeInfo *node_parent)
3026 if (node->node_group)
3027 setTreeInfoParentNodes(node->node_group, node);
3029 node->node_parent = node_parent;
3036 // ----------------------------------------------------------------------------
3037 // functions for handling level and custom artwork info cache
3038 // ----------------------------------------------------------------------------
3040 static void LoadArtworkInfoCache(void)
3042 InitCacheDirectory();
3044 if (artworkinfo_cache_old == NULL)
3046 char *filename = getPath2(getCacheDir(), ARTWORKINFO_CACHE_FILE);
3048 // try to load artwork info hash from already existing cache file
3049 artworkinfo_cache_old = loadSetupFileHash(filename);
3051 // try to get program version that artwork info cache was written with
3052 char *version = getHashEntry(artworkinfo_cache_old, "program.version");
3054 // check program version of artwork info cache against current version
3055 if (!strEqual(version, program.version_string))
3057 freeSetupFileHash(artworkinfo_cache_old);
3059 artworkinfo_cache_old = NULL;
3062 // if no artwork info cache file was found, start with empty hash
3063 if (artworkinfo_cache_old == NULL)
3064 artworkinfo_cache_old = newSetupFileHash();
3069 if (artworkinfo_cache_new == NULL)
3070 artworkinfo_cache_new = newSetupFileHash();
3072 update_artworkinfo_cache = FALSE;
3075 static void SaveArtworkInfoCache(void)
3077 if (!update_artworkinfo_cache)
3080 char *filename = getPath2(getCacheDir(), ARTWORKINFO_CACHE_FILE);
3082 InitCacheDirectory();
3084 saveSetupFileHash(artworkinfo_cache_new, filename);
3089 static char *getCacheTokenPrefix(char *prefix1, char *prefix2)
3091 static char *prefix = NULL;
3093 checked_free(prefix);
3095 prefix = getStringCat2WithSeparator(prefix1, prefix2, ".");
3100 // (identical to above function, but separate string buffer needed -- nasty)
3101 static char *getCacheToken(char *prefix, char *suffix)
3103 static char *token = NULL;
3105 checked_free(token);
3107 token = getStringCat2WithSeparator(prefix, suffix, ".");
3112 static char *getFileTimestampString(char *filename)
3114 return getStringCopy(i_to_a(getFileTimestampEpochSeconds(filename)));
3117 static boolean modifiedFileTimestamp(char *filename, char *timestamp_string)
3119 struct stat file_status;
3121 if (timestamp_string == NULL)
3124 if (!fileExists(filename)) // file does not exist
3125 return (atoi(timestamp_string) != 0);
3127 if (stat(filename, &file_status) != 0) // cannot stat file
3130 return (file_status.st_mtime != atoi(timestamp_string));
3133 static TreeInfo *getArtworkInfoCacheEntry(LevelDirTree *level_node, int type)
3135 char *identifier = level_node->subdir;
3136 char *type_string = ARTWORK_DIRECTORY(type);
3137 char *token_prefix = getCacheTokenPrefix(type_string, identifier);
3138 char *token_main = getCacheToken(token_prefix, "CACHED");
3139 char *cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
3140 boolean cached = (cache_entry != NULL && strEqual(cache_entry, "true"));
3141 TreeInfo *artwork_info = NULL;
3143 if (!use_artworkinfo_cache)
3146 if (optional_tokens_hash == NULL)
3150 // create hash from list of optional tokens (for quick access)
3151 optional_tokens_hash = newSetupFileHash();
3152 for (i = 0; optional_tokens[i] != NULL; i++)
3153 setHashEntry(optional_tokens_hash, optional_tokens[i], "");
3160 artwork_info = newTreeInfo();
3161 setTreeInfoToDefaults(artwork_info, type);
3163 // set all structure fields according to the token/value pairs
3164 ldi = *artwork_info;
3165 for (i = 0; artworkinfo_tokens[i].type != -1; i++)
3167 char *token_suffix = artworkinfo_tokens[i].text;
3168 char *token = getCacheToken(token_prefix, token_suffix);
3169 char *value = getHashEntry(artworkinfo_cache_old, token);
3171 (getHashEntry(optional_tokens_hash, token_suffix) != NULL);
3173 setSetupInfo(artworkinfo_tokens, i, value);
3175 // check if cache entry for this item is mandatory, but missing
3176 if (value == NULL && !optional)
3178 Warn("missing cache entry '%s'", token);
3184 *artwork_info = ldi;
3189 char *filename_levelinfo = getPath2(getLevelDirFromTreeInfo(level_node),
3190 LEVELINFO_FILENAME);
3191 char *filename_artworkinfo = getPath2(getSetupArtworkDir(artwork_info),
3192 ARTWORKINFO_FILENAME(type));
3194 // check if corresponding "levelinfo.conf" file has changed
3195 token_main = getCacheToken(token_prefix, "TIMESTAMP_LEVELINFO");
3196 cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
3198 if (modifiedFileTimestamp(filename_levelinfo, cache_entry))
3201 // check if corresponding "<artworkinfo>.conf" file has changed
3202 token_main = getCacheToken(token_prefix, "TIMESTAMP_ARTWORKINFO");
3203 cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
3205 if (modifiedFileTimestamp(filename_artworkinfo, cache_entry))
3208 checked_free(filename_levelinfo);
3209 checked_free(filename_artworkinfo);
3212 if (!cached && artwork_info != NULL)
3214 freeTreeInfo(artwork_info);
3219 return artwork_info;
3222 static void setArtworkInfoCacheEntry(TreeInfo *artwork_info,
3223 LevelDirTree *level_node, int type)
3225 char *identifier = level_node->subdir;
3226 char *type_string = ARTWORK_DIRECTORY(type);
3227 char *token_prefix = getCacheTokenPrefix(type_string, identifier);
3228 char *token_main = getCacheToken(token_prefix, "CACHED");
3229 boolean set_cache_timestamps = TRUE;
3232 setHashEntry(artworkinfo_cache_new, token_main, "true");
3234 if (set_cache_timestamps)
3236 char *filename_levelinfo = getPath2(getLevelDirFromTreeInfo(level_node),
3237 LEVELINFO_FILENAME);
3238 char *filename_artworkinfo = getPath2(getSetupArtworkDir(artwork_info),
3239 ARTWORKINFO_FILENAME(type));
3240 char *timestamp_levelinfo = getFileTimestampString(filename_levelinfo);
3241 char *timestamp_artworkinfo = getFileTimestampString(filename_artworkinfo);
3243 token_main = getCacheToken(token_prefix, "TIMESTAMP_LEVELINFO");
3244 setHashEntry(artworkinfo_cache_new, token_main, timestamp_levelinfo);
3246 token_main = getCacheToken(token_prefix, "TIMESTAMP_ARTWORKINFO");
3247 setHashEntry(artworkinfo_cache_new, token_main, timestamp_artworkinfo);
3249 checked_free(filename_levelinfo);
3250 checked_free(filename_artworkinfo);
3251 checked_free(timestamp_levelinfo);
3252 checked_free(timestamp_artworkinfo);
3255 ldi = *artwork_info;
3256 for (i = 0; artworkinfo_tokens[i].type != -1; i++)
3258 char *token = getCacheToken(token_prefix, artworkinfo_tokens[i].text);
3259 char *value = getSetupValue(artworkinfo_tokens[i].type,
3260 artworkinfo_tokens[i].value);
3262 setHashEntry(artworkinfo_cache_new, token, value);
3267 // ----------------------------------------------------------------------------
3268 // functions for loading level info and custom artwork info
3269 // ----------------------------------------------------------------------------
3271 int GetZipFileTreeType(char *zip_filename)
3273 static char *top_dir_path = NULL;
3274 static char *top_dir_conf_filename[NUM_BASE_TREE_TYPES] = { NULL };
3275 static char *conf_basename[NUM_BASE_TREE_TYPES] =
3277 GRAPHICSINFO_FILENAME,
3278 SOUNDSINFO_FILENAME,
3284 checked_free(top_dir_path);
3285 top_dir_path = NULL;
3287 for (j = 0; j < NUM_BASE_TREE_TYPES; j++)
3289 checked_free(top_dir_conf_filename[j]);
3290 top_dir_conf_filename[j] = NULL;
3293 char **zip_entries = zip_list(zip_filename);
3295 // check if zip file successfully opened
3296 if (zip_entries == NULL || zip_entries[0] == NULL)
3297 return TREE_TYPE_UNDEFINED;
3299 // first zip file entry is expected to be top level directory
3300 char *top_dir = zip_entries[0];
3302 // check if valid top level directory found in zip file
3303 if (!strSuffix(top_dir, "/"))
3304 return TREE_TYPE_UNDEFINED;
3306 // get filenames of valid configuration files in top level directory
3307 for (j = 0; j < NUM_BASE_TREE_TYPES; j++)
3308 top_dir_conf_filename[j] = getStringCat2(top_dir, conf_basename[j]);
3310 int tree_type = TREE_TYPE_UNDEFINED;
3313 while (zip_entries[e] != NULL)
3315 // check if every zip file entry is below top level directory
3316 if (!strPrefix(zip_entries[e], top_dir))
3317 return TREE_TYPE_UNDEFINED;
3319 // check if this zip file entry is a valid configuration filename
3320 for (j = 0; j < NUM_BASE_TREE_TYPES; j++)
3322 if (strEqual(zip_entries[e], top_dir_conf_filename[j]))
3324 // only exactly one valid configuration file allowed
3325 if (tree_type != TREE_TYPE_UNDEFINED)
3326 return TREE_TYPE_UNDEFINED;
3338 static boolean CheckZipFileForDirectory(char *zip_filename, char *directory,
3341 static char *top_dir_path = NULL;
3342 static char *top_dir_conf_filename = NULL;
3344 checked_free(top_dir_path);
3345 checked_free(top_dir_conf_filename);
3347 top_dir_path = NULL;
3348 top_dir_conf_filename = NULL;
3350 char *conf_basename = (tree_type == TREE_TYPE_LEVEL_DIR ? LEVELINFO_FILENAME :
3351 ARTWORKINFO_FILENAME(tree_type));
3353 // check if valid configuration filename determined
3354 if (conf_basename == NULL || strEqual(conf_basename, ""))
3357 char **zip_entries = zip_list(zip_filename);
3359 // check if zip file successfully opened
3360 if (zip_entries == NULL || zip_entries[0] == NULL)
3363 // first zip file entry is expected to be top level directory
3364 char *top_dir = zip_entries[0];
3366 // check if valid top level directory found in zip file
3367 if (!strSuffix(top_dir, "/"))
3370 // get path of extracted top level directory
3371 top_dir_path = getPath2(directory, top_dir);
3373 // remove trailing directory separator from top level directory path
3374 // (required to be able to check for file and directory in next step)
3375 top_dir_path[strlen(top_dir_path) - 1] = '\0';
3377 // check if zip file's top level directory already exists in target directory
3378 if (fileExists(top_dir_path)) // (checks for file and directory)
3381 // get filename of configuration file in top level directory
3382 top_dir_conf_filename = getStringCat2(top_dir, conf_basename);
3384 boolean found_top_dir_conf_filename = FALSE;
3387 while (zip_entries[i] != NULL)
3389 // check if every zip file entry is below top level directory
3390 if (!strPrefix(zip_entries[i], top_dir))
3393 // check if this zip file entry is the configuration filename
3394 if (strEqual(zip_entries[i], top_dir_conf_filename))
3395 found_top_dir_conf_filename = TRUE;
3400 // check if valid configuration filename was found in zip file
3401 if (!found_top_dir_conf_filename)
3407 char *ExtractZipFileIntoDirectory(char *zip_filename, char *directory,
3410 boolean zip_file_valid = CheckZipFileForDirectory(zip_filename, directory,
3413 if (!zip_file_valid)
3415 Warn("zip file '%s' rejected!", zip_filename);
3420 char **zip_entries = zip_extract(zip_filename, directory);
3422 if (zip_entries == NULL)
3424 Warn("zip file '%s' could not be extracted!", zip_filename);
3429 Info("zip file '%s' successfully extracted!", zip_filename);
3431 // first zip file entry contains top level directory
3432 char *top_dir = zip_entries[0];
3434 // remove trailing directory separator from top level directory
3435 top_dir[strlen(top_dir) - 1] = '\0';
3440 static void ProcessZipFilesInDirectory(char *directory, int tree_type)
3443 DirectoryEntry *dir_entry;
3445 if ((dir = openDirectory(directory)) == NULL)
3447 // display error if directory is main "options.graphics_directory" etc.
3448 if (tree_type == TREE_TYPE_LEVEL_DIR ||
3449 directory == OPTIONS_ARTWORK_DIRECTORY(tree_type))
3450 Warn("cannot read directory '%s'", directory);
3455 while ((dir_entry = readDirectory(dir)) != NULL) // loop all entries
3457 // skip non-zip files (and also directories with zip extension)
3458 if (!strSuffixLower(dir_entry->basename, ".zip") || dir_entry->is_directory)
3461 char *zip_filename = getPath2(directory, dir_entry->basename);
3462 char *zip_filename_extracted = getStringCat2(zip_filename, ".extracted");
3463 char *zip_filename_rejected = getStringCat2(zip_filename, ".rejected");
3465 // check if zip file hasn't already been extracted or rejected
3466 if (!fileExists(zip_filename_extracted) &&
3467 !fileExists(zip_filename_rejected))
3469 char *top_dir = ExtractZipFileIntoDirectory(zip_filename, directory,
3471 char *marker_filename = (top_dir != NULL ? zip_filename_extracted :
3472 zip_filename_rejected);
3475 // create empty file to mark zip file as extracted or rejected
3476 if ((marker_file = fopen(marker_filename, MODE_WRITE)))
3477 fclose(marker_file);
3480 free(zip_filename_extracted);
3481 free(zip_filename_rejected);
3485 closeDirectory(dir);
3488 // forward declaration for recursive call by "LoadLevelInfoFromLevelDir()"
3489 static void LoadLevelInfoFromLevelDir(TreeInfo **, TreeInfo *, char *);
3491 static boolean LoadLevelInfoFromLevelConf(TreeInfo **node_first,
3492 TreeInfo *node_parent,
3493 char *level_directory,
3494 char *directory_name)
3496 char *directory_path = getPath2(level_directory, directory_name);
3497 char *filename = getPath2(directory_path, LEVELINFO_FILENAME);
3498 SetupFileHash *setup_file_hash;
3499 LevelDirTree *leveldir_new = NULL;
3502 // unless debugging, silently ignore directories without "levelinfo.conf"
3503 if (!options.debug && !fileExists(filename))
3505 free(directory_path);
3511 setup_file_hash = loadSetupFileHash(filename);
3513 if (setup_file_hash == NULL)
3515 #if DEBUG_NO_CONFIG_FILE
3516 Debug("setup", "ignoring level directory '%s'", directory_path);
3519 free(directory_path);
3525 leveldir_new = newTreeInfo();
3528 setTreeInfoToDefaultsFromParent(leveldir_new, node_parent);
3530 setTreeInfoToDefaults(leveldir_new, TREE_TYPE_LEVEL_DIR);
3532 leveldir_new->subdir = getStringCopy(directory_name);
3534 // set all structure fields according to the token/value pairs
3535 ldi = *leveldir_new;
3536 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
3537 setSetupInfo(levelinfo_tokens, i,
3538 getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
3539 *leveldir_new = ldi;
3541 if (strEqual(leveldir_new->name, ANONYMOUS_NAME))
3542 setString(&leveldir_new->name, leveldir_new->subdir);
3544 if (leveldir_new->identifier == NULL)
3545 leveldir_new->identifier = getStringCopy(leveldir_new->subdir);
3547 if (leveldir_new->name_sorting == NULL)
3548 leveldir_new->name_sorting = getStringCopy(leveldir_new->name);
3550 if (node_parent == NULL) // top level group
3552 leveldir_new->basepath = getStringCopy(level_directory);
3553 leveldir_new->fullpath = getStringCopy(leveldir_new->subdir);
3555 else // sub level group
3557 leveldir_new->basepath = getStringCopy(node_parent->basepath);
3558 leveldir_new->fullpath = getPath2(node_parent->fullpath, directory_name);
3561 leveldir_new->last_level =
3562 leveldir_new->first_level + leveldir_new->levels - 1;
3564 leveldir_new->in_user_dir =
3565 (!strEqual(leveldir_new->basepath, options.level_directory));
3567 // adjust some settings if user's private level directory was detected
3568 if (leveldir_new->sort_priority == LEVELCLASS_UNDEFINED &&
3569 leveldir_new->in_user_dir &&
3570 (strEqual(leveldir_new->subdir, getLoginName()) ||
3571 strEqual(leveldir_new->name, getLoginName()) ||
3572 strEqual(leveldir_new->author, getRealName())))
3574 leveldir_new->sort_priority = LEVELCLASS_PRIVATE_START;
3575 leveldir_new->readonly = FALSE;
3578 leveldir_new->user_defined =
3579 (leveldir_new->in_user_dir && IS_LEVELCLASS_PRIVATE(leveldir_new));
3581 setString(&leveldir_new->class_desc, getLevelClassDescription(leveldir_new));
3583 leveldir_new->handicap_level = // set handicap to default value
3584 (leveldir_new->user_defined || !leveldir_new->handicap ?
3585 leveldir_new->last_level : leveldir_new->first_level);
3587 DrawInitText(leveldir_new->name, 150, FC_YELLOW);
3589 pushTreeInfo(node_first, leveldir_new);
3591 freeSetupFileHash(setup_file_hash);
3593 if (leveldir_new->level_group)
3595 // create node to link back to current level directory
3596 createParentTreeInfoNode(leveldir_new);
3598 // recursively step into sub-directory and look for more level series
3599 LoadLevelInfoFromLevelDir(&leveldir_new->node_group,
3600 leveldir_new, directory_path);
3603 free(directory_path);
3609 static void LoadLevelInfoFromLevelDir(TreeInfo **node_first,
3610 TreeInfo *node_parent,
3611 char *level_directory)
3613 // ---------- 1st stage: process any level set zip files ----------
3615 ProcessZipFilesInDirectory(level_directory, TREE_TYPE_LEVEL_DIR);
3617 // ---------- 2nd stage: check for level set directories ----------
3620 DirectoryEntry *dir_entry;
3621 boolean valid_entry_found = FALSE;
3623 if ((dir = openDirectory(level_directory)) == NULL)
3625 Warn("cannot read level directory '%s'", level_directory);
3630 while ((dir_entry = readDirectory(dir)) != NULL) // loop all entries
3632 char *directory_name = dir_entry->basename;
3633 char *directory_path = getPath2(level_directory, directory_name);
3635 // skip entries for current and parent directory
3636 if (strEqual(directory_name, ".") ||
3637 strEqual(directory_name, ".."))
3639 free(directory_path);
3644 // find out if directory entry is itself a directory
3645 if (!dir_entry->is_directory) // not a directory
3647 free(directory_path);
3652 free(directory_path);
3654 if (strEqual(directory_name, GRAPHICS_DIRECTORY) ||
3655 strEqual(directory_name, SOUNDS_DIRECTORY) ||
3656 strEqual(directory_name, MUSIC_DIRECTORY))
3659 valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
3664 closeDirectory(dir);
3666 // special case: top level directory may directly contain "levelinfo.conf"
3667 if (node_parent == NULL && !valid_entry_found)
3669 // check if this directory directly contains a file "levelinfo.conf"
3670 valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
3671 level_directory, ".");
3674 if (!valid_entry_found)
3675 Warn("cannot find any valid level series in directory '%s'",
3679 boolean AdjustGraphicsForEMC(void)
3681 boolean settings_changed = FALSE;
3683 settings_changed |= adjustTreeGraphicsForEMC(leveldir_first_all);
3684 settings_changed |= adjustTreeGraphicsForEMC(leveldir_first);
3686 return settings_changed;
3689 boolean AdjustSoundsForEMC(void)
3691 boolean settings_changed = FALSE;
3693 settings_changed |= adjustTreeSoundsForEMC(leveldir_first_all);
3694 settings_changed |= adjustTreeSoundsForEMC(leveldir_first);
3696 return settings_changed;
3699 void LoadLevelInfo(void)
3701 InitUserLevelDirectory(getLoginName());
3703 DrawInitText("Loading level series", 120, FC_GREEN);
3705 LoadLevelInfoFromLevelDir(&leveldir_first, NULL, options.level_directory);
3706 LoadLevelInfoFromLevelDir(&leveldir_first, NULL, getUserLevelDir(NULL));
3708 leveldir_first = createTopTreeInfoNode(leveldir_first);
3710 /* after loading all level set information, clone the level directory tree
3711 and remove all level sets without levels (these may still contain artwork
3712 to be offered in the setup menu as "custom artwork", and are therefore
3713 checked for existing artwork in the function "LoadLevelArtworkInfo()") */
3714 leveldir_first_all = leveldir_first;
3715 cloneTree(&leveldir_first, leveldir_first_all, TRUE);
3717 AdjustGraphicsForEMC();
3718 AdjustSoundsForEMC();
3720 // before sorting, the first entries will be from the user directory
3721 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
3723 if (leveldir_first == NULL)
3724 Fail("cannot find any valid level series in any directory");
3726 sortTreeInfo(&leveldir_first);
3728 #if ENABLE_UNUSED_CODE
3729 dumpTreeInfo(leveldir_first, 0);
3733 static boolean LoadArtworkInfoFromArtworkConf(TreeInfo **node_first,
3734 TreeInfo *node_parent,
3735 char *base_directory,
3736 char *directory_name, int type)
3738 char *directory_path = getPath2(base_directory, directory_name);
3739 char *filename = getPath2(directory_path, ARTWORKINFO_FILENAME(type));
3740 SetupFileHash *setup_file_hash = NULL;
3741 TreeInfo *artwork_new = NULL;
3744 if (fileExists(filename))
3745 setup_file_hash = loadSetupFileHash(filename);
3747 if (setup_file_hash == NULL) // no config file -- look for artwork files
3750 DirectoryEntry *dir_entry;
3751 boolean valid_file_found = FALSE;
3753 if ((dir = openDirectory(directory_path)) != NULL)
3755 while ((dir_entry = readDirectory(dir)) != NULL)
3757 if (FileIsArtworkType(dir_entry->filename, type))
3759 valid_file_found = TRUE;
3765 closeDirectory(dir);
3768 if (!valid_file_found)
3770 #if DEBUG_NO_CONFIG_FILE
3771 if (!strEqual(directory_name, "."))
3772 Debug("setup", "ignoring artwork directory '%s'", directory_path);
3775 free(directory_path);
3782 artwork_new = newTreeInfo();
3785 setTreeInfoToDefaultsFromParent(artwork_new, node_parent);
3787 setTreeInfoToDefaults(artwork_new, type);
3789 artwork_new->subdir = getStringCopy(directory_name);
3791 if (setup_file_hash) // (before defining ".color" and ".class_desc")
3793 // set all structure fields according to the token/value pairs
3795 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
3796 setSetupInfo(levelinfo_tokens, i,
3797 getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
3800 if (strEqual(artwork_new->name, ANONYMOUS_NAME))
3801 setString(&artwork_new->name, artwork_new->subdir);
3803 if (artwork_new->identifier == NULL)
3804 artwork_new->identifier = getStringCopy(artwork_new->subdir);
3806 if (artwork_new->name_sorting == NULL)
3807 artwork_new->name_sorting = getStringCopy(artwork_new->name);
3810 if (node_parent == NULL) // top level group
3812 artwork_new->basepath = getStringCopy(base_directory);
3813 artwork_new->fullpath = getStringCopy(artwork_new->subdir);
3815 else // sub level group
3817 artwork_new->basepath = getStringCopy(node_parent->basepath);
3818 artwork_new->fullpath = getPath2(node_parent->fullpath, directory_name);
3821 artwork_new->in_user_dir =
3822 (!strEqual(artwork_new->basepath, OPTIONS_ARTWORK_DIRECTORY(type)));
3824 setString(&artwork_new->class_desc, getLevelClassDescription(artwork_new));
3826 if (setup_file_hash == NULL) // (after determining ".user_defined")
3828 if (strEqual(artwork_new->subdir, "."))
3830 if (artwork_new->user_defined)
3832 setString(&artwork_new->identifier, "private");
3833 artwork_new->sort_priority = ARTWORKCLASS_PRIVATE;
3837 setString(&artwork_new->identifier, "classic");
3838 artwork_new->sort_priority = ARTWORKCLASS_CLASSICS;
3841 setString(&artwork_new->class_desc,
3842 getLevelClassDescription(artwork_new));
3846 setString(&artwork_new->identifier, artwork_new->subdir);
3849 setString(&artwork_new->name, artwork_new->identifier);
3850 setString(&artwork_new->name_sorting, artwork_new->name);
3853 pushTreeInfo(node_first, artwork_new);
3855 freeSetupFileHash(setup_file_hash);
3857 free(directory_path);
3863 static void LoadArtworkInfoFromArtworkDir(TreeInfo **node_first,
3864 TreeInfo *node_parent,
3865 char *base_directory, int type)
3867 // ---------- 1st stage: process any artwork set zip files ----------
3869 ProcessZipFilesInDirectory(base_directory, type);
3871 // ---------- 2nd stage: check for artwork set directories ----------
3874 DirectoryEntry *dir_entry;
3875 boolean valid_entry_found = FALSE;
3877 if ((dir = openDirectory(base_directory)) == NULL)
3879 // display error if directory is main "options.graphics_directory" etc.
3880 if (base_directory == OPTIONS_ARTWORK_DIRECTORY(type))
3881 Warn("cannot read directory '%s'", base_directory);
3886 while ((dir_entry = readDirectory(dir)) != NULL) // loop all entries
3888 char *directory_name = dir_entry->basename;
3889 char *directory_path = getPath2(base_directory, directory_name);
3891 // skip directory entries for current and parent directory
3892 if (strEqual(directory_name, ".") ||
3893 strEqual(directory_name, ".."))
3895 free(directory_path);
3900 // skip directory entries which are not a directory
3901 if (!dir_entry->is_directory) // not a directory
3903 free(directory_path);
3908 free(directory_path);
3910 // check if this directory contains artwork with or without config file
3911 valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
3913 directory_name, type);
3916 closeDirectory(dir);
3918 // check if this directory directly contains artwork itself
3919 valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
3920 base_directory, ".",
3922 if (!valid_entry_found)
3923 Warn("cannot find any valid artwork in directory '%s'", base_directory);
3926 static TreeInfo *getDummyArtworkInfo(int type)
3928 // this is only needed when there is completely no artwork available
3929 TreeInfo *artwork_new = newTreeInfo();
3931 setTreeInfoToDefaults(artwork_new, type);
3933 setString(&artwork_new->subdir, UNDEFINED_FILENAME);
3934 setString(&artwork_new->fullpath, UNDEFINED_FILENAME);
3935 setString(&artwork_new->basepath, UNDEFINED_FILENAME);
3937 setString(&artwork_new->identifier, UNDEFINED_FILENAME);
3938 setString(&artwork_new->name, UNDEFINED_FILENAME);
3939 setString(&artwork_new->name_sorting, UNDEFINED_FILENAME);
3944 void SetCurrentArtwork(int type)
3946 ArtworkDirTree **current_ptr = ARTWORK_CURRENT_PTR(artwork, type);
3947 ArtworkDirTree *first_node = ARTWORK_FIRST_NODE(artwork, type);
3948 char *setup_set = SETUP_ARTWORK_SET(setup, type);
3949 char *default_subdir = ARTWORK_DEFAULT_SUBDIR(type);
3951 // set current artwork to artwork configured in setup menu
3952 *current_ptr = getTreeInfoFromIdentifier(first_node, setup_set);
3954 // if not found, set current artwork to default artwork
3955 if (*current_ptr == NULL)
3956 *current_ptr = getTreeInfoFromIdentifier(first_node, default_subdir);
3958 // if not found, set current artwork to first artwork in tree
3959 if (*current_ptr == NULL)
3960 *current_ptr = getFirstValidTreeInfoEntry(first_node);
3963 void ChangeCurrentArtworkIfNeeded(int type)
3965 char *current_identifier = ARTWORK_CURRENT_IDENTIFIER(artwork, type);
3966 char *setup_set = SETUP_ARTWORK_SET(setup, type);
3968 if (!strEqual(current_identifier, setup_set))
3969 SetCurrentArtwork(type);
3972 void LoadArtworkInfo(void)
3974 LoadArtworkInfoCache();
3976 DrawInitText("Looking for custom artwork", 120, FC_GREEN);
3978 LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
3979 options.graphics_directory,
3980 TREE_TYPE_GRAPHICS_DIR);
3981 LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
3982 getUserGraphicsDir(),
3983 TREE_TYPE_GRAPHICS_DIR);
3985 LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
3986 options.sounds_directory,
3987 TREE_TYPE_SOUNDS_DIR);
3988 LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
3990 TREE_TYPE_SOUNDS_DIR);
3992 LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
3993 options.music_directory,
3994 TREE_TYPE_MUSIC_DIR);
3995 LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
3997 TREE_TYPE_MUSIC_DIR);
3999 if (artwork.gfx_first == NULL)
4000 artwork.gfx_first = getDummyArtworkInfo(TREE_TYPE_GRAPHICS_DIR);
4001 if (artwork.snd_first == NULL)
4002 artwork.snd_first = getDummyArtworkInfo(TREE_TYPE_SOUNDS_DIR);
4003 if (artwork.mus_first == NULL)
4004 artwork.mus_first = getDummyArtworkInfo(TREE_TYPE_MUSIC_DIR);
4006 // before sorting, the first entries will be from the user directory
4007 SetCurrentArtwork(ARTWORK_TYPE_GRAPHICS);
4008 SetCurrentArtwork(ARTWORK_TYPE_SOUNDS);
4009 SetCurrentArtwork(ARTWORK_TYPE_MUSIC);
4011 artwork.gfx_current_identifier = artwork.gfx_current->identifier;
4012 artwork.snd_current_identifier = artwork.snd_current->identifier;
4013 artwork.mus_current_identifier = artwork.mus_current->identifier;
4015 #if ENABLE_UNUSED_CODE
4016 Debug("setup:LoadArtworkInfo", "graphics set == %s",
4017 artwork.gfx_current_identifier);
4018 Debug("setup:LoadArtworkInfo", "sounds set == %s",
4019 artwork.snd_current_identifier);
4020 Debug("setup:LoadArtworkInfo", "music set == %s",
4021 artwork.mus_current_identifier);
4024 sortTreeInfo(&artwork.gfx_first);
4025 sortTreeInfo(&artwork.snd_first);
4026 sortTreeInfo(&artwork.mus_first);
4028 #if ENABLE_UNUSED_CODE
4029 dumpTreeInfo(artwork.gfx_first, 0);
4030 dumpTreeInfo(artwork.snd_first, 0);
4031 dumpTreeInfo(artwork.mus_first, 0);
4035 static void MoveArtworkInfoIntoSubTree(ArtworkDirTree **artwork_node)
4037 ArtworkDirTree *artwork_new = newTreeInfo();
4038 char *top_node_name = "standalone artwork";
4040 setTreeInfoToDefaults(artwork_new, (*artwork_node)->type);
4042 artwork_new->level_group = TRUE;
4044 setString(&artwork_new->identifier, top_node_name);
4045 setString(&artwork_new->name, top_node_name);
4046 setString(&artwork_new->name_sorting, top_node_name);
4048 // create node to link back to current custom artwork directory
4049 createParentTreeInfoNode(artwork_new);
4051 // move existing custom artwork tree into newly created sub-tree
4052 artwork_new->node_group->next = *artwork_node;
4054 // change custom artwork tree to contain only newly created node
4055 *artwork_node = artwork_new;
4058 static void LoadArtworkInfoFromLevelInfoExt(ArtworkDirTree **artwork_node,
4059 ArtworkDirTree *node_parent,
4060 LevelDirTree *level_node,
4061 boolean empty_level_set_mode)
4063 int type = (*artwork_node)->type;
4065 // recursively check all level directories for artwork sub-directories
4069 boolean empty_level_set = (level_node->levels == 0);
4071 // check all tree entries for artwork, but skip parent link entries
4072 if (!level_node->parent_link && empty_level_set == empty_level_set_mode)
4074 TreeInfo *artwork_new = getArtworkInfoCacheEntry(level_node, type);
4075 boolean cached = (artwork_new != NULL);
4079 pushTreeInfo(artwork_node, artwork_new);
4083 TreeInfo *topnode_last = *artwork_node;
4084 char *path = getPath2(getLevelDirFromTreeInfo(level_node),
4085 ARTWORK_DIRECTORY(type));
4087 LoadArtworkInfoFromArtworkDir(artwork_node, NULL, path, type);
4089 if (topnode_last != *artwork_node) // check for newly added node
4091 artwork_new = *artwork_node;
4093 setString(&artwork_new->identifier, level_node->subdir);
4094 setString(&artwork_new->name, level_node->name);
4095 setString(&artwork_new->name_sorting, level_node->name_sorting);
4097 artwork_new->sort_priority = level_node->sort_priority;
4098 artwork_new->in_user_dir = level_node->in_user_dir;
4100 update_artworkinfo_cache = TRUE;
4106 // insert artwork info (from old cache or filesystem) into new cache
4107 if (artwork_new != NULL)
4108 setArtworkInfoCacheEntry(artwork_new, level_node, type);
4111 DrawInitText(level_node->name, 150, FC_YELLOW);
4113 if (level_node->node_group != NULL)
4115 TreeInfo *artwork_new = newTreeInfo();
4118 setTreeInfoToDefaultsFromParent(artwork_new, node_parent);
4120 setTreeInfoToDefaults(artwork_new, type);
4122 artwork_new->level_group = TRUE;
4124 setString(&artwork_new->identifier, level_node->subdir);
4126 if (node_parent == NULL) // check for top tree node
4128 char *top_node_name = (empty_level_set_mode ?
4129 "artwork for certain level sets" :
4130 "artwork included in level sets");
4132 setString(&artwork_new->name, top_node_name);
4133 setString(&artwork_new->name_sorting, top_node_name);
4137 setString(&artwork_new->name, level_node->name);
4138 setString(&artwork_new->name_sorting, level_node->name_sorting);
4141 pushTreeInfo(artwork_node, artwork_new);
4143 // create node to link back to current custom artwork directory
4144 createParentTreeInfoNode(artwork_new);
4146 // recursively step into sub-directory and look for more custom artwork
4147 LoadArtworkInfoFromLevelInfoExt(&artwork_new->node_group, artwork_new,
4148 level_node->node_group,
4149 empty_level_set_mode);
4151 // if sub-tree has no custom artwork at all, remove it
4152 if (artwork_new->node_group->next == NULL)
4153 removeTreeInfo(artwork_node);
4156 level_node = level_node->next;
4160 static void LoadArtworkInfoFromLevelInfo(ArtworkDirTree **artwork_node)
4162 // move peviously loaded artwork tree into separate sub-tree
4163 MoveArtworkInfoIntoSubTree(artwork_node);
4165 // load artwork from level sets into separate sub-trees
4166 LoadArtworkInfoFromLevelInfoExt(artwork_node, NULL, leveldir_first_all, TRUE);
4167 LoadArtworkInfoFromLevelInfoExt(artwork_node, NULL, leveldir_first_all, FALSE);
4169 // add top tree node over all three separate sub-trees
4170 *artwork_node = createTopTreeInfoNode(*artwork_node);
4172 // set all parent links (back links) in complete artwork tree
4173 setTreeInfoParentNodes(*artwork_node, NULL);
4176 void LoadLevelArtworkInfo(void)
4178 print_timestamp_init("LoadLevelArtworkInfo");
4180 DrawInitText("Looking for custom level artwork", 120, FC_GREEN);
4182 print_timestamp_time("DrawTimeText");
4184 LoadArtworkInfoFromLevelInfo(&artwork.gfx_first);
4185 print_timestamp_time("LoadArtworkInfoFromLevelInfo (gfx)");
4186 LoadArtworkInfoFromLevelInfo(&artwork.snd_first);
4187 print_timestamp_time("LoadArtworkInfoFromLevelInfo (snd)");
4188 LoadArtworkInfoFromLevelInfo(&artwork.mus_first);
4189 print_timestamp_time("LoadArtworkInfoFromLevelInfo (mus)");
4191 SaveArtworkInfoCache();
4193 print_timestamp_time("SaveArtworkInfoCache");
4195 // needed for reloading level artwork not known at ealier stage
4196 ChangeCurrentArtworkIfNeeded(ARTWORK_TYPE_GRAPHICS);
4197 ChangeCurrentArtworkIfNeeded(ARTWORK_TYPE_SOUNDS);
4198 ChangeCurrentArtworkIfNeeded(ARTWORK_TYPE_MUSIC);
4200 print_timestamp_time("getTreeInfoFromIdentifier");
4202 sortTreeInfo(&artwork.gfx_first);
4203 sortTreeInfo(&artwork.snd_first);
4204 sortTreeInfo(&artwork.mus_first);
4206 print_timestamp_time("sortTreeInfo");
4208 #if ENABLE_UNUSED_CODE
4209 dumpTreeInfo(artwork.gfx_first, 0);
4210 dumpTreeInfo(artwork.snd_first, 0);
4211 dumpTreeInfo(artwork.mus_first, 0);
4214 print_timestamp_done("LoadLevelArtworkInfo");
4217 static boolean AddTreeSetToTreeInfoExt(TreeInfo *tree_node_old, char *tree_dir,
4218 char *tree_subdir_new, int type)
4220 if (tree_node_old == NULL)
4222 if (type == TREE_TYPE_LEVEL_DIR)
4224 // get level info tree node of personal user level set
4225 tree_node_old = getTreeInfoFromIdentifier(leveldir_first, getLoginName());
4227 // this may happen if "setup.internal.create_user_levelset" is FALSE
4228 // or if file "levelinfo.conf" is missing in personal user level set
4229 if (tree_node_old == NULL)
4230 tree_node_old = leveldir_first->node_group;
4234 // get artwork info tree node of first artwork set
4235 tree_node_old = ARTWORK_FIRST_NODE(artwork, type);
4239 if (tree_dir == NULL)
4240 tree_dir = TREE_USERDIR(type);
4242 if (tree_node_old == NULL ||
4244 tree_subdir_new == NULL) // should not happen
4247 int draw_deactivation_mask = GetDrawDeactivationMask();
4249 // override draw deactivation mask (temporarily disable drawing)
4250 SetDrawDeactivationMask(REDRAW_ALL);
4252 if (type == TREE_TYPE_LEVEL_DIR)
4254 // load new level set config and add it next to first user level set
4255 LoadLevelInfoFromLevelConf(&tree_node_old->next,
4256 tree_node_old->node_parent,
4257 tree_dir, tree_subdir_new);
4261 // load new artwork set config and add it next to first artwork set
4262 LoadArtworkInfoFromArtworkConf(&tree_node_old->next,
4263 tree_node_old->node_parent,
4264 tree_dir, tree_subdir_new, type);
4267 // set draw deactivation mask to previous value
4268 SetDrawDeactivationMask(draw_deactivation_mask);
4270 // get first node of level or artwork info tree
4271 TreeInfo **tree_node_first = TREE_FIRST_NODE_PTR(type);
4273 // get tree info node of newly added level or artwork set
4274 TreeInfo *tree_node_new = getTreeInfoFromIdentifier(*tree_node_first,
4277 if (tree_node_new == NULL) // should not happen
4280 // correct top link and parent node link of newly created tree node
4281 tree_node_new->node_top = tree_node_old->node_top;
4282 tree_node_new->node_parent = tree_node_old->node_parent;
4284 // sort tree info to adjust position of newly added tree set
4285 sortTreeInfo(tree_node_first);
4290 void AddTreeSetToTreeInfo(TreeInfo *tree_node, char *tree_dir,
4291 char *tree_subdir_new, int type)
4293 if (!AddTreeSetToTreeInfoExt(tree_node, tree_dir, tree_subdir_new, type))
4294 Fail("internal tree info structure corrupted -- aborting");
4297 void AddUserLevelSetToLevelInfo(char *level_subdir_new)
4299 AddTreeSetToTreeInfo(NULL, NULL, level_subdir_new, TREE_TYPE_LEVEL_DIR);
4302 char *getArtworkIdentifierForUserLevelSet(int type)
4304 char *classic_artwork_set = getClassicArtworkSet(type);
4306 // check for custom artwork configured in "levelinfo.conf"
4307 char *leveldir_artwork_set =
4308 *LEVELDIR_ARTWORK_SET_PTR(leveldir_current, type);
4309 boolean has_leveldir_artwork_set =
4310 (leveldir_artwork_set != NULL && !strEqual(leveldir_artwork_set,
4311 classic_artwork_set));
4313 // check for custom artwork in sub-directory "graphics" etc.
4314 TreeInfo *artwork_first_node = ARTWORK_FIRST_NODE(artwork, type);
4315 char *leveldir_identifier = leveldir_current->identifier;
4316 boolean has_artwork_subdir =
4317 (getTreeInfoFromIdentifier(artwork_first_node,
4318 leveldir_identifier) != NULL);
4320 return (has_leveldir_artwork_set ? leveldir_artwork_set :
4321 has_artwork_subdir ? leveldir_identifier :
4322 classic_artwork_set);
4325 TreeInfo *getArtworkTreeInfoForUserLevelSet(int type)
4327 char *artwork_set = getArtworkIdentifierForUserLevelSet(type);
4328 TreeInfo *artwork_first_node = ARTWORK_FIRST_NODE(artwork, type);
4329 TreeInfo *ti = getTreeInfoFromIdentifier(artwork_first_node, artwork_set);
4333 ti = getTreeInfoFromIdentifier(artwork_first_node,
4334 ARTWORK_DEFAULT_SUBDIR(type));
4336 Fail("cannot find default graphics -- should not happen");
4342 boolean checkIfCustomArtworkExistsForCurrentLevelSet(void)
4344 char *graphics_set =
4345 getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_GRAPHICS);
4347 getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_SOUNDS);
4349 getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_MUSIC);
4351 return (!strEqual(graphics_set, GFX_CLASSIC_SUBDIR) ||
4352 !strEqual(sounds_set, SND_CLASSIC_SUBDIR) ||
4353 !strEqual(music_set, MUS_CLASSIC_SUBDIR));
4356 boolean UpdateUserLevelSet(char *level_subdir, char *level_name,
4357 char *level_author, int num_levels)
4359 char *filename = getPath2(getUserLevelDir(level_subdir), LEVELINFO_FILENAME);
4360 char *filename_tmp = getStringCat2(filename, ".tmp");
4362 FILE *file_tmp = NULL;
4363 char line[MAX_LINE_LEN];
4364 boolean success = FALSE;
4365 LevelDirTree *leveldir = getTreeInfoFromIdentifier(leveldir_first,
4367 // update values in level directory tree
4369 if (level_name != NULL)
4370 setString(&leveldir->name, level_name);
4372 if (level_author != NULL)
4373 setString(&leveldir->author, level_author);
4375 if (num_levels != -1)
4376 leveldir->levels = num_levels;
4378 // update values that depend on other values
4380 setString(&leveldir->name_sorting, leveldir->name);
4382 leveldir->last_level = leveldir->first_level + leveldir->levels - 1;
4384 // sort order of level sets may have changed
4385 sortTreeInfo(&leveldir_first);
4387 if ((file = fopen(filename, MODE_READ)) &&
4388 (file_tmp = fopen(filename_tmp, MODE_WRITE)))
4390 while (fgets(line, MAX_LINE_LEN, file))
4392 if (strPrefix(line, "name:") && level_name != NULL)
4393 fprintf(file_tmp, "%-32s%s\n", "name:", level_name);
4394 else if (strPrefix(line, "author:") && level_author != NULL)
4395 fprintf(file_tmp, "%-32s%s\n", "author:", level_author);
4396 else if (strPrefix(line, "levels:") && num_levels != -1)
4397 fprintf(file_tmp, "%-32s%d\n", "levels:", num_levels);
4399 fputs(line, file_tmp);
4412 success = (rename(filename_tmp, filename) == 0);
4420 boolean CreateUserLevelSet(char *level_subdir, char *level_name,
4421 char *level_author, int num_levels,
4422 boolean use_artwork_set)
4424 LevelDirTree *level_info;
4429 // create user level sub-directory, if needed
4430 createDirectory(getUserLevelDir(level_subdir), "user level", PERMS_PRIVATE);
4432 filename = getPath2(getUserLevelDir(level_subdir), LEVELINFO_FILENAME);
4434 if (!(file = fopen(filename, MODE_WRITE)))
4436 Warn("cannot write level info file '%s'", filename);
4443 level_info = newTreeInfo();
4445 // always start with reliable default values
4446 setTreeInfoToDefaults(level_info, TREE_TYPE_LEVEL_DIR);
4448 setString(&level_info->name, level_name);
4449 setString(&level_info->author, level_author);
4450 level_info->levels = num_levels;
4451 level_info->first_level = 1;
4452 level_info->sort_priority = LEVELCLASS_PRIVATE_START;
4453 level_info->readonly = FALSE;
4455 if (use_artwork_set)
4457 level_info->graphics_set =
4458 getStringCopy(getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_GRAPHICS));
4459 level_info->sounds_set =
4460 getStringCopy(getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_SOUNDS));
4461 level_info->music_set =
4462 getStringCopy(getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_MUSIC));
4465 token_value_position = TOKEN_VALUE_POSITION_SHORT;
4467 fprintFileHeader(file, LEVELINFO_FILENAME);
4470 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
4472 if (i == LEVELINFO_TOKEN_NAME ||
4473 i == LEVELINFO_TOKEN_AUTHOR ||
4474 i == LEVELINFO_TOKEN_LEVELS ||
4475 i == LEVELINFO_TOKEN_FIRST_LEVEL ||
4476 i == LEVELINFO_TOKEN_SORT_PRIORITY ||
4477 i == LEVELINFO_TOKEN_READONLY ||
4478 (use_artwork_set && (i == LEVELINFO_TOKEN_GRAPHICS_SET ||
4479 i == LEVELINFO_TOKEN_SOUNDS_SET ||
4480 i == LEVELINFO_TOKEN_MUSIC_SET)))
4481 fprintf(file, "%s\n", getSetupLine(levelinfo_tokens, "", i));
4483 // just to make things nicer :)
4484 if (i == LEVELINFO_TOKEN_AUTHOR ||
4485 i == LEVELINFO_TOKEN_FIRST_LEVEL ||
4486 (use_artwork_set && i == LEVELINFO_TOKEN_READONLY))
4487 fprintf(file, "\n");
4490 token_value_position = TOKEN_VALUE_POSITION_DEFAULT;
4494 SetFilePermissions(filename, PERMS_PRIVATE);
4496 freeTreeInfo(level_info);
4502 static void SaveUserLevelInfo(void)
4504 CreateUserLevelSet(getLoginName(), getLoginName(), getRealName(), 100, FALSE);
4507 char *getSetupValue(int type, void *value)
4509 static char value_string[MAX_LINE_LEN];
4517 strcpy(value_string, (*(boolean *)value ? "true" : "false"));
4521 strcpy(value_string, (*(boolean *)value ? "on" : "off"));
4525 strcpy(value_string, (*(int *)value == AUTO ? "auto" :
4526 *(int *)value == FALSE ? "off" : "on"));
4530 strcpy(value_string, (*(boolean *)value ? "yes" : "no"));
4533 case TYPE_YES_NO_AUTO:
4534 strcpy(value_string, (*(int *)value == AUTO ? "auto" :
4535 *(int *)value == FALSE ? "no" : "yes"));
4539 strcpy(value_string, (*(boolean *)value ? "AGA" : "ECS"));
4543 strcpy(value_string, getKeyNameFromKey(*(Key *)value));
4547 strcpy(value_string, getX11KeyNameFromKey(*(Key *)value));
4551 sprintf(value_string, "%d", *(int *)value);
4555 if (*(char **)value == NULL)
4558 strcpy(value_string, *(char **)value);
4562 sprintf(value_string, "player_%d", *(int *)value + 1);
4566 value_string[0] = '\0';
4570 if (type & TYPE_GHOSTED)
4571 strcpy(value_string, "n/a");
4573 return value_string;
4576 char *getSetupLine(struct TokenInfo *token_info, char *prefix, int token_nr)
4580 static char token_string[MAX_LINE_LEN];
4581 int token_type = token_info[token_nr].type;
4582 void *setup_value = token_info[token_nr].value;
4583 char *token_text = token_info[token_nr].text;
4584 char *value_string = getSetupValue(token_type, setup_value);
4586 // build complete token string
4587 sprintf(token_string, "%s%s", prefix, token_text);
4589 // build setup entry line
4590 line = getFormattedSetupEntry(token_string, value_string);
4592 if (token_type == TYPE_KEY_X11)
4594 Key key = *(Key *)setup_value;
4595 char *keyname = getKeyNameFromKey(key);
4597 // add comment, if useful
4598 if (!strEqual(keyname, "(undefined)") &&
4599 !strEqual(keyname, "(unknown)"))
4601 // add at least one whitespace
4603 for (i = strlen(line); i < token_comment_position; i++)
4607 strcat(line, keyname);
4614 static void InitLastPlayedLevels_ParentNode(void)
4616 LevelDirTree **leveldir_top = &leveldir_first->node_group->next;
4617 LevelDirTree *leveldir_new = NULL;
4619 // check if parent node for last played levels already exists
4620 if (strEqual((*leveldir_top)->identifier, TOKEN_STR_LAST_LEVEL_SERIES))
4623 leveldir_new = newTreeInfo();
4625 setTreeInfoToDefaultsFromParent(leveldir_new, leveldir_first);
4627 leveldir_new->level_group = TRUE;
4628 leveldir_new->sort_priority = LEVELCLASS_LAST_PLAYED_LEVEL;
4630 setString(&leveldir_new->identifier, TOKEN_STR_LAST_LEVEL_SERIES);
4631 setString(&leveldir_new->name, "<< (last played level sets)");
4632 setString(&leveldir_new->name_sorting, leveldir_new->name);
4634 pushTreeInfo(leveldir_top, leveldir_new);
4636 // create node to link back to current level directory
4637 createParentTreeInfoNode(leveldir_new);
4640 void UpdateLastPlayedLevels_TreeInfo(void)
4642 char **last_level_series = setup.level_setup.last_level_series;
4643 LevelDirTree *leveldir_last;
4644 TreeInfo **node_new = NULL;
4647 if (last_level_series[0] == NULL)
4650 InitLastPlayedLevels_ParentNode();
4652 leveldir_last = getTreeInfoFromIdentifierExt(leveldir_first,
4653 TOKEN_STR_LAST_LEVEL_SERIES,
4654 TREE_NODE_TYPE_GROUP);
4655 if (leveldir_last == NULL)
4658 node_new = &leveldir_last->node_group->next;
4660 freeTreeInfo(*node_new);
4664 for (i = 0; last_level_series[i] != NULL; i++)
4666 LevelDirTree *node_last = getTreeInfoFromIdentifier(leveldir_first,
4667 last_level_series[i]);
4668 if (node_last == NULL)
4671 *node_new = getTreeInfoCopy(node_last); // copy complete node
4673 (*node_new)->node_top = &leveldir_first; // correct top node link
4674 (*node_new)->node_parent = leveldir_last; // correct parent node link
4676 (*node_new)->is_copy = TRUE; // mark entry as node copy
4678 (*node_new)->node_group = NULL;
4679 (*node_new)->next = NULL;
4681 (*node_new)->cl_first = -1; // force setting tree cursor
4683 node_new = &((*node_new)->next);
4687 static void UpdateLastPlayedLevels_List(void)
4689 char **last_level_series = setup.level_setup.last_level_series;
4690 int pos = MAX_LEVELDIR_HISTORY - 1;
4693 // search for potentially already existing entry in list of level sets
4694 for (i = 0; last_level_series[i] != NULL; i++)
4695 if (strEqual(last_level_series[i], leveldir_current->identifier))
4698 // move list of level sets one entry down (using potentially free entry)
4699 for (i = pos; i > 0; i--)
4700 setString(&last_level_series[i], last_level_series[i - 1]);
4702 // put last played level set at top position
4703 setString(&last_level_series[0], leveldir_current->identifier);
4706 static TreeInfo *StoreOrRestoreLastPlayedLevels(TreeInfo *node, boolean store)
4708 static char *identifier = NULL;
4712 setString(&identifier, (node && node->is_copy ? node->identifier : NULL));
4714 return NULL; // not used
4718 TreeInfo *node_new = getTreeInfoFromIdentifierExt(leveldir_first,
4720 TREE_NODE_TYPE_COPY);
4721 return (node_new != NULL ? node_new : node);
4725 void StoreLastPlayedLevels(TreeInfo *node)
4727 StoreOrRestoreLastPlayedLevels(node, TRUE);
4730 void RestoreLastPlayedLevels(TreeInfo **node)
4732 *node = StoreOrRestoreLastPlayedLevels(*node, FALSE);
4735 void LoadLevelSetup_LastSeries(void)
4737 // --------------------------------------------------------------------------
4738 // ~/.<program>/levelsetup.conf
4739 // --------------------------------------------------------------------------
4741 char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
4742 SetupFileHash *level_setup_hash = NULL;
4746 // always start with reliable default values
4747 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
4749 // start with empty history of last played level sets
4750 setString(&setup.level_setup.last_level_series[0], NULL);
4752 if (!strEqual(DEFAULT_LEVELSET, UNDEFINED_LEVELSET))
4754 leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
4756 if (leveldir_current == NULL)
4757 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
4760 if ((level_setup_hash = loadSetupFileHash(filename)))
4762 char *last_level_series =
4763 getHashEntry(level_setup_hash, TOKEN_STR_LAST_LEVEL_SERIES);
4765 leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
4767 if (leveldir_current == NULL)
4768 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
4770 for (i = 0; i < MAX_LEVELDIR_HISTORY; i++)
4772 char token[strlen(TOKEN_STR_LAST_LEVEL_SERIES) + 10];
4773 LevelDirTree *leveldir_last;
4775 sprintf(token, "%s.%03d", TOKEN_STR_LAST_LEVEL_SERIES, i);
4777 last_level_series = getHashEntry(level_setup_hash, token);
4779 leveldir_last = getTreeInfoFromIdentifier(leveldir_first,
4781 if (leveldir_last != NULL)
4782 setString(&setup.level_setup.last_level_series[pos++],
4786 setString(&setup.level_setup.last_level_series[pos], NULL);
4788 freeSetupFileHash(level_setup_hash);
4792 Debug("setup", "using default setup values");
4798 static void SaveLevelSetup_LastSeries_Ext(boolean deactivate_last_level_series)
4800 // --------------------------------------------------------------------------
4801 // ~/.<program>/levelsetup.conf
4802 // --------------------------------------------------------------------------
4804 // check if the current level directory structure is available at this point
4805 if (leveldir_current == NULL)
4808 char **last_level_series = setup.level_setup.last_level_series;
4809 char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
4813 InitUserDataDirectory();
4815 UpdateLastPlayedLevels_List();
4817 if (!(file = fopen(filename, MODE_WRITE)))
4819 Warn("cannot write setup file '%s'", filename);
4826 fprintFileHeader(file, LEVELSETUP_FILENAME);
4828 if (deactivate_last_level_series)
4829 fprintf(file, "# %s\n# ", "the following level set may have caused a problem and was deactivated");
4831 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_LAST_LEVEL_SERIES,
4832 leveldir_current->identifier));
4834 for (i = 0; last_level_series[i] != NULL; i++)
4836 char token[strlen(TOKEN_STR_LAST_LEVEL_SERIES) + 10];
4838 sprintf(token, "%s.%03d", TOKEN_STR_LAST_LEVEL_SERIES, i);
4840 fprintf(file, "%s\n", getFormattedSetupEntry(token, last_level_series[i]));
4845 SetFilePermissions(filename, PERMS_PRIVATE);
4850 void SaveLevelSetup_LastSeries(void)
4852 SaveLevelSetup_LastSeries_Ext(FALSE);
4855 void SaveLevelSetup_LastSeries_Deactivate(void)
4857 SaveLevelSetup_LastSeries_Ext(TRUE);
4860 static void checkSeriesInfo(void)
4862 static char *level_directory = NULL;
4865 DirectoryEntry *dir_entry;
4868 checked_free(level_directory);
4870 // check for more levels besides the 'levels' field of 'levelinfo.conf'
4872 level_directory = getPath2((leveldir_current->in_user_dir ?
4873 getUserLevelDir(NULL) :
4874 options.level_directory),
4875 leveldir_current->fullpath);
4877 if ((dir = openDirectory(level_directory)) == NULL)
4879 Warn("cannot read level directory '%s'", level_directory);
4885 while ((dir_entry = readDirectory(dir)) != NULL) // loop all entries
4887 if (strlen(dir_entry->basename) > 4 &&
4888 dir_entry->basename[3] == '.' &&
4889 strEqual(&dir_entry->basename[4], LEVELFILE_EXTENSION))
4891 char levelnum_str[4];
4894 strncpy(levelnum_str, dir_entry->basename, 3);
4895 levelnum_str[3] = '\0';
4897 levelnum_value = atoi(levelnum_str);
4899 if (levelnum_value < leveldir_current->first_level)
4901 Warn("additional level %d found", levelnum_value);
4903 leveldir_current->first_level = levelnum_value;
4905 else if (levelnum_value > leveldir_current->last_level)
4907 Warn("additional level %d found", levelnum_value);
4909 leveldir_current->last_level = levelnum_value;
4915 closeDirectory(dir);
4918 void LoadLevelSetup_SeriesInfo(void)
4921 SetupFileHash *level_setup_hash = NULL;
4922 char *level_subdir = leveldir_current->subdir;
4925 // always start with reliable default values
4926 level_nr = leveldir_current->first_level;
4928 for (i = 0; i < MAX_LEVELS; i++)
4930 LevelStats_setPlayed(i, 0);
4931 LevelStats_setSolved(i, 0);
4936 // --------------------------------------------------------------------------
4937 // ~/.<program>/levelsetup/<level series>/levelsetup.conf
4938 // --------------------------------------------------------------------------
4940 level_subdir = leveldir_current->subdir;
4942 filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
4944 if ((level_setup_hash = loadSetupFileHash(filename)))
4948 // get last played level in this level set
4950 token_value = getHashEntry(level_setup_hash, TOKEN_STR_LAST_PLAYED_LEVEL);
4954 level_nr = atoi(token_value);
4956 if (level_nr < leveldir_current->first_level)
4957 level_nr = leveldir_current->first_level;
4958 if (level_nr > leveldir_current->last_level)
4959 level_nr = leveldir_current->last_level;
4962 // get handicap level in this level set
4964 token_value = getHashEntry(level_setup_hash, TOKEN_STR_HANDICAP_LEVEL);
4968 int level_nr = atoi(token_value);
4970 if (level_nr < leveldir_current->first_level)
4971 level_nr = leveldir_current->first_level;
4972 if (level_nr > leveldir_current->last_level + 1)
4973 level_nr = leveldir_current->last_level;
4975 if (leveldir_current->user_defined || !leveldir_current->handicap)
4976 level_nr = leveldir_current->last_level;
4978 leveldir_current->handicap_level = level_nr;
4981 // get number of played and solved levels in this level set
4983 BEGIN_HASH_ITERATION(level_setup_hash, itr)
4985 char *token = HASH_ITERATION_TOKEN(itr);
4986 char *value = HASH_ITERATION_VALUE(itr);
4988 if (strlen(token) == 3 &&
4989 token[0] >= '0' && token[0] <= '9' &&
4990 token[1] >= '0' && token[1] <= '9' &&
4991 token[2] >= '0' && token[2] <= '9')
4993 int level_nr = atoi(token);
4996 LevelStats_setPlayed(level_nr, atoi(value)); // read 1st column
4998 value = strchr(value, ' ');
5001 LevelStats_setSolved(level_nr, atoi(value)); // read 2nd column
5004 END_HASH_ITERATION(hash, itr)
5006 freeSetupFileHash(level_setup_hash);
5010 Debug("setup", "using default setup values");
5016 void SaveLevelSetup_SeriesInfo(void)
5019 char *level_subdir = leveldir_current->subdir;
5020 char *level_nr_str = int2str(level_nr, 0);
5021 char *handicap_level_str = int2str(leveldir_current->handicap_level, 0);
5025 // --------------------------------------------------------------------------
5026 // ~/.<program>/levelsetup/<level series>/levelsetup.conf
5027 // --------------------------------------------------------------------------
5029 InitLevelSetupDirectory(level_subdir);
5031 filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
5033 if (!(file = fopen(filename, MODE_WRITE)))
5035 Warn("cannot write setup file '%s'", filename);
5042 fprintFileHeader(file, LEVELSETUP_FILENAME);
5044 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_PLAYED_LEVEL,
5046 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_HANDICAP_LEVEL,
5047 handicap_level_str));
5049 for (i = leveldir_current->first_level; i <= leveldir_current->last_level;
5052 if (LevelStats_getPlayed(i) > 0 ||
5053 LevelStats_getSolved(i) > 0)
5058 sprintf(token, "%03d", i);
5059 sprintf(value, "%d %d", LevelStats_getPlayed(i), LevelStats_getSolved(i));
5061 fprintf(file, "%s\n", getFormattedSetupEntry(token, value));
5067 SetFilePermissions(filename, PERMS_PRIVATE);
5072 int LevelStats_getPlayed(int nr)
5074 return (nr >= 0 && nr < MAX_LEVELS ? level_stats[nr].played : 0);
5077 int LevelStats_getSolved(int nr)
5079 return (nr >= 0 && nr < MAX_LEVELS ? level_stats[nr].solved : 0);
5082 void LevelStats_setPlayed(int nr, int value)
5084 if (nr >= 0 && nr < MAX_LEVELS)
5085 level_stats[nr].played = value;
5088 void LevelStats_setSolved(int nr, int value)
5090 if (nr >= 0 && nr < MAX_LEVELS)
5091 level_stats[nr].solved = value;
5094 void LevelStats_incPlayed(int nr)
5096 if (nr >= 0 && nr < MAX_LEVELS)
5097 level_stats[nr].played++;
5100 void LevelStats_incSolved(int nr)
5102 if (nr >= 0 && nr < MAX_LEVELS)
5103 level_stats[nr].solved++;
5106 void LoadUserSetup(void)
5108 // --------------------------------------------------------------------------
5109 // ~/.<program>/usersetup.conf
5110 // --------------------------------------------------------------------------
5112 char *filename = getPath2(getMainUserGameDataDir(), USERSETUP_FILENAME);
5113 SetupFileHash *user_setup_hash = NULL;
5115 // always start with reliable default values
5118 if ((user_setup_hash = loadSetupFileHash(filename)))
5122 // get last selected user number
5123 token_value = getHashEntry(user_setup_hash, TOKEN_STR_LAST_USER);
5126 user.nr = MIN(MAX(0, atoi(token_value)), MAX_PLAYER_NAMES - 1);
5128 freeSetupFileHash(user_setup_hash);
5132 Debug("setup", "using default setup values");
5138 void SaveUserSetup(void)
5140 // --------------------------------------------------------------------------
5141 // ~/.<program>/usersetup.conf
5142 // --------------------------------------------------------------------------
5144 char *filename = getPath2(getMainUserGameDataDir(), USERSETUP_FILENAME);
5147 InitMainUserDataDirectory();
5149 if (!(file = fopen(filename, MODE_WRITE)))
5151 Warn("cannot write setup file '%s'", filename);
5158 fprintFileHeader(file, USERSETUP_FILENAME);
5160 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_USER,
5164 SetFilePermissions(filename, PERMS_PRIVATE);