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)
108 score_dir = getPath2(getMainUserGameDataDir(), score_subdir);
110 if (level_subdir != NULL)
112 checked_free(score_level_dir);
114 score_level_dir = getPath2(score_dir, level_subdir);
116 return score_level_dir;
122 static char *getScoreCacheDir(char *level_subdir)
124 static char *score_dir = NULL;
125 static char *score_level_dir = NULL;
126 char *score_subdir = SCORES_DIRECTORY;
128 if (score_dir == NULL)
129 score_dir = getPath2(getCacheDir(), score_subdir);
131 if (level_subdir != NULL)
133 checked_free(score_level_dir);
135 score_level_dir = getPath2(score_dir, level_subdir);
137 return score_level_dir;
143 static char *getUserSubdir(int nr)
145 static char user_subdir[16] = { 0 };
147 sprintf(user_subdir, "%03d", nr);
152 static char *getUserDir(int nr)
154 static char *user_dir = NULL;
155 char *main_data_dir = getMainUserGameDataDir();
156 char *users_subdir = USERS_DIRECTORY;
157 char *user_subdir = getUserSubdir(nr);
159 checked_free(user_dir);
162 user_dir = getPath3(main_data_dir, users_subdir, user_subdir);
164 user_dir = getPath2(main_data_dir, users_subdir);
169 static char *getLevelSetupDir(char *level_subdir)
171 static char *levelsetup_dir = NULL;
172 char *data_dir = getUserGameDataDir();
173 char *levelsetup_subdir = LEVELSETUP_DIRECTORY;
175 checked_free(levelsetup_dir);
177 if (level_subdir != NULL)
178 levelsetup_dir = getPath3(data_dir, levelsetup_subdir, level_subdir);
180 levelsetup_dir = getPath2(data_dir, levelsetup_subdir);
182 return levelsetup_dir;
185 static char *getNetworkDir(void)
187 static char *network_dir = NULL;
189 if (network_dir == NULL)
190 network_dir = getPath2(getMainUserGameDataDir(), NETWORK_DIRECTORY);
195 char *getLevelDirFromTreeInfo(TreeInfo *node)
197 static char *level_dir = NULL;
200 return options.level_directory;
202 checked_free(level_dir);
204 level_dir = getPath2((node->in_user_dir ? getUserLevelDir(NULL) :
205 options.level_directory), node->fullpath);
210 char *getUserLevelDir(char *level_subdir)
212 static char *userlevel_dir = NULL;
213 char *data_dir = getMainUserGameDataDir();
214 char *userlevel_subdir = LEVELS_DIRECTORY;
216 checked_free(userlevel_dir);
218 if (level_subdir != NULL)
219 userlevel_dir = getPath3(data_dir, userlevel_subdir, level_subdir);
221 userlevel_dir = getPath2(data_dir, userlevel_subdir);
223 return userlevel_dir;
226 char *getNetworkLevelDir(char *level_subdir)
228 static char *network_level_dir = NULL;
229 char *data_dir = getNetworkDir();
230 char *networklevel_subdir = LEVELS_DIRECTORY;
232 checked_free(network_level_dir);
234 if (level_subdir != NULL)
235 network_level_dir = getPath3(data_dir, networklevel_subdir, level_subdir);
237 network_level_dir = getPath2(data_dir, networklevel_subdir);
239 return network_level_dir;
242 char *getCurrentLevelDir(void)
244 return getLevelDirFromTreeInfo(leveldir_current);
247 char *getNewUserLevelSubdir(void)
249 static char *new_level_subdir = NULL;
250 char *subdir_prefix = getLoginName();
251 char subdir_suffix[10];
252 int max_suffix_number = 1000;
255 while (++i < max_suffix_number)
257 sprintf(subdir_suffix, "_%d", i);
259 checked_free(new_level_subdir);
260 new_level_subdir = getStringCat2(subdir_prefix, subdir_suffix);
262 if (!directoryExists(getUserLevelDir(new_level_subdir)))
266 return new_level_subdir;
269 char *getTapeDir(char *level_subdir)
271 static char *tape_dir = NULL;
272 char *data_dir = getUserGameDataDir();
273 char *tape_subdir = TAPES_DIRECTORY;
275 checked_free(tape_dir);
277 if (level_subdir != NULL)
278 tape_dir = getPath3(data_dir, tape_subdir, level_subdir);
280 tape_dir = getPath2(data_dir, tape_subdir);
285 static char *getSolutionTapeDir(void)
287 static char *tape_dir = NULL;
288 char *data_dir = getCurrentLevelDir();
289 char *tape_subdir = TAPES_DIRECTORY;
291 checked_free(tape_dir);
293 tape_dir = getPath2(data_dir, tape_subdir);
298 static char *getDefaultGraphicsDir(char *graphics_subdir)
300 static char *graphics_dir = NULL;
302 if (graphics_subdir == NULL)
303 return options.graphics_directory;
305 checked_free(graphics_dir);
307 graphics_dir = getPath2(options.graphics_directory, graphics_subdir);
312 static char *getDefaultSoundsDir(char *sounds_subdir)
314 static char *sounds_dir = NULL;
316 if (sounds_subdir == NULL)
317 return options.sounds_directory;
319 checked_free(sounds_dir);
321 sounds_dir = getPath2(options.sounds_directory, sounds_subdir);
326 static char *getDefaultMusicDir(char *music_subdir)
328 static char *music_dir = NULL;
330 if (music_subdir == NULL)
331 return options.music_directory;
333 checked_free(music_dir);
335 music_dir = getPath2(options.music_directory, music_subdir);
340 static char *getClassicArtworkSet(int type)
342 return (type == TREE_TYPE_GRAPHICS_DIR ? GFX_CLASSIC_SUBDIR :
343 type == TREE_TYPE_SOUNDS_DIR ? SND_CLASSIC_SUBDIR :
344 type == TREE_TYPE_MUSIC_DIR ? MUS_CLASSIC_SUBDIR : "");
347 static char *getClassicArtworkDir(int type)
349 return (type == TREE_TYPE_GRAPHICS_DIR ?
350 getDefaultGraphicsDir(GFX_CLASSIC_SUBDIR) :
351 type == TREE_TYPE_SOUNDS_DIR ?
352 getDefaultSoundsDir(SND_CLASSIC_SUBDIR) :
353 type == TREE_TYPE_MUSIC_DIR ?
354 getDefaultMusicDir(MUS_CLASSIC_SUBDIR) : "");
357 char *getUserGraphicsDir(void)
359 static char *usergraphics_dir = NULL;
361 if (usergraphics_dir == NULL)
362 usergraphics_dir = getPath2(getMainUserGameDataDir(), GRAPHICS_DIRECTORY);
364 return usergraphics_dir;
367 char *getUserSoundsDir(void)
369 static char *usersounds_dir = NULL;
371 if (usersounds_dir == NULL)
372 usersounds_dir = getPath2(getMainUserGameDataDir(), SOUNDS_DIRECTORY);
374 return usersounds_dir;
377 char *getUserMusicDir(void)
379 static char *usermusic_dir = NULL;
381 if (usermusic_dir == NULL)
382 usermusic_dir = getPath2(getMainUserGameDataDir(), MUSIC_DIRECTORY);
384 return usermusic_dir;
387 static char *getSetupArtworkDir(TreeInfo *ti)
389 static char *artwork_dir = NULL;
394 checked_free(artwork_dir);
396 artwork_dir = getPath2(ti->basepath, ti->fullpath);
401 char *setLevelArtworkDir(TreeInfo *ti)
403 char **artwork_path_ptr, **artwork_set_ptr;
404 TreeInfo *level_artwork;
406 if (ti == NULL || leveldir_current == NULL)
409 artwork_path_ptr = LEVELDIR_ARTWORK_PATH_PTR(leveldir_current, ti->type);
410 artwork_set_ptr = LEVELDIR_ARTWORK_SET_PTR( leveldir_current, ti->type);
412 checked_free(*artwork_path_ptr);
414 if ((level_artwork = getTreeInfoFromIdentifier(ti, *artwork_set_ptr)))
416 *artwork_path_ptr = getStringCopy(getSetupArtworkDir(level_artwork));
421 No (or non-existing) artwork configured in "levelinfo.conf". This would
422 normally result in using the artwork configured in the setup menu. But
423 if an artwork subdirectory exists (which might contain custom artwork
424 or an artwork configuration file), this level artwork must be treated
425 as relative to the default "classic" artwork, not to the artwork that
426 is currently configured in the setup menu.
428 Update: For "special" versions of R'n'D (like "R'n'D jue"), do not use
429 the "default" artwork (which would be "jue0" for "R'n'D jue"), but use
430 the real "classic" artwork from the original R'n'D (like "gfx_classic").
433 char *dir = getPath2(getCurrentLevelDir(), ARTWORK_DIRECTORY(ti->type));
435 checked_free(*artwork_set_ptr);
437 if (directoryExists(dir))
439 *artwork_path_ptr = getStringCopy(getClassicArtworkDir(ti->type));
440 *artwork_set_ptr = getStringCopy(getClassicArtworkSet(ti->type));
444 *artwork_path_ptr = getStringCopy(UNDEFINED_FILENAME);
445 *artwork_set_ptr = NULL;
451 return *artwork_set_ptr;
454 static char *getLevelArtworkSet(int type)
456 if (leveldir_current == NULL)
459 return LEVELDIR_ARTWORK_SET(leveldir_current, type);
462 static char *getLevelArtworkDir(int type)
464 if (leveldir_current == NULL)
465 return UNDEFINED_FILENAME;
467 return LEVELDIR_ARTWORK_PATH(leveldir_current, type);
470 char *getProgramMainDataPath(char *command_filename, char *base_path)
472 // check if the program's main data base directory is configured
473 if (!strEqual(base_path, "."))
474 return getStringCopy(base_path);
476 /* if the program is configured to start from current directory (default),
477 determine program package directory from program binary (some versions
478 of KDE/Konqueror and Mac OS X (especially "Mavericks") apparently do not
479 set the current working directory to the program package directory) */
480 char *main_data_path = getBasePath(command_filename);
482 #if defined(PLATFORM_MACOSX)
483 if (strSuffix(main_data_path, MAC_APP_BINARY_SUBDIR))
485 char *main_data_path_old = main_data_path;
487 // cut relative path to Mac OS X application binary directory from path
488 main_data_path[strlen(main_data_path) -
489 strlen(MAC_APP_BINARY_SUBDIR)] = '\0';
491 // cut trailing path separator from path (but not if path is root directory)
492 if (strSuffix(main_data_path, "/") && !strEqual(main_data_path, "/"))
493 main_data_path[strlen(main_data_path) - 1] = '\0';
495 // replace empty path with current directory
496 if (strEqual(main_data_path, ""))
497 main_data_path = ".";
499 // add relative path to Mac OS X application resources directory to path
500 main_data_path = getPath2(main_data_path, MAC_APP_FILES_SUBDIR);
502 free(main_data_path_old);
506 return main_data_path;
509 char *getProgramConfigFilename(char *command_filename)
511 static char *config_filename_1 = NULL;
512 static char *config_filename_2 = NULL;
513 static char *config_filename_3 = NULL;
514 static boolean initialized = FALSE;
518 char *command_filename_1 = getStringCopy(command_filename);
520 // strip trailing executable suffix from command filename
521 if (strSuffix(command_filename_1, ".exe"))
522 command_filename_1[strlen(command_filename_1) - 4] = '\0';
524 char *base_path = getProgramMainDataPath(command_filename, BASE_PATH);
525 char *conf_directory = getPath2(base_path, CONF_DIRECTORY);
527 char *command_basepath = getBasePath(command_filename);
528 char *command_basename = getBaseNameNoSuffix(command_filename);
529 char *command_filename_2 = getPath2(command_basepath, command_basename);
531 config_filename_1 = getStringCat2(command_filename_1, ".conf");
532 config_filename_2 = getStringCat2(command_filename_2, ".conf");
533 config_filename_3 = getPath2(conf_directory, SETUP_FILENAME);
535 checked_free(base_path);
536 checked_free(conf_directory);
538 checked_free(command_basepath);
539 checked_free(command_basename);
541 checked_free(command_filename_1);
542 checked_free(command_filename_2);
547 // 1st try: look for config file that exactly matches the binary filename
548 if (fileExists(config_filename_1))
549 return config_filename_1;
551 // 2nd try: look for config file that matches binary filename without suffix
552 if (fileExists(config_filename_2))
553 return config_filename_2;
555 // 3rd try: return setup config filename in global program config directory
556 return config_filename_3;
559 char *getTapeFilename(int nr)
561 static char *filename = NULL;
562 char basename[MAX_FILENAME_LEN];
564 checked_free(filename);
566 sprintf(basename, "%03d.%s", nr, TAPEFILE_EXTENSION);
567 filename = getPath2(getTapeDir(leveldir_current->subdir), basename);
572 char *getTemporaryTapeFilename(void)
574 static char *filename = NULL;
575 char basename[MAX_FILENAME_LEN];
577 checked_free(filename);
579 sprintf(basename, "tmp.%s", TAPEFILE_EXTENSION);
580 filename = getPath2(getTapeDir(NULL), basename);
585 char *getDefaultSolutionTapeFilename(int nr)
587 static char *filename = NULL;
588 char basename[MAX_FILENAME_LEN];
590 checked_free(filename);
592 sprintf(basename, "%03d.%s", nr, TAPEFILE_EXTENSION);
593 filename = getPath2(getSolutionTapeDir(), basename);
598 char *getSokobanSolutionTapeFilename(int nr)
600 static char *filename = NULL;
601 char basename[MAX_FILENAME_LEN];
603 checked_free(filename);
605 sprintf(basename, "%03d.sln", nr);
606 filename = getPath2(getSolutionTapeDir(), basename);
611 char *getSolutionTapeFilename(int nr)
613 char *filename = getDefaultSolutionTapeFilename(nr);
615 if (!fileExists(filename))
617 char *filename2 = getSokobanSolutionTapeFilename(nr);
619 if (fileExists(filename2))
626 char *getScoreFilename(int nr)
628 static char *filename = NULL;
629 char basename[MAX_FILENAME_LEN];
631 checked_free(filename);
633 sprintf(basename, "%03d.%s", nr, SCOREFILE_EXTENSION);
635 // used instead of "leveldir_current->subdir" (for network games)
636 filename = getPath2(getScoreDir(levelset.identifier), basename);
641 char *getScoreCacheFilename(int nr)
643 static char *filename = NULL;
644 char basename[MAX_FILENAME_LEN];
646 checked_free(filename);
648 sprintf(basename, "%03d.%s", nr, SCOREFILE_EXTENSION);
650 // used instead of "leveldir_current->subdir" (for network games)
651 filename = getPath2(getScoreCacheDir(levelset.identifier), basename);
656 char *getScoreTapeBasename(char *name)
658 static char basename[MAX_FILENAME_LEN];
659 char basename_raw[MAX_FILENAME_LEN];
662 sprintf(timestamp, "%s", getCurrentTimestamp());
663 sprintf(basename_raw, "%s-%s", timestamp, name);
664 sprintf(basename, "%s-%08x", timestamp, get_hash_from_key(basename_raw));
669 char *getScoreTapeFilename(char *basename_no_ext, int nr)
671 static char *filename = NULL;
672 char basename[MAX_FILENAME_LEN];
674 checked_free(filename);
676 sprintf(basename, "%03d.%s.%s", nr, basename_no_ext, TAPEFILE_EXTENSION);
678 // used instead of "leveldir_current->subdir" (for network games)
679 filename = getPath2(getScoreDir(levelset.identifier), basename);
684 char *getSetupFilename(void)
686 static char *filename = NULL;
688 checked_free(filename);
690 filename = getPath2(getSetupDir(), SETUP_FILENAME);
695 char *getDefaultSetupFilename(void)
697 return program.config_filename;
700 char *getEditorSetupFilename(void)
702 static char *filename = NULL;
704 checked_free(filename);
705 filename = getPath2(getCurrentLevelDir(), EDITORSETUP_FILENAME);
707 if (fileExists(filename))
710 checked_free(filename);
711 filename = getPath2(getSetupDir(), EDITORSETUP_FILENAME);
716 char *getHelpAnimFilename(void)
718 static char *filename = NULL;
720 checked_free(filename);
722 filename = getPath2(getCurrentLevelDir(), HELPANIM_FILENAME);
727 char *getHelpTextFilename(void)
729 static char *filename = NULL;
731 checked_free(filename);
733 filename = getPath2(getCurrentLevelDir(), HELPTEXT_FILENAME);
738 char *getLevelSetInfoFilename(void)
740 static char *filename = NULL;
755 for (i = 0; basenames[i] != NULL; i++)
757 checked_free(filename);
758 filename = getPath2(getCurrentLevelDir(), basenames[i]);
760 if (fileExists(filename))
767 static char *getLevelSetTitleMessageBasename(int nr, boolean initial)
769 static char basename[32];
771 sprintf(basename, "%s_%d.txt",
772 (initial ? "titlemessage_initial" : "titlemessage"), nr + 1);
777 char *getLevelSetTitleMessageFilename(int nr, boolean initial)
779 static char *filename = NULL;
781 boolean skip_setup_artwork = FALSE;
783 checked_free(filename);
785 basename = getLevelSetTitleMessageBasename(nr, initial);
787 if (!gfx.override_level_graphics)
789 // 1st try: look for special artwork in current level series directory
790 filename = getPath3(getCurrentLevelDir(), GRAPHICS_DIRECTORY, basename);
791 if (fileExists(filename))
796 // 2nd try: look for message file in current level set directory
797 filename = getPath2(getCurrentLevelDir(), basename);
798 if (fileExists(filename))
803 // check if there is special artwork configured in level series config
804 if (getLevelArtworkSet(ARTWORK_TYPE_GRAPHICS) != NULL)
806 // 3rd try: look for special artwork configured in level series config
807 filename = getPath2(getLevelArtworkDir(ARTWORK_TYPE_GRAPHICS), basename);
808 if (fileExists(filename))
813 // take missing artwork configured in level set config from default
814 skip_setup_artwork = TRUE;
818 if (!skip_setup_artwork)
820 // 4th try: look for special artwork in configured artwork directory
821 filename = getPath2(getSetupArtworkDir(artwork.gfx_current), basename);
822 if (fileExists(filename))
828 // 5th try: look for default artwork in new default artwork directory
829 filename = getPath2(getDefaultGraphicsDir(GFX_DEFAULT_SUBDIR), basename);
830 if (fileExists(filename))
835 // 6th try: look for default artwork in old default artwork directory
836 filename = getPath2(options.graphics_directory, basename);
837 if (fileExists(filename))
840 return NULL; // cannot find specified artwork file anywhere
843 static char *getCorrectedArtworkBasename(char *basename)
848 char *getCustomImageFilename(char *basename)
850 static char *filename = NULL;
851 boolean skip_setup_artwork = FALSE;
853 checked_free(filename);
855 basename = getCorrectedArtworkBasename(basename);
857 if (!gfx.override_level_graphics)
859 // 1st try: look for special artwork in current level series directory
860 filename = getImg3(getCurrentLevelDir(), GRAPHICS_DIRECTORY, basename);
861 if (fileExists(filename))
866 // check if there is special artwork configured in level series config
867 if (getLevelArtworkSet(ARTWORK_TYPE_GRAPHICS) != NULL)
869 // 2nd try: look for special artwork configured in level series config
870 filename = getImg2(getLevelArtworkDir(ARTWORK_TYPE_GRAPHICS), basename);
871 if (fileExists(filename))
876 // take missing artwork configured in level set config from default
877 skip_setup_artwork = TRUE;
881 if (!skip_setup_artwork)
883 // 3rd try: look for special artwork in configured artwork directory
884 filename = getImg2(getSetupArtworkDir(artwork.gfx_current), basename);
885 if (fileExists(filename))
891 // 4th try: look for default artwork in new default artwork directory
892 filename = getImg2(getDefaultGraphicsDir(GFX_DEFAULT_SUBDIR), basename);
893 if (fileExists(filename))
898 // 5th try: look for default artwork in old default artwork directory
899 filename = getImg2(options.graphics_directory, basename);
900 if (fileExists(filename))
903 if (!strEqual(GFX_FALLBACK_FILENAME, UNDEFINED_FILENAME))
907 Warn("cannot find artwork file '%s' (using fallback)", basename);
909 // 6th try: look for fallback artwork in old default artwork directory
910 // (needed to prevent errors when trying to access unused artwork files)
911 filename = getImg2(options.graphics_directory, GFX_FALLBACK_FILENAME);
912 if (fileExists(filename))
916 return NULL; // cannot find specified artwork file anywhere
919 char *getCustomSoundFilename(char *basename)
921 static char *filename = NULL;
922 boolean skip_setup_artwork = FALSE;
924 checked_free(filename);
926 basename = getCorrectedArtworkBasename(basename);
928 if (!gfx.override_level_sounds)
930 // 1st try: look for special artwork in current level series directory
931 filename = getPath3(getCurrentLevelDir(), SOUNDS_DIRECTORY, basename);
932 if (fileExists(filename))
937 // check if there is special artwork configured in level series config
938 if (getLevelArtworkSet(ARTWORK_TYPE_SOUNDS) != NULL)
940 // 2nd try: look for special artwork configured in level series config
941 filename = getPath2(getLevelArtworkDir(TREE_TYPE_SOUNDS_DIR), basename);
942 if (fileExists(filename))
947 // take missing artwork configured in level set config from default
948 skip_setup_artwork = TRUE;
952 if (!skip_setup_artwork)
954 // 3rd try: look for special artwork in configured artwork directory
955 filename = getPath2(getSetupArtworkDir(artwork.snd_current), basename);
956 if (fileExists(filename))
962 // 4th try: look for default artwork in new default artwork directory
963 filename = getPath2(getDefaultSoundsDir(SND_DEFAULT_SUBDIR), basename);
964 if (fileExists(filename))
969 // 5th try: look for default artwork in old default artwork directory
970 filename = getPath2(options.sounds_directory, basename);
971 if (fileExists(filename))
974 if (!strEqual(SND_FALLBACK_FILENAME, UNDEFINED_FILENAME))
978 Warn("cannot find artwork file '%s' (using fallback)", basename);
980 // 6th try: look for fallback artwork in old default artwork directory
981 // (needed to prevent errors when trying to access unused artwork files)
982 filename = getPath2(options.sounds_directory, SND_FALLBACK_FILENAME);
983 if (fileExists(filename))
987 return NULL; // cannot find specified artwork file anywhere
990 char *getCustomMusicFilename(char *basename)
992 static char *filename = NULL;
993 boolean skip_setup_artwork = FALSE;
995 checked_free(filename);
997 basename = getCorrectedArtworkBasename(basename);
999 if (!gfx.override_level_music)
1001 // 1st try: look for special artwork in current level series directory
1002 filename = getPath3(getCurrentLevelDir(), MUSIC_DIRECTORY, basename);
1003 if (fileExists(filename))
1008 // check if there is special artwork configured in level series config
1009 if (getLevelArtworkSet(ARTWORK_TYPE_MUSIC) != NULL)
1011 // 2nd try: look for special artwork configured in level series config
1012 filename = getPath2(getLevelArtworkDir(TREE_TYPE_MUSIC_DIR), basename);
1013 if (fileExists(filename))
1018 // take missing artwork configured in level set config from default
1019 skip_setup_artwork = TRUE;
1023 if (!skip_setup_artwork)
1025 // 3rd try: look for special artwork in configured artwork directory
1026 filename = getPath2(getSetupArtworkDir(artwork.mus_current), basename);
1027 if (fileExists(filename))
1033 // 4th try: look for default artwork in new default artwork directory
1034 filename = getPath2(getDefaultMusicDir(MUS_DEFAULT_SUBDIR), basename);
1035 if (fileExists(filename))
1040 // 5th try: look for default artwork in old default artwork directory
1041 filename = getPath2(options.music_directory, basename);
1042 if (fileExists(filename))
1045 if (!strEqual(MUS_FALLBACK_FILENAME, UNDEFINED_FILENAME))
1049 Warn("cannot find artwork file '%s' (using fallback)", basename);
1051 // 6th try: look for fallback artwork in old default artwork directory
1052 // (needed to prevent errors when trying to access unused artwork files)
1053 filename = getPath2(options.music_directory, MUS_FALLBACK_FILENAME);
1054 if (fileExists(filename))
1058 return NULL; // cannot find specified artwork file anywhere
1061 char *getCustomArtworkFilename(char *basename, int type)
1063 if (type == ARTWORK_TYPE_GRAPHICS)
1064 return getCustomImageFilename(basename);
1065 else if (type == ARTWORK_TYPE_SOUNDS)
1066 return getCustomSoundFilename(basename);
1067 else if (type == ARTWORK_TYPE_MUSIC)
1068 return getCustomMusicFilename(basename);
1070 return UNDEFINED_FILENAME;
1073 char *getCustomArtworkConfigFilename(int type)
1075 return getCustomArtworkFilename(ARTWORKINFO_FILENAME(type), type);
1078 char *getCustomArtworkLevelConfigFilename(int type)
1080 static char *filename = NULL;
1082 checked_free(filename);
1084 filename = getPath2(getLevelArtworkDir(type), ARTWORKINFO_FILENAME(type));
1089 char *getCustomMusicDirectory(void)
1091 static char *directory = NULL;
1092 boolean skip_setup_artwork = FALSE;
1094 checked_free(directory);
1096 if (!gfx.override_level_music)
1098 // 1st try: look for special artwork in current level series directory
1099 directory = getPath2(getCurrentLevelDir(), MUSIC_DIRECTORY);
1100 if (directoryExists(directory))
1105 // check if there is special artwork configured in level series config
1106 if (getLevelArtworkSet(ARTWORK_TYPE_MUSIC) != NULL)
1108 // 2nd try: look for special artwork configured in level series config
1109 directory = getStringCopy(getLevelArtworkDir(TREE_TYPE_MUSIC_DIR));
1110 if (directoryExists(directory))
1115 // take missing artwork configured in level set config from default
1116 skip_setup_artwork = TRUE;
1120 if (!skip_setup_artwork)
1122 // 3rd try: look for special artwork in configured artwork directory
1123 directory = getStringCopy(getSetupArtworkDir(artwork.mus_current));
1124 if (directoryExists(directory))
1130 // 4th try: look for default artwork in new default artwork directory
1131 directory = getStringCopy(getDefaultMusicDir(MUS_DEFAULT_SUBDIR));
1132 if (directoryExists(directory))
1137 // 5th try: look for default artwork in old default artwork directory
1138 directory = getStringCopy(options.music_directory);
1139 if (directoryExists(directory))
1142 return NULL; // cannot find specified artwork file anywhere
1145 void InitTapeDirectory(char *level_subdir)
1147 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
1148 createDirectory(getTapeDir(NULL), "main tape", PERMS_PRIVATE);
1149 createDirectory(getTapeDir(level_subdir), "level tape", PERMS_PRIVATE);
1152 void InitScoreDirectory(char *level_subdir)
1154 createDirectory(getMainUserGameDataDir(), "main user data", PERMS_PRIVATE);
1155 createDirectory(getScoreDir(NULL), "main score", PERMS_PRIVATE);
1156 createDirectory(getScoreDir(level_subdir), "level score", PERMS_PRIVATE);
1159 void InitScoreCacheDirectory(char *level_subdir)
1161 createDirectory(getMainUserGameDataDir(), "main user data", PERMS_PRIVATE);
1162 createDirectory(getCacheDir(), "cache data", PERMS_PRIVATE);
1163 createDirectory(getScoreCacheDir(NULL), "main score", PERMS_PRIVATE);
1164 createDirectory(getScoreCacheDir(level_subdir), "level score", PERMS_PRIVATE);
1167 static void SaveUserLevelInfo(void);
1169 void InitUserLevelDirectory(char *level_subdir)
1171 if (!directoryExists(getUserLevelDir(level_subdir)))
1173 createDirectory(getMainUserGameDataDir(), "main user data", PERMS_PRIVATE);
1174 createDirectory(getUserLevelDir(NULL), "main user level", PERMS_PRIVATE);
1175 createDirectory(getUserLevelDir(level_subdir), "user level", PERMS_PRIVATE);
1177 if (setup.internal.create_user_levelset)
1178 SaveUserLevelInfo();
1182 void InitNetworkLevelDirectory(char *level_subdir)
1184 if (!directoryExists(getNetworkLevelDir(level_subdir)))
1186 createDirectory(getMainUserGameDataDir(), "main user data", PERMS_PRIVATE);
1187 createDirectory(getNetworkDir(), "network data", PERMS_PRIVATE);
1188 createDirectory(getNetworkLevelDir(NULL), "main network level", PERMS_PRIVATE);
1189 createDirectory(getNetworkLevelDir(level_subdir), "network level", PERMS_PRIVATE);
1193 void InitLevelSetupDirectory(char *level_subdir)
1195 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
1196 createDirectory(getLevelSetupDir(NULL), "main level setup", PERMS_PRIVATE);
1197 createDirectory(getLevelSetupDir(level_subdir), "level setup", PERMS_PRIVATE);
1200 static void InitCacheDirectory(void)
1202 createDirectory(getMainUserGameDataDir(), "main user data", PERMS_PRIVATE);
1203 createDirectory(getCacheDir(), "cache data", PERMS_PRIVATE);
1207 // ----------------------------------------------------------------------------
1208 // some functions to handle lists of level and artwork directories
1209 // ----------------------------------------------------------------------------
1211 TreeInfo *newTreeInfo(void)
1213 return checked_calloc(sizeof(TreeInfo));
1216 TreeInfo *newTreeInfo_setDefaults(int type)
1218 TreeInfo *ti = newTreeInfo();
1220 setTreeInfoToDefaults(ti, type);
1225 void pushTreeInfo(TreeInfo **node_first, TreeInfo *node_new)
1227 node_new->next = *node_first;
1228 *node_first = node_new;
1231 void removeTreeInfo(TreeInfo **node_first)
1233 TreeInfo *node_old = *node_first;
1235 *node_first = node_old->next;
1236 node_old->next = NULL;
1238 freeTreeInfo(node_old);
1241 int numTreeInfo(TreeInfo *node)
1254 boolean validLevelSeries(TreeInfo *node)
1256 // in a number of cases, tree node is no valid level set
1257 if (node == NULL || node->node_group || node->parent_link || node->is_copy)
1263 TreeInfo *getValidLevelSeries(TreeInfo *node, TreeInfo *default_node)
1265 if (validLevelSeries(node))
1267 else if (node->is_copy)
1268 return getTreeInfoFromIdentifier(leveldir_first, node->identifier);
1270 return getFirstValidTreeInfoEntry(default_node);
1273 static TreeInfo *getValidTreeInfoEntryExt(TreeInfo *node, boolean get_next_node)
1278 if (node->node_group) // enter node group (step down into tree)
1279 return getFirstValidTreeInfoEntry(node->node_group);
1281 if (node->parent_link) // skip first node (back link) of node group
1282 get_next_node = TRUE;
1284 if (!get_next_node) // get current regular tree node
1287 // get next regular tree node, or step up until one is found
1288 while (node->next == NULL && node->node_parent != NULL)
1289 node = node->node_parent;
1291 return getFirstValidTreeInfoEntry(node->next);
1294 TreeInfo *getFirstValidTreeInfoEntry(TreeInfo *node)
1296 return getValidTreeInfoEntryExt(node, FALSE);
1299 TreeInfo *getNextValidTreeInfoEntry(TreeInfo *node)
1301 return getValidTreeInfoEntryExt(node, TRUE);
1304 TreeInfo *getTreeInfoFirstGroupEntry(TreeInfo *node)
1309 if (node->node_parent == NULL) // top level group
1310 return *node->node_top;
1311 else // sub level group
1312 return node->node_parent->node_group;
1315 int numTreeInfoInGroup(TreeInfo *node)
1317 return numTreeInfo(getTreeInfoFirstGroupEntry(node));
1320 int getPosFromTreeInfo(TreeInfo *node)
1322 TreeInfo *node_cmp = getTreeInfoFirstGroupEntry(node);
1327 if (node_cmp == node)
1331 node_cmp = node_cmp->next;
1337 TreeInfo *getTreeInfoFromPos(TreeInfo *node, int pos)
1339 TreeInfo *node_default = node;
1351 return node_default;
1354 static TreeInfo *getTreeInfoFromIdentifierExt(TreeInfo *node, char *identifier,
1355 int node_type_wanted)
1357 if (identifier == NULL)
1362 if (TREE_NODE_TYPE(node) == node_type_wanted &&
1363 strEqual(identifier, node->identifier))
1366 if (node->node_group)
1368 TreeInfo *node_group = getTreeInfoFromIdentifierExt(node->node_group,
1381 TreeInfo *getTreeInfoFromIdentifier(TreeInfo *node, char *identifier)
1383 return getTreeInfoFromIdentifierExt(node, identifier, TREE_NODE_TYPE_DEFAULT);
1386 static TreeInfo *cloneTreeNode(TreeInfo **node_top, TreeInfo *node_parent,
1387 TreeInfo *node, boolean skip_sets_without_levels)
1394 if (!node->parent_link && !node->level_group &&
1395 skip_sets_without_levels && node->levels == 0)
1396 return cloneTreeNode(node_top, node_parent, node->next,
1397 skip_sets_without_levels);
1399 node_new = getTreeInfoCopy(node); // copy complete node
1401 node_new->node_top = node_top; // correct top node link
1402 node_new->node_parent = node_parent; // correct parent node link
1404 if (node->level_group)
1405 node_new->node_group = cloneTreeNode(node_top, node_new, node->node_group,
1406 skip_sets_without_levels);
1408 node_new->next = cloneTreeNode(node_top, node_parent, node->next,
1409 skip_sets_without_levels);
1414 static void cloneTree(TreeInfo **ti_new, TreeInfo *ti, boolean skip_empty_sets)
1416 TreeInfo *ti_cloned = cloneTreeNode(ti_new, NULL, ti, skip_empty_sets);
1418 *ti_new = ti_cloned;
1421 static boolean adjustTreeGraphicsForEMC(TreeInfo *node)
1423 boolean settings_changed = FALSE;
1427 boolean want_ecs = (setup.prefer_aga_graphics == FALSE);
1428 boolean want_aga = (setup.prefer_aga_graphics == TRUE);
1429 boolean has_only_ecs = (!node->graphics_set && !node->graphics_set_aga);
1430 boolean has_only_aga = (!node->graphics_set && !node->graphics_set_ecs);
1431 char *graphics_set = NULL;
1433 if (node->graphics_set_ecs && (want_ecs || has_only_ecs))
1434 graphics_set = node->graphics_set_ecs;
1436 if (node->graphics_set_aga && (want_aga || has_only_aga))
1437 graphics_set = node->graphics_set_aga;
1439 if (graphics_set && !strEqual(node->graphics_set, graphics_set))
1441 setString(&node->graphics_set, graphics_set);
1442 settings_changed = TRUE;
1445 if (node->node_group != NULL)
1446 settings_changed |= adjustTreeGraphicsForEMC(node->node_group);
1451 return settings_changed;
1454 static boolean adjustTreeSoundsForEMC(TreeInfo *node)
1456 boolean settings_changed = FALSE;
1460 boolean want_default = (setup.prefer_lowpass_sounds == FALSE);
1461 boolean want_lowpass = (setup.prefer_lowpass_sounds == TRUE);
1462 boolean has_only_default = (!node->sounds_set && !node->sounds_set_lowpass);
1463 boolean has_only_lowpass = (!node->sounds_set && !node->sounds_set_default);
1464 char *sounds_set = NULL;
1466 if (node->sounds_set_default && (want_default || has_only_default))
1467 sounds_set = node->sounds_set_default;
1469 if (node->sounds_set_lowpass && (want_lowpass || has_only_lowpass))
1470 sounds_set = node->sounds_set_lowpass;
1472 if (sounds_set && !strEqual(node->sounds_set, sounds_set))
1474 setString(&node->sounds_set, sounds_set);
1475 settings_changed = TRUE;
1478 if (node->node_group != NULL)
1479 settings_changed |= adjustTreeSoundsForEMC(node->node_group);
1484 return settings_changed;
1487 int dumpTreeInfo(TreeInfo *node, int depth)
1489 char bullet_list[] = { '-', '*', 'o' };
1490 int num_leaf_nodes = 0;
1494 Debug("tree", "Dumping TreeInfo:");
1498 char bullet = bullet_list[depth % ARRAY_SIZE(bullet_list)];
1500 for (i = 0; i < depth * 2; i++)
1501 DebugContinued("", " ");
1503 DebugContinued("tree", "%c '%s' ['%s] [PARENT: '%s'] %s\n",
1504 bullet, node->name, node->identifier,
1505 (node->node_parent ? node->node_parent->identifier : "-"),
1506 (node->node_group ? "[GROUP]" :
1507 node->is_copy ? "[COPY]" : ""));
1509 if (!node->node_group && !node->parent_link)
1513 // use for dumping artwork info tree
1514 Debug("tree", "subdir == '%s' ['%s', '%s'] [%d])",
1515 node->subdir, node->fullpath, node->basepath, node->in_user_dir);
1518 if (node->node_group != NULL)
1519 num_leaf_nodes += dumpTreeInfo(node->node_group, depth + 1);
1525 Debug("tree", "Summary: %d leaf nodes found", num_leaf_nodes);
1527 return num_leaf_nodes;
1530 void sortTreeInfoBySortFunction(TreeInfo **node_first,
1531 int (*compare_function)(const void *,
1534 int num_nodes = numTreeInfo(*node_first);
1535 TreeInfo **sort_array;
1536 TreeInfo *node = *node_first;
1542 // allocate array for sorting structure pointers
1543 sort_array = checked_calloc(num_nodes * sizeof(TreeInfo *));
1545 // writing structure pointers to sorting array
1546 while (i < num_nodes && node) // double boundary check...
1548 sort_array[i] = node;
1554 // sorting the structure pointers in the sorting array
1555 qsort(sort_array, num_nodes, sizeof(TreeInfo *),
1558 // update the linkage of list elements with the sorted node array
1559 for (i = 0; i < num_nodes - 1; i++)
1560 sort_array[i]->next = sort_array[i + 1];
1561 sort_array[num_nodes - 1]->next = NULL;
1563 // update the linkage of the main list anchor pointer
1564 *node_first = sort_array[0];
1568 // now recursively sort the level group structures
1572 if (node->node_group != NULL)
1573 sortTreeInfoBySortFunction(&node->node_group, compare_function);
1579 void sortTreeInfo(TreeInfo **node_first)
1581 sortTreeInfoBySortFunction(node_first, compareTreeInfoEntries);
1585 // ============================================================================
1586 // some stuff from "files.c"
1587 // ============================================================================
1589 #if defined(PLATFORM_WIN32)
1591 #define S_IRGRP S_IRUSR
1594 #define S_IROTH S_IRUSR
1597 #define S_IWGRP S_IWUSR
1600 #define S_IWOTH S_IWUSR
1603 #define S_IXGRP S_IXUSR
1606 #define S_IXOTH S_IXUSR
1609 #define S_IRWXG (S_IRGRP | S_IWGRP | S_IXGRP)
1614 #endif // PLATFORM_WIN32
1616 // file permissions for newly written files
1617 #define MODE_R_ALL (S_IRUSR | S_IRGRP | S_IROTH)
1618 #define MODE_W_ALL (S_IWUSR | S_IWGRP | S_IWOTH)
1619 #define MODE_X_ALL (S_IXUSR | S_IXGRP | S_IXOTH)
1621 #define MODE_W_PRIVATE (S_IWUSR)
1622 #define MODE_W_PUBLIC_FILE (S_IWUSR | S_IWGRP)
1623 #define MODE_W_PUBLIC_DIR (S_IWUSR | S_IWGRP | S_ISGID)
1625 #define DIR_PERMS_PRIVATE (MODE_R_ALL | MODE_X_ALL | MODE_W_PRIVATE)
1626 #define DIR_PERMS_PUBLIC (MODE_R_ALL | MODE_X_ALL | MODE_W_PUBLIC_DIR)
1627 #define DIR_PERMS_PUBLIC_ALL (MODE_R_ALL | MODE_X_ALL | MODE_W_ALL)
1629 #define FILE_PERMS_PRIVATE (MODE_R_ALL | MODE_W_PRIVATE)
1630 #define FILE_PERMS_PUBLIC (MODE_R_ALL | MODE_W_PUBLIC_FILE)
1631 #define FILE_PERMS_PUBLIC_ALL (MODE_R_ALL | MODE_W_ALL)
1634 char *getHomeDir(void)
1636 static char *dir = NULL;
1638 #if defined(PLATFORM_WIN32)
1641 dir = checked_malloc(MAX_PATH + 1);
1643 if (!SUCCEEDED(SHGetFolderPath(NULL, CSIDL_PERSONAL, NULL, 0, dir)))
1646 #elif defined(PLATFORM_EMSCRIPTEN)
1647 dir = "/persistent";
1648 #elif defined(PLATFORM_UNIX)
1651 if ((dir = getenv("HOME")) == NULL)
1653 dir = getUnixHomeDir();
1656 dir = getStringCopy(dir);
1668 char *getPersonalDataDir(void)
1670 static char *personal_data_dir = NULL;
1672 #if defined(PLATFORM_MACOSX)
1673 if (personal_data_dir == NULL)
1674 personal_data_dir = getPath2(getHomeDir(), "Documents");
1676 if (personal_data_dir == NULL)
1677 personal_data_dir = getHomeDir();
1680 return personal_data_dir;
1683 char *getMainUserGameDataDir(void)
1685 static char *main_user_data_dir = NULL;
1687 #if defined(PLATFORM_ANDROID)
1688 if (main_user_data_dir == NULL)
1689 main_user_data_dir = (char *)(SDL_AndroidGetExternalStorageState() &
1690 SDL_ANDROID_EXTERNAL_STORAGE_WRITE ?
1691 SDL_AndroidGetExternalStoragePath() :
1692 SDL_AndroidGetInternalStoragePath());
1694 if (main_user_data_dir == NULL)
1695 main_user_data_dir = getPath2(getPersonalDataDir(),
1696 program.userdata_subdir);
1699 return main_user_data_dir;
1702 char *getUserGameDataDir(void)
1705 return getMainUserGameDataDir();
1707 return getUserDir(user.nr);
1710 char *getSetupDir(void)
1712 return getUserGameDataDir();
1715 static mode_t posix_umask(mode_t mask)
1717 #if defined(PLATFORM_UNIX)
1724 static int posix_mkdir(const char *pathname, mode_t mode)
1726 #if defined(PLATFORM_WIN32)
1727 return mkdir(pathname);
1729 return mkdir(pathname, mode);
1733 static boolean posix_process_running_setgid(void)
1735 #if defined(PLATFORM_UNIX)
1736 return (getgid() != getegid());
1742 void createDirectory(char *dir, char *text, int permission_class)
1744 if (directoryExists(dir))
1747 // leave "other" permissions in umask untouched, but ensure group parts
1748 // of USERDATA_DIR_MODE are not masked
1749 mode_t dir_mode = (permission_class == PERMS_PRIVATE ?
1750 DIR_PERMS_PRIVATE : DIR_PERMS_PUBLIC);
1751 mode_t last_umask = posix_umask(0);
1752 mode_t group_umask = ~(dir_mode & S_IRWXG);
1753 int running_setgid = posix_process_running_setgid();
1755 if (permission_class == PERMS_PUBLIC)
1757 // if we're setgid, protect files against "other"
1758 // else keep umask(0) to make the dir world-writable
1761 posix_umask(last_umask & group_umask);
1763 dir_mode = DIR_PERMS_PUBLIC_ALL;
1766 if (posix_mkdir(dir, dir_mode) != 0)
1767 Warn("cannot create %s directory '%s': %s", text, dir, strerror(errno));
1769 if (permission_class == PERMS_PUBLIC && !running_setgid)
1770 chmod(dir, dir_mode);
1772 posix_umask(last_umask); // restore previous umask
1775 void InitMainUserDataDirectory(void)
1777 createDirectory(getMainUserGameDataDir(), "main user data", PERMS_PRIVATE);
1780 void InitUserDataDirectory(void)
1782 createDirectory(getMainUserGameDataDir(), "main user data", PERMS_PRIVATE);
1786 createDirectory(getUserDir(-1), "users", PERMS_PRIVATE);
1787 createDirectory(getUserDir(user.nr), "user data", PERMS_PRIVATE);
1791 void SetFilePermissions(char *filename, int permission_class)
1793 int running_setgid = posix_process_running_setgid();
1794 int perms = (permission_class == PERMS_PRIVATE ?
1795 FILE_PERMS_PRIVATE : FILE_PERMS_PUBLIC);
1797 if (permission_class == PERMS_PUBLIC && !running_setgid)
1798 perms = FILE_PERMS_PUBLIC_ALL;
1800 chmod(filename, perms);
1803 char *getCookie(char *file_type)
1805 static char cookie[MAX_COOKIE_LEN + 1];
1807 if (strlen(program.cookie_prefix) + 1 +
1808 strlen(file_type) + strlen("_FILE_VERSION_x.x") > MAX_COOKIE_LEN)
1809 return "[COOKIE ERROR]"; // should never happen
1811 sprintf(cookie, "%s_%s_FILE_VERSION_%d.%d",
1812 program.cookie_prefix, file_type,
1813 program.version_super, program.version_major);
1818 void fprintFileHeader(FILE *file, char *basename)
1820 char *prefix = "# ";
1823 fprintf_line_with_prefix(file, prefix, sep1, 77);
1824 fprintf(file, "%s%s\n", prefix, basename);
1825 fprintf_line_with_prefix(file, prefix, sep1, 77);
1826 fprintf(file, "\n");
1829 int getFileVersionFromCookieString(const char *cookie)
1831 const char *ptr_cookie1, *ptr_cookie2;
1832 const char *pattern1 = "_FILE_VERSION_";
1833 const char *pattern2 = "?.?";
1834 const int len_cookie = strlen(cookie);
1835 const int len_pattern1 = strlen(pattern1);
1836 const int len_pattern2 = strlen(pattern2);
1837 const int len_pattern = len_pattern1 + len_pattern2;
1838 int version_super, version_major;
1840 if (len_cookie <= len_pattern)
1843 ptr_cookie1 = &cookie[len_cookie - len_pattern];
1844 ptr_cookie2 = &cookie[len_cookie - len_pattern2];
1846 if (strncmp(ptr_cookie1, pattern1, len_pattern1) != 0)
1849 if (ptr_cookie2[0] < '0' || ptr_cookie2[0] > '9' ||
1850 ptr_cookie2[1] != '.' ||
1851 ptr_cookie2[2] < '0' || ptr_cookie2[2] > '9')
1854 version_super = ptr_cookie2[0] - '0';
1855 version_major = ptr_cookie2[2] - '0';
1857 return VERSION_IDENT(version_super, version_major, 0, 0);
1860 boolean checkCookieString(const char *cookie, const char *template)
1862 const char *pattern = "_FILE_VERSION_?.?";
1863 const int len_cookie = strlen(cookie);
1864 const int len_template = strlen(template);
1865 const int len_pattern = strlen(pattern);
1867 if (len_cookie != len_template)
1870 if (strncmp(cookie, template, len_cookie - len_pattern) != 0)
1877 // ----------------------------------------------------------------------------
1878 // setup file list and hash handling functions
1879 // ----------------------------------------------------------------------------
1881 char *getFormattedSetupEntry(char *token, char *value)
1884 static char entry[MAX_LINE_LEN];
1886 // if value is an empty string, just return token without value
1890 // start with the token and some spaces to format output line
1891 sprintf(entry, "%s:", token);
1892 for (i = strlen(entry); i < token_value_position; i++)
1895 // continue with the token's value
1896 strcat(entry, value);
1901 SetupFileList *newSetupFileList(char *token, char *value)
1903 SetupFileList *new = checked_malloc(sizeof(SetupFileList));
1905 new->token = getStringCopy(token);
1906 new->value = getStringCopy(value);
1913 void freeSetupFileList(SetupFileList *list)
1918 checked_free(list->token);
1919 checked_free(list->value);
1922 freeSetupFileList(list->next);
1927 char *getListEntry(SetupFileList *list, char *token)
1932 if (strEqual(list->token, token))
1935 return getListEntry(list->next, token);
1938 SetupFileList *setListEntry(SetupFileList *list, char *token, char *value)
1943 if (strEqual(list->token, token))
1945 checked_free(list->value);
1947 list->value = getStringCopy(value);
1951 else if (list->next == NULL)
1952 return (list->next = newSetupFileList(token, value));
1954 return setListEntry(list->next, token, value);
1957 SetupFileList *addListEntry(SetupFileList *list, char *token, char *value)
1962 if (list->next == NULL)
1963 return (list->next = newSetupFileList(token, value));
1965 return addListEntry(list->next, token, value);
1968 #if ENABLE_UNUSED_CODE
1970 static void printSetupFileList(SetupFileList *list)
1975 Debug("setup:printSetupFileList", "token: '%s'", list->token);
1976 Debug("setup:printSetupFileList", "value: '%s'", list->value);
1978 printSetupFileList(list->next);
1984 DEFINE_HASHTABLE_INSERT(insert_hash_entry, char, char);
1985 DEFINE_HASHTABLE_SEARCH(search_hash_entry, char, char);
1986 DEFINE_HASHTABLE_CHANGE(change_hash_entry, char, char);
1987 DEFINE_HASHTABLE_REMOVE(remove_hash_entry, char, char);
1989 #define insert_hash_entry hashtable_insert
1990 #define search_hash_entry hashtable_search
1991 #define change_hash_entry hashtable_change
1992 #define remove_hash_entry hashtable_remove
1995 unsigned int get_hash_from_key(void *key)
2000 This algorithm (k=33) was first reported by Dan Bernstein many years ago in
2001 'comp.lang.c'. Another version of this algorithm (now favored by Bernstein)
2002 uses XOR: hash(i) = hash(i - 1) * 33 ^ str[i]; the magic of number 33 (why
2003 it works better than many other constants, prime or not) has never been
2004 adequately explained.
2006 If you just want to have a good hash function, and cannot wait, djb2
2007 is one of the best string hash functions i know. It has excellent
2008 distribution and speed on many different sets of keys and table sizes.
2009 You are not likely to do better with one of the "well known" functions
2010 such as PJW, K&R, etc.
2012 Ozan (oz) Yigit [http://www.cs.yorku.ca/~oz/hash.html]
2015 char *str = (char *)key;
2016 unsigned int hash = 5381;
2019 while ((c = *str++))
2020 hash = ((hash << 5) + hash) + c; // hash * 33 + c
2025 static int keys_are_equal(void *key1, void *key2)
2027 return (strEqual((char *)key1, (char *)key2));
2030 SetupFileHash *newSetupFileHash(void)
2032 SetupFileHash *new_hash =
2033 create_hashtable(16, 0.75, get_hash_from_key, keys_are_equal);
2035 if (new_hash == NULL)
2036 Fail("create_hashtable() failed -- out of memory");
2041 void freeSetupFileHash(SetupFileHash *hash)
2046 hashtable_destroy(hash, 1); // 1 == also free values stored in hash
2049 char *getHashEntry(SetupFileHash *hash, char *token)
2054 return search_hash_entry(hash, token);
2057 void setHashEntry(SetupFileHash *hash, char *token, char *value)
2064 value_copy = getStringCopy(value);
2066 // change value; if it does not exist, insert it as new
2067 if (!change_hash_entry(hash, token, value_copy))
2068 if (!insert_hash_entry(hash, getStringCopy(token), value_copy))
2069 Fail("cannot insert into hash -- aborting");
2072 char *removeHashEntry(SetupFileHash *hash, char *token)
2077 return remove_hash_entry(hash, token);
2080 #if ENABLE_UNUSED_CODE
2082 static void printSetupFileHash(SetupFileHash *hash)
2084 BEGIN_HASH_ITERATION(hash, itr)
2086 Debug("setup:printSetupFileHash", "token: '%s'", HASH_ITERATION_TOKEN(itr));
2087 Debug("setup:printSetupFileHash", "value: '%s'", HASH_ITERATION_VALUE(itr));
2089 END_HASH_ITERATION(hash, itr)
2094 #define ALLOW_TOKEN_VALUE_SEPARATOR_BEING_WHITESPACE 1
2095 #define CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING 0
2096 #define CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH 0
2098 static boolean token_value_separator_found = FALSE;
2099 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2100 static boolean token_value_separator_warning = FALSE;
2102 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2103 static boolean token_already_exists_warning = FALSE;
2106 static boolean getTokenValueFromSetupLineExt(char *line,
2107 char **token_ptr, char **value_ptr,
2108 char *filename, char *line_raw,
2110 boolean separator_required)
2112 static char line_copy[MAX_LINE_LEN + 1], line_raw_copy[MAX_LINE_LEN + 1];
2113 char *token, *value, *line_ptr;
2115 // when externally invoked via ReadTokenValueFromLine(), copy line buffers
2116 if (line_raw == NULL)
2118 strncpy(line_copy, line, MAX_LINE_LEN);
2119 line_copy[MAX_LINE_LEN] = '\0';
2122 strcpy(line_raw_copy, line_copy);
2123 line_raw = line_raw_copy;
2126 // cut trailing comment from input line
2127 for (line_ptr = line; *line_ptr; line_ptr++)
2129 if (*line_ptr == '#')
2136 // cut trailing whitespaces from input line
2137 for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
2138 if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
2141 // ignore empty lines
2145 // cut leading whitespaces from token
2146 for (token = line; *token; token++)
2147 if (*token != ' ' && *token != '\t')
2150 // start with empty value as reliable default
2153 token_value_separator_found = FALSE;
2155 // find end of token to determine start of value
2156 for (line_ptr = token; *line_ptr; line_ptr++)
2158 // first look for an explicit token/value separator, like ':' or '='
2159 if (*line_ptr == ':' || *line_ptr == '=')
2161 *line_ptr = '\0'; // terminate token string
2162 value = line_ptr + 1; // set beginning of value
2164 token_value_separator_found = TRUE;
2170 #if ALLOW_TOKEN_VALUE_SEPARATOR_BEING_WHITESPACE
2171 // fallback: if no token/value separator found, also allow whitespaces
2172 if (!token_value_separator_found && !separator_required)
2174 for (line_ptr = token; *line_ptr; line_ptr++)
2176 if (*line_ptr == ' ' || *line_ptr == '\t')
2178 *line_ptr = '\0'; // terminate token string
2179 value = line_ptr + 1; // set beginning of value
2181 token_value_separator_found = TRUE;
2187 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2188 if (token_value_separator_found)
2190 if (!token_value_separator_warning)
2192 Debug("setup", "---");
2194 if (filename != NULL)
2196 Debug("setup", "missing token/value separator(s) in config file:");
2197 Debug("setup", "- config file: '%s'", filename);
2201 Debug("setup", "missing token/value separator(s):");
2204 token_value_separator_warning = TRUE;
2207 if (filename != NULL)
2208 Debug("setup", "- line %d: '%s'", line_nr, line_raw);
2210 Debug("setup", "- line: '%s'", line_raw);
2216 // cut trailing whitespaces from token
2217 for (line_ptr = &token[strlen(token)]; line_ptr >= token; line_ptr--)
2218 if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
2221 // cut leading whitespaces from value
2222 for (; *value; value++)
2223 if (*value != ' ' && *value != '\t')
2232 boolean getTokenValueFromSetupLine(char *line, char **token, char **value)
2234 // while the internal (old) interface does not require a token/value
2235 // separator (for downwards compatibility with existing files which
2236 // don't use them), it is mandatory for the external (new) interface
2238 return getTokenValueFromSetupLineExt(line, token, value, NULL, NULL, 0, TRUE);
2241 static boolean loadSetupFileData(void *setup_file_data, char *filename,
2242 boolean top_recursion_level, boolean is_hash)
2244 static SetupFileHash *include_filename_hash = NULL;
2245 char line[MAX_LINE_LEN], line_raw[MAX_LINE_LEN], previous_line[MAX_LINE_LEN];
2246 char *token, *value, *line_ptr;
2247 void *insert_ptr = NULL;
2248 boolean read_continued_line = FALSE;
2250 int line_nr = 0, token_count = 0, include_count = 0;
2252 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2253 token_value_separator_warning = FALSE;
2256 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2257 token_already_exists_warning = FALSE;
2260 if (!(file = openFile(filename, MODE_READ)))
2262 #if DEBUG_NO_CONFIG_FILE
2263 Debug("setup", "cannot open configuration file '%s'", filename);
2269 // use "insert pointer" to store list end for constant insertion complexity
2271 insert_ptr = setup_file_data;
2273 // on top invocation, create hash to mark included files (to prevent loops)
2274 if (top_recursion_level)
2275 include_filename_hash = newSetupFileHash();
2277 // mark this file as already included (to prevent including it again)
2278 setHashEntry(include_filename_hash, getBaseNamePtr(filename), "true");
2280 while (!checkEndOfFile(file))
2282 // read next line of input file
2283 if (!getStringFromFile(file, line, MAX_LINE_LEN))
2286 // check if line was completely read and is terminated by line break
2287 if (strlen(line) > 0 && line[strlen(line) - 1] == '\n')
2290 // cut trailing line break (this can be newline and/or carriage return)
2291 for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
2292 if ((*line_ptr == '\n' || *line_ptr == '\r') && *(line_ptr + 1) == '\0')
2295 // copy raw input line for later use (mainly debugging output)
2296 strcpy(line_raw, line);
2298 if (read_continued_line)
2300 // append new line to existing line, if there is enough space
2301 if (strlen(previous_line) + strlen(line_ptr) < MAX_LINE_LEN)
2302 strcat(previous_line, line_ptr);
2304 strcpy(line, previous_line); // copy storage buffer to line
2306 read_continued_line = FALSE;
2309 // if the last character is '\', continue at next line
2310 if (strlen(line) > 0 && line[strlen(line) - 1] == '\\')
2312 line[strlen(line) - 1] = '\0'; // cut off trailing backslash
2313 strcpy(previous_line, line); // copy line to storage buffer
2315 read_continued_line = TRUE;
2320 if (!getTokenValueFromSetupLineExt(line, &token, &value, filename,
2321 line_raw, line_nr, FALSE))
2326 if (strEqual(token, "include"))
2328 if (getHashEntry(include_filename_hash, value) == NULL)
2330 char *basepath = getBasePath(filename);
2331 char *basename = getBaseName(value);
2332 char *filename_include = getPath2(basepath, basename);
2334 loadSetupFileData(setup_file_data, filename_include, FALSE, is_hash);
2338 free(filename_include);
2344 Warn("ignoring already processed file '%s'", value);
2351 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2353 getHashEntry((SetupFileHash *)setup_file_data, token);
2355 if (old_value != NULL)
2357 if (!token_already_exists_warning)
2359 Debug("setup", "---");
2360 Debug("setup", "duplicate token(s) found in config file:");
2361 Debug("setup", "- config file: '%s'", filename);
2363 token_already_exists_warning = TRUE;
2366 Debug("setup", "- token: '%s' (in line %d)", token, line_nr);
2367 Debug("setup", " old value: '%s'", old_value);
2368 Debug("setup", " new value: '%s'", value);
2372 setHashEntry((SetupFileHash *)setup_file_data, token, value);
2376 insert_ptr = addListEntry((SetupFileList *)insert_ptr, token, value);
2386 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2387 if (token_value_separator_warning)
2388 Debug("setup", "---");
2391 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2392 if (token_already_exists_warning)
2393 Debug("setup", "---");
2396 if (token_count == 0 && include_count == 0)
2397 Warn("configuration file '%s' is empty", filename);
2399 if (top_recursion_level)
2400 freeSetupFileHash(include_filename_hash);
2405 static int compareSetupFileData(const void *object1, const void *object2)
2407 const struct ConfigInfo *entry1 = (struct ConfigInfo *)object1;
2408 const struct ConfigInfo *entry2 = (struct ConfigInfo *)object2;
2410 return strcmp(entry1->token, entry2->token);
2413 static void saveSetupFileHash(SetupFileHash *hash, char *filename)
2415 int item_count = hashtable_count(hash);
2416 int item_size = sizeof(struct ConfigInfo);
2417 struct ConfigInfo *sort_array = checked_malloc(item_count * item_size);
2421 // copy string pointers from hash to array
2422 BEGIN_HASH_ITERATION(hash, itr)
2424 sort_array[i].token = HASH_ITERATION_TOKEN(itr);
2425 sort_array[i].value = HASH_ITERATION_VALUE(itr);
2429 if (i > item_count) // should never happen
2432 END_HASH_ITERATION(hash, itr)
2434 // sort string pointers from hash in array
2435 qsort(sort_array, item_count, item_size, compareSetupFileData);
2437 if (!(file = fopen(filename, MODE_WRITE)))
2439 Warn("cannot write configuration file '%s'", filename);
2444 fprintf(file, "%s\n\n", getFormattedSetupEntry("program.version",
2445 program.version_string));
2446 for (i = 0; i < item_count; i++)
2447 fprintf(file, "%s\n", getFormattedSetupEntry(sort_array[i].token,
2448 sort_array[i].value));
2451 checked_free(sort_array);
2454 SetupFileList *loadSetupFileList(char *filename)
2456 SetupFileList *setup_file_list = newSetupFileList("", "");
2457 SetupFileList *first_valid_list_entry;
2459 if (!loadSetupFileData(setup_file_list, filename, TRUE, FALSE))
2461 freeSetupFileList(setup_file_list);
2466 first_valid_list_entry = setup_file_list->next;
2468 // free empty list header
2469 setup_file_list->next = NULL;
2470 freeSetupFileList(setup_file_list);
2472 return first_valid_list_entry;
2475 SetupFileHash *loadSetupFileHash(char *filename)
2477 SetupFileHash *setup_file_hash = newSetupFileHash();
2479 if (!loadSetupFileData(setup_file_hash, filename, TRUE, TRUE))
2481 freeSetupFileHash(setup_file_hash);
2486 return setup_file_hash;
2490 // ============================================================================
2492 // ============================================================================
2494 #define TOKEN_STR_LAST_LEVEL_SERIES "last_level_series"
2495 #define TOKEN_STR_LAST_PLAYED_LEVEL "last_played_level"
2496 #define TOKEN_STR_HANDICAP_LEVEL "handicap_level"
2497 #define TOKEN_STR_LAST_USER "last_user"
2499 // level directory info
2500 #define LEVELINFO_TOKEN_IDENTIFIER 0
2501 #define LEVELINFO_TOKEN_NAME 1
2502 #define LEVELINFO_TOKEN_NAME_SORTING 2
2503 #define LEVELINFO_TOKEN_AUTHOR 3
2504 #define LEVELINFO_TOKEN_YEAR 4
2505 #define LEVELINFO_TOKEN_PROGRAM_TITLE 5
2506 #define LEVELINFO_TOKEN_PROGRAM_COPYRIGHT 6
2507 #define LEVELINFO_TOKEN_PROGRAM_COMPANY 7
2508 #define LEVELINFO_TOKEN_IMPORTED_FROM 8
2509 #define LEVELINFO_TOKEN_IMPORTED_BY 9
2510 #define LEVELINFO_TOKEN_TESTED_BY 10
2511 #define LEVELINFO_TOKEN_LEVELS 11
2512 #define LEVELINFO_TOKEN_FIRST_LEVEL 12
2513 #define LEVELINFO_TOKEN_SORT_PRIORITY 13
2514 #define LEVELINFO_TOKEN_LATEST_ENGINE 14
2515 #define LEVELINFO_TOKEN_LEVEL_GROUP 15
2516 #define LEVELINFO_TOKEN_READONLY 16
2517 #define LEVELINFO_TOKEN_GRAPHICS_SET_ECS 17
2518 #define LEVELINFO_TOKEN_GRAPHICS_SET_AGA 18
2519 #define LEVELINFO_TOKEN_GRAPHICS_SET 19
2520 #define LEVELINFO_TOKEN_SOUNDS_SET_DEFAULT 20
2521 #define LEVELINFO_TOKEN_SOUNDS_SET_LOWPASS 21
2522 #define LEVELINFO_TOKEN_SOUNDS_SET 22
2523 #define LEVELINFO_TOKEN_MUSIC_SET 23
2524 #define LEVELINFO_TOKEN_FILENAME 24
2525 #define LEVELINFO_TOKEN_FILETYPE 25
2526 #define LEVELINFO_TOKEN_SPECIAL_FLAGS 26
2527 #define LEVELINFO_TOKEN_HANDICAP 27
2528 #define LEVELINFO_TOKEN_SKIP_LEVELS 28
2529 #define LEVELINFO_TOKEN_USE_EMC_TILES 29
2531 #define NUM_LEVELINFO_TOKENS 30
2533 static LevelDirTree ldi;
2535 static struct TokenInfo levelinfo_tokens[] =
2537 // level directory info
2538 { TYPE_STRING, &ldi.identifier, "identifier" },
2539 { TYPE_STRING, &ldi.name, "name" },
2540 { TYPE_STRING, &ldi.name_sorting, "name_sorting" },
2541 { TYPE_STRING, &ldi.author, "author" },
2542 { TYPE_STRING, &ldi.year, "year" },
2543 { TYPE_STRING, &ldi.program_title, "program_title" },
2544 { TYPE_STRING, &ldi.program_copyright, "program_copyright" },
2545 { TYPE_STRING, &ldi.program_company, "program_company" },
2546 { TYPE_STRING, &ldi.imported_from, "imported_from" },
2547 { TYPE_STRING, &ldi.imported_by, "imported_by" },
2548 { TYPE_STRING, &ldi.tested_by, "tested_by" },
2549 { TYPE_INTEGER, &ldi.levels, "levels" },
2550 { TYPE_INTEGER, &ldi.first_level, "first_level" },
2551 { TYPE_INTEGER, &ldi.sort_priority, "sort_priority" },
2552 { TYPE_BOOLEAN, &ldi.latest_engine, "latest_engine" },
2553 { TYPE_BOOLEAN, &ldi.level_group, "level_group" },
2554 { TYPE_BOOLEAN, &ldi.readonly, "readonly" },
2555 { TYPE_STRING, &ldi.graphics_set_ecs, "graphics_set.ecs" },
2556 { TYPE_STRING, &ldi.graphics_set_aga, "graphics_set.aga" },
2557 { TYPE_STRING, &ldi.graphics_set, "graphics_set" },
2558 { TYPE_STRING, &ldi.sounds_set_default,"sounds_set.default" },
2559 { TYPE_STRING, &ldi.sounds_set_lowpass,"sounds_set.lowpass" },
2560 { TYPE_STRING, &ldi.sounds_set, "sounds_set" },
2561 { TYPE_STRING, &ldi.music_set, "music_set" },
2562 { TYPE_STRING, &ldi.level_filename, "filename" },
2563 { TYPE_STRING, &ldi.level_filetype, "filetype" },
2564 { TYPE_STRING, &ldi.special_flags, "special_flags" },
2565 { TYPE_BOOLEAN, &ldi.handicap, "handicap" },
2566 { TYPE_BOOLEAN, &ldi.skip_levels, "skip_levels" },
2567 { TYPE_BOOLEAN, &ldi.use_emc_tiles, "use_emc_tiles" }
2570 static struct TokenInfo artworkinfo_tokens[] =
2572 // artwork directory info
2573 { TYPE_STRING, &ldi.identifier, "identifier" },
2574 { TYPE_STRING, &ldi.subdir, "subdir" },
2575 { TYPE_STRING, &ldi.name, "name" },
2576 { TYPE_STRING, &ldi.name_sorting, "name_sorting" },
2577 { TYPE_STRING, &ldi.author, "author" },
2578 { TYPE_STRING, &ldi.program_title, "program_title" },
2579 { TYPE_STRING, &ldi.program_copyright, "program_copyright" },
2580 { TYPE_STRING, &ldi.program_company, "program_company" },
2581 { TYPE_INTEGER, &ldi.sort_priority, "sort_priority" },
2582 { TYPE_STRING, &ldi.basepath, "basepath" },
2583 { TYPE_STRING, &ldi.fullpath, "fullpath" },
2584 { TYPE_BOOLEAN, &ldi.in_user_dir, "in_user_dir" },
2585 { TYPE_STRING, &ldi.class_desc, "class_desc" },
2590 static char *optional_tokens[] =
2593 "program_copyright",
2599 static void setTreeInfoToDefaults(TreeInfo *ti, int type)
2603 ti->node_top = (ti->type == TREE_TYPE_LEVEL_DIR ? &leveldir_first :
2604 ti->type == TREE_TYPE_GRAPHICS_DIR ? &artwork.gfx_first :
2605 ti->type == TREE_TYPE_SOUNDS_DIR ? &artwork.snd_first :
2606 ti->type == TREE_TYPE_MUSIC_DIR ? &artwork.mus_first :
2609 ti->node_parent = NULL;
2610 ti->node_group = NULL;
2617 ti->fullpath = NULL;
2618 ti->basepath = NULL;
2619 ti->identifier = NULL;
2620 ti->name = getStringCopy(ANONYMOUS_NAME);
2621 ti->name_sorting = NULL;
2622 ti->author = getStringCopy(ANONYMOUS_NAME);
2625 ti->program_title = NULL;
2626 ti->program_copyright = NULL;
2627 ti->program_company = NULL;
2629 ti->sort_priority = LEVELCLASS_UNDEFINED; // default: least priority
2630 ti->latest_engine = FALSE; // default: get from level
2631 ti->parent_link = FALSE;
2632 ti->is_copy = FALSE;
2633 ti->in_user_dir = FALSE;
2634 ti->user_defined = FALSE;
2636 ti->class_desc = NULL;
2638 ti->infotext = getStringCopy(TREE_INFOTEXT(ti->type));
2640 if (ti->type == TREE_TYPE_LEVEL_DIR)
2642 ti->imported_from = NULL;
2643 ti->imported_by = NULL;
2644 ti->tested_by = NULL;
2646 ti->graphics_set_ecs = NULL;
2647 ti->graphics_set_aga = NULL;
2648 ti->graphics_set = NULL;
2649 ti->sounds_set_default = NULL;
2650 ti->sounds_set_lowpass = NULL;
2651 ti->sounds_set = NULL;
2652 ti->music_set = NULL;
2653 ti->graphics_path = getStringCopy(UNDEFINED_FILENAME);
2654 ti->sounds_path = getStringCopy(UNDEFINED_FILENAME);
2655 ti->music_path = getStringCopy(UNDEFINED_FILENAME);
2657 ti->level_filename = NULL;
2658 ti->level_filetype = NULL;
2660 ti->special_flags = NULL;
2663 ti->first_level = 0;
2665 ti->level_group = FALSE;
2666 ti->handicap_level = 0;
2667 ti->readonly = TRUE;
2668 ti->handicap = TRUE;
2669 ti->skip_levels = FALSE;
2671 ti->use_emc_tiles = FALSE;
2675 static void setTreeInfoToDefaultsFromParent(TreeInfo *ti, TreeInfo *parent)
2679 Warn("setTreeInfoToDefaultsFromParent(): parent == NULL");
2681 setTreeInfoToDefaults(ti, TREE_TYPE_UNDEFINED);
2686 // copy all values from the parent structure
2688 ti->type = parent->type;
2690 ti->node_top = parent->node_top;
2691 ti->node_parent = parent;
2692 ti->node_group = NULL;
2699 ti->fullpath = NULL;
2700 ti->basepath = NULL;
2701 ti->identifier = NULL;
2702 ti->name = getStringCopy(ANONYMOUS_NAME);
2703 ti->name_sorting = NULL;
2704 ti->author = getStringCopy(parent->author);
2705 ti->year = getStringCopy(parent->year);
2707 ti->program_title = getStringCopy(parent->program_title);
2708 ti->program_copyright = getStringCopy(parent->program_copyright);
2709 ti->program_company = getStringCopy(parent->program_company);
2711 ti->sort_priority = parent->sort_priority;
2712 ti->latest_engine = parent->latest_engine;
2713 ti->parent_link = FALSE;
2714 ti->is_copy = FALSE;
2715 ti->in_user_dir = parent->in_user_dir;
2716 ti->user_defined = parent->user_defined;
2717 ti->color = parent->color;
2718 ti->class_desc = getStringCopy(parent->class_desc);
2720 ti->infotext = getStringCopy(parent->infotext);
2722 if (ti->type == TREE_TYPE_LEVEL_DIR)
2724 ti->imported_from = getStringCopy(parent->imported_from);
2725 ti->imported_by = getStringCopy(parent->imported_by);
2726 ti->tested_by = getStringCopy(parent->tested_by);
2728 ti->graphics_set_ecs = getStringCopy(parent->graphics_set_ecs);
2729 ti->graphics_set_aga = getStringCopy(parent->graphics_set_aga);
2730 ti->graphics_set = getStringCopy(parent->graphics_set);
2731 ti->sounds_set_default = getStringCopy(parent->sounds_set_default);
2732 ti->sounds_set_lowpass = getStringCopy(parent->sounds_set_lowpass);
2733 ti->sounds_set = getStringCopy(parent->sounds_set);
2734 ti->music_set = getStringCopy(parent->music_set);
2735 ti->graphics_path = getStringCopy(UNDEFINED_FILENAME);
2736 ti->sounds_path = getStringCopy(UNDEFINED_FILENAME);
2737 ti->music_path = getStringCopy(UNDEFINED_FILENAME);
2739 ti->level_filename = getStringCopy(parent->level_filename);
2740 ti->level_filetype = getStringCopy(parent->level_filetype);
2742 ti->special_flags = getStringCopy(parent->special_flags);
2744 ti->levels = parent->levels;
2745 ti->first_level = parent->first_level;
2746 ti->last_level = parent->last_level;
2747 ti->level_group = FALSE;
2748 ti->handicap_level = parent->handicap_level;
2749 ti->readonly = parent->readonly;
2750 ti->handicap = parent->handicap;
2751 ti->skip_levels = parent->skip_levels;
2753 ti->use_emc_tiles = parent->use_emc_tiles;
2757 static TreeInfo *getTreeInfoCopy(TreeInfo *ti)
2759 TreeInfo *ti_copy = newTreeInfo();
2761 // copy all values from the original structure
2763 ti_copy->type = ti->type;
2765 ti_copy->node_top = ti->node_top;
2766 ti_copy->node_parent = ti->node_parent;
2767 ti_copy->node_group = ti->node_group;
2768 ti_copy->next = ti->next;
2770 ti_copy->cl_first = ti->cl_first;
2771 ti_copy->cl_cursor = ti->cl_cursor;
2773 ti_copy->subdir = getStringCopy(ti->subdir);
2774 ti_copy->fullpath = getStringCopy(ti->fullpath);
2775 ti_copy->basepath = getStringCopy(ti->basepath);
2776 ti_copy->identifier = getStringCopy(ti->identifier);
2777 ti_copy->name = getStringCopy(ti->name);
2778 ti_copy->name_sorting = getStringCopy(ti->name_sorting);
2779 ti_copy->author = getStringCopy(ti->author);
2780 ti_copy->year = getStringCopy(ti->year);
2782 ti_copy->program_title = getStringCopy(ti->program_title);
2783 ti_copy->program_copyright = getStringCopy(ti->program_copyright);
2784 ti_copy->program_company = getStringCopy(ti->program_company);
2786 ti_copy->imported_from = getStringCopy(ti->imported_from);
2787 ti_copy->imported_by = getStringCopy(ti->imported_by);
2788 ti_copy->tested_by = getStringCopy(ti->tested_by);
2790 ti_copy->graphics_set_ecs = getStringCopy(ti->graphics_set_ecs);
2791 ti_copy->graphics_set_aga = getStringCopy(ti->graphics_set_aga);
2792 ti_copy->graphics_set = getStringCopy(ti->graphics_set);
2793 ti_copy->sounds_set_default = getStringCopy(ti->sounds_set_default);
2794 ti_copy->sounds_set_lowpass = getStringCopy(ti->sounds_set_lowpass);
2795 ti_copy->sounds_set = getStringCopy(ti->sounds_set);
2796 ti_copy->music_set = getStringCopy(ti->music_set);
2797 ti_copy->graphics_path = getStringCopy(ti->graphics_path);
2798 ti_copy->sounds_path = getStringCopy(ti->sounds_path);
2799 ti_copy->music_path = getStringCopy(ti->music_path);
2801 ti_copy->level_filename = getStringCopy(ti->level_filename);
2802 ti_copy->level_filetype = getStringCopy(ti->level_filetype);
2804 ti_copy->special_flags = getStringCopy(ti->special_flags);
2806 ti_copy->levels = ti->levels;
2807 ti_copy->first_level = ti->first_level;
2808 ti_copy->last_level = ti->last_level;
2809 ti_copy->sort_priority = ti->sort_priority;
2811 ti_copy->latest_engine = ti->latest_engine;
2813 ti_copy->level_group = ti->level_group;
2814 ti_copy->parent_link = ti->parent_link;
2815 ti_copy->is_copy = ti->is_copy;
2816 ti_copy->in_user_dir = ti->in_user_dir;
2817 ti_copy->user_defined = ti->user_defined;
2818 ti_copy->readonly = ti->readonly;
2819 ti_copy->handicap = ti->handicap;
2820 ti_copy->skip_levels = ti->skip_levels;
2822 ti_copy->use_emc_tiles = ti->use_emc_tiles;
2824 ti_copy->color = ti->color;
2825 ti_copy->class_desc = getStringCopy(ti->class_desc);
2826 ti_copy->handicap_level = ti->handicap_level;
2828 ti_copy->infotext = getStringCopy(ti->infotext);
2833 void freeTreeInfo(TreeInfo *ti)
2838 checked_free(ti->subdir);
2839 checked_free(ti->fullpath);
2840 checked_free(ti->basepath);
2841 checked_free(ti->identifier);
2843 checked_free(ti->name);
2844 checked_free(ti->name_sorting);
2845 checked_free(ti->author);
2846 checked_free(ti->year);
2848 checked_free(ti->program_title);
2849 checked_free(ti->program_copyright);
2850 checked_free(ti->program_company);
2852 checked_free(ti->class_desc);
2854 checked_free(ti->infotext);
2856 if (ti->type == TREE_TYPE_LEVEL_DIR)
2858 checked_free(ti->imported_from);
2859 checked_free(ti->imported_by);
2860 checked_free(ti->tested_by);
2862 checked_free(ti->graphics_set_ecs);
2863 checked_free(ti->graphics_set_aga);
2864 checked_free(ti->graphics_set);
2865 checked_free(ti->sounds_set_default);
2866 checked_free(ti->sounds_set_lowpass);
2867 checked_free(ti->sounds_set);
2868 checked_free(ti->music_set);
2870 checked_free(ti->graphics_path);
2871 checked_free(ti->sounds_path);
2872 checked_free(ti->music_path);
2874 checked_free(ti->level_filename);
2875 checked_free(ti->level_filetype);
2877 checked_free(ti->special_flags);
2880 // recursively free child node
2882 freeTreeInfo(ti->node_group);
2884 // recursively free next node
2886 freeTreeInfo(ti->next);
2891 void setSetupInfo(struct TokenInfo *token_info,
2892 int token_nr, char *token_value)
2894 int token_type = token_info[token_nr].type;
2895 void *setup_value = token_info[token_nr].value;
2897 if (token_value == NULL)
2900 // set setup field to corresponding token value
2905 *(boolean *)setup_value = get_boolean_from_string(token_value);
2909 *(int *)setup_value = get_switch3_from_string(token_value);
2913 *(Key *)setup_value = getKeyFromKeyName(token_value);
2917 *(Key *)setup_value = getKeyFromX11KeyName(token_value);
2921 *(int *)setup_value = get_integer_from_string(token_value);
2925 checked_free(*(char **)setup_value);
2926 *(char **)setup_value = getStringCopy(token_value);
2930 *(int *)setup_value = get_player_nr_from_string(token_value);
2938 static int compareTreeInfoEntries(const void *object1, const void *object2)
2940 const TreeInfo *entry1 = *((TreeInfo **)object1);
2941 const TreeInfo *entry2 = *((TreeInfo **)object2);
2942 int tree_sorting1 = TREE_SORTING(entry1);
2943 int tree_sorting2 = TREE_SORTING(entry2);
2945 if (tree_sorting1 != tree_sorting2)
2946 return (tree_sorting1 - tree_sorting2);
2948 return strcasecmp(entry1->name_sorting, entry2->name_sorting);
2951 static TreeInfo *createParentTreeInfoNode(TreeInfo *node_parent)
2955 if (node_parent == NULL)
2958 ti_new = newTreeInfo();
2959 setTreeInfoToDefaults(ti_new, node_parent->type);
2961 ti_new->node_parent = node_parent;
2962 ti_new->parent_link = TRUE;
2964 setString(&ti_new->identifier, node_parent->identifier);
2965 setString(&ti_new->name, BACKLINK_TEXT_PARENT);
2966 setString(&ti_new->name_sorting, ti_new->name);
2968 setString(&ti_new->subdir, STRING_PARENT_DIRECTORY);
2969 setString(&ti_new->fullpath, node_parent->fullpath);
2971 ti_new->sort_priority = LEVELCLASS_PARENT;
2972 ti_new->latest_engine = node_parent->latest_engine;
2974 setString(&ti_new->class_desc, getLevelClassDescription(ti_new));
2976 pushTreeInfo(&node_parent->node_group, ti_new);
2981 static TreeInfo *createTopTreeInfoNode(TreeInfo *node_first)
2983 if (node_first == NULL)
2986 TreeInfo *ti_new = newTreeInfo();
2987 int type = node_first->type;
2989 setTreeInfoToDefaults(ti_new, type);
2991 ti_new->node_parent = NULL;
2992 ti_new->parent_link = FALSE;
2994 setString(&ti_new->identifier, "top_tree_node");
2995 setString(&ti_new->name, TREE_INFOTEXT(type));
2996 setString(&ti_new->name_sorting, ti_new->name);
2998 setString(&ti_new->subdir, STRING_TOP_DIRECTORY);
2999 setString(&ti_new->fullpath, ".");
3001 ti_new->sort_priority = LEVELCLASS_TOP;
3002 ti_new->latest_engine = node_first->latest_engine;
3004 setString(&ti_new->class_desc, TREE_INFOTEXT(type));
3006 ti_new->node_group = node_first;
3007 ti_new->level_group = TRUE;
3009 TreeInfo *ti_new2 = createParentTreeInfoNode(ti_new);
3011 setString(&ti_new2->name, TREE_BACKLINK_TEXT(type));
3012 setString(&ti_new2->name_sorting, ti_new2->name);
3017 static void setTreeInfoParentNodes(TreeInfo *node, TreeInfo *node_parent)
3021 if (node->node_group)
3022 setTreeInfoParentNodes(node->node_group, node);
3024 node->node_parent = node_parent;
3031 // ----------------------------------------------------------------------------
3032 // functions for handling level and custom artwork info cache
3033 // ----------------------------------------------------------------------------
3035 static void LoadArtworkInfoCache(void)
3037 InitCacheDirectory();
3039 if (artworkinfo_cache_old == NULL)
3041 char *filename = getPath2(getCacheDir(), ARTWORKINFO_CACHE_FILE);
3043 // try to load artwork info hash from already existing cache file
3044 artworkinfo_cache_old = loadSetupFileHash(filename);
3046 // try to get program version that artwork info cache was written with
3047 char *version = getHashEntry(artworkinfo_cache_old, "program.version");
3049 // check program version of artwork info cache against current version
3050 if (!strEqual(version, program.version_string))
3052 freeSetupFileHash(artworkinfo_cache_old);
3054 artworkinfo_cache_old = NULL;
3057 // if no artwork info cache file was found, start with empty hash
3058 if (artworkinfo_cache_old == NULL)
3059 artworkinfo_cache_old = newSetupFileHash();
3064 if (artworkinfo_cache_new == NULL)
3065 artworkinfo_cache_new = newSetupFileHash();
3067 update_artworkinfo_cache = FALSE;
3070 static void SaveArtworkInfoCache(void)
3072 if (!update_artworkinfo_cache)
3075 char *filename = getPath2(getCacheDir(), ARTWORKINFO_CACHE_FILE);
3077 InitCacheDirectory();
3079 saveSetupFileHash(artworkinfo_cache_new, filename);
3084 static char *getCacheTokenPrefix(char *prefix1, char *prefix2)
3086 static char *prefix = NULL;
3088 checked_free(prefix);
3090 prefix = getStringCat2WithSeparator(prefix1, prefix2, ".");
3095 // (identical to above function, but separate string buffer needed -- nasty)
3096 static char *getCacheToken(char *prefix, char *suffix)
3098 static char *token = NULL;
3100 checked_free(token);
3102 token = getStringCat2WithSeparator(prefix, suffix, ".");
3107 static char *getFileTimestampString(char *filename)
3109 return getStringCopy(i_to_a(getFileTimestampEpochSeconds(filename)));
3112 static boolean modifiedFileTimestamp(char *filename, char *timestamp_string)
3114 struct stat file_status;
3116 if (timestamp_string == NULL)
3119 if (!fileExists(filename)) // file does not exist
3120 return (atoi(timestamp_string) != 0);
3122 if (stat(filename, &file_status) != 0) // cannot stat file
3125 return (file_status.st_mtime != atoi(timestamp_string));
3128 static TreeInfo *getArtworkInfoCacheEntry(LevelDirTree *level_node, int type)
3130 char *identifier = level_node->subdir;
3131 char *type_string = ARTWORK_DIRECTORY(type);
3132 char *token_prefix = getCacheTokenPrefix(type_string, identifier);
3133 char *token_main = getCacheToken(token_prefix, "CACHED");
3134 char *cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
3135 boolean cached = (cache_entry != NULL && strEqual(cache_entry, "true"));
3136 TreeInfo *artwork_info = NULL;
3138 if (!use_artworkinfo_cache)
3141 if (optional_tokens_hash == NULL)
3145 // create hash from list of optional tokens (for quick access)
3146 optional_tokens_hash = newSetupFileHash();
3147 for (i = 0; optional_tokens[i] != NULL; i++)
3148 setHashEntry(optional_tokens_hash, optional_tokens[i], "");
3155 artwork_info = newTreeInfo();
3156 setTreeInfoToDefaults(artwork_info, type);
3158 // set all structure fields according to the token/value pairs
3159 ldi = *artwork_info;
3160 for (i = 0; artworkinfo_tokens[i].type != -1; i++)
3162 char *token_suffix = artworkinfo_tokens[i].text;
3163 char *token = getCacheToken(token_prefix, token_suffix);
3164 char *value = getHashEntry(artworkinfo_cache_old, token);
3166 (getHashEntry(optional_tokens_hash, token_suffix) != NULL);
3168 setSetupInfo(artworkinfo_tokens, i, value);
3170 // check if cache entry for this item is mandatory, but missing
3171 if (value == NULL && !optional)
3173 Warn("missing cache entry '%s'", token);
3179 *artwork_info = ldi;
3184 char *filename_levelinfo = getPath2(getLevelDirFromTreeInfo(level_node),
3185 LEVELINFO_FILENAME);
3186 char *filename_artworkinfo = getPath2(getSetupArtworkDir(artwork_info),
3187 ARTWORKINFO_FILENAME(type));
3189 // check if corresponding "levelinfo.conf" file has changed
3190 token_main = getCacheToken(token_prefix, "TIMESTAMP_LEVELINFO");
3191 cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
3193 if (modifiedFileTimestamp(filename_levelinfo, cache_entry))
3196 // check if corresponding "<artworkinfo>.conf" file has changed
3197 token_main = getCacheToken(token_prefix, "TIMESTAMP_ARTWORKINFO");
3198 cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
3200 if (modifiedFileTimestamp(filename_artworkinfo, cache_entry))
3203 checked_free(filename_levelinfo);
3204 checked_free(filename_artworkinfo);
3207 if (!cached && artwork_info != NULL)
3209 freeTreeInfo(artwork_info);
3214 return artwork_info;
3217 static void setArtworkInfoCacheEntry(TreeInfo *artwork_info,
3218 LevelDirTree *level_node, int type)
3220 char *identifier = level_node->subdir;
3221 char *type_string = ARTWORK_DIRECTORY(type);
3222 char *token_prefix = getCacheTokenPrefix(type_string, identifier);
3223 char *token_main = getCacheToken(token_prefix, "CACHED");
3224 boolean set_cache_timestamps = TRUE;
3227 setHashEntry(artworkinfo_cache_new, token_main, "true");
3229 if (set_cache_timestamps)
3231 char *filename_levelinfo = getPath2(getLevelDirFromTreeInfo(level_node),
3232 LEVELINFO_FILENAME);
3233 char *filename_artworkinfo = getPath2(getSetupArtworkDir(artwork_info),
3234 ARTWORKINFO_FILENAME(type));
3235 char *timestamp_levelinfo = getFileTimestampString(filename_levelinfo);
3236 char *timestamp_artworkinfo = getFileTimestampString(filename_artworkinfo);
3238 token_main = getCacheToken(token_prefix, "TIMESTAMP_LEVELINFO");
3239 setHashEntry(artworkinfo_cache_new, token_main, timestamp_levelinfo);
3241 token_main = getCacheToken(token_prefix, "TIMESTAMP_ARTWORKINFO");
3242 setHashEntry(artworkinfo_cache_new, token_main, timestamp_artworkinfo);
3244 checked_free(filename_levelinfo);
3245 checked_free(filename_artworkinfo);
3246 checked_free(timestamp_levelinfo);
3247 checked_free(timestamp_artworkinfo);
3250 ldi = *artwork_info;
3251 for (i = 0; artworkinfo_tokens[i].type != -1; i++)
3253 char *token = getCacheToken(token_prefix, artworkinfo_tokens[i].text);
3254 char *value = getSetupValue(artworkinfo_tokens[i].type,
3255 artworkinfo_tokens[i].value);
3257 setHashEntry(artworkinfo_cache_new, token, value);
3262 // ----------------------------------------------------------------------------
3263 // functions for loading level info and custom artwork info
3264 // ----------------------------------------------------------------------------
3266 int GetZipFileTreeType(char *zip_filename)
3268 static char *top_dir_path = NULL;
3269 static char *top_dir_conf_filename[NUM_BASE_TREE_TYPES] = { NULL };
3270 static char *conf_basename[NUM_BASE_TREE_TYPES] =
3272 GRAPHICSINFO_FILENAME,
3273 SOUNDSINFO_FILENAME,
3279 checked_free(top_dir_path);
3280 top_dir_path = NULL;
3282 for (j = 0; j < NUM_BASE_TREE_TYPES; j++)
3284 checked_free(top_dir_conf_filename[j]);
3285 top_dir_conf_filename[j] = NULL;
3288 char **zip_entries = zip_list(zip_filename);
3290 // check if zip file successfully opened
3291 if (zip_entries == NULL || zip_entries[0] == NULL)
3292 return TREE_TYPE_UNDEFINED;
3294 // first zip file entry is expected to be top level directory
3295 char *top_dir = zip_entries[0];
3297 // check if valid top level directory found in zip file
3298 if (!strSuffix(top_dir, "/"))
3299 return TREE_TYPE_UNDEFINED;
3301 // get filenames of valid configuration files in top level directory
3302 for (j = 0; j < NUM_BASE_TREE_TYPES; j++)
3303 top_dir_conf_filename[j] = getStringCat2(top_dir, conf_basename[j]);
3305 int tree_type = TREE_TYPE_UNDEFINED;
3308 while (zip_entries[e] != NULL)
3310 // check if every zip file entry is below top level directory
3311 if (!strPrefix(zip_entries[e], top_dir))
3312 return TREE_TYPE_UNDEFINED;
3314 // check if this zip file entry is a valid configuration filename
3315 for (j = 0; j < NUM_BASE_TREE_TYPES; j++)
3317 if (strEqual(zip_entries[e], top_dir_conf_filename[j]))
3319 // only exactly one valid configuration file allowed
3320 if (tree_type != TREE_TYPE_UNDEFINED)
3321 return TREE_TYPE_UNDEFINED;
3333 static boolean CheckZipFileForDirectory(char *zip_filename, char *directory,
3336 static char *top_dir_path = NULL;
3337 static char *top_dir_conf_filename = NULL;
3339 checked_free(top_dir_path);
3340 checked_free(top_dir_conf_filename);
3342 top_dir_path = NULL;
3343 top_dir_conf_filename = NULL;
3345 char *conf_basename = (tree_type == TREE_TYPE_LEVEL_DIR ? LEVELINFO_FILENAME :
3346 ARTWORKINFO_FILENAME(tree_type));
3348 // check if valid configuration filename determined
3349 if (conf_basename == NULL || strEqual(conf_basename, ""))
3352 char **zip_entries = zip_list(zip_filename);
3354 // check if zip file successfully opened
3355 if (zip_entries == NULL || zip_entries[0] == NULL)
3358 // first zip file entry is expected to be top level directory
3359 char *top_dir = zip_entries[0];
3361 // check if valid top level directory found in zip file
3362 if (!strSuffix(top_dir, "/"))
3365 // get path of extracted top level directory
3366 top_dir_path = getPath2(directory, top_dir);
3368 // remove trailing directory separator from top level directory path
3369 // (required to be able to check for file and directory in next step)
3370 top_dir_path[strlen(top_dir_path) - 1] = '\0';
3372 // check if zip file's top level directory already exists in target directory
3373 if (fileExists(top_dir_path)) // (checks for file and directory)
3376 // get filename of configuration file in top level directory
3377 top_dir_conf_filename = getStringCat2(top_dir, conf_basename);
3379 boolean found_top_dir_conf_filename = FALSE;
3382 while (zip_entries[i] != NULL)
3384 // check if every zip file entry is below top level directory
3385 if (!strPrefix(zip_entries[i], top_dir))
3388 // check if this zip file entry is the configuration filename
3389 if (strEqual(zip_entries[i], top_dir_conf_filename))
3390 found_top_dir_conf_filename = TRUE;
3395 // check if valid configuration filename was found in zip file
3396 if (!found_top_dir_conf_filename)
3402 char *ExtractZipFileIntoDirectory(char *zip_filename, char *directory,
3405 boolean zip_file_valid = CheckZipFileForDirectory(zip_filename, directory,
3408 if (!zip_file_valid)
3410 Warn("zip file '%s' rejected!", zip_filename);
3415 char **zip_entries = zip_extract(zip_filename, directory);
3417 if (zip_entries == NULL)
3419 Warn("zip file '%s' could not be extracted!", zip_filename);
3424 Info("zip file '%s' successfully extracted!", zip_filename);
3426 // first zip file entry contains top level directory
3427 char *top_dir = zip_entries[0];
3429 // remove trailing directory separator from top level directory
3430 top_dir[strlen(top_dir) - 1] = '\0';
3435 static void ProcessZipFilesInDirectory(char *directory, int tree_type)
3438 DirectoryEntry *dir_entry;
3440 if ((dir = openDirectory(directory)) == NULL)
3442 // display error if directory is main "options.graphics_directory" etc.
3443 if (tree_type == TREE_TYPE_LEVEL_DIR ||
3444 directory == OPTIONS_ARTWORK_DIRECTORY(tree_type))
3445 Warn("cannot read directory '%s'", directory);
3450 while ((dir_entry = readDirectory(dir)) != NULL) // loop all entries
3452 // skip non-zip files (and also directories with zip extension)
3453 if (!strSuffixLower(dir_entry->basename, ".zip") || dir_entry->is_directory)
3456 char *zip_filename = getPath2(directory, dir_entry->basename);
3457 char *zip_filename_extracted = getStringCat2(zip_filename, ".extracted");
3458 char *zip_filename_rejected = getStringCat2(zip_filename, ".rejected");
3460 // check if zip file hasn't already been extracted or rejected
3461 if (!fileExists(zip_filename_extracted) &&
3462 !fileExists(zip_filename_rejected))
3464 char *top_dir = ExtractZipFileIntoDirectory(zip_filename, directory,
3466 char *marker_filename = (top_dir != NULL ? zip_filename_extracted :
3467 zip_filename_rejected);
3470 // create empty file to mark zip file as extracted or rejected
3471 if ((marker_file = fopen(marker_filename, MODE_WRITE)))
3472 fclose(marker_file);
3475 free(zip_filename_extracted);
3476 free(zip_filename_rejected);
3480 closeDirectory(dir);
3483 // forward declaration for recursive call by "LoadLevelInfoFromLevelDir()"
3484 static void LoadLevelInfoFromLevelDir(TreeInfo **, TreeInfo *, char *);
3486 static boolean LoadLevelInfoFromLevelConf(TreeInfo **node_first,
3487 TreeInfo *node_parent,
3488 char *level_directory,
3489 char *directory_name)
3491 char *directory_path = getPath2(level_directory, directory_name);
3492 char *filename = getPath2(directory_path, LEVELINFO_FILENAME);
3493 SetupFileHash *setup_file_hash;
3494 LevelDirTree *leveldir_new = NULL;
3497 // unless debugging, silently ignore directories without "levelinfo.conf"
3498 if (!options.debug && !fileExists(filename))
3500 free(directory_path);
3506 setup_file_hash = loadSetupFileHash(filename);
3508 if (setup_file_hash == NULL)
3510 #if DEBUG_NO_CONFIG_FILE
3511 Debug("setup", "ignoring level directory '%s'", directory_path);
3514 free(directory_path);
3520 leveldir_new = newTreeInfo();
3523 setTreeInfoToDefaultsFromParent(leveldir_new, node_parent);
3525 setTreeInfoToDefaults(leveldir_new, TREE_TYPE_LEVEL_DIR);
3527 leveldir_new->subdir = getStringCopy(directory_name);
3529 // set all structure fields according to the token/value pairs
3530 ldi = *leveldir_new;
3531 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
3532 setSetupInfo(levelinfo_tokens, i,
3533 getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
3534 *leveldir_new = ldi;
3536 if (strEqual(leveldir_new->name, ANONYMOUS_NAME))
3537 setString(&leveldir_new->name, leveldir_new->subdir);
3539 if (leveldir_new->identifier == NULL)
3540 leveldir_new->identifier = getStringCopy(leveldir_new->subdir);
3542 if (leveldir_new->name_sorting == NULL)
3543 leveldir_new->name_sorting = getStringCopy(leveldir_new->name);
3545 if (node_parent == NULL) // top level group
3547 leveldir_new->basepath = getStringCopy(level_directory);
3548 leveldir_new->fullpath = getStringCopy(leveldir_new->subdir);
3550 else // sub level group
3552 leveldir_new->basepath = getStringCopy(node_parent->basepath);
3553 leveldir_new->fullpath = getPath2(node_parent->fullpath, directory_name);
3556 leveldir_new->last_level =
3557 leveldir_new->first_level + leveldir_new->levels - 1;
3559 leveldir_new->in_user_dir =
3560 (!strEqual(leveldir_new->basepath, options.level_directory));
3562 // adjust some settings if user's private level directory was detected
3563 if (leveldir_new->sort_priority == LEVELCLASS_UNDEFINED &&
3564 leveldir_new->in_user_dir &&
3565 (strEqual(leveldir_new->subdir, getLoginName()) ||
3566 strEqual(leveldir_new->name, getLoginName()) ||
3567 strEqual(leveldir_new->author, getRealName())))
3569 leveldir_new->sort_priority = LEVELCLASS_PRIVATE_START;
3570 leveldir_new->readonly = FALSE;
3573 leveldir_new->user_defined =
3574 (leveldir_new->in_user_dir && IS_LEVELCLASS_PRIVATE(leveldir_new));
3576 setString(&leveldir_new->class_desc, getLevelClassDescription(leveldir_new));
3578 leveldir_new->handicap_level = // set handicap to default value
3579 (leveldir_new->user_defined || !leveldir_new->handicap ?
3580 leveldir_new->last_level : leveldir_new->first_level);
3582 DrawInitTextItem(leveldir_new->name);
3584 pushTreeInfo(node_first, leveldir_new);
3586 freeSetupFileHash(setup_file_hash);
3588 if (leveldir_new->level_group)
3590 // create node to link back to current level directory
3591 createParentTreeInfoNode(leveldir_new);
3593 // recursively step into sub-directory and look for more level series
3594 LoadLevelInfoFromLevelDir(&leveldir_new->node_group,
3595 leveldir_new, directory_path);
3598 free(directory_path);
3604 static void LoadLevelInfoFromLevelDir(TreeInfo **node_first,
3605 TreeInfo *node_parent,
3606 char *level_directory)
3608 // ---------- 1st stage: process any level set zip files ----------
3610 ProcessZipFilesInDirectory(level_directory, TREE_TYPE_LEVEL_DIR);
3612 // ---------- 2nd stage: check for level set directories ----------
3615 DirectoryEntry *dir_entry;
3616 boolean valid_entry_found = FALSE;
3618 if ((dir = openDirectory(level_directory)) == NULL)
3620 Warn("cannot read level directory '%s'", level_directory);
3625 while ((dir_entry = readDirectory(dir)) != NULL) // loop all entries
3627 char *directory_name = dir_entry->basename;
3628 char *directory_path = getPath2(level_directory, directory_name);
3630 // skip entries for current and parent directory
3631 if (strEqual(directory_name, ".") ||
3632 strEqual(directory_name, ".."))
3634 free(directory_path);
3639 // find out if directory entry is itself a directory
3640 if (!dir_entry->is_directory) // not a directory
3642 free(directory_path);
3647 free(directory_path);
3649 if (strEqual(directory_name, GRAPHICS_DIRECTORY) ||
3650 strEqual(directory_name, SOUNDS_DIRECTORY) ||
3651 strEqual(directory_name, MUSIC_DIRECTORY))
3654 valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
3659 closeDirectory(dir);
3661 // special case: top level directory may directly contain "levelinfo.conf"
3662 if (node_parent == NULL && !valid_entry_found)
3664 // check if this directory directly contains a file "levelinfo.conf"
3665 valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
3666 level_directory, ".");
3669 if (!valid_entry_found)
3670 Warn("cannot find any valid level series in directory '%s'",
3674 boolean AdjustGraphicsForEMC(void)
3676 boolean settings_changed = FALSE;
3678 settings_changed |= adjustTreeGraphicsForEMC(leveldir_first_all);
3679 settings_changed |= adjustTreeGraphicsForEMC(leveldir_first);
3681 return settings_changed;
3684 boolean AdjustSoundsForEMC(void)
3686 boolean settings_changed = FALSE;
3688 settings_changed |= adjustTreeSoundsForEMC(leveldir_first_all);
3689 settings_changed |= adjustTreeSoundsForEMC(leveldir_first);
3691 return settings_changed;
3694 void LoadLevelInfo(void)
3696 InitUserLevelDirectory(getLoginName());
3698 DrawInitTextHead("Loading level series");
3700 LoadLevelInfoFromLevelDir(&leveldir_first, NULL, options.level_directory);
3701 LoadLevelInfoFromLevelDir(&leveldir_first, NULL, getUserLevelDir(NULL));
3703 leveldir_first = createTopTreeInfoNode(leveldir_first);
3705 /* after loading all level set information, clone the level directory tree
3706 and remove all level sets without levels (these may still contain artwork
3707 to be offered in the setup menu as "custom artwork", and are therefore
3708 checked for existing artwork in the function "LoadLevelArtworkInfo()") */
3709 leveldir_first_all = leveldir_first;
3710 cloneTree(&leveldir_first, leveldir_first_all, TRUE);
3712 AdjustGraphicsForEMC();
3713 AdjustSoundsForEMC();
3715 // before sorting, the first entries will be from the user directory
3716 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
3718 if (leveldir_first == NULL)
3719 Fail("cannot find any valid level series in any directory");
3721 sortTreeInfo(&leveldir_first);
3723 #if ENABLE_UNUSED_CODE
3724 dumpTreeInfo(leveldir_first, 0);
3728 static boolean LoadArtworkInfoFromArtworkConf(TreeInfo **node_first,
3729 TreeInfo *node_parent,
3730 char *base_directory,
3731 char *directory_name, int type)
3733 char *directory_path = getPath2(base_directory, directory_name);
3734 char *filename = getPath2(directory_path, ARTWORKINFO_FILENAME(type));
3735 SetupFileHash *setup_file_hash = NULL;
3736 TreeInfo *artwork_new = NULL;
3739 if (fileExists(filename))
3740 setup_file_hash = loadSetupFileHash(filename);
3742 if (setup_file_hash == NULL) // no config file -- look for artwork files
3745 DirectoryEntry *dir_entry;
3746 boolean valid_file_found = FALSE;
3748 if ((dir = openDirectory(directory_path)) != NULL)
3750 while ((dir_entry = readDirectory(dir)) != NULL)
3752 if (FileIsArtworkType(dir_entry->filename, type))
3754 valid_file_found = TRUE;
3760 closeDirectory(dir);
3763 if (!valid_file_found)
3765 #if DEBUG_NO_CONFIG_FILE
3766 if (!strEqual(directory_name, "."))
3767 Debug("setup", "ignoring artwork directory '%s'", directory_path);
3770 free(directory_path);
3777 artwork_new = newTreeInfo();
3780 setTreeInfoToDefaultsFromParent(artwork_new, node_parent);
3782 setTreeInfoToDefaults(artwork_new, type);
3784 artwork_new->subdir = getStringCopy(directory_name);
3786 if (setup_file_hash) // (before defining ".color" and ".class_desc")
3788 // set all structure fields according to the token/value pairs
3790 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
3791 setSetupInfo(levelinfo_tokens, i,
3792 getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
3795 if (strEqual(artwork_new->name, ANONYMOUS_NAME))
3796 setString(&artwork_new->name, artwork_new->subdir);
3798 if (artwork_new->identifier == NULL)
3799 artwork_new->identifier = getStringCopy(artwork_new->subdir);
3801 if (artwork_new->name_sorting == NULL)
3802 artwork_new->name_sorting = getStringCopy(artwork_new->name);
3805 if (node_parent == NULL) // top level group
3807 artwork_new->basepath = getStringCopy(base_directory);
3808 artwork_new->fullpath = getStringCopy(artwork_new->subdir);
3810 else // sub level group
3812 artwork_new->basepath = getStringCopy(node_parent->basepath);
3813 artwork_new->fullpath = getPath2(node_parent->fullpath, directory_name);
3816 artwork_new->in_user_dir =
3817 (!strEqual(artwork_new->basepath, OPTIONS_ARTWORK_DIRECTORY(type)));
3819 setString(&artwork_new->class_desc, getLevelClassDescription(artwork_new));
3821 if (setup_file_hash == NULL) // (after determining ".user_defined")
3823 if (strEqual(artwork_new->subdir, "."))
3825 if (artwork_new->user_defined)
3827 setString(&artwork_new->identifier, "private");
3828 artwork_new->sort_priority = ARTWORKCLASS_PRIVATE;
3832 setString(&artwork_new->identifier, "classic");
3833 artwork_new->sort_priority = ARTWORKCLASS_CLASSICS;
3836 setString(&artwork_new->class_desc,
3837 getLevelClassDescription(artwork_new));
3841 setString(&artwork_new->identifier, artwork_new->subdir);
3844 setString(&artwork_new->name, artwork_new->identifier);
3845 setString(&artwork_new->name_sorting, artwork_new->name);
3848 pushTreeInfo(node_first, artwork_new);
3850 freeSetupFileHash(setup_file_hash);
3852 free(directory_path);
3858 static void LoadArtworkInfoFromArtworkDir(TreeInfo **node_first,
3859 TreeInfo *node_parent,
3860 char *base_directory, int type)
3862 // ---------- 1st stage: process any artwork set zip files ----------
3864 ProcessZipFilesInDirectory(base_directory, type);
3866 // ---------- 2nd stage: check for artwork set directories ----------
3869 DirectoryEntry *dir_entry;
3870 boolean valid_entry_found = FALSE;
3872 if ((dir = openDirectory(base_directory)) == NULL)
3874 // display error if directory is main "options.graphics_directory" etc.
3875 if (base_directory == OPTIONS_ARTWORK_DIRECTORY(type))
3876 Warn("cannot read directory '%s'", base_directory);
3881 while ((dir_entry = readDirectory(dir)) != NULL) // loop all entries
3883 char *directory_name = dir_entry->basename;
3884 char *directory_path = getPath2(base_directory, directory_name);
3886 // skip directory entries for current and parent directory
3887 if (strEqual(directory_name, ".") ||
3888 strEqual(directory_name, ".."))
3890 free(directory_path);
3895 // skip directory entries which are not a directory
3896 if (!dir_entry->is_directory) // not a directory
3898 free(directory_path);
3903 free(directory_path);
3905 // check if this directory contains artwork with or without config file
3906 valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
3908 directory_name, type);
3911 closeDirectory(dir);
3913 // check if this directory directly contains artwork itself
3914 valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
3915 base_directory, ".",
3917 if (!valid_entry_found)
3918 Warn("cannot find any valid artwork in directory '%s'", base_directory);
3921 static TreeInfo *getDummyArtworkInfo(int type)
3923 // this is only needed when there is completely no artwork available
3924 TreeInfo *artwork_new = newTreeInfo();
3926 setTreeInfoToDefaults(artwork_new, type);
3928 setString(&artwork_new->subdir, UNDEFINED_FILENAME);
3929 setString(&artwork_new->fullpath, UNDEFINED_FILENAME);
3930 setString(&artwork_new->basepath, UNDEFINED_FILENAME);
3932 setString(&artwork_new->identifier, UNDEFINED_FILENAME);
3933 setString(&artwork_new->name, UNDEFINED_FILENAME);
3934 setString(&artwork_new->name_sorting, UNDEFINED_FILENAME);
3939 void SetCurrentArtwork(int type)
3941 ArtworkDirTree **current_ptr = ARTWORK_CURRENT_PTR(artwork, type);
3942 ArtworkDirTree *first_node = ARTWORK_FIRST_NODE(artwork, type);
3943 char *setup_set = SETUP_ARTWORK_SET(setup, type);
3944 char *default_subdir = ARTWORK_DEFAULT_SUBDIR(type);
3946 // set current artwork to artwork configured in setup menu
3947 *current_ptr = getTreeInfoFromIdentifier(first_node, setup_set);
3949 // if not found, set current artwork to default artwork
3950 if (*current_ptr == NULL)
3951 *current_ptr = getTreeInfoFromIdentifier(first_node, default_subdir);
3953 // if not found, set current artwork to first artwork in tree
3954 if (*current_ptr == NULL)
3955 *current_ptr = getFirstValidTreeInfoEntry(first_node);
3958 void ChangeCurrentArtworkIfNeeded(int type)
3960 char *current_identifier = ARTWORK_CURRENT_IDENTIFIER(artwork, type);
3961 char *setup_set = SETUP_ARTWORK_SET(setup, type);
3963 if (!strEqual(current_identifier, setup_set))
3964 SetCurrentArtwork(type);
3967 void LoadArtworkInfo(void)
3969 LoadArtworkInfoCache();
3971 DrawInitTextHead("Looking for custom artwork");
3973 LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
3974 options.graphics_directory,
3975 TREE_TYPE_GRAPHICS_DIR);
3976 LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
3977 getUserGraphicsDir(),
3978 TREE_TYPE_GRAPHICS_DIR);
3980 LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
3981 options.sounds_directory,
3982 TREE_TYPE_SOUNDS_DIR);
3983 LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
3985 TREE_TYPE_SOUNDS_DIR);
3987 LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
3988 options.music_directory,
3989 TREE_TYPE_MUSIC_DIR);
3990 LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
3992 TREE_TYPE_MUSIC_DIR);
3994 if (artwork.gfx_first == NULL)
3995 artwork.gfx_first = getDummyArtworkInfo(TREE_TYPE_GRAPHICS_DIR);
3996 if (artwork.snd_first == NULL)
3997 artwork.snd_first = getDummyArtworkInfo(TREE_TYPE_SOUNDS_DIR);
3998 if (artwork.mus_first == NULL)
3999 artwork.mus_first = getDummyArtworkInfo(TREE_TYPE_MUSIC_DIR);
4001 // before sorting, the first entries will be from the user directory
4002 SetCurrentArtwork(ARTWORK_TYPE_GRAPHICS);
4003 SetCurrentArtwork(ARTWORK_TYPE_SOUNDS);
4004 SetCurrentArtwork(ARTWORK_TYPE_MUSIC);
4006 artwork.gfx_current_identifier = artwork.gfx_current->identifier;
4007 artwork.snd_current_identifier = artwork.snd_current->identifier;
4008 artwork.mus_current_identifier = artwork.mus_current->identifier;
4010 #if ENABLE_UNUSED_CODE
4011 Debug("setup:LoadArtworkInfo", "graphics set == %s",
4012 artwork.gfx_current_identifier);
4013 Debug("setup:LoadArtworkInfo", "sounds set == %s",
4014 artwork.snd_current_identifier);
4015 Debug("setup:LoadArtworkInfo", "music set == %s",
4016 artwork.mus_current_identifier);
4019 sortTreeInfo(&artwork.gfx_first);
4020 sortTreeInfo(&artwork.snd_first);
4021 sortTreeInfo(&artwork.mus_first);
4023 #if ENABLE_UNUSED_CODE
4024 dumpTreeInfo(artwork.gfx_first, 0);
4025 dumpTreeInfo(artwork.snd_first, 0);
4026 dumpTreeInfo(artwork.mus_first, 0);
4030 static void MoveArtworkInfoIntoSubTree(ArtworkDirTree **artwork_node)
4032 ArtworkDirTree *artwork_new = newTreeInfo();
4033 char *top_node_name = "standalone artwork";
4035 setTreeInfoToDefaults(artwork_new, (*artwork_node)->type);
4037 artwork_new->level_group = TRUE;
4039 setString(&artwork_new->identifier, top_node_name);
4040 setString(&artwork_new->name, top_node_name);
4041 setString(&artwork_new->name_sorting, top_node_name);
4043 // create node to link back to current custom artwork directory
4044 createParentTreeInfoNode(artwork_new);
4046 // move existing custom artwork tree into newly created sub-tree
4047 artwork_new->node_group->next = *artwork_node;
4049 // change custom artwork tree to contain only newly created node
4050 *artwork_node = artwork_new;
4053 static void LoadArtworkInfoFromLevelInfoExt(ArtworkDirTree **artwork_node,
4054 ArtworkDirTree *node_parent,
4055 LevelDirTree *level_node,
4056 boolean empty_level_set_mode)
4058 int type = (*artwork_node)->type;
4060 // recursively check all level directories for artwork sub-directories
4064 boolean empty_level_set = (level_node->levels == 0);
4066 // check all tree entries for artwork, but skip parent link entries
4067 if (!level_node->parent_link && empty_level_set == empty_level_set_mode)
4069 TreeInfo *artwork_new = getArtworkInfoCacheEntry(level_node, type);
4070 boolean cached = (artwork_new != NULL);
4074 pushTreeInfo(artwork_node, artwork_new);
4078 TreeInfo *topnode_last = *artwork_node;
4079 char *path = getPath2(getLevelDirFromTreeInfo(level_node),
4080 ARTWORK_DIRECTORY(type));
4082 LoadArtworkInfoFromArtworkDir(artwork_node, NULL, path, type);
4084 if (topnode_last != *artwork_node) // check for newly added node
4086 artwork_new = *artwork_node;
4088 setString(&artwork_new->identifier, level_node->subdir);
4089 setString(&artwork_new->name, level_node->name);
4090 setString(&artwork_new->name_sorting, level_node->name_sorting);
4092 artwork_new->sort_priority = level_node->sort_priority;
4093 artwork_new->in_user_dir = level_node->in_user_dir;
4095 update_artworkinfo_cache = TRUE;
4101 // insert artwork info (from old cache or filesystem) into new cache
4102 if (artwork_new != NULL)
4103 setArtworkInfoCacheEntry(artwork_new, level_node, type);
4106 DrawInitTextItem(level_node->name);
4108 if (level_node->node_group != NULL)
4110 TreeInfo *artwork_new = newTreeInfo();
4113 setTreeInfoToDefaultsFromParent(artwork_new, node_parent);
4115 setTreeInfoToDefaults(artwork_new, type);
4117 artwork_new->level_group = TRUE;
4119 setString(&artwork_new->identifier, level_node->subdir);
4121 if (node_parent == NULL) // check for top tree node
4123 char *top_node_name = (empty_level_set_mode ?
4124 "artwork for certain level sets" :
4125 "artwork included in level sets");
4127 setString(&artwork_new->name, top_node_name);
4128 setString(&artwork_new->name_sorting, top_node_name);
4132 setString(&artwork_new->name, level_node->name);
4133 setString(&artwork_new->name_sorting, level_node->name_sorting);
4136 pushTreeInfo(artwork_node, artwork_new);
4138 // create node to link back to current custom artwork directory
4139 createParentTreeInfoNode(artwork_new);
4141 // recursively step into sub-directory and look for more custom artwork
4142 LoadArtworkInfoFromLevelInfoExt(&artwork_new->node_group, artwork_new,
4143 level_node->node_group,
4144 empty_level_set_mode);
4146 // if sub-tree has no custom artwork at all, remove it
4147 if (artwork_new->node_group->next == NULL)
4148 removeTreeInfo(artwork_node);
4151 level_node = level_node->next;
4155 static void LoadArtworkInfoFromLevelInfo(ArtworkDirTree **artwork_node)
4157 // move peviously loaded artwork tree into separate sub-tree
4158 MoveArtworkInfoIntoSubTree(artwork_node);
4160 // load artwork from level sets into separate sub-trees
4161 LoadArtworkInfoFromLevelInfoExt(artwork_node, NULL, leveldir_first_all, TRUE);
4162 LoadArtworkInfoFromLevelInfoExt(artwork_node, NULL, leveldir_first_all, FALSE);
4164 // add top tree node over all three separate sub-trees
4165 *artwork_node = createTopTreeInfoNode(*artwork_node);
4167 // set all parent links (back links) in complete artwork tree
4168 setTreeInfoParentNodes(*artwork_node, NULL);
4171 void LoadLevelArtworkInfo(void)
4173 print_timestamp_init("LoadLevelArtworkInfo");
4175 DrawInitTextHead("Looking for custom level artwork");
4177 print_timestamp_time("DrawTimeText");
4179 LoadArtworkInfoFromLevelInfo(&artwork.gfx_first);
4180 print_timestamp_time("LoadArtworkInfoFromLevelInfo (gfx)");
4181 LoadArtworkInfoFromLevelInfo(&artwork.snd_first);
4182 print_timestamp_time("LoadArtworkInfoFromLevelInfo (snd)");
4183 LoadArtworkInfoFromLevelInfo(&artwork.mus_first);
4184 print_timestamp_time("LoadArtworkInfoFromLevelInfo (mus)");
4186 SaveArtworkInfoCache();
4188 print_timestamp_time("SaveArtworkInfoCache");
4190 // needed for reloading level artwork not known at ealier stage
4191 ChangeCurrentArtworkIfNeeded(ARTWORK_TYPE_GRAPHICS);
4192 ChangeCurrentArtworkIfNeeded(ARTWORK_TYPE_SOUNDS);
4193 ChangeCurrentArtworkIfNeeded(ARTWORK_TYPE_MUSIC);
4195 print_timestamp_time("getTreeInfoFromIdentifier");
4197 sortTreeInfo(&artwork.gfx_first);
4198 sortTreeInfo(&artwork.snd_first);
4199 sortTreeInfo(&artwork.mus_first);
4201 print_timestamp_time("sortTreeInfo");
4203 #if ENABLE_UNUSED_CODE
4204 dumpTreeInfo(artwork.gfx_first, 0);
4205 dumpTreeInfo(artwork.snd_first, 0);
4206 dumpTreeInfo(artwork.mus_first, 0);
4209 print_timestamp_done("LoadLevelArtworkInfo");
4212 static boolean AddTreeSetToTreeInfoExt(TreeInfo *tree_node_old, char *tree_dir,
4213 char *tree_subdir_new, int type)
4215 if (tree_node_old == NULL)
4217 if (type == TREE_TYPE_LEVEL_DIR)
4219 // get level info tree node of personal user level set
4220 tree_node_old = getTreeInfoFromIdentifier(leveldir_first, getLoginName());
4222 // this may happen if "setup.internal.create_user_levelset" is FALSE
4223 // or if file "levelinfo.conf" is missing in personal user level set
4224 if (tree_node_old == NULL)
4225 tree_node_old = leveldir_first->node_group;
4229 // get artwork info tree node of first artwork set
4230 tree_node_old = ARTWORK_FIRST_NODE(artwork, type);
4234 if (tree_dir == NULL)
4235 tree_dir = TREE_USERDIR(type);
4237 if (tree_node_old == NULL ||
4239 tree_subdir_new == NULL) // should not happen
4242 int draw_deactivation_mask = GetDrawDeactivationMask();
4244 // override draw deactivation mask (temporarily disable drawing)
4245 SetDrawDeactivationMask(REDRAW_ALL);
4247 if (type == TREE_TYPE_LEVEL_DIR)
4249 // load new level set config and add it next to first user level set
4250 LoadLevelInfoFromLevelConf(&tree_node_old->next,
4251 tree_node_old->node_parent,
4252 tree_dir, tree_subdir_new);
4256 // load new artwork set config and add it next to first artwork set
4257 LoadArtworkInfoFromArtworkConf(&tree_node_old->next,
4258 tree_node_old->node_parent,
4259 tree_dir, tree_subdir_new, type);
4262 // set draw deactivation mask to previous value
4263 SetDrawDeactivationMask(draw_deactivation_mask);
4265 // get first node of level or artwork info tree
4266 TreeInfo **tree_node_first = TREE_FIRST_NODE_PTR(type);
4268 // get tree info node of newly added level or artwork set
4269 TreeInfo *tree_node_new = getTreeInfoFromIdentifier(*tree_node_first,
4272 if (tree_node_new == NULL) // should not happen
4275 // correct top link and parent node link of newly created tree node
4276 tree_node_new->node_top = tree_node_old->node_top;
4277 tree_node_new->node_parent = tree_node_old->node_parent;
4279 // sort tree info to adjust position of newly added tree set
4280 sortTreeInfo(tree_node_first);
4285 void AddTreeSetToTreeInfo(TreeInfo *tree_node, char *tree_dir,
4286 char *tree_subdir_new, int type)
4288 if (!AddTreeSetToTreeInfoExt(tree_node, tree_dir, tree_subdir_new, type))
4289 Fail("internal tree info structure corrupted -- aborting");
4292 void AddUserLevelSetToLevelInfo(char *level_subdir_new)
4294 AddTreeSetToTreeInfo(NULL, NULL, level_subdir_new, TREE_TYPE_LEVEL_DIR);
4297 char *getArtworkIdentifierForUserLevelSet(int type)
4299 char *classic_artwork_set = getClassicArtworkSet(type);
4301 // check for custom artwork configured in "levelinfo.conf"
4302 char *leveldir_artwork_set =
4303 *LEVELDIR_ARTWORK_SET_PTR(leveldir_current, type);
4304 boolean has_leveldir_artwork_set =
4305 (leveldir_artwork_set != NULL && !strEqual(leveldir_artwork_set,
4306 classic_artwork_set));
4308 // check for custom artwork in sub-directory "graphics" etc.
4309 TreeInfo *artwork_first_node = ARTWORK_FIRST_NODE(artwork, type);
4310 char *leveldir_identifier = leveldir_current->identifier;
4311 boolean has_artwork_subdir =
4312 (getTreeInfoFromIdentifier(artwork_first_node,
4313 leveldir_identifier) != NULL);
4315 return (has_leveldir_artwork_set ? leveldir_artwork_set :
4316 has_artwork_subdir ? leveldir_identifier :
4317 classic_artwork_set);
4320 TreeInfo *getArtworkTreeInfoForUserLevelSet(int type)
4322 char *artwork_set = getArtworkIdentifierForUserLevelSet(type);
4323 TreeInfo *artwork_first_node = ARTWORK_FIRST_NODE(artwork, type);
4324 TreeInfo *ti = getTreeInfoFromIdentifier(artwork_first_node, artwork_set);
4328 ti = getTreeInfoFromIdentifier(artwork_first_node,
4329 ARTWORK_DEFAULT_SUBDIR(type));
4331 Fail("cannot find default graphics -- should not happen");
4337 boolean checkIfCustomArtworkExistsForCurrentLevelSet(void)
4339 char *graphics_set =
4340 getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_GRAPHICS);
4342 getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_SOUNDS);
4344 getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_MUSIC);
4346 return (!strEqual(graphics_set, GFX_CLASSIC_SUBDIR) ||
4347 !strEqual(sounds_set, SND_CLASSIC_SUBDIR) ||
4348 !strEqual(music_set, MUS_CLASSIC_SUBDIR));
4351 boolean UpdateUserLevelSet(char *level_subdir, char *level_name,
4352 char *level_author, int num_levels)
4354 char *filename = getPath2(getUserLevelDir(level_subdir), LEVELINFO_FILENAME);
4355 char *filename_tmp = getStringCat2(filename, ".tmp");
4357 FILE *file_tmp = NULL;
4358 char line[MAX_LINE_LEN];
4359 boolean success = FALSE;
4360 LevelDirTree *leveldir = getTreeInfoFromIdentifier(leveldir_first,
4362 // update values in level directory tree
4364 if (level_name != NULL)
4365 setString(&leveldir->name, level_name);
4367 if (level_author != NULL)
4368 setString(&leveldir->author, level_author);
4370 if (num_levels != -1)
4371 leveldir->levels = num_levels;
4373 // update values that depend on other values
4375 setString(&leveldir->name_sorting, leveldir->name);
4377 leveldir->last_level = leveldir->first_level + leveldir->levels - 1;
4379 // sort order of level sets may have changed
4380 sortTreeInfo(&leveldir_first);
4382 if ((file = fopen(filename, MODE_READ)) &&
4383 (file_tmp = fopen(filename_tmp, MODE_WRITE)))
4385 while (fgets(line, MAX_LINE_LEN, file))
4387 if (strPrefix(line, "name:") && level_name != NULL)
4388 fprintf(file_tmp, "%-32s%s\n", "name:", level_name);
4389 else if (strPrefix(line, "author:") && level_author != NULL)
4390 fprintf(file_tmp, "%-32s%s\n", "author:", level_author);
4391 else if (strPrefix(line, "levels:") && num_levels != -1)
4392 fprintf(file_tmp, "%-32s%d\n", "levels:", num_levels);
4394 fputs(line, file_tmp);
4407 success = (rename(filename_tmp, filename) == 0);
4415 boolean CreateUserLevelSet(char *level_subdir, char *level_name,
4416 char *level_author, int num_levels,
4417 boolean use_artwork_set)
4419 LevelDirTree *level_info;
4424 // create user level sub-directory, if needed
4425 createDirectory(getUserLevelDir(level_subdir), "user level", PERMS_PRIVATE);
4427 filename = getPath2(getUserLevelDir(level_subdir), LEVELINFO_FILENAME);
4429 if (!(file = fopen(filename, MODE_WRITE)))
4431 Warn("cannot write level info file '%s'", filename);
4438 level_info = newTreeInfo();
4440 // always start with reliable default values
4441 setTreeInfoToDefaults(level_info, TREE_TYPE_LEVEL_DIR);
4443 setString(&level_info->name, level_name);
4444 setString(&level_info->author, level_author);
4445 level_info->levels = num_levels;
4446 level_info->first_level = 1;
4447 level_info->sort_priority = LEVELCLASS_PRIVATE_START;
4448 level_info->readonly = FALSE;
4450 if (use_artwork_set)
4452 level_info->graphics_set =
4453 getStringCopy(getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_GRAPHICS));
4454 level_info->sounds_set =
4455 getStringCopy(getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_SOUNDS));
4456 level_info->music_set =
4457 getStringCopy(getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_MUSIC));
4460 token_value_position = TOKEN_VALUE_POSITION_SHORT;
4462 fprintFileHeader(file, LEVELINFO_FILENAME);
4465 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
4467 if (i == LEVELINFO_TOKEN_NAME ||
4468 i == LEVELINFO_TOKEN_AUTHOR ||
4469 i == LEVELINFO_TOKEN_LEVELS ||
4470 i == LEVELINFO_TOKEN_FIRST_LEVEL ||
4471 i == LEVELINFO_TOKEN_SORT_PRIORITY ||
4472 i == LEVELINFO_TOKEN_READONLY ||
4473 (use_artwork_set && (i == LEVELINFO_TOKEN_GRAPHICS_SET ||
4474 i == LEVELINFO_TOKEN_SOUNDS_SET ||
4475 i == LEVELINFO_TOKEN_MUSIC_SET)))
4476 fprintf(file, "%s\n", getSetupLine(levelinfo_tokens, "", i));
4478 // just to make things nicer :)
4479 if (i == LEVELINFO_TOKEN_AUTHOR ||
4480 i == LEVELINFO_TOKEN_FIRST_LEVEL ||
4481 (use_artwork_set && i == LEVELINFO_TOKEN_READONLY))
4482 fprintf(file, "\n");
4485 token_value_position = TOKEN_VALUE_POSITION_DEFAULT;
4489 SetFilePermissions(filename, PERMS_PRIVATE);
4491 freeTreeInfo(level_info);
4497 static void SaveUserLevelInfo(void)
4499 CreateUserLevelSet(getLoginName(), getLoginName(), getRealName(), 100, FALSE);
4502 char *getSetupValue(int type, void *value)
4504 static char value_string[MAX_LINE_LEN];
4512 strcpy(value_string, (*(boolean *)value ? "true" : "false"));
4516 strcpy(value_string, (*(boolean *)value ? "on" : "off"));
4520 strcpy(value_string, (*(int *)value == AUTO ? "auto" :
4521 *(int *)value == FALSE ? "off" : "on"));
4525 strcpy(value_string, (*(boolean *)value ? "yes" : "no"));
4528 case TYPE_YES_NO_AUTO:
4529 strcpy(value_string, (*(int *)value == AUTO ? "auto" :
4530 *(int *)value == FALSE ? "no" : "yes"));
4534 strcpy(value_string, (*(boolean *)value ? "AGA" : "ECS"));
4538 strcpy(value_string, getKeyNameFromKey(*(Key *)value));
4542 strcpy(value_string, getX11KeyNameFromKey(*(Key *)value));
4546 sprintf(value_string, "%d", *(int *)value);
4550 if (*(char **)value == NULL)
4553 strcpy(value_string, *(char **)value);
4557 sprintf(value_string, "player_%d", *(int *)value + 1);
4561 value_string[0] = '\0';
4565 if (type & TYPE_GHOSTED)
4566 strcpy(value_string, "n/a");
4568 return value_string;
4571 char *getSetupLine(struct TokenInfo *token_info, char *prefix, int token_nr)
4575 static char token_string[MAX_LINE_LEN];
4576 int token_type = token_info[token_nr].type;
4577 void *setup_value = token_info[token_nr].value;
4578 char *token_text = token_info[token_nr].text;
4579 char *value_string = getSetupValue(token_type, setup_value);
4581 // build complete token string
4582 sprintf(token_string, "%s%s", prefix, token_text);
4584 // build setup entry line
4585 line = getFormattedSetupEntry(token_string, value_string);
4587 if (token_type == TYPE_KEY_X11)
4589 Key key = *(Key *)setup_value;
4590 char *keyname = getKeyNameFromKey(key);
4592 // add comment, if useful
4593 if (!strEqual(keyname, "(undefined)") &&
4594 !strEqual(keyname, "(unknown)"))
4596 // add at least one whitespace
4598 for (i = strlen(line); i < token_comment_position; i++)
4602 strcat(line, keyname);
4609 static void InitLastPlayedLevels_ParentNode(void)
4611 LevelDirTree **leveldir_top = &leveldir_first->node_group->next;
4612 LevelDirTree *leveldir_new = NULL;
4614 // check if parent node for last played levels already exists
4615 if (strEqual((*leveldir_top)->identifier, TOKEN_STR_LAST_LEVEL_SERIES))
4618 leveldir_new = newTreeInfo();
4620 setTreeInfoToDefaultsFromParent(leveldir_new, leveldir_first);
4622 leveldir_new->level_group = TRUE;
4623 leveldir_new->sort_priority = LEVELCLASS_LAST_PLAYED_LEVEL;
4625 setString(&leveldir_new->identifier, TOKEN_STR_LAST_LEVEL_SERIES);
4626 setString(&leveldir_new->name, "<< (last played level sets)");
4627 setString(&leveldir_new->name_sorting, leveldir_new->name);
4629 pushTreeInfo(leveldir_top, leveldir_new);
4631 // create node to link back to current level directory
4632 createParentTreeInfoNode(leveldir_new);
4635 void UpdateLastPlayedLevels_TreeInfo(void)
4637 char **last_level_series = setup.level_setup.last_level_series;
4638 LevelDirTree *leveldir_last;
4639 TreeInfo **node_new = NULL;
4642 if (last_level_series[0] == NULL)
4645 InitLastPlayedLevels_ParentNode();
4647 leveldir_last = getTreeInfoFromIdentifierExt(leveldir_first,
4648 TOKEN_STR_LAST_LEVEL_SERIES,
4649 TREE_NODE_TYPE_GROUP);
4650 if (leveldir_last == NULL)
4653 node_new = &leveldir_last->node_group->next;
4655 freeTreeInfo(*node_new);
4659 for (i = 0; last_level_series[i] != NULL; i++)
4661 LevelDirTree *node_last = getTreeInfoFromIdentifier(leveldir_first,
4662 last_level_series[i]);
4663 if (node_last == NULL)
4666 *node_new = getTreeInfoCopy(node_last); // copy complete node
4668 (*node_new)->node_top = &leveldir_first; // correct top node link
4669 (*node_new)->node_parent = leveldir_last; // correct parent node link
4671 (*node_new)->is_copy = TRUE; // mark entry as node copy
4673 (*node_new)->node_group = NULL;
4674 (*node_new)->next = NULL;
4676 (*node_new)->cl_first = -1; // force setting tree cursor
4678 node_new = &((*node_new)->next);
4682 static void UpdateLastPlayedLevels_List(void)
4684 char **last_level_series = setup.level_setup.last_level_series;
4685 int pos = MAX_LEVELDIR_HISTORY - 1;
4688 // search for potentially already existing entry in list of level sets
4689 for (i = 0; last_level_series[i] != NULL; i++)
4690 if (strEqual(last_level_series[i], leveldir_current->identifier))
4693 // move list of level sets one entry down (using potentially free entry)
4694 for (i = pos; i > 0; i--)
4695 setString(&last_level_series[i], last_level_series[i - 1]);
4697 // put last played level set at top position
4698 setString(&last_level_series[0], leveldir_current->identifier);
4701 static TreeInfo *StoreOrRestoreLastPlayedLevels(TreeInfo *node, boolean store)
4703 static char *identifier = NULL;
4707 setString(&identifier, (node && node->is_copy ? node->identifier : NULL));
4709 return NULL; // not used
4713 TreeInfo *node_new = getTreeInfoFromIdentifierExt(leveldir_first,
4715 TREE_NODE_TYPE_COPY);
4716 return (node_new != NULL ? node_new : node);
4720 void StoreLastPlayedLevels(TreeInfo *node)
4722 StoreOrRestoreLastPlayedLevels(node, TRUE);
4725 void RestoreLastPlayedLevels(TreeInfo **node)
4727 *node = StoreOrRestoreLastPlayedLevels(*node, FALSE);
4730 void LoadLevelSetup_LastSeries(void)
4732 // --------------------------------------------------------------------------
4733 // ~/.<program>/levelsetup.conf
4734 // --------------------------------------------------------------------------
4736 char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
4737 SetupFileHash *level_setup_hash = NULL;
4741 // always start with reliable default values
4742 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
4744 // start with empty history of last played level sets
4745 setString(&setup.level_setup.last_level_series[0], NULL);
4747 if (!strEqual(DEFAULT_LEVELSET, UNDEFINED_LEVELSET))
4749 leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
4751 if (leveldir_current == NULL)
4752 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
4755 if ((level_setup_hash = loadSetupFileHash(filename)))
4757 char *last_level_series =
4758 getHashEntry(level_setup_hash, TOKEN_STR_LAST_LEVEL_SERIES);
4760 leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
4762 if (leveldir_current == NULL)
4763 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
4765 for (i = 0; i < MAX_LEVELDIR_HISTORY; i++)
4767 char token[strlen(TOKEN_STR_LAST_LEVEL_SERIES) + 10];
4768 LevelDirTree *leveldir_last;
4770 sprintf(token, "%s.%03d", TOKEN_STR_LAST_LEVEL_SERIES, i);
4772 last_level_series = getHashEntry(level_setup_hash, token);
4774 leveldir_last = getTreeInfoFromIdentifier(leveldir_first,
4776 if (leveldir_last != NULL)
4777 setString(&setup.level_setup.last_level_series[pos++],
4781 setString(&setup.level_setup.last_level_series[pos], NULL);
4783 freeSetupFileHash(level_setup_hash);
4787 Debug("setup", "using default setup values");
4793 static void SaveLevelSetup_LastSeries_Ext(boolean deactivate_last_level_series)
4795 // --------------------------------------------------------------------------
4796 // ~/.<program>/levelsetup.conf
4797 // --------------------------------------------------------------------------
4799 // check if the current level directory structure is available at this point
4800 if (leveldir_current == NULL)
4803 char **last_level_series = setup.level_setup.last_level_series;
4804 char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
4808 InitUserDataDirectory();
4810 UpdateLastPlayedLevels_List();
4812 if (!(file = fopen(filename, MODE_WRITE)))
4814 Warn("cannot write setup file '%s'", filename);
4821 fprintFileHeader(file, LEVELSETUP_FILENAME);
4823 if (deactivate_last_level_series)
4824 fprintf(file, "# %s\n# ", "the following level set may have caused a problem and was deactivated");
4826 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_LAST_LEVEL_SERIES,
4827 leveldir_current->identifier));
4829 for (i = 0; last_level_series[i] != NULL; i++)
4831 char token[strlen(TOKEN_STR_LAST_LEVEL_SERIES) + 1 + 10 + 1];
4833 sprintf(token, "%s.%03d", TOKEN_STR_LAST_LEVEL_SERIES, i);
4835 fprintf(file, "%s\n", getFormattedSetupEntry(token, last_level_series[i]));
4840 SetFilePermissions(filename, PERMS_PRIVATE);
4845 void SaveLevelSetup_LastSeries(void)
4847 SaveLevelSetup_LastSeries_Ext(FALSE);
4850 void SaveLevelSetup_LastSeries_Deactivate(void)
4852 SaveLevelSetup_LastSeries_Ext(TRUE);
4855 static void checkSeriesInfo(void)
4857 static char *level_directory = NULL;
4860 DirectoryEntry *dir_entry;
4863 checked_free(level_directory);
4865 // check for more levels besides the 'levels' field of 'levelinfo.conf'
4867 level_directory = getPath2((leveldir_current->in_user_dir ?
4868 getUserLevelDir(NULL) :
4869 options.level_directory),
4870 leveldir_current->fullpath);
4872 if ((dir = openDirectory(level_directory)) == NULL)
4874 Warn("cannot read level directory '%s'", level_directory);
4880 while ((dir_entry = readDirectory(dir)) != NULL) // loop all entries
4882 if (strlen(dir_entry->basename) > 4 &&
4883 dir_entry->basename[3] == '.' &&
4884 strEqual(&dir_entry->basename[4], LEVELFILE_EXTENSION))
4886 char levelnum_str[4];
4889 strncpy(levelnum_str, dir_entry->basename, 3);
4890 levelnum_str[3] = '\0';
4892 levelnum_value = atoi(levelnum_str);
4894 if (levelnum_value < leveldir_current->first_level)
4896 Warn("additional level %d found", levelnum_value);
4898 leveldir_current->first_level = levelnum_value;
4900 else if (levelnum_value > leveldir_current->last_level)
4902 Warn("additional level %d found", levelnum_value);
4904 leveldir_current->last_level = levelnum_value;
4910 closeDirectory(dir);
4913 void LoadLevelSetup_SeriesInfo(void)
4916 SetupFileHash *level_setup_hash = NULL;
4917 char *level_subdir = leveldir_current->subdir;
4920 // always start with reliable default values
4921 level_nr = leveldir_current->first_level;
4923 for (i = 0; i < MAX_LEVELS; i++)
4925 LevelStats_setPlayed(i, 0);
4926 LevelStats_setSolved(i, 0);
4931 // --------------------------------------------------------------------------
4932 // ~/.<program>/levelsetup/<level series>/levelsetup.conf
4933 // --------------------------------------------------------------------------
4935 level_subdir = leveldir_current->subdir;
4937 filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
4939 if ((level_setup_hash = loadSetupFileHash(filename)))
4943 // get last played level in this level set
4945 token_value = getHashEntry(level_setup_hash, TOKEN_STR_LAST_PLAYED_LEVEL);
4949 level_nr = atoi(token_value);
4951 if (level_nr < leveldir_current->first_level)
4952 level_nr = leveldir_current->first_level;
4953 if (level_nr > leveldir_current->last_level)
4954 level_nr = leveldir_current->last_level;
4957 // get handicap level in this level set
4959 token_value = getHashEntry(level_setup_hash, TOKEN_STR_HANDICAP_LEVEL);
4963 int level_nr = atoi(token_value);
4965 if (level_nr < leveldir_current->first_level)
4966 level_nr = leveldir_current->first_level;
4967 if (level_nr > leveldir_current->last_level + 1)
4968 level_nr = leveldir_current->last_level;
4970 if (leveldir_current->user_defined || !leveldir_current->handicap)
4971 level_nr = leveldir_current->last_level;
4973 leveldir_current->handicap_level = level_nr;
4976 // get number of played and solved levels in this level set
4978 BEGIN_HASH_ITERATION(level_setup_hash, itr)
4980 char *token = HASH_ITERATION_TOKEN(itr);
4981 char *value = HASH_ITERATION_VALUE(itr);
4983 if (strlen(token) == 3 &&
4984 token[0] >= '0' && token[0] <= '9' &&
4985 token[1] >= '0' && token[1] <= '9' &&
4986 token[2] >= '0' && token[2] <= '9')
4988 int level_nr = atoi(token);
4991 LevelStats_setPlayed(level_nr, atoi(value)); // read 1st column
4993 value = strchr(value, ' ');
4996 LevelStats_setSolved(level_nr, atoi(value)); // read 2nd column
4999 END_HASH_ITERATION(hash, itr)
5001 freeSetupFileHash(level_setup_hash);
5005 Debug("setup", "using default setup values");
5011 void SaveLevelSetup_SeriesInfo(void)
5014 char *level_subdir = leveldir_current->subdir;
5015 char *level_nr_str = int2str(level_nr, 0);
5016 char *handicap_level_str = int2str(leveldir_current->handicap_level, 0);
5020 // --------------------------------------------------------------------------
5021 // ~/.<program>/levelsetup/<level series>/levelsetup.conf
5022 // --------------------------------------------------------------------------
5024 InitLevelSetupDirectory(level_subdir);
5026 filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
5028 if (!(file = fopen(filename, MODE_WRITE)))
5030 Warn("cannot write setup file '%s'", filename);
5037 fprintFileHeader(file, LEVELSETUP_FILENAME);
5039 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_PLAYED_LEVEL,
5041 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_HANDICAP_LEVEL,
5042 handicap_level_str));
5044 for (i = leveldir_current->first_level; i <= leveldir_current->last_level;
5047 if (LevelStats_getPlayed(i) > 0 ||
5048 LevelStats_getSolved(i) > 0)
5053 sprintf(token, "%03d", i);
5054 sprintf(value, "%d %d", LevelStats_getPlayed(i), LevelStats_getSolved(i));
5056 fprintf(file, "%s\n", getFormattedSetupEntry(token, value));
5062 SetFilePermissions(filename, PERMS_PRIVATE);
5067 int LevelStats_getPlayed(int nr)
5069 return (nr >= 0 && nr < MAX_LEVELS ? level_stats[nr].played : 0);
5072 int LevelStats_getSolved(int nr)
5074 return (nr >= 0 && nr < MAX_LEVELS ? level_stats[nr].solved : 0);
5077 void LevelStats_setPlayed(int nr, int value)
5079 if (nr >= 0 && nr < MAX_LEVELS)
5080 level_stats[nr].played = value;
5083 void LevelStats_setSolved(int nr, int value)
5085 if (nr >= 0 && nr < MAX_LEVELS)
5086 level_stats[nr].solved = value;
5089 void LevelStats_incPlayed(int nr)
5091 if (nr >= 0 && nr < MAX_LEVELS)
5092 level_stats[nr].played++;
5095 void LevelStats_incSolved(int nr)
5097 if (nr >= 0 && nr < MAX_LEVELS)
5098 level_stats[nr].solved++;
5101 void LoadUserSetup(void)
5103 // --------------------------------------------------------------------------
5104 // ~/.<program>/usersetup.conf
5105 // --------------------------------------------------------------------------
5107 char *filename = getPath2(getMainUserGameDataDir(), USERSETUP_FILENAME);
5108 SetupFileHash *user_setup_hash = NULL;
5110 // always start with reliable default values
5113 if ((user_setup_hash = loadSetupFileHash(filename)))
5117 // get last selected user number
5118 token_value = getHashEntry(user_setup_hash, TOKEN_STR_LAST_USER);
5121 user.nr = MIN(MAX(0, atoi(token_value)), MAX_PLAYER_NAMES - 1);
5123 freeSetupFileHash(user_setup_hash);
5127 Debug("setup", "using default setup values");
5133 void SaveUserSetup(void)
5135 // --------------------------------------------------------------------------
5136 // ~/.<program>/usersetup.conf
5137 // --------------------------------------------------------------------------
5139 char *filename = getPath2(getMainUserGameDataDir(), USERSETUP_FILENAME);
5142 InitMainUserDataDirectory();
5144 if (!(file = fopen(filename, MODE_WRITE)))
5146 Warn("cannot write setup file '%s'", filename);
5153 fprintFileHeader(file, USERSETUP_FILENAME);
5155 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_USER,
5159 SetFilePermissions(filename, PERMS_PRIVATE);