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 *getScoreDir(char *level_subdir)
93 static char *score_dir = NULL;
94 static char *score_level_dir = NULL;
95 char *score_subdir = SCORES_DIRECTORY;
97 if (score_dir == NULL)
99 if (program.global_scores)
100 score_dir = getPath2(getCommonDataDir(), score_subdir);
102 score_dir = getPath2(getMainUserGameDataDir(), score_subdir);
105 if (level_subdir != NULL)
107 checked_free(score_level_dir);
109 score_level_dir = getPath2(score_dir, level_subdir);
111 return score_level_dir;
117 static char *getScoreTapeDir(char *level_subdir, int nr)
119 static char *score_tape_dir = NULL;
120 char tape_subdir[MAX_FILENAME_LEN];
122 checked_free(score_tape_dir);
124 sprintf(tape_subdir, "%03d", nr);
125 score_tape_dir = getPath2(getScoreDir(level_subdir), tape_subdir);
127 return score_tape_dir;
130 static char *getUserSubdir(int nr)
132 static char user_subdir[16] = { 0 };
134 sprintf(user_subdir, "%03d", nr);
139 static char *getUserDir(int nr)
141 static char *user_dir = NULL;
142 char *main_data_dir = getMainUserGameDataDir();
143 char *users_subdir = USERS_DIRECTORY;
144 char *user_subdir = getUserSubdir(nr);
146 checked_free(user_dir);
149 user_dir = getPath3(main_data_dir, users_subdir, user_subdir);
151 user_dir = getPath2(main_data_dir, users_subdir);
156 static char *getLevelSetupDir(char *level_subdir)
158 static char *levelsetup_dir = NULL;
159 char *data_dir = getUserGameDataDir();
160 char *levelsetup_subdir = LEVELSETUP_DIRECTORY;
162 checked_free(levelsetup_dir);
164 if (level_subdir != NULL)
165 levelsetup_dir = getPath3(data_dir, levelsetup_subdir, level_subdir);
167 levelsetup_dir = getPath2(data_dir, levelsetup_subdir);
169 return levelsetup_dir;
172 static char *getCacheDir(void)
174 static char *cache_dir = NULL;
176 if (cache_dir == NULL)
177 cache_dir = getPath2(getMainUserGameDataDir(), CACHE_DIRECTORY);
182 static char *getNetworkDir(void)
184 static char *network_dir = NULL;
186 if (network_dir == NULL)
187 network_dir = getPath2(getMainUserGameDataDir(), NETWORK_DIRECTORY);
192 char *getLevelDirFromTreeInfo(TreeInfo *node)
194 static char *level_dir = NULL;
197 return options.level_directory;
199 checked_free(level_dir);
201 level_dir = getPath2((node->in_user_dir ? getUserLevelDir(NULL) :
202 options.level_directory), node->fullpath);
207 char *getUserLevelDir(char *level_subdir)
209 static char *userlevel_dir = NULL;
210 char *data_dir = getMainUserGameDataDir();
211 char *userlevel_subdir = LEVELS_DIRECTORY;
213 checked_free(userlevel_dir);
215 if (level_subdir != NULL)
216 userlevel_dir = getPath3(data_dir, userlevel_subdir, level_subdir);
218 userlevel_dir = getPath2(data_dir, userlevel_subdir);
220 return userlevel_dir;
223 char *getNetworkLevelDir(char *level_subdir)
225 static char *network_level_dir = NULL;
226 char *data_dir = getNetworkDir();
227 char *networklevel_subdir = LEVELS_DIRECTORY;
229 checked_free(network_level_dir);
231 if (level_subdir != NULL)
232 network_level_dir = getPath3(data_dir, networklevel_subdir, level_subdir);
234 network_level_dir = getPath2(data_dir, networklevel_subdir);
236 return network_level_dir;
239 char *getCurrentLevelDir(void)
241 return getLevelDirFromTreeInfo(leveldir_current);
244 char *getNewUserLevelSubdir(void)
246 static char *new_level_subdir = NULL;
247 char *subdir_prefix = getLoginName();
248 char subdir_suffix[10];
249 int max_suffix_number = 1000;
252 while (++i < max_suffix_number)
254 sprintf(subdir_suffix, "_%d", i);
256 checked_free(new_level_subdir);
257 new_level_subdir = getStringCat2(subdir_prefix, subdir_suffix);
259 if (!directoryExists(getUserLevelDir(new_level_subdir)))
263 return new_level_subdir;
266 static char *getTapeDir(char *level_subdir)
268 static char *tape_dir = NULL;
269 char *data_dir = getUserGameDataDir();
270 char *tape_subdir = TAPES_DIRECTORY;
272 checked_free(tape_dir);
274 if (level_subdir != NULL)
275 tape_dir = getPath3(data_dir, tape_subdir, level_subdir);
277 tape_dir = getPath2(data_dir, tape_subdir);
282 static char *getSolutionTapeDir(void)
284 static char *tape_dir = NULL;
285 char *data_dir = getCurrentLevelDir();
286 char *tape_subdir = TAPES_DIRECTORY;
288 checked_free(tape_dir);
290 tape_dir = getPath2(data_dir, tape_subdir);
295 static char *getDefaultGraphicsDir(char *graphics_subdir)
297 static char *graphics_dir = NULL;
299 if (graphics_subdir == NULL)
300 return options.graphics_directory;
302 checked_free(graphics_dir);
304 graphics_dir = getPath2(options.graphics_directory, graphics_subdir);
309 static char *getDefaultSoundsDir(char *sounds_subdir)
311 static char *sounds_dir = NULL;
313 if (sounds_subdir == NULL)
314 return options.sounds_directory;
316 checked_free(sounds_dir);
318 sounds_dir = getPath2(options.sounds_directory, sounds_subdir);
323 static char *getDefaultMusicDir(char *music_subdir)
325 static char *music_dir = NULL;
327 if (music_subdir == NULL)
328 return options.music_directory;
330 checked_free(music_dir);
332 music_dir = getPath2(options.music_directory, music_subdir);
337 static char *getClassicArtworkSet(int type)
339 return (type == TREE_TYPE_GRAPHICS_DIR ? GFX_CLASSIC_SUBDIR :
340 type == TREE_TYPE_SOUNDS_DIR ? SND_CLASSIC_SUBDIR :
341 type == TREE_TYPE_MUSIC_DIR ? MUS_CLASSIC_SUBDIR : "");
344 static char *getClassicArtworkDir(int type)
346 return (type == TREE_TYPE_GRAPHICS_DIR ?
347 getDefaultGraphicsDir(GFX_CLASSIC_SUBDIR) :
348 type == TREE_TYPE_SOUNDS_DIR ?
349 getDefaultSoundsDir(SND_CLASSIC_SUBDIR) :
350 type == TREE_TYPE_MUSIC_DIR ?
351 getDefaultMusicDir(MUS_CLASSIC_SUBDIR) : "");
354 char *getUserGraphicsDir(void)
356 static char *usergraphics_dir = NULL;
358 if (usergraphics_dir == NULL)
359 usergraphics_dir = getPath2(getMainUserGameDataDir(), GRAPHICS_DIRECTORY);
361 return usergraphics_dir;
364 char *getUserSoundsDir(void)
366 static char *usersounds_dir = NULL;
368 if (usersounds_dir == NULL)
369 usersounds_dir = getPath2(getMainUserGameDataDir(), SOUNDS_DIRECTORY);
371 return usersounds_dir;
374 char *getUserMusicDir(void)
376 static char *usermusic_dir = NULL;
378 if (usermusic_dir == NULL)
379 usermusic_dir = getPath2(getMainUserGameDataDir(), MUSIC_DIRECTORY);
381 return usermusic_dir;
384 static char *getSetupArtworkDir(TreeInfo *ti)
386 static char *artwork_dir = NULL;
391 checked_free(artwork_dir);
393 artwork_dir = getPath2(ti->basepath, ti->fullpath);
398 char *setLevelArtworkDir(TreeInfo *ti)
400 char **artwork_path_ptr, **artwork_set_ptr;
401 TreeInfo *level_artwork;
403 if (ti == NULL || leveldir_current == NULL)
406 artwork_path_ptr = LEVELDIR_ARTWORK_PATH_PTR(leveldir_current, ti->type);
407 artwork_set_ptr = LEVELDIR_ARTWORK_SET_PTR( leveldir_current, ti->type);
409 checked_free(*artwork_path_ptr);
411 if ((level_artwork = getTreeInfoFromIdentifier(ti, *artwork_set_ptr)))
413 *artwork_path_ptr = getStringCopy(getSetupArtworkDir(level_artwork));
418 No (or non-existing) artwork configured in "levelinfo.conf". This would
419 normally result in using the artwork configured in the setup menu. But
420 if an artwork subdirectory exists (which might contain custom artwork
421 or an artwork configuration file), this level artwork must be treated
422 as relative to the default "classic" artwork, not to the artwork that
423 is currently configured in the setup menu.
425 Update: For "special" versions of R'n'D (like "R'n'D jue"), do not use
426 the "default" artwork (which would be "jue0" for "R'n'D jue"), but use
427 the real "classic" artwork from the original R'n'D (like "gfx_classic").
430 char *dir = getPath2(getCurrentLevelDir(), ARTWORK_DIRECTORY(ti->type));
432 checked_free(*artwork_set_ptr);
434 if (directoryExists(dir))
436 *artwork_path_ptr = getStringCopy(getClassicArtworkDir(ti->type));
437 *artwork_set_ptr = getStringCopy(getClassicArtworkSet(ti->type));
441 *artwork_path_ptr = getStringCopy(UNDEFINED_FILENAME);
442 *artwork_set_ptr = NULL;
448 return *artwork_set_ptr;
451 static char *getLevelArtworkSet(int type)
453 if (leveldir_current == NULL)
456 return LEVELDIR_ARTWORK_SET(leveldir_current, type);
459 static char *getLevelArtworkDir(int type)
461 if (leveldir_current == NULL)
462 return UNDEFINED_FILENAME;
464 return LEVELDIR_ARTWORK_PATH(leveldir_current, type);
467 char *getProgramMainDataPath(char *command_filename, char *base_path)
469 // check if the program's main data base directory is configured
470 if (!strEqual(base_path, "."))
471 return getStringCopy(base_path);
473 /* if the program is configured to start from current directory (default),
474 determine program package directory from program binary (some versions
475 of KDE/Konqueror and Mac OS X (especially "Mavericks") apparently do not
476 set the current working directory to the program package directory) */
477 char *main_data_path = getBasePath(command_filename);
479 #if defined(PLATFORM_MACOSX)
480 if (strSuffix(main_data_path, MAC_APP_BINARY_SUBDIR))
482 char *main_data_path_old = main_data_path;
484 // cut relative path to Mac OS X application binary directory from path
485 main_data_path[strlen(main_data_path) -
486 strlen(MAC_APP_BINARY_SUBDIR)] = '\0';
488 // cut trailing path separator from path (but not if path is root directory)
489 if (strSuffix(main_data_path, "/") && !strEqual(main_data_path, "/"))
490 main_data_path[strlen(main_data_path) - 1] = '\0';
492 // replace empty path with current directory
493 if (strEqual(main_data_path, ""))
494 main_data_path = ".";
496 // add relative path to Mac OS X application resources directory to path
497 main_data_path = getPath2(main_data_path, MAC_APP_FILES_SUBDIR);
499 free(main_data_path_old);
503 return main_data_path;
506 char *getProgramConfigFilename(char *command_filename)
508 static char *config_filename_1 = NULL;
509 static char *config_filename_2 = NULL;
510 static char *config_filename_3 = NULL;
511 static boolean initialized = FALSE;
515 char *command_filename_1 = getStringCopy(command_filename);
517 // strip trailing executable suffix from command filename
518 if (strSuffix(command_filename_1, ".exe"))
519 command_filename_1[strlen(command_filename_1) - 4] = '\0';
521 char *ro_base_path = getProgramMainDataPath(command_filename, RO_BASE_PATH);
522 char *conf_directory = getPath2(ro_base_path, CONF_DIRECTORY);
524 char *command_basepath = getBasePath(command_filename);
525 char *command_basename = getBaseNameNoSuffix(command_filename);
526 char *command_filename_2 = getPath2(command_basepath, command_basename);
528 config_filename_1 = getStringCat2(command_filename_1, ".conf");
529 config_filename_2 = getStringCat2(command_filename_2, ".conf");
530 config_filename_3 = getPath2(conf_directory, SETUP_FILENAME);
532 checked_free(ro_base_path);
533 checked_free(conf_directory);
535 checked_free(command_basepath);
536 checked_free(command_basename);
538 checked_free(command_filename_1);
539 checked_free(command_filename_2);
544 // 1st try: look for config file that exactly matches the binary filename
545 if (fileExists(config_filename_1))
546 return config_filename_1;
548 // 2nd try: look for config file that matches binary filename without suffix
549 if (fileExists(config_filename_2))
550 return config_filename_2;
552 // 3rd try: return setup config filename in global program config directory
553 return config_filename_3;
556 char *getTapeFilename(int nr)
558 static char *filename = NULL;
559 char basename[MAX_FILENAME_LEN];
561 checked_free(filename);
563 sprintf(basename, "%03d.%s", nr, TAPEFILE_EXTENSION);
564 filename = getPath2(getTapeDir(leveldir_current->subdir), basename);
569 char *getSolutionTapeFilename(int nr)
571 static char *filename = NULL;
572 char basename[MAX_FILENAME_LEN];
574 checked_free(filename);
576 sprintf(basename, "%03d.%s", nr, TAPEFILE_EXTENSION);
577 filename = getPath2(getSolutionTapeDir(), basename);
579 if (!fileExists(filename))
581 static char *filename_sln = NULL;
583 checked_free(filename_sln);
585 sprintf(basename, "%03d.sln", nr);
586 filename_sln = getPath2(getSolutionTapeDir(), basename);
588 if (fileExists(filename_sln))
595 char *getScoreFilename(int nr)
597 static char *filename = NULL;
598 char basename[MAX_FILENAME_LEN];
600 checked_free(filename);
602 sprintf(basename, "%03d.%s", nr, SCOREFILE_EXTENSION);
604 // used instead of "leveldir_current->subdir" (for network games)
605 filename = getPath2(getScoreDir(levelset.identifier), basename);
610 char *getScoreTapeBasename(char *name)
612 static char basename[MAX_FILENAME_LEN];
613 char basename_raw[MAX_FILENAME_LEN];
616 sprintf(timestamp, "%s", getCurrentTimestamp());
617 sprintf(basename_raw, "%s-%s", timestamp, name);
618 sprintf(basename, "%s-%08x", timestamp, get_hash_from_key(basename_raw));
623 char *getScoreTapeFilename(char *basename_no_ext, int nr)
625 static char *filename = NULL;
626 char basename[MAX_FILENAME_LEN];
628 checked_free(filename);
630 sprintf(basename, "%s.%s", basename_no_ext, TAPEFILE_EXTENSION);
632 // used instead of "leveldir_current->subdir" (for network games)
633 filename = getPath2(getScoreTapeDir(levelset.identifier, nr), basename);
638 char *getSetupFilename(void)
640 static char *filename = NULL;
642 checked_free(filename);
644 filename = getPath2(getSetupDir(), SETUP_FILENAME);
649 char *getDefaultSetupFilename(void)
651 return program.config_filename;
654 char *getEditorSetupFilename(void)
656 static char *filename = NULL;
658 checked_free(filename);
659 filename = getPath2(getCurrentLevelDir(), EDITORSETUP_FILENAME);
661 if (fileExists(filename))
664 checked_free(filename);
665 filename = getPath2(getSetupDir(), EDITORSETUP_FILENAME);
670 char *getHelpAnimFilename(void)
672 static char *filename = NULL;
674 checked_free(filename);
676 filename = getPath2(getCurrentLevelDir(), HELPANIM_FILENAME);
681 char *getHelpTextFilename(void)
683 static char *filename = NULL;
685 checked_free(filename);
687 filename = getPath2(getCurrentLevelDir(), HELPTEXT_FILENAME);
692 char *getLevelSetInfoFilename(void)
694 static char *filename = NULL;
709 for (i = 0; basenames[i] != NULL; i++)
711 checked_free(filename);
712 filename = getPath2(getCurrentLevelDir(), basenames[i]);
714 if (fileExists(filename))
721 static char *getLevelSetTitleMessageBasename(int nr, boolean initial)
723 static char basename[32];
725 sprintf(basename, "%s_%d.txt",
726 (initial ? "titlemessage_initial" : "titlemessage"), nr + 1);
731 char *getLevelSetTitleMessageFilename(int nr, boolean initial)
733 static char *filename = NULL;
735 boolean skip_setup_artwork = FALSE;
737 checked_free(filename);
739 basename = getLevelSetTitleMessageBasename(nr, initial);
741 if (!gfx.override_level_graphics)
743 // 1st try: look for special artwork in current level series directory
744 filename = getPath3(getCurrentLevelDir(), GRAPHICS_DIRECTORY, basename);
745 if (fileExists(filename))
750 // 2nd try: look for message file in current level set directory
751 filename = getPath2(getCurrentLevelDir(), basename);
752 if (fileExists(filename))
757 // check if there is special artwork configured in level series config
758 if (getLevelArtworkSet(ARTWORK_TYPE_GRAPHICS) != NULL)
760 // 3rd try: look for special artwork configured in level series config
761 filename = getPath2(getLevelArtworkDir(ARTWORK_TYPE_GRAPHICS), basename);
762 if (fileExists(filename))
767 // take missing artwork configured in level set config from default
768 skip_setup_artwork = TRUE;
772 if (!skip_setup_artwork)
774 // 4th try: look for special artwork in configured artwork directory
775 filename = getPath2(getSetupArtworkDir(artwork.gfx_current), basename);
776 if (fileExists(filename))
782 // 5th try: look for default artwork in new default artwork directory
783 filename = getPath2(getDefaultGraphicsDir(GFX_DEFAULT_SUBDIR), basename);
784 if (fileExists(filename))
789 // 6th try: look for default artwork in old default artwork directory
790 filename = getPath2(options.graphics_directory, basename);
791 if (fileExists(filename))
794 return NULL; // cannot find specified artwork file anywhere
797 static char *getCorrectedArtworkBasename(char *basename)
802 char *getCustomImageFilename(char *basename)
804 static char *filename = NULL;
805 boolean skip_setup_artwork = FALSE;
807 checked_free(filename);
809 basename = getCorrectedArtworkBasename(basename);
811 if (!gfx.override_level_graphics)
813 // 1st try: look for special artwork in current level series directory
814 filename = getImg3(getCurrentLevelDir(), GRAPHICS_DIRECTORY, basename);
815 if (fileExists(filename))
820 // check if there is special artwork configured in level series config
821 if (getLevelArtworkSet(ARTWORK_TYPE_GRAPHICS) != NULL)
823 // 2nd try: look for special artwork configured in level series config
824 filename = getImg2(getLevelArtworkDir(ARTWORK_TYPE_GRAPHICS), basename);
825 if (fileExists(filename))
830 // take missing artwork configured in level set config from default
831 skip_setup_artwork = TRUE;
835 if (!skip_setup_artwork)
837 // 3rd try: look for special artwork in configured artwork directory
838 filename = getImg2(getSetupArtworkDir(artwork.gfx_current), basename);
839 if (fileExists(filename))
845 // 4th try: look for default artwork in new default artwork directory
846 filename = getImg2(getDefaultGraphicsDir(GFX_DEFAULT_SUBDIR), basename);
847 if (fileExists(filename))
852 // 5th try: look for default artwork in old default artwork directory
853 filename = getImg2(options.graphics_directory, basename);
854 if (fileExists(filename))
857 if (!strEqual(GFX_FALLBACK_FILENAME, UNDEFINED_FILENAME))
861 Warn("cannot find artwork file '%s' (using fallback)", basename);
863 // 6th try: look for fallback artwork in old default artwork directory
864 // (needed to prevent errors when trying to access unused artwork files)
865 filename = getImg2(options.graphics_directory, GFX_FALLBACK_FILENAME);
866 if (fileExists(filename))
870 return NULL; // cannot find specified artwork file anywhere
873 char *getCustomSoundFilename(char *basename)
875 static char *filename = NULL;
876 boolean skip_setup_artwork = FALSE;
878 checked_free(filename);
880 basename = getCorrectedArtworkBasename(basename);
882 if (!gfx.override_level_sounds)
884 // 1st try: look for special artwork in current level series directory
885 filename = getPath3(getCurrentLevelDir(), SOUNDS_DIRECTORY, basename);
886 if (fileExists(filename))
891 // check if there is special artwork configured in level series config
892 if (getLevelArtworkSet(ARTWORK_TYPE_SOUNDS) != NULL)
894 // 2nd try: look for special artwork configured in level series config
895 filename = getPath2(getLevelArtworkDir(TREE_TYPE_SOUNDS_DIR), basename);
896 if (fileExists(filename))
901 // take missing artwork configured in level set config from default
902 skip_setup_artwork = TRUE;
906 if (!skip_setup_artwork)
908 // 3rd try: look for special artwork in configured artwork directory
909 filename = getPath2(getSetupArtworkDir(artwork.snd_current), basename);
910 if (fileExists(filename))
916 // 4th try: look for default artwork in new default artwork directory
917 filename = getPath2(getDefaultSoundsDir(SND_DEFAULT_SUBDIR), basename);
918 if (fileExists(filename))
923 // 5th try: look for default artwork in old default artwork directory
924 filename = getPath2(options.sounds_directory, basename);
925 if (fileExists(filename))
928 if (!strEqual(SND_FALLBACK_FILENAME, UNDEFINED_FILENAME))
932 Warn("cannot find artwork file '%s' (using fallback)", basename);
934 // 6th try: look for fallback artwork in old default artwork directory
935 // (needed to prevent errors when trying to access unused artwork files)
936 filename = getPath2(options.sounds_directory, SND_FALLBACK_FILENAME);
937 if (fileExists(filename))
941 return NULL; // cannot find specified artwork file anywhere
944 char *getCustomMusicFilename(char *basename)
946 static char *filename = NULL;
947 boolean skip_setup_artwork = FALSE;
949 checked_free(filename);
951 basename = getCorrectedArtworkBasename(basename);
953 if (!gfx.override_level_music)
955 // 1st try: look for special artwork in current level series directory
956 filename = getPath3(getCurrentLevelDir(), MUSIC_DIRECTORY, basename);
957 if (fileExists(filename))
962 // check if there is special artwork configured in level series config
963 if (getLevelArtworkSet(ARTWORK_TYPE_MUSIC) != NULL)
965 // 2nd try: look for special artwork configured in level series config
966 filename = getPath2(getLevelArtworkDir(TREE_TYPE_MUSIC_DIR), basename);
967 if (fileExists(filename))
972 // take missing artwork configured in level set config from default
973 skip_setup_artwork = TRUE;
977 if (!skip_setup_artwork)
979 // 3rd try: look for special artwork in configured artwork directory
980 filename = getPath2(getSetupArtworkDir(artwork.mus_current), basename);
981 if (fileExists(filename))
987 // 4th try: look for default artwork in new default artwork directory
988 filename = getPath2(getDefaultMusicDir(MUS_DEFAULT_SUBDIR), basename);
989 if (fileExists(filename))
994 // 5th try: look for default artwork in old default artwork directory
995 filename = getPath2(options.music_directory, basename);
996 if (fileExists(filename))
999 if (!strEqual(MUS_FALLBACK_FILENAME, UNDEFINED_FILENAME))
1003 Warn("cannot find artwork file '%s' (using fallback)", basename);
1005 // 6th try: look for fallback artwork in old default artwork directory
1006 // (needed to prevent errors when trying to access unused artwork files)
1007 filename = getPath2(options.music_directory, MUS_FALLBACK_FILENAME);
1008 if (fileExists(filename))
1012 return NULL; // cannot find specified artwork file anywhere
1015 char *getCustomArtworkFilename(char *basename, int type)
1017 if (type == ARTWORK_TYPE_GRAPHICS)
1018 return getCustomImageFilename(basename);
1019 else if (type == ARTWORK_TYPE_SOUNDS)
1020 return getCustomSoundFilename(basename);
1021 else if (type == ARTWORK_TYPE_MUSIC)
1022 return getCustomMusicFilename(basename);
1024 return UNDEFINED_FILENAME;
1027 char *getCustomArtworkConfigFilename(int type)
1029 return getCustomArtworkFilename(ARTWORKINFO_FILENAME(type), type);
1032 char *getCustomArtworkLevelConfigFilename(int type)
1034 static char *filename = NULL;
1036 checked_free(filename);
1038 filename = getPath2(getLevelArtworkDir(type), ARTWORKINFO_FILENAME(type));
1043 char *getCustomMusicDirectory(void)
1045 static char *directory = NULL;
1046 boolean skip_setup_artwork = FALSE;
1048 checked_free(directory);
1050 if (!gfx.override_level_music)
1052 // 1st try: look for special artwork in current level series directory
1053 directory = getPath2(getCurrentLevelDir(), MUSIC_DIRECTORY);
1054 if (directoryExists(directory))
1059 // check if there is special artwork configured in level series config
1060 if (getLevelArtworkSet(ARTWORK_TYPE_MUSIC) != NULL)
1062 // 2nd try: look for special artwork configured in level series config
1063 directory = getStringCopy(getLevelArtworkDir(TREE_TYPE_MUSIC_DIR));
1064 if (directoryExists(directory))
1069 // take missing artwork configured in level set config from default
1070 skip_setup_artwork = TRUE;
1074 if (!skip_setup_artwork)
1076 // 3rd try: look for special artwork in configured artwork directory
1077 directory = getStringCopy(getSetupArtworkDir(artwork.mus_current));
1078 if (directoryExists(directory))
1084 // 4th try: look for default artwork in new default artwork directory
1085 directory = getStringCopy(getDefaultMusicDir(MUS_DEFAULT_SUBDIR));
1086 if (directoryExists(directory))
1091 // 5th try: look for default artwork in old default artwork directory
1092 directory = getStringCopy(options.music_directory);
1093 if (directoryExists(directory))
1096 return NULL; // cannot find specified artwork file anywhere
1099 void InitTapeDirectory(char *level_subdir)
1101 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
1102 createDirectory(getTapeDir(NULL), "main tape", PERMS_PRIVATE);
1103 createDirectory(getTapeDir(level_subdir), "level tape", PERMS_PRIVATE);
1106 void InitScoreDirectory(char *level_subdir)
1108 int permissions = (program.global_scores ? PERMS_PUBLIC : PERMS_PRIVATE);
1110 if (program.global_scores)
1111 createDirectory(getCommonDataDir(), "common data", permissions);
1113 createDirectory(getMainUserGameDataDir(), "main user data", permissions);
1115 createDirectory(getScoreDir(NULL), "main score", permissions);
1116 createDirectory(getScoreDir(level_subdir), "level score", permissions);
1119 void InitScoreTapeDirectory(char *level_subdir, int nr)
1121 int permissions = (program.global_scores ? PERMS_PUBLIC : PERMS_PRIVATE);
1123 InitScoreDirectory(level_subdir);
1125 createDirectory(getScoreTapeDir(level_subdir, nr), "score tape", permissions);
1128 static void SaveUserLevelInfo(void);
1130 void InitUserLevelDirectory(char *level_subdir)
1132 if (!directoryExists(getUserLevelDir(level_subdir)))
1134 createDirectory(getMainUserGameDataDir(), "main user data", PERMS_PRIVATE);
1135 createDirectory(getUserLevelDir(NULL), "main user level", PERMS_PRIVATE);
1136 createDirectory(getUserLevelDir(level_subdir), "user level", PERMS_PRIVATE);
1138 if (setup.internal.create_user_levelset)
1139 SaveUserLevelInfo();
1143 void InitNetworkLevelDirectory(char *level_subdir)
1145 if (!directoryExists(getNetworkLevelDir(level_subdir)))
1147 createDirectory(getMainUserGameDataDir(), "main user data", PERMS_PRIVATE);
1148 createDirectory(getNetworkDir(), "network data", PERMS_PRIVATE);
1149 createDirectory(getNetworkLevelDir(NULL), "main network level", PERMS_PRIVATE);
1150 createDirectory(getNetworkLevelDir(level_subdir), "network level", PERMS_PRIVATE);
1154 void InitLevelSetupDirectory(char *level_subdir)
1156 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
1157 createDirectory(getLevelSetupDir(NULL), "main level setup", PERMS_PRIVATE);
1158 createDirectory(getLevelSetupDir(level_subdir), "level setup", PERMS_PRIVATE);
1161 static void InitCacheDirectory(void)
1163 createDirectory(getMainUserGameDataDir(), "main user data", PERMS_PRIVATE);
1164 createDirectory(getCacheDir(), "cache data", PERMS_PRIVATE);
1168 // ----------------------------------------------------------------------------
1169 // some functions to handle lists of level and artwork directories
1170 // ----------------------------------------------------------------------------
1172 TreeInfo *newTreeInfo(void)
1174 return checked_calloc(sizeof(TreeInfo));
1177 TreeInfo *newTreeInfo_setDefaults(int type)
1179 TreeInfo *ti = newTreeInfo();
1181 setTreeInfoToDefaults(ti, type);
1186 void pushTreeInfo(TreeInfo **node_first, TreeInfo *node_new)
1188 node_new->next = *node_first;
1189 *node_first = node_new;
1192 void removeTreeInfo(TreeInfo **node_first)
1194 TreeInfo *node_old = *node_first;
1196 *node_first = node_old->next;
1197 node_old->next = NULL;
1199 freeTreeInfo(node_old);
1202 int numTreeInfo(TreeInfo *node)
1215 boolean validLevelSeries(TreeInfo *node)
1217 // in a number of cases, tree node is no valid level set
1218 if (node == NULL || node->node_group || node->parent_link || node->is_copy)
1224 TreeInfo *getValidLevelSeries(TreeInfo *node, TreeInfo *default_node)
1226 if (validLevelSeries(node))
1228 else if (node->is_copy)
1229 return getTreeInfoFromIdentifier(leveldir_first, node->identifier);
1231 return getFirstValidTreeInfoEntry(default_node);
1234 TreeInfo *getFirstValidTreeInfoEntry(TreeInfo *node)
1239 if (node->node_group) // enter level group (step down into tree)
1240 return getFirstValidTreeInfoEntry(node->node_group);
1241 else if (node->parent_link) // skip start entry of level group
1243 if (node->next) // get first real level series entry
1244 return getFirstValidTreeInfoEntry(node->next);
1245 else // leave empty level group and go on
1246 return getFirstValidTreeInfoEntry(node->node_parent->next);
1248 else // this seems to be a regular level series
1252 TreeInfo *getTreeInfoFirstGroupEntry(TreeInfo *node)
1257 if (node->node_parent == NULL) // top level group
1258 return *node->node_top;
1259 else // sub level group
1260 return node->node_parent->node_group;
1263 int numTreeInfoInGroup(TreeInfo *node)
1265 return numTreeInfo(getTreeInfoFirstGroupEntry(node));
1268 int getPosFromTreeInfo(TreeInfo *node)
1270 TreeInfo *node_cmp = getTreeInfoFirstGroupEntry(node);
1275 if (node_cmp == node)
1279 node_cmp = node_cmp->next;
1285 TreeInfo *getTreeInfoFromPos(TreeInfo *node, int pos)
1287 TreeInfo *node_default = node;
1299 return node_default;
1302 static TreeInfo *getTreeInfoFromIdentifierExt(TreeInfo *node, char *identifier,
1303 int node_type_wanted)
1305 if (identifier == NULL)
1310 if (TREE_NODE_TYPE(node) == node_type_wanted &&
1311 strEqual(identifier, node->identifier))
1314 if (node->node_group)
1316 TreeInfo *node_group = getTreeInfoFromIdentifierExt(node->node_group,
1329 TreeInfo *getTreeInfoFromIdentifier(TreeInfo *node, char *identifier)
1331 return getTreeInfoFromIdentifierExt(node, identifier, TREE_NODE_TYPE_DEFAULT);
1334 static TreeInfo *cloneTreeNode(TreeInfo **node_top, TreeInfo *node_parent,
1335 TreeInfo *node, boolean skip_sets_without_levels)
1342 if (!node->parent_link && !node->level_group &&
1343 skip_sets_without_levels && node->levels == 0)
1344 return cloneTreeNode(node_top, node_parent, node->next,
1345 skip_sets_without_levels);
1347 node_new = getTreeInfoCopy(node); // copy complete node
1349 node_new->node_top = node_top; // correct top node link
1350 node_new->node_parent = node_parent; // correct parent node link
1352 if (node->level_group)
1353 node_new->node_group = cloneTreeNode(node_top, node_new, node->node_group,
1354 skip_sets_without_levels);
1356 node_new->next = cloneTreeNode(node_top, node_parent, node->next,
1357 skip_sets_without_levels);
1362 static void cloneTree(TreeInfo **ti_new, TreeInfo *ti, boolean skip_empty_sets)
1364 TreeInfo *ti_cloned = cloneTreeNode(ti_new, NULL, ti, skip_empty_sets);
1366 *ti_new = ti_cloned;
1369 static boolean adjustTreeGraphicsForEMC(TreeInfo *node)
1371 boolean settings_changed = FALSE;
1375 boolean want_ecs = (setup.prefer_aga_graphics == FALSE);
1376 boolean want_aga = (setup.prefer_aga_graphics == TRUE);
1377 boolean has_only_ecs = (!node->graphics_set && !node->graphics_set_aga);
1378 boolean has_only_aga = (!node->graphics_set && !node->graphics_set_ecs);
1379 char *graphics_set = NULL;
1381 if (node->graphics_set_ecs && (want_ecs || has_only_ecs))
1382 graphics_set = node->graphics_set_ecs;
1384 if (node->graphics_set_aga && (want_aga || has_only_aga))
1385 graphics_set = node->graphics_set_aga;
1387 if (graphics_set && !strEqual(node->graphics_set, graphics_set))
1389 setString(&node->graphics_set, graphics_set);
1390 settings_changed = TRUE;
1393 if (node->node_group != NULL)
1394 settings_changed |= adjustTreeGraphicsForEMC(node->node_group);
1399 return settings_changed;
1402 static boolean adjustTreeSoundsForEMC(TreeInfo *node)
1404 boolean settings_changed = FALSE;
1408 boolean want_default = (setup.prefer_lowpass_sounds == FALSE);
1409 boolean want_lowpass = (setup.prefer_lowpass_sounds == TRUE);
1410 boolean has_only_default = (!node->sounds_set && !node->sounds_set_lowpass);
1411 boolean has_only_lowpass = (!node->sounds_set && !node->sounds_set_default);
1412 char *sounds_set = NULL;
1414 if (node->sounds_set_default && (want_default || has_only_default))
1415 sounds_set = node->sounds_set_default;
1417 if (node->sounds_set_lowpass && (want_lowpass || has_only_lowpass))
1418 sounds_set = node->sounds_set_lowpass;
1420 if (sounds_set && !strEqual(node->sounds_set, sounds_set))
1422 setString(&node->sounds_set, sounds_set);
1423 settings_changed = TRUE;
1426 if (node->node_group != NULL)
1427 settings_changed |= adjustTreeSoundsForEMC(node->node_group);
1432 return settings_changed;
1435 void dumpTreeInfo(TreeInfo *node, int depth)
1437 char bullet_list[] = { '-', '*', 'o' };
1441 Debug("tree", "Dumping TreeInfo:");
1445 char bullet = bullet_list[depth % ARRAY_SIZE(bullet_list)];
1447 for (i = 0; i < depth * 2; i++)
1448 DebugContinued("", " ");
1450 DebugContinued("tree", "%c '%s' ['%s] [PARENT: '%s'] %s\n",
1451 bullet, node->name, node->identifier,
1452 (node->node_parent ? node->node_parent->identifier : "-"),
1453 (node->node_group ? "[GROUP]" : ""));
1456 // use for dumping artwork info tree
1457 Debug("tree", "subdir == '%s' ['%s', '%s'] [%d])",
1458 node->subdir, node->fullpath, node->basepath, node->in_user_dir);
1461 if (node->node_group != NULL)
1462 dumpTreeInfo(node->node_group, depth + 1);
1468 void sortTreeInfoBySortFunction(TreeInfo **node_first,
1469 int (*compare_function)(const void *,
1472 int num_nodes = numTreeInfo(*node_first);
1473 TreeInfo **sort_array;
1474 TreeInfo *node = *node_first;
1480 // allocate array for sorting structure pointers
1481 sort_array = checked_calloc(num_nodes * sizeof(TreeInfo *));
1483 // writing structure pointers to sorting array
1484 while (i < num_nodes && node) // double boundary check...
1486 sort_array[i] = node;
1492 // sorting the structure pointers in the sorting array
1493 qsort(sort_array, num_nodes, sizeof(TreeInfo *),
1496 // update the linkage of list elements with the sorted node array
1497 for (i = 0; i < num_nodes - 1; i++)
1498 sort_array[i]->next = sort_array[i + 1];
1499 sort_array[num_nodes - 1]->next = NULL;
1501 // update the linkage of the main list anchor pointer
1502 *node_first = sort_array[0];
1506 // now recursively sort the level group structures
1510 if (node->node_group != NULL)
1511 sortTreeInfoBySortFunction(&node->node_group, compare_function);
1517 void sortTreeInfo(TreeInfo **node_first)
1519 sortTreeInfoBySortFunction(node_first, compareTreeInfoEntries);
1523 // ============================================================================
1524 // some stuff from "files.c"
1525 // ============================================================================
1527 #if defined(PLATFORM_WIN32)
1529 #define S_IRGRP S_IRUSR
1532 #define S_IROTH S_IRUSR
1535 #define S_IWGRP S_IWUSR
1538 #define S_IWOTH S_IWUSR
1541 #define S_IXGRP S_IXUSR
1544 #define S_IXOTH S_IXUSR
1547 #define S_IRWXG (S_IRGRP | S_IWGRP | S_IXGRP)
1552 #endif // PLATFORM_WIN32
1554 // file permissions for newly written files
1555 #define MODE_R_ALL (S_IRUSR | S_IRGRP | S_IROTH)
1556 #define MODE_W_ALL (S_IWUSR | S_IWGRP | S_IWOTH)
1557 #define MODE_X_ALL (S_IXUSR | S_IXGRP | S_IXOTH)
1559 #define MODE_W_PRIVATE (S_IWUSR)
1560 #define MODE_W_PUBLIC_FILE (S_IWUSR | S_IWGRP)
1561 #define MODE_W_PUBLIC_DIR (S_IWUSR | S_IWGRP | S_ISGID)
1563 #define DIR_PERMS_PRIVATE (MODE_R_ALL | MODE_X_ALL | MODE_W_PRIVATE)
1564 #define DIR_PERMS_PUBLIC (MODE_R_ALL | MODE_X_ALL | MODE_W_PUBLIC_DIR)
1565 #define DIR_PERMS_PUBLIC_ALL (MODE_R_ALL | MODE_X_ALL | MODE_W_ALL)
1567 #define FILE_PERMS_PRIVATE (MODE_R_ALL | MODE_W_PRIVATE)
1568 #define FILE_PERMS_PUBLIC (MODE_R_ALL | MODE_W_PUBLIC_FILE)
1569 #define FILE_PERMS_PUBLIC_ALL (MODE_R_ALL | MODE_W_ALL)
1572 char *getHomeDir(void)
1574 static char *dir = NULL;
1576 #if defined(PLATFORM_WIN32)
1579 dir = checked_malloc(MAX_PATH + 1);
1581 if (!SUCCEEDED(SHGetFolderPath(NULL, CSIDL_PERSONAL, NULL, 0, dir)))
1584 #elif defined(PLATFORM_EMSCRIPTEN)
1585 dir = "/persistent";
1586 #elif defined(PLATFORM_UNIX)
1589 if ((dir = getenv("HOME")) == NULL)
1591 dir = getUnixHomeDir();
1594 dir = getStringCopy(dir);
1606 char *getCommonDataDir(void)
1608 static char *common_data_dir = NULL;
1610 #if defined(PLATFORM_WIN32)
1611 if (common_data_dir == NULL)
1613 char *dir = checked_malloc(MAX_PATH + 1);
1615 if (SUCCEEDED(SHGetFolderPath(NULL, CSIDL_COMMON_DOCUMENTS, NULL, 0, dir))
1616 && !strEqual(dir, "")) // empty for Windows 95/98
1617 common_data_dir = getPath2(dir, program.userdata_subdir);
1619 common_data_dir = options.rw_base_directory;
1622 if (common_data_dir == NULL)
1623 common_data_dir = options.rw_base_directory;
1626 return common_data_dir;
1629 char *getPersonalDataDir(void)
1631 static char *personal_data_dir = NULL;
1633 #if defined(PLATFORM_MACOSX)
1634 if (personal_data_dir == NULL)
1635 personal_data_dir = getPath2(getHomeDir(), "Documents");
1637 if (personal_data_dir == NULL)
1638 personal_data_dir = getHomeDir();
1641 return personal_data_dir;
1644 char *getMainUserGameDataDir(void)
1646 static char *main_user_data_dir = NULL;
1648 #if defined(PLATFORM_ANDROID)
1649 if (main_user_data_dir == NULL)
1650 main_user_data_dir = (char *)(SDL_AndroidGetExternalStorageState() &
1651 SDL_ANDROID_EXTERNAL_STORAGE_WRITE ?
1652 SDL_AndroidGetExternalStoragePath() :
1653 SDL_AndroidGetInternalStoragePath());
1655 if (main_user_data_dir == NULL)
1656 main_user_data_dir = getPath2(getPersonalDataDir(),
1657 program.userdata_subdir);
1660 return main_user_data_dir;
1663 char *getUserGameDataDir(void)
1666 return getMainUserGameDataDir();
1668 return getUserDir(user.nr);
1671 char *getSetupDir(void)
1673 return getUserGameDataDir();
1676 static mode_t posix_umask(mode_t mask)
1678 #if defined(PLATFORM_UNIX)
1685 static int posix_mkdir(const char *pathname, mode_t mode)
1687 #if defined(PLATFORM_WIN32)
1688 return mkdir(pathname);
1690 return mkdir(pathname, mode);
1694 static boolean posix_process_running_setgid(void)
1696 #if defined(PLATFORM_UNIX)
1697 return (getgid() != getegid());
1703 void createDirectory(char *dir, char *text, int permission_class)
1705 if (directoryExists(dir))
1708 // leave "other" permissions in umask untouched, but ensure group parts
1709 // of USERDATA_DIR_MODE are not masked
1710 mode_t dir_mode = (permission_class == PERMS_PRIVATE ?
1711 DIR_PERMS_PRIVATE : DIR_PERMS_PUBLIC);
1712 mode_t last_umask = posix_umask(0);
1713 mode_t group_umask = ~(dir_mode & S_IRWXG);
1714 int running_setgid = posix_process_running_setgid();
1716 if (permission_class == PERMS_PUBLIC)
1718 // if we're setgid, protect files against "other"
1719 // else keep umask(0) to make the dir world-writable
1722 posix_umask(last_umask & group_umask);
1724 dir_mode = DIR_PERMS_PUBLIC_ALL;
1727 if (posix_mkdir(dir, dir_mode) != 0)
1728 Warn("cannot create %s directory '%s': %s", text, dir, strerror(errno));
1730 if (permission_class == PERMS_PUBLIC && !running_setgid)
1731 chmod(dir, dir_mode);
1733 posix_umask(last_umask); // restore previous umask
1736 void InitMainUserDataDirectory(void)
1738 createDirectory(getMainUserGameDataDir(), "main user data", PERMS_PRIVATE);
1741 void InitUserDataDirectory(void)
1743 createDirectory(getMainUserGameDataDir(), "main user data", PERMS_PRIVATE);
1747 createDirectory(getUserDir(-1), "users", PERMS_PRIVATE);
1748 createDirectory(getUserDir(user.nr), "user data", PERMS_PRIVATE);
1752 void SetFilePermissions(char *filename, int permission_class)
1754 int running_setgid = posix_process_running_setgid();
1755 int perms = (permission_class == PERMS_PRIVATE ?
1756 FILE_PERMS_PRIVATE : FILE_PERMS_PUBLIC);
1758 if (permission_class == PERMS_PUBLIC && !running_setgid)
1759 perms = FILE_PERMS_PUBLIC_ALL;
1761 chmod(filename, perms);
1764 char *getCookie(char *file_type)
1766 static char cookie[MAX_COOKIE_LEN + 1];
1768 if (strlen(program.cookie_prefix) + 1 +
1769 strlen(file_type) + strlen("_FILE_VERSION_x.x") > MAX_COOKIE_LEN)
1770 return "[COOKIE ERROR]"; // should never happen
1772 sprintf(cookie, "%s_%s_FILE_VERSION_%d.%d",
1773 program.cookie_prefix, file_type,
1774 program.version_super, program.version_major);
1779 void fprintFileHeader(FILE *file, char *basename)
1781 char *prefix = "# ";
1784 fprintf_line_with_prefix(file, prefix, sep1, 77);
1785 fprintf(file, "%s%s\n", prefix, basename);
1786 fprintf_line_with_prefix(file, prefix, sep1, 77);
1787 fprintf(file, "\n");
1790 int getFileVersionFromCookieString(const char *cookie)
1792 const char *ptr_cookie1, *ptr_cookie2;
1793 const char *pattern1 = "_FILE_VERSION_";
1794 const char *pattern2 = "?.?";
1795 const int len_cookie = strlen(cookie);
1796 const int len_pattern1 = strlen(pattern1);
1797 const int len_pattern2 = strlen(pattern2);
1798 const int len_pattern = len_pattern1 + len_pattern2;
1799 int version_super, version_major;
1801 if (len_cookie <= len_pattern)
1804 ptr_cookie1 = &cookie[len_cookie - len_pattern];
1805 ptr_cookie2 = &cookie[len_cookie - len_pattern2];
1807 if (strncmp(ptr_cookie1, pattern1, len_pattern1) != 0)
1810 if (ptr_cookie2[0] < '0' || ptr_cookie2[0] > '9' ||
1811 ptr_cookie2[1] != '.' ||
1812 ptr_cookie2[2] < '0' || ptr_cookie2[2] > '9')
1815 version_super = ptr_cookie2[0] - '0';
1816 version_major = ptr_cookie2[2] - '0';
1818 return VERSION_IDENT(version_super, version_major, 0, 0);
1821 boolean checkCookieString(const char *cookie, const char *template)
1823 const char *pattern = "_FILE_VERSION_?.?";
1824 const int len_cookie = strlen(cookie);
1825 const int len_template = strlen(template);
1826 const int len_pattern = strlen(pattern);
1828 if (len_cookie != len_template)
1831 if (strncmp(cookie, template, len_cookie - len_pattern) != 0)
1838 // ----------------------------------------------------------------------------
1839 // setup file list and hash handling functions
1840 // ----------------------------------------------------------------------------
1842 char *getFormattedSetupEntry(char *token, char *value)
1845 static char entry[MAX_LINE_LEN];
1847 // if value is an empty string, just return token without value
1851 // start with the token and some spaces to format output line
1852 sprintf(entry, "%s:", token);
1853 for (i = strlen(entry); i < token_value_position; i++)
1856 // continue with the token's value
1857 strcat(entry, value);
1862 SetupFileList *newSetupFileList(char *token, char *value)
1864 SetupFileList *new = checked_malloc(sizeof(SetupFileList));
1866 new->token = getStringCopy(token);
1867 new->value = getStringCopy(value);
1874 void freeSetupFileList(SetupFileList *list)
1879 checked_free(list->token);
1880 checked_free(list->value);
1883 freeSetupFileList(list->next);
1888 char *getListEntry(SetupFileList *list, char *token)
1893 if (strEqual(list->token, token))
1896 return getListEntry(list->next, token);
1899 SetupFileList *setListEntry(SetupFileList *list, char *token, char *value)
1904 if (strEqual(list->token, token))
1906 checked_free(list->value);
1908 list->value = getStringCopy(value);
1912 else if (list->next == NULL)
1913 return (list->next = newSetupFileList(token, value));
1915 return setListEntry(list->next, token, value);
1918 SetupFileList *addListEntry(SetupFileList *list, char *token, char *value)
1923 if (list->next == NULL)
1924 return (list->next = newSetupFileList(token, value));
1926 return addListEntry(list->next, token, value);
1929 #if ENABLE_UNUSED_CODE
1931 static void printSetupFileList(SetupFileList *list)
1936 Debug("setup:printSetupFileList", "token: '%s'", list->token);
1937 Debug("setup:printSetupFileList", "value: '%s'", list->value);
1939 printSetupFileList(list->next);
1945 DEFINE_HASHTABLE_INSERT(insert_hash_entry, char, char);
1946 DEFINE_HASHTABLE_SEARCH(search_hash_entry, char, char);
1947 DEFINE_HASHTABLE_CHANGE(change_hash_entry, char, char);
1948 DEFINE_HASHTABLE_REMOVE(remove_hash_entry, char, char);
1950 #define insert_hash_entry hashtable_insert
1951 #define search_hash_entry hashtable_search
1952 #define change_hash_entry hashtable_change
1953 #define remove_hash_entry hashtable_remove
1956 unsigned int get_hash_from_key(void *key)
1961 This algorithm (k=33) was first reported by Dan Bernstein many years ago in
1962 'comp.lang.c'. Another version of this algorithm (now favored by Bernstein)
1963 uses XOR: hash(i) = hash(i - 1) * 33 ^ str[i]; the magic of number 33 (why
1964 it works better than many other constants, prime or not) has never been
1965 adequately explained.
1967 If you just want to have a good hash function, and cannot wait, djb2
1968 is one of the best string hash functions i know. It has excellent
1969 distribution and speed on many different sets of keys and table sizes.
1970 You are not likely to do better with one of the "well known" functions
1971 such as PJW, K&R, etc.
1973 Ozan (oz) Yigit [http://www.cs.yorku.ca/~oz/hash.html]
1976 char *str = (char *)key;
1977 unsigned int hash = 5381;
1980 while ((c = *str++))
1981 hash = ((hash << 5) + hash) + c; // hash * 33 + c
1986 static int keys_are_equal(void *key1, void *key2)
1988 return (strEqual((char *)key1, (char *)key2));
1991 SetupFileHash *newSetupFileHash(void)
1993 SetupFileHash *new_hash =
1994 create_hashtable(16, 0.75, get_hash_from_key, keys_are_equal);
1996 if (new_hash == NULL)
1997 Fail("create_hashtable() failed -- out of memory");
2002 void freeSetupFileHash(SetupFileHash *hash)
2007 hashtable_destroy(hash, 1); // 1 == also free values stored in hash
2010 char *getHashEntry(SetupFileHash *hash, char *token)
2015 return search_hash_entry(hash, token);
2018 void setHashEntry(SetupFileHash *hash, char *token, char *value)
2025 value_copy = getStringCopy(value);
2027 // change value; if it does not exist, insert it as new
2028 if (!change_hash_entry(hash, token, value_copy))
2029 if (!insert_hash_entry(hash, getStringCopy(token), value_copy))
2030 Fail("cannot insert into hash -- aborting");
2033 char *removeHashEntry(SetupFileHash *hash, char *token)
2038 return remove_hash_entry(hash, token);
2041 #if ENABLE_UNUSED_CODE
2043 static void printSetupFileHash(SetupFileHash *hash)
2045 BEGIN_HASH_ITERATION(hash, itr)
2047 Debug("setup:printSetupFileHash", "token: '%s'", HASH_ITERATION_TOKEN(itr));
2048 Debug("setup:printSetupFileHash", "value: '%s'", HASH_ITERATION_VALUE(itr));
2050 END_HASH_ITERATION(hash, itr)
2055 #define ALLOW_TOKEN_VALUE_SEPARATOR_BEING_WHITESPACE 1
2056 #define CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING 0
2057 #define CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH 0
2059 static boolean token_value_separator_found = FALSE;
2060 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2061 static boolean token_value_separator_warning = FALSE;
2063 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2064 static boolean token_already_exists_warning = FALSE;
2067 static boolean getTokenValueFromSetupLineExt(char *line,
2068 char **token_ptr, char **value_ptr,
2069 char *filename, char *line_raw,
2071 boolean separator_required)
2073 static char line_copy[MAX_LINE_LEN + 1], line_raw_copy[MAX_LINE_LEN + 1];
2074 char *token, *value, *line_ptr;
2076 // when externally invoked via ReadTokenValueFromLine(), copy line buffers
2077 if (line_raw == NULL)
2079 strncpy(line_copy, line, MAX_LINE_LEN);
2080 line_copy[MAX_LINE_LEN] = '\0';
2083 strcpy(line_raw_copy, line_copy);
2084 line_raw = line_raw_copy;
2087 // cut trailing comment from input line
2088 for (line_ptr = line; *line_ptr; line_ptr++)
2090 if (*line_ptr == '#')
2097 // cut trailing whitespaces from input line
2098 for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
2099 if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
2102 // ignore empty lines
2106 // cut leading whitespaces from token
2107 for (token = line; *token; token++)
2108 if (*token != ' ' && *token != '\t')
2111 // start with empty value as reliable default
2114 token_value_separator_found = FALSE;
2116 // find end of token to determine start of value
2117 for (line_ptr = token; *line_ptr; line_ptr++)
2119 // first look for an explicit token/value separator, like ':' or '='
2120 if (*line_ptr == ':' || *line_ptr == '=')
2122 *line_ptr = '\0'; // terminate token string
2123 value = line_ptr + 1; // set beginning of value
2125 token_value_separator_found = TRUE;
2131 #if ALLOW_TOKEN_VALUE_SEPARATOR_BEING_WHITESPACE
2132 // fallback: if no token/value separator found, also allow whitespaces
2133 if (!token_value_separator_found && !separator_required)
2135 for (line_ptr = token; *line_ptr; line_ptr++)
2137 if (*line_ptr == ' ' || *line_ptr == '\t')
2139 *line_ptr = '\0'; // terminate token string
2140 value = line_ptr + 1; // set beginning of value
2142 token_value_separator_found = TRUE;
2148 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2149 if (token_value_separator_found)
2151 if (!token_value_separator_warning)
2153 Debug("setup", "---");
2155 if (filename != NULL)
2157 Debug("setup", "missing token/value separator(s) in config file:");
2158 Debug("setup", "- config file: '%s'", filename);
2162 Debug("setup", "missing token/value separator(s):");
2165 token_value_separator_warning = TRUE;
2168 if (filename != NULL)
2169 Debug("setup", "- line %d: '%s'", line_nr, line_raw);
2171 Debug("setup", "- line: '%s'", line_raw);
2177 // cut trailing whitespaces from token
2178 for (line_ptr = &token[strlen(token)]; line_ptr >= token; line_ptr--)
2179 if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
2182 // cut leading whitespaces from value
2183 for (; *value; value++)
2184 if (*value != ' ' && *value != '\t')
2193 boolean getTokenValueFromSetupLine(char *line, char **token, char **value)
2195 // while the internal (old) interface does not require a token/value
2196 // separator (for downwards compatibility with existing files which
2197 // don't use them), it is mandatory for the external (new) interface
2199 return getTokenValueFromSetupLineExt(line, token, value, NULL, NULL, 0, TRUE);
2202 static boolean loadSetupFileData(void *setup_file_data, char *filename,
2203 boolean top_recursion_level, boolean is_hash)
2205 static SetupFileHash *include_filename_hash = NULL;
2206 char line[MAX_LINE_LEN], line_raw[MAX_LINE_LEN], previous_line[MAX_LINE_LEN];
2207 char *token, *value, *line_ptr;
2208 void *insert_ptr = NULL;
2209 boolean read_continued_line = FALSE;
2211 int line_nr = 0, token_count = 0, include_count = 0;
2213 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2214 token_value_separator_warning = FALSE;
2217 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2218 token_already_exists_warning = FALSE;
2221 if (!(file = openFile(filename, MODE_READ)))
2223 #if DEBUG_NO_CONFIG_FILE
2224 Debug("setup", "cannot open configuration file '%s'", filename);
2230 // use "insert pointer" to store list end for constant insertion complexity
2232 insert_ptr = setup_file_data;
2234 // on top invocation, create hash to mark included files (to prevent loops)
2235 if (top_recursion_level)
2236 include_filename_hash = newSetupFileHash();
2238 // mark this file as already included (to prevent including it again)
2239 setHashEntry(include_filename_hash, getBaseNamePtr(filename), "true");
2241 while (!checkEndOfFile(file))
2243 // read next line of input file
2244 if (!getStringFromFile(file, line, MAX_LINE_LEN))
2247 // check if line was completely read and is terminated by line break
2248 if (strlen(line) > 0 && line[strlen(line) - 1] == '\n')
2251 // cut trailing line break (this can be newline and/or carriage return)
2252 for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
2253 if ((*line_ptr == '\n' || *line_ptr == '\r') && *(line_ptr + 1) == '\0')
2256 // copy raw input line for later use (mainly debugging output)
2257 strcpy(line_raw, line);
2259 if (read_continued_line)
2261 // append new line to existing line, if there is enough space
2262 if (strlen(previous_line) + strlen(line_ptr) < MAX_LINE_LEN)
2263 strcat(previous_line, line_ptr);
2265 strcpy(line, previous_line); // copy storage buffer to line
2267 read_continued_line = FALSE;
2270 // if the last character is '\', continue at next line
2271 if (strlen(line) > 0 && line[strlen(line) - 1] == '\\')
2273 line[strlen(line) - 1] = '\0'; // cut off trailing backslash
2274 strcpy(previous_line, line); // copy line to storage buffer
2276 read_continued_line = TRUE;
2281 if (!getTokenValueFromSetupLineExt(line, &token, &value, filename,
2282 line_raw, line_nr, FALSE))
2287 if (strEqual(token, "include"))
2289 if (getHashEntry(include_filename_hash, value) == NULL)
2291 char *basepath = getBasePath(filename);
2292 char *basename = getBaseName(value);
2293 char *filename_include = getPath2(basepath, basename);
2295 loadSetupFileData(setup_file_data, filename_include, FALSE, is_hash);
2299 free(filename_include);
2305 Warn("ignoring already processed file '%s'", value);
2312 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2314 getHashEntry((SetupFileHash *)setup_file_data, token);
2316 if (old_value != NULL)
2318 if (!token_already_exists_warning)
2320 Debug("setup", "---");
2321 Debug("setup", "duplicate token(s) found in config file:");
2322 Debug("setup", "- config file: '%s'", filename);
2324 token_already_exists_warning = TRUE;
2327 Debug("setup", "- token: '%s' (in line %d)", token, line_nr);
2328 Debug("setup", " old value: '%s'", old_value);
2329 Debug("setup", " new value: '%s'", value);
2333 setHashEntry((SetupFileHash *)setup_file_data, token, value);
2337 insert_ptr = addListEntry((SetupFileList *)insert_ptr, token, value);
2347 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2348 if (token_value_separator_warning)
2349 Debug("setup", "---");
2352 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2353 if (token_already_exists_warning)
2354 Debug("setup", "---");
2357 if (token_count == 0 && include_count == 0)
2358 Warn("configuration file '%s' is empty", filename);
2360 if (top_recursion_level)
2361 freeSetupFileHash(include_filename_hash);
2366 static int compareSetupFileData(const void *object1, const void *object2)
2368 const struct ConfigInfo *entry1 = (struct ConfigInfo *)object1;
2369 const struct ConfigInfo *entry2 = (struct ConfigInfo *)object2;
2371 return strcmp(entry1->token, entry2->token);
2374 static void saveSetupFileHash(SetupFileHash *hash, char *filename)
2376 int item_count = hashtable_count(hash);
2377 int item_size = sizeof(struct ConfigInfo);
2378 struct ConfigInfo *sort_array = checked_malloc(item_count * item_size);
2382 // copy string pointers from hash to array
2383 BEGIN_HASH_ITERATION(hash, itr)
2385 sort_array[i].token = HASH_ITERATION_TOKEN(itr);
2386 sort_array[i].value = HASH_ITERATION_VALUE(itr);
2390 if (i > item_count) // should never happen
2393 END_HASH_ITERATION(hash, itr)
2395 // sort string pointers from hash in array
2396 qsort(sort_array, item_count, item_size, compareSetupFileData);
2398 if (!(file = fopen(filename, MODE_WRITE)))
2400 Warn("cannot write configuration file '%s'", filename);
2405 fprintf(file, "%s\n\n", getFormattedSetupEntry("program.version",
2406 program.version_string));
2407 for (i = 0; i < item_count; i++)
2408 fprintf(file, "%s\n", getFormattedSetupEntry(sort_array[i].token,
2409 sort_array[i].value));
2412 checked_free(sort_array);
2415 SetupFileList *loadSetupFileList(char *filename)
2417 SetupFileList *setup_file_list = newSetupFileList("", "");
2418 SetupFileList *first_valid_list_entry;
2420 if (!loadSetupFileData(setup_file_list, filename, TRUE, FALSE))
2422 freeSetupFileList(setup_file_list);
2427 first_valid_list_entry = setup_file_list->next;
2429 // free empty list header
2430 setup_file_list->next = NULL;
2431 freeSetupFileList(setup_file_list);
2433 return first_valid_list_entry;
2436 SetupFileHash *loadSetupFileHash(char *filename)
2438 SetupFileHash *setup_file_hash = newSetupFileHash();
2440 if (!loadSetupFileData(setup_file_hash, filename, TRUE, TRUE))
2442 freeSetupFileHash(setup_file_hash);
2447 return setup_file_hash;
2451 // ============================================================================
2453 // ============================================================================
2455 #define TOKEN_STR_LAST_LEVEL_SERIES "last_level_series"
2456 #define TOKEN_STR_LAST_PLAYED_LEVEL "last_played_level"
2457 #define TOKEN_STR_HANDICAP_LEVEL "handicap_level"
2458 #define TOKEN_STR_LAST_USER "last_user"
2460 // level directory info
2461 #define LEVELINFO_TOKEN_IDENTIFIER 0
2462 #define LEVELINFO_TOKEN_NAME 1
2463 #define LEVELINFO_TOKEN_NAME_SORTING 2
2464 #define LEVELINFO_TOKEN_AUTHOR 3
2465 #define LEVELINFO_TOKEN_YEAR 4
2466 #define LEVELINFO_TOKEN_PROGRAM_TITLE 5
2467 #define LEVELINFO_TOKEN_PROGRAM_COPYRIGHT 6
2468 #define LEVELINFO_TOKEN_PROGRAM_COMPANY 7
2469 #define LEVELINFO_TOKEN_IMPORTED_FROM 8
2470 #define LEVELINFO_TOKEN_IMPORTED_BY 9
2471 #define LEVELINFO_TOKEN_TESTED_BY 10
2472 #define LEVELINFO_TOKEN_LEVELS 11
2473 #define LEVELINFO_TOKEN_FIRST_LEVEL 12
2474 #define LEVELINFO_TOKEN_SORT_PRIORITY 13
2475 #define LEVELINFO_TOKEN_LATEST_ENGINE 14
2476 #define LEVELINFO_TOKEN_LEVEL_GROUP 15
2477 #define LEVELINFO_TOKEN_READONLY 16
2478 #define LEVELINFO_TOKEN_GRAPHICS_SET_ECS 17
2479 #define LEVELINFO_TOKEN_GRAPHICS_SET_AGA 18
2480 #define LEVELINFO_TOKEN_GRAPHICS_SET 19
2481 #define LEVELINFO_TOKEN_SOUNDS_SET_DEFAULT 20
2482 #define LEVELINFO_TOKEN_SOUNDS_SET_LOWPASS 21
2483 #define LEVELINFO_TOKEN_SOUNDS_SET 22
2484 #define LEVELINFO_TOKEN_MUSIC_SET 23
2485 #define LEVELINFO_TOKEN_FILENAME 24
2486 #define LEVELINFO_TOKEN_FILETYPE 25
2487 #define LEVELINFO_TOKEN_SPECIAL_FLAGS 26
2488 #define LEVELINFO_TOKEN_HANDICAP 27
2489 #define LEVELINFO_TOKEN_SKIP_LEVELS 28
2490 #define LEVELINFO_TOKEN_USE_EMC_TILES 29
2492 #define NUM_LEVELINFO_TOKENS 30
2494 static LevelDirTree ldi;
2496 static struct TokenInfo levelinfo_tokens[] =
2498 // level directory info
2499 { TYPE_STRING, &ldi.identifier, "identifier" },
2500 { TYPE_STRING, &ldi.name, "name" },
2501 { TYPE_STRING, &ldi.name_sorting, "name_sorting" },
2502 { TYPE_STRING, &ldi.author, "author" },
2503 { TYPE_STRING, &ldi.year, "year" },
2504 { TYPE_STRING, &ldi.program_title, "program_title" },
2505 { TYPE_STRING, &ldi.program_copyright, "program_copyright" },
2506 { TYPE_STRING, &ldi.program_company, "program_company" },
2507 { TYPE_STRING, &ldi.imported_from, "imported_from" },
2508 { TYPE_STRING, &ldi.imported_by, "imported_by" },
2509 { TYPE_STRING, &ldi.tested_by, "tested_by" },
2510 { TYPE_INTEGER, &ldi.levels, "levels" },
2511 { TYPE_INTEGER, &ldi.first_level, "first_level" },
2512 { TYPE_INTEGER, &ldi.sort_priority, "sort_priority" },
2513 { TYPE_BOOLEAN, &ldi.latest_engine, "latest_engine" },
2514 { TYPE_BOOLEAN, &ldi.level_group, "level_group" },
2515 { TYPE_BOOLEAN, &ldi.readonly, "readonly" },
2516 { TYPE_STRING, &ldi.graphics_set_ecs, "graphics_set.ecs" },
2517 { TYPE_STRING, &ldi.graphics_set_aga, "graphics_set.aga" },
2518 { TYPE_STRING, &ldi.graphics_set, "graphics_set" },
2519 { TYPE_STRING, &ldi.sounds_set_default,"sounds_set.default" },
2520 { TYPE_STRING, &ldi.sounds_set_lowpass,"sounds_set.lowpass" },
2521 { TYPE_STRING, &ldi.sounds_set, "sounds_set" },
2522 { TYPE_STRING, &ldi.music_set, "music_set" },
2523 { TYPE_STRING, &ldi.level_filename, "filename" },
2524 { TYPE_STRING, &ldi.level_filetype, "filetype" },
2525 { TYPE_STRING, &ldi.special_flags, "special_flags" },
2526 { TYPE_BOOLEAN, &ldi.handicap, "handicap" },
2527 { TYPE_BOOLEAN, &ldi.skip_levels, "skip_levels" },
2528 { TYPE_BOOLEAN, &ldi.use_emc_tiles, "use_emc_tiles" }
2531 static struct TokenInfo artworkinfo_tokens[] =
2533 // artwork directory info
2534 { TYPE_STRING, &ldi.identifier, "identifier" },
2535 { TYPE_STRING, &ldi.subdir, "subdir" },
2536 { TYPE_STRING, &ldi.name, "name" },
2537 { TYPE_STRING, &ldi.name_sorting, "name_sorting" },
2538 { TYPE_STRING, &ldi.author, "author" },
2539 { TYPE_STRING, &ldi.program_title, "program_title" },
2540 { TYPE_STRING, &ldi.program_copyright, "program_copyright" },
2541 { TYPE_STRING, &ldi.program_company, "program_company" },
2542 { TYPE_INTEGER, &ldi.sort_priority, "sort_priority" },
2543 { TYPE_STRING, &ldi.basepath, "basepath" },
2544 { TYPE_STRING, &ldi.fullpath, "fullpath" },
2545 { TYPE_BOOLEAN, &ldi.in_user_dir, "in_user_dir" },
2546 { TYPE_STRING, &ldi.class_desc, "class_desc" },
2551 static char *optional_tokens[] =
2554 "program_copyright",
2560 static void setTreeInfoToDefaults(TreeInfo *ti, int type)
2564 ti->node_top = (ti->type == TREE_TYPE_LEVEL_DIR ? &leveldir_first :
2565 ti->type == TREE_TYPE_GRAPHICS_DIR ? &artwork.gfx_first :
2566 ti->type == TREE_TYPE_SOUNDS_DIR ? &artwork.snd_first :
2567 ti->type == TREE_TYPE_MUSIC_DIR ? &artwork.mus_first :
2570 ti->node_parent = NULL;
2571 ti->node_group = NULL;
2578 ti->fullpath = NULL;
2579 ti->basepath = NULL;
2580 ti->identifier = NULL;
2581 ti->name = getStringCopy(ANONYMOUS_NAME);
2582 ti->name_sorting = NULL;
2583 ti->author = getStringCopy(ANONYMOUS_NAME);
2586 ti->program_title = NULL;
2587 ti->program_copyright = NULL;
2588 ti->program_company = NULL;
2590 ti->sort_priority = LEVELCLASS_UNDEFINED; // default: least priority
2591 ti->latest_engine = FALSE; // default: get from level
2592 ti->parent_link = FALSE;
2593 ti->is_copy = FALSE;
2594 ti->in_user_dir = FALSE;
2595 ti->user_defined = FALSE;
2597 ti->class_desc = NULL;
2599 ti->infotext = getStringCopy(TREE_INFOTEXT(ti->type));
2601 if (ti->type == TREE_TYPE_LEVEL_DIR)
2603 ti->imported_from = NULL;
2604 ti->imported_by = NULL;
2605 ti->tested_by = NULL;
2607 ti->graphics_set_ecs = NULL;
2608 ti->graphics_set_aga = NULL;
2609 ti->graphics_set = NULL;
2610 ti->sounds_set_default = NULL;
2611 ti->sounds_set_lowpass = NULL;
2612 ti->sounds_set = NULL;
2613 ti->music_set = NULL;
2614 ti->graphics_path = getStringCopy(UNDEFINED_FILENAME);
2615 ti->sounds_path = getStringCopy(UNDEFINED_FILENAME);
2616 ti->music_path = getStringCopy(UNDEFINED_FILENAME);
2618 ti->level_filename = NULL;
2619 ti->level_filetype = NULL;
2621 ti->special_flags = NULL;
2624 ti->first_level = 0;
2626 ti->level_group = FALSE;
2627 ti->handicap_level = 0;
2628 ti->readonly = TRUE;
2629 ti->handicap = TRUE;
2630 ti->skip_levels = FALSE;
2632 ti->use_emc_tiles = FALSE;
2636 static void setTreeInfoToDefaultsFromParent(TreeInfo *ti, TreeInfo *parent)
2640 Warn("setTreeInfoToDefaultsFromParent(): parent == NULL");
2642 setTreeInfoToDefaults(ti, TREE_TYPE_UNDEFINED);
2647 // copy all values from the parent structure
2649 ti->type = parent->type;
2651 ti->node_top = parent->node_top;
2652 ti->node_parent = parent;
2653 ti->node_group = NULL;
2660 ti->fullpath = NULL;
2661 ti->basepath = NULL;
2662 ti->identifier = NULL;
2663 ti->name = getStringCopy(ANONYMOUS_NAME);
2664 ti->name_sorting = NULL;
2665 ti->author = getStringCopy(parent->author);
2666 ti->year = getStringCopy(parent->year);
2668 ti->program_title = getStringCopy(parent->program_title);
2669 ti->program_copyright = getStringCopy(parent->program_copyright);
2670 ti->program_company = getStringCopy(parent->program_company);
2672 ti->sort_priority = parent->sort_priority;
2673 ti->latest_engine = parent->latest_engine;
2674 ti->parent_link = FALSE;
2675 ti->is_copy = FALSE;
2676 ti->in_user_dir = parent->in_user_dir;
2677 ti->user_defined = parent->user_defined;
2678 ti->color = parent->color;
2679 ti->class_desc = getStringCopy(parent->class_desc);
2681 ti->infotext = getStringCopy(parent->infotext);
2683 if (ti->type == TREE_TYPE_LEVEL_DIR)
2685 ti->imported_from = getStringCopy(parent->imported_from);
2686 ti->imported_by = getStringCopy(parent->imported_by);
2687 ti->tested_by = getStringCopy(parent->tested_by);
2689 ti->graphics_set_ecs = getStringCopy(parent->graphics_set_ecs);
2690 ti->graphics_set_aga = getStringCopy(parent->graphics_set_aga);
2691 ti->graphics_set = getStringCopy(parent->graphics_set);
2692 ti->sounds_set_default = getStringCopy(parent->sounds_set_default);
2693 ti->sounds_set_lowpass = getStringCopy(parent->sounds_set_lowpass);
2694 ti->sounds_set = getStringCopy(parent->sounds_set);
2695 ti->music_set = getStringCopy(parent->music_set);
2696 ti->graphics_path = getStringCopy(UNDEFINED_FILENAME);
2697 ti->sounds_path = getStringCopy(UNDEFINED_FILENAME);
2698 ti->music_path = getStringCopy(UNDEFINED_FILENAME);
2700 ti->level_filename = getStringCopy(parent->level_filename);
2701 ti->level_filetype = getStringCopy(parent->level_filetype);
2703 ti->special_flags = getStringCopy(parent->special_flags);
2705 ti->levels = parent->levels;
2706 ti->first_level = parent->first_level;
2707 ti->last_level = parent->last_level;
2708 ti->level_group = FALSE;
2709 ti->handicap_level = parent->handicap_level;
2710 ti->readonly = parent->readonly;
2711 ti->handicap = parent->handicap;
2712 ti->skip_levels = parent->skip_levels;
2714 ti->use_emc_tiles = parent->use_emc_tiles;
2718 static TreeInfo *getTreeInfoCopy(TreeInfo *ti)
2720 TreeInfo *ti_copy = newTreeInfo();
2722 // copy all values from the original structure
2724 ti_copy->type = ti->type;
2726 ti_copy->node_top = ti->node_top;
2727 ti_copy->node_parent = ti->node_parent;
2728 ti_copy->node_group = ti->node_group;
2729 ti_copy->next = ti->next;
2731 ti_copy->cl_first = ti->cl_first;
2732 ti_copy->cl_cursor = ti->cl_cursor;
2734 ti_copy->subdir = getStringCopy(ti->subdir);
2735 ti_copy->fullpath = getStringCopy(ti->fullpath);
2736 ti_copy->basepath = getStringCopy(ti->basepath);
2737 ti_copy->identifier = getStringCopy(ti->identifier);
2738 ti_copy->name = getStringCopy(ti->name);
2739 ti_copy->name_sorting = getStringCopy(ti->name_sorting);
2740 ti_copy->author = getStringCopy(ti->author);
2741 ti_copy->year = getStringCopy(ti->year);
2743 ti_copy->program_title = getStringCopy(ti->program_title);
2744 ti_copy->program_copyright = getStringCopy(ti->program_copyright);
2745 ti_copy->program_company = getStringCopy(ti->program_company);
2747 ti_copy->imported_from = getStringCopy(ti->imported_from);
2748 ti_copy->imported_by = getStringCopy(ti->imported_by);
2749 ti_copy->tested_by = getStringCopy(ti->tested_by);
2751 ti_copy->graphics_set_ecs = getStringCopy(ti->graphics_set_ecs);
2752 ti_copy->graphics_set_aga = getStringCopy(ti->graphics_set_aga);
2753 ti_copy->graphics_set = getStringCopy(ti->graphics_set);
2754 ti_copy->sounds_set_default = getStringCopy(ti->sounds_set_default);
2755 ti_copy->sounds_set_lowpass = getStringCopy(ti->sounds_set_lowpass);
2756 ti_copy->sounds_set = getStringCopy(ti->sounds_set);
2757 ti_copy->music_set = getStringCopy(ti->music_set);
2758 ti_copy->graphics_path = getStringCopy(ti->graphics_path);
2759 ti_copy->sounds_path = getStringCopy(ti->sounds_path);
2760 ti_copy->music_path = getStringCopy(ti->music_path);
2762 ti_copy->level_filename = getStringCopy(ti->level_filename);
2763 ti_copy->level_filetype = getStringCopy(ti->level_filetype);
2765 ti_copy->special_flags = getStringCopy(ti->special_flags);
2767 ti_copy->levels = ti->levels;
2768 ti_copy->first_level = ti->first_level;
2769 ti_copy->last_level = ti->last_level;
2770 ti_copy->sort_priority = ti->sort_priority;
2772 ti_copy->latest_engine = ti->latest_engine;
2774 ti_copy->level_group = ti->level_group;
2775 ti_copy->parent_link = ti->parent_link;
2776 ti_copy->is_copy = ti->is_copy;
2777 ti_copy->in_user_dir = ti->in_user_dir;
2778 ti_copy->user_defined = ti->user_defined;
2779 ti_copy->readonly = ti->readonly;
2780 ti_copy->handicap = ti->handicap;
2781 ti_copy->skip_levels = ti->skip_levels;
2783 ti_copy->use_emc_tiles = ti->use_emc_tiles;
2785 ti_copy->color = ti->color;
2786 ti_copy->class_desc = getStringCopy(ti->class_desc);
2787 ti_copy->handicap_level = ti->handicap_level;
2789 ti_copy->infotext = getStringCopy(ti->infotext);
2794 void freeTreeInfo(TreeInfo *ti)
2799 checked_free(ti->subdir);
2800 checked_free(ti->fullpath);
2801 checked_free(ti->basepath);
2802 checked_free(ti->identifier);
2804 checked_free(ti->name);
2805 checked_free(ti->name_sorting);
2806 checked_free(ti->author);
2807 checked_free(ti->year);
2809 checked_free(ti->program_title);
2810 checked_free(ti->program_copyright);
2811 checked_free(ti->program_company);
2813 checked_free(ti->class_desc);
2815 checked_free(ti->infotext);
2817 if (ti->type == TREE_TYPE_LEVEL_DIR)
2819 checked_free(ti->imported_from);
2820 checked_free(ti->imported_by);
2821 checked_free(ti->tested_by);
2823 checked_free(ti->graphics_set_ecs);
2824 checked_free(ti->graphics_set_aga);
2825 checked_free(ti->graphics_set);
2826 checked_free(ti->sounds_set_default);
2827 checked_free(ti->sounds_set_lowpass);
2828 checked_free(ti->sounds_set);
2829 checked_free(ti->music_set);
2831 checked_free(ti->graphics_path);
2832 checked_free(ti->sounds_path);
2833 checked_free(ti->music_path);
2835 checked_free(ti->level_filename);
2836 checked_free(ti->level_filetype);
2838 checked_free(ti->special_flags);
2841 // recursively free child node
2843 freeTreeInfo(ti->node_group);
2845 // recursively free next node
2847 freeTreeInfo(ti->next);
2852 void setSetupInfo(struct TokenInfo *token_info,
2853 int token_nr, char *token_value)
2855 int token_type = token_info[token_nr].type;
2856 void *setup_value = token_info[token_nr].value;
2858 if (token_value == NULL)
2861 // set setup field to corresponding token value
2866 *(boolean *)setup_value = get_boolean_from_string(token_value);
2870 *(int *)setup_value = get_switch3_from_string(token_value);
2874 *(Key *)setup_value = getKeyFromKeyName(token_value);
2878 *(Key *)setup_value = getKeyFromX11KeyName(token_value);
2882 *(int *)setup_value = get_integer_from_string(token_value);
2886 checked_free(*(char **)setup_value);
2887 *(char **)setup_value = getStringCopy(token_value);
2891 *(int *)setup_value = get_player_nr_from_string(token_value);
2899 static int compareTreeInfoEntries(const void *object1, const void *object2)
2901 const TreeInfo *entry1 = *((TreeInfo **)object1);
2902 const TreeInfo *entry2 = *((TreeInfo **)object2);
2903 int tree_sorting1 = TREE_SORTING(entry1);
2904 int tree_sorting2 = TREE_SORTING(entry2);
2906 if (tree_sorting1 != tree_sorting2)
2907 return (tree_sorting1 - tree_sorting2);
2909 return strcasecmp(entry1->name_sorting, entry2->name_sorting);
2912 static TreeInfo *createParentTreeInfoNode(TreeInfo *node_parent)
2916 if (node_parent == NULL)
2919 ti_new = newTreeInfo();
2920 setTreeInfoToDefaults(ti_new, node_parent->type);
2922 ti_new->node_parent = node_parent;
2923 ti_new->parent_link = TRUE;
2925 setString(&ti_new->identifier, node_parent->identifier);
2926 setString(&ti_new->name, BACKLINK_TEXT_PARENT);
2927 setString(&ti_new->name_sorting, ti_new->name);
2929 setString(&ti_new->subdir, STRING_PARENT_DIRECTORY);
2930 setString(&ti_new->fullpath, node_parent->fullpath);
2932 ti_new->sort_priority = LEVELCLASS_PARENT;
2933 ti_new->latest_engine = node_parent->latest_engine;
2935 setString(&ti_new->class_desc, getLevelClassDescription(ti_new));
2937 pushTreeInfo(&node_parent->node_group, ti_new);
2942 static TreeInfo *createTopTreeInfoNode(TreeInfo *node_first)
2944 if (node_first == NULL)
2947 TreeInfo *ti_new = newTreeInfo();
2948 int type = node_first->type;
2950 setTreeInfoToDefaults(ti_new, type);
2952 ti_new->node_parent = NULL;
2953 ti_new->parent_link = FALSE;
2955 setString(&ti_new->identifier, "top_tree_node");
2956 setString(&ti_new->name, TREE_INFOTEXT(type));
2957 setString(&ti_new->name_sorting, ti_new->name);
2959 setString(&ti_new->subdir, STRING_TOP_DIRECTORY);
2960 setString(&ti_new->fullpath, ".");
2962 ti_new->sort_priority = LEVELCLASS_TOP;
2963 ti_new->latest_engine = node_first->latest_engine;
2965 setString(&ti_new->class_desc, TREE_INFOTEXT(type));
2967 ti_new->node_group = node_first;
2968 ti_new->level_group = TRUE;
2970 TreeInfo *ti_new2 = createParentTreeInfoNode(ti_new);
2972 setString(&ti_new2->name, TREE_BACKLINK_TEXT(type));
2973 setString(&ti_new2->name_sorting, ti_new2->name);
2978 static void setTreeInfoParentNodes(TreeInfo *node, TreeInfo *node_parent)
2982 if (node->node_group)
2983 setTreeInfoParentNodes(node->node_group, node);
2985 node->node_parent = node_parent;
2992 // ----------------------------------------------------------------------------
2993 // functions for handling level and custom artwork info cache
2994 // ----------------------------------------------------------------------------
2996 static void LoadArtworkInfoCache(void)
2998 InitCacheDirectory();
3000 if (artworkinfo_cache_old == NULL)
3002 char *filename = getPath2(getCacheDir(), ARTWORKINFO_CACHE_FILE);
3004 // try to load artwork info hash from already existing cache file
3005 artworkinfo_cache_old = loadSetupFileHash(filename);
3007 // try to get program version that artwork info cache was written with
3008 char *version = getHashEntry(artworkinfo_cache_old, "program.version");
3010 // check program version of artwork info cache against current version
3011 if (!strEqual(version, program.version_string))
3013 freeSetupFileHash(artworkinfo_cache_old);
3015 artworkinfo_cache_old = NULL;
3018 // if no artwork info cache file was found, start with empty hash
3019 if (artworkinfo_cache_old == NULL)
3020 artworkinfo_cache_old = newSetupFileHash();
3025 if (artworkinfo_cache_new == NULL)
3026 artworkinfo_cache_new = newSetupFileHash();
3028 update_artworkinfo_cache = FALSE;
3031 static void SaveArtworkInfoCache(void)
3033 if (!update_artworkinfo_cache)
3036 char *filename = getPath2(getCacheDir(), ARTWORKINFO_CACHE_FILE);
3038 InitCacheDirectory();
3040 saveSetupFileHash(artworkinfo_cache_new, filename);
3045 static char *getCacheTokenPrefix(char *prefix1, char *prefix2)
3047 static char *prefix = NULL;
3049 checked_free(prefix);
3051 prefix = getStringCat2WithSeparator(prefix1, prefix2, ".");
3056 // (identical to above function, but separate string buffer needed -- nasty)
3057 static char *getCacheToken(char *prefix, char *suffix)
3059 static char *token = NULL;
3061 checked_free(token);
3063 token = getStringCat2WithSeparator(prefix, suffix, ".");
3068 static char *getFileTimestampString(char *filename)
3070 return getStringCopy(i_to_a(getFileTimestampEpochSeconds(filename)));
3073 static boolean modifiedFileTimestamp(char *filename, char *timestamp_string)
3075 struct stat file_status;
3077 if (timestamp_string == NULL)
3080 if (!fileExists(filename)) // file does not exist
3081 return (atoi(timestamp_string) != 0);
3083 if (stat(filename, &file_status) != 0) // cannot stat file
3086 return (file_status.st_mtime != atoi(timestamp_string));
3089 static TreeInfo *getArtworkInfoCacheEntry(LevelDirTree *level_node, int type)
3091 char *identifier = level_node->subdir;
3092 char *type_string = ARTWORK_DIRECTORY(type);
3093 char *token_prefix = getCacheTokenPrefix(type_string, identifier);
3094 char *token_main = getCacheToken(token_prefix, "CACHED");
3095 char *cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
3096 boolean cached = (cache_entry != NULL && strEqual(cache_entry, "true"));
3097 TreeInfo *artwork_info = NULL;
3099 if (!use_artworkinfo_cache)
3102 if (optional_tokens_hash == NULL)
3106 // create hash from list of optional tokens (for quick access)
3107 optional_tokens_hash = newSetupFileHash();
3108 for (i = 0; optional_tokens[i] != NULL; i++)
3109 setHashEntry(optional_tokens_hash, optional_tokens[i], "");
3116 artwork_info = newTreeInfo();
3117 setTreeInfoToDefaults(artwork_info, type);
3119 // set all structure fields according to the token/value pairs
3120 ldi = *artwork_info;
3121 for (i = 0; artworkinfo_tokens[i].type != -1; i++)
3123 char *token_suffix = artworkinfo_tokens[i].text;
3124 char *token = getCacheToken(token_prefix, token_suffix);
3125 char *value = getHashEntry(artworkinfo_cache_old, token);
3127 (getHashEntry(optional_tokens_hash, token_suffix) != NULL);
3129 setSetupInfo(artworkinfo_tokens, i, value);
3131 // check if cache entry for this item is mandatory, but missing
3132 if (value == NULL && !optional)
3134 Warn("missing cache entry '%s'", token);
3140 *artwork_info = ldi;
3145 char *filename_levelinfo = getPath2(getLevelDirFromTreeInfo(level_node),
3146 LEVELINFO_FILENAME);
3147 char *filename_artworkinfo = getPath2(getSetupArtworkDir(artwork_info),
3148 ARTWORKINFO_FILENAME(type));
3150 // check if corresponding "levelinfo.conf" file has changed
3151 token_main = getCacheToken(token_prefix, "TIMESTAMP_LEVELINFO");
3152 cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
3154 if (modifiedFileTimestamp(filename_levelinfo, cache_entry))
3157 // check if corresponding "<artworkinfo>.conf" file has changed
3158 token_main = getCacheToken(token_prefix, "TIMESTAMP_ARTWORKINFO");
3159 cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
3161 if (modifiedFileTimestamp(filename_artworkinfo, cache_entry))
3164 checked_free(filename_levelinfo);
3165 checked_free(filename_artworkinfo);
3168 if (!cached && artwork_info != NULL)
3170 freeTreeInfo(artwork_info);
3175 return artwork_info;
3178 static void setArtworkInfoCacheEntry(TreeInfo *artwork_info,
3179 LevelDirTree *level_node, int type)
3181 char *identifier = level_node->subdir;
3182 char *type_string = ARTWORK_DIRECTORY(type);
3183 char *token_prefix = getCacheTokenPrefix(type_string, identifier);
3184 char *token_main = getCacheToken(token_prefix, "CACHED");
3185 boolean set_cache_timestamps = TRUE;
3188 setHashEntry(artworkinfo_cache_new, token_main, "true");
3190 if (set_cache_timestamps)
3192 char *filename_levelinfo = getPath2(getLevelDirFromTreeInfo(level_node),
3193 LEVELINFO_FILENAME);
3194 char *filename_artworkinfo = getPath2(getSetupArtworkDir(artwork_info),
3195 ARTWORKINFO_FILENAME(type));
3196 char *timestamp_levelinfo = getFileTimestampString(filename_levelinfo);
3197 char *timestamp_artworkinfo = getFileTimestampString(filename_artworkinfo);
3199 token_main = getCacheToken(token_prefix, "TIMESTAMP_LEVELINFO");
3200 setHashEntry(artworkinfo_cache_new, token_main, timestamp_levelinfo);
3202 token_main = getCacheToken(token_prefix, "TIMESTAMP_ARTWORKINFO");
3203 setHashEntry(artworkinfo_cache_new, token_main, timestamp_artworkinfo);
3205 checked_free(filename_levelinfo);
3206 checked_free(filename_artworkinfo);
3207 checked_free(timestamp_levelinfo);
3208 checked_free(timestamp_artworkinfo);
3211 ldi = *artwork_info;
3212 for (i = 0; artworkinfo_tokens[i].type != -1; i++)
3214 char *token = getCacheToken(token_prefix, artworkinfo_tokens[i].text);
3215 char *value = getSetupValue(artworkinfo_tokens[i].type,
3216 artworkinfo_tokens[i].value);
3218 setHashEntry(artworkinfo_cache_new, token, value);
3223 // ----------------------------------------------------------------------------
3224 // functions for loading level info and custom artwork info
3225 // ----------------------------------------------------------------------------
3227 int GetZipFileTreeType(char *zip_filename)
3229 static char *top_dir_path = NULL;
3230 static char *top_dir_conf_filename[NUM_BASE_TREE_TYPES] = { NULL };
3231 static char *conf_basename[NUM_BASE_TREE_TYPES] =
3233 GRAPHICSINFO_FILENAME,
3234 SOUNDSINFO_FILENAME,
3240 checked_free(top_dir_path);
3241 top_dir_path = NULL;
3243 for (j = 0; j < NUM_BASE_TREE_TYPES; j++)
3245 checked_free(top_dir_conf_filename[j]);
3246 top_dir_conf_filename[j] = NULL;
3249 char **zip_entries = zip_list(zip_filename);
3251 // check if zip file successfully opened
3252 if (zip_entries == NULL || zip_entries[0] == NULL)
3253 return TREE_TYPE_UNDEFINED;
3255 // first zip file entry is expected to be top level directory
3256 char *top_dir = zip_entries[0];
3258 // check if valid top level directory found in zip file
3259 if (!strSuffix(top_dir, "/"))
3260 return TREE_TYPE_UNDEFINED;
3262 // get filenames of valid configuration files in top level directory
3263 for (j = 0; j < NUM_BASE_TREE_TYPES; j++)
3264 top_dir_conf_filename[j] = getStringCat2(top_dir, conf_basename[j]);
3266 int tree_type = TREE_TYPE_UNDEFINED;
3269 while (zip_entries[e] != NULL)
3271 // check if every zip file entry is below top level directory
3272 if (!strPrefix(zip_entries[e], top_dir))
3273 return TREE_TYPE_UNDEFINED;
3275 // check if this zip file entry is a valid configuration filename
3276 for (j = 0; j < NUM_BASE_TREE_TYPES; j++)
3278 if (strEqual(zip_entries[e], top_dir_conf_filename[j]))
3280 // only exactly one valid configuration file allowed
3281 if (tree_type != TREE_TYPE_UNDEFINED)
3282 return TREE_TYPE_UNDEFINED;
3294 static boolean CheckZipFileForDirectory(char *zip_filename, char *directory,
3297 static char *top_dir_path = NULL;
3298 static char *top_dir_conf_filename = NULL;
3300 checked_free(top_dir_path);
3301 checked_free(top_dir_conf_filename);
3303 top_dir_path = NULL;
3304 top_dir_conf_filename = NULL;
3306 char *conf_basename = (tree_type == TREE_TYPE_LEVEL_DIR ? LEVELINFO_FILENAME :
3307 ARTWORKINFO_FILENAME(tree_type));
3309 // check if valid configuration filename determined
3310 if (conf_basename == NULL || strEqual(conf_basename, ""))
3313 char **zip_entries = zip_list(zip_filename);
3315 // check if zip file successfully opened
3316 if (zip_entries == NULL || zip_entries[0] == NULL)
3319 // first zip file entry is expected to be top level directory
3320 char *top_dir = zip_entries[0];
3322 // check if valid top level directory found in zip file
3323 if (!strSuffix(top_dir, "/"))
3326 // get path of extracted top level directory
3327 top_dir_path = getPath2(directory, top_dir);
3329 // remove trailing directory separator from top level directory path
3330 // (required to be able to check for file and directory in next step)
3331 top_dir_path[strlen(top_dir_path) - 1] = '\0';
3333 // check if zip file's top level directory already exists in target directory
3334 if (fileExists(top_dir_path)) // (checks for file and directory)
3337 // get filename of configuration file in top level directory
3338 top_dir_conf_filename = getStringCat2(top_dir, conf_basename);
3340 boolean found_top_dir_conf_filename = FALSE;
3343 while (zip_entries[i] != NULL)
3345 // check if every zip file entry is below top level directory
3346 if (!strPrefix(zip_entries[i], top_dir))
3349 // check if this zip file entry is the configuration filename
3350 if (strEqual(zip_entries[i], top_dir_conf_filename))
3351 found_top_dir_conf_filename = TRUE;
3356 // check if valid configuration filename was found in zip file
3357 if (!found_top_dir_conf_filename)
3363 char *ExtractZipFileIntoDirectory(char *zip_filename, char *directory,
3366 boolean zip_file_valid = CheckZipFileForDirectory(zip_filename, directory,
3369 if (!zip_file_valid)
3371 Warn("zip file '%s' rejected!", zip_filename);
3376 char **zip_entries = zip_extract(zip_filename, directory);
3378 if (zip_entries == NULL)
3380 Warn("zip file '%s' could not be extracted!", zip_filename);
3385 Info("zip file '%s' successfully extracted!", zip_filename);
3387 // first zip file entry contains top level directory
3388 char *top_dir = zip_entries[0];
3390 // remove trailing directory separator from top level directory
3391 top_dir[strlen(top_dir) - 1] = '\0';
3396 static void ProcessZipFilesInDirectory(char *directory, int tree_type)
3399 DirectoryEntry *dir_entry;
3401 if ((dir = openDirectory(directory)) == NULL)
3403 // display error if directory is main "options.graphics_directory" etc.
3404 if (tree_type == TREE_TYPE_LEVEL_DIR ||
3405 directory == OPTIONS_ARTWORK_DIRECTORY(tree_type))
3406 Warn("cannot read directory '%s'", directory);
3411 while ((dir_entry = readDirectory(dir)) != NULL) // loop all entries
3413 // skip non-zip files (and also directories with zip extension)
3414 if (!strSuffixLower(dir_entry->basename, ".zip") || dir_entry->is_directory)
3417 char *zip_filename = getPath2(directory, dir_entry->basename);
3418 char *zip_filename_extracted = getStringCat2(zip_filename, ".extracted");
3419 char *zip_filename_rejected = getStringCat2(zip_filename, ".rejected");
3421 // check if zip file hasn't already been extracted or rejected
3422 if (!fileExists(zip_filename_extracted) &&
3423 !fileExists(zip_filename_rejected))
3425 char *top_dir = ExtractZipFileIntoDirectory(zip_filename, directory,
3427 char *marker_filename = (top_dir != NULL ? zip_filename_extracted :
3428 zip_filename_rejected);
3431 // create empty file to mark zip file as extracted or rejected
3432 if ((marker_file = fopen(marker_filename, MODE_WRITE)))
3433 fclose(marker_file);
3436 free(zip_filename_extracted);
3437 free(zip_filename_rejected);
3441 closeDirectory(dir);
3444 // forward declaration for recursive call by "LoadLevelInfoFromLevelDir()"
3445 static void LoadLevelInfoFromLevelDir(TreeInfo **, TreeInfo *, char *);
3447 static boolean LoadLevelInfoFromLevelConf(TreeInfo **node_first,
3448 TreeInfo *node_parent,
3449 char *level_directory,
3450 char *directory_name)
3452 char *directory_path = getPath2(level_directory, directory_name);
3453 char *filename = getPath2(directory_path, LEVELINFO_FILENAME);
3454 SetupFileHash *setup_file_hash;
3455 LevelDirTree *leveldir_new = NULL;
3458 // unless debugging, silently ignore directories without "levelinfo.conf"
3459 if (!options.debug && !fileExists(filename))
3461 free(directory_path);
3467 setup_file_hash = loadSetupFileHash(filename);
3469 if (setup_file_hash == NULL)
3471 #if DEBUG_NO_CONFIG_FILE
3472 Debug("setup", "ignoring level directory '%s'", directory_path);
3475 free(directory_path);
3481 leveldir_new = newTreeInfo();
3484 setTreeInfoToDefaultsFromParent(leveldir_new, node_parent);
3486 setTreeInfoToDefaults(leveldir_new, TREE_TYPE_LEVEL_DIR);
3488 leveldir_new->subdir = getStringCopy(directory_name);
3490 // set all structure fields according to the token/value pairs
3491 ldi = *leveldir_new;
3492 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
3493 setSetupInfo(levelinfo_tokens, i,
3494 getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
3495 *leveldir_new = ldi;
3497 if (strEqual(leveldir_new->name, ANONYMOUS_NAME))
3498 setString(&leveldir_new->name, leveldir_new->subdir);
3500 if (leveldir_new->identifier == NULL)
3501 leveldir_new->identifier = getStringCopy(leveldir_new->subdir);
3503 if (leveldir_new->name_sorting == NULL)
3504 leveldir_new->name_sorting = getStringCopy(leveldir_new->name);
3506 if (node_parent == NULL) // top level group
3508 leveldir_new->basepath = getStringCopy(level_directory);
3509 leveldir_new->fullpath = getStringCopy(leveldir_new->subdir);
3511 else // sub level group
3513 leveldir_new->basepath = getStringCopy(node_parent->basepath);
3514 leveldir_new->fullpath = getPath2(node_parent->fullpath, directory_name);
3517 leveldir_new->last_level =
3518 leveldir_new->first_level + leveldir_new->levels - 1;
3520 leveldir_new->in_user_dir =
3521 (!strEqual(leveldir_new->basepath, options.level_directory));
3523 // adjust some settings if user's private level directory was detected
3524 if (leveldir_new->sort_priority == LEVELCLASS_UNDEFINED &&
3525 leveldir_new->in_user_dir &&
3526 (strEqual(leveldir_new->subdir, getLoginName()) ||
3527 strEqual(leveldir_new->name, getLoginName()) ||
3528 strEqual(leveldir_new->author, getRealName())))
3530 leveldir_new->sort_priority = LEVELCLASS_PRIVATE_START;
3531 leveldir_new->readonly = FALSE;
3534 leveldir_new->user_defined =
3535 (leveldir_new->in_user_dir && IS_LEVELCLASS_PRIVATE(leveldir_new));
3537 setString(&leveldir_new->class_desc, getLevelClassDescription(leveldir_new));
3539 leveldir_new->handicap_level = // set handicap to default value
3540 (leveldir_new->user_defined || !leveldir_new->handicap ?
3541 leveldir_new->last_level : leveldir_new->first_level);
3543 DrawInitText(leveldir_new->name, 150, FC_YELLOW);
3545 pushTreeInfo(node_first, leveldir_new);
3547 freeSetupFileHash(setup_file_hash);
3549 if (leveldir_new->level_group)
3551 // create node to link back to current level directory
3552 createParentTreeInfoNode(leveldir_new);
3554 // recursively step into sub-directory and look for more level series
3555 LoadLevelInfoFromLevelDir(&leveldir_new->node_group,
3556 leveldir_new, directory_path);
3559 free(directory_path);
3565 static void LoadLevelInfoFromLevelDir(TreeInfo **node_first,
3566 TreeInfo *node_parent,
3567 char *level_directory)
3569 // ---------- 1st stage: process any level set zip files ----------
3571 ProcessZipFilesInDirectory(level_directory, TREE_TYPE_LEVEL_DIR);
3573 // ---------- 2nd stage: check for level set directories ----------
3576 DirectoryEntry *dir_entry;
3577 boolean valid_entry_found = FALSE;
3579 if ((dir = openDirectory(level_directory)) == NULL)
3581 Warn("cannot read level directory '%s'", level_directory);
3586 while ((dir_entry = readDirectory(dir)) != NULL) // loop all entries
3588 char *directory_name = dir_entry->basename;
3589 char *directory_path = getPath2(level_directory, directory_name);
3591 // skip entries for current and parent directory
3592 if (strEqual(directory_name, ".") ||
3593 strEqual(directory_name, ".."))
3595 free(directory_path);
3600 // find out if directory entry is itself a directory
3601 if (!dir_entry->is_directory) // not a directory
3603 free(directory_path);
3608 free(directory_path);
3610 if (strEqual(directory_name, GRAPHICS_DIRECTORY) ||
3611 strEqual(directory_name, SOUNDS_DIRECTORY) ||
3612 strEqual(directory_name, MUSIC_DIRECTORY))
3615 valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
3620 closeDirectory(dir);
3622 // special case: top level directory may directly contain "levelinfo.conf"
3623 if (node_parent == NULL && !valid_entry_found)
3625 // check if this directory directly contains a file "levelinfo.conf"
3626 valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
3627 level_directory, ".");
3630 if (!valid_entry_found)
3631 Warn("cannot find any valid level series in directory '%s'",
3635 boolean AdjustGraphicsForEMC(void)
3637 boolean settings_changed = FALSE;
3639 settings_changed |= adjustTreeGraphicsForEMC(leveldir_first_all);
3640 settings_changed |= adjustTreeGraphicsForEMC(leveldir_first);
3642 return settings_changed;
3645 boolean AdjustSoundsForEMC(void)
3647 boolean settings_changed = FALSE;
3649 settings_changed |= adjustTreeSoundsForEMC(leveldir_first_all);
3650 settings_changed |= adjustTreeSoundsForEMC(leveldir_first);
3652 return settings_changed;
3655 void LoadLevelInfo(void)
3657 InitUserLevelDirectory(getLoginName());
3659 DrawInitText("Loading level series", 120, FC_GREEN);
3661 LoadLevelInfoFromLevelDir(&leveldir_first, NULL, options.level_directory);
3662 LoadLevelInfoFromLevelDir(&leveldir_first, NULL, getUserLevelDir(NULL));
3664 leveldir_first = createTopTreeInfoNode(leveldir_first);
3666 /* after loading all level set information, clone the level directory tree
3667 and remove all level sets without levels (these may still contain artwork
3668 to be offered in the setup menu as "custom artwork", and are therefore
3669 checked for existing artwork in the function "LoadLevelArtworkInfo()") */
3670 leveldir_first_all = leveldir_first;
3671 cloneTree(&leveldir_first, leveldir_first_all, TRUE);
3673 AdjustGraphicsForEMC();
3674 AdjustSoundsForEMC();
3676 // before sorting, the first entries will be from the user directory
3677 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
3679 if (leveldir_first == NULL)
3680 Fail("cannot find any valid level series in any directory");
3682 sortTreeInfo(&leveldir_first);
3684 #if ENABLE_UNUSED_CODE
3685 dumpTreeInfo(leveldir_first, 0);
3689 static boolean LoadArtworkInfoFromArtworkConf(TreeInfo **node_first,
3690 TreeInfo *node_parent,
3691 char *base_directory,
3692 char *directory_name, int type)
3694 char *directory_path = getPath2(base_directory, directory_name);
3695 char *filename = getPath2(directory_path, ARTWORKINFO_FILENAME(type));
3696 SetupFileHash *setup_file_hash = NULL;
3697 TreeInfo *artwork_new = NULL;
3700 if (fileExists(filename))
3701 setup_file_hash = loadSetupFileHash(filename);
3703 if (setup_file_hash == NULL) // no config file -- look for artwork files
3706 DirectoryEntry *dir_entry;
3707 boolean valid_file_found = FALSE;
3709 if ((dir = openDirectory(directory_path)) != NULL)
3711 while ((dir_entry = readDirectory(dir)) != NULL)
3713 if (FileIsArtworkType(dir_entry->filename, type))
3715 valid_file_found = TRUE;
3721 closeDirectory(dir);
3724 if (!valid_file_found)
3726 #if DEBUG_NO_CONFIG_FILE
3727 if (!strEqual(directory_name, "."))
3728 Debug("setup", "ignoring artwork directory '%s'", directory_path);
3731 free(directory_path);
3738 artwork_new = newTreeInfo();
3741 setTreeInfoToDefaultsFromParent(artwork_new, node_parent);
3743 setTreeInfoToDefaults(artwork_new, type);
3745 artwork_new->subdir = getStringCopy(directory_name);
3747 if (setup_file_hash) // (before defining ".color" and ".class_desc")
3749 // set all structure fields according to the token/value pairs
3751 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
3752 setSetupInfo(levelinfo_tokens, i,
3753 getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
3756 if (strEqual(artwork_new->name, ANONYMOUS_NAME))
3757 setString(&artwork_new->name, artwork_new->subdir);
3759 if (artwork_new->identifier == NULL)
3760 artwork_new->identifier = getStringCopy(artwork_new->subdir);
3762 if (artwork_new->name_sorting == NULL)
3763 artwork_new->name_sorting = getStringCopy(artwork_new->name);
3766 if (node_parent == NULL) // top level group
3768 artwork_new->basepath = getStringCopy(base_directory);
3769 artwork_new->fullpath = getStringCopy(artwork_new->subdir);
3771 else // sub level group
3773 artwork_new->basepath = getStringCopy(node_parent->basepath);
3774 artwork_new->fullpath = getPath2(node_parent->fullpath, directory_name);
3777 artwork_new->in_user_dir =
3778 (!strEqual(artwork_new->basepath, OPTIONS_ARTWORK_DIRECTORY(type)));
3780 setString(&artwork_new->class_desc, getLevelClassDescription(artwork_new));
3782 if (setup_file_hash == NULL) // (after determining ".user_defined")
3784 if (strEqual(artwork_new->subdir, "."))
3786 if (artwork_new->user_defined)
3788 setString(&artwork_new->identifier, "private");
3789 artwork_new->sort_priority = ARTWORKCLASS_PRIVATE;
3793 setString(&artwork_new->identifier, "classic");
3794 artwork_new->sort_priority = ARTWORKCLASS_CLASSICS;
3797 setString(&artwork_new->class_desc,
3798 getLevelClassDescription(artwork_new));
3802 setString(&artwork_new->identifier, artwork_new->subdir);
3805 setString(&artwork_new->name, artwork_new->identifier);
3806 setString(&artwork_new->name_sorting, artwork_new->name);
3809 pushTreeInfo(node_first, artwork_new);
3811 freeSetupFileHash(setup_file_hash);
3813 free(directory_path);
3819 static void LoadArtworkInfoFromArtworkDir(TreeInfo **node_first,
3820 TreeInfo *node_parent,
3821 char *base_directory, int type)
3823 // ---------- 1st stage: process any artwork set zip files ----------
3825 ProcessZipFilesInDirectory(base_directory, type);
3827 // ---------- 2nd stage: check for artwork set directories ----------
3830 DirectoryEntry *dir_entry;
3831 boolean valid_entry_found = FALSE;
3833 if ((dir = openDirectory(base_directory)) == NULL)
3835 // display error if directory is main "options.graphics_directory" etc.
3836 if (base_directory == OPTIONS_ARTWORK_DIRECTORY(type))
3837 Warn("cannot read directory '%s'", base_directory);
3842 while ((dir_entry = readDirectory(dir)) != NULL) // loop all entries
3844 char *directory_name = dir_entry->basename;
3845 char *directory_path = getPath2(base_directory, directory_name);
3847 // skip directory entries for current and parent directory
3848 if (strEqual(directory_name, ".") ||
3849 strEqual(directory_name, ".."))
3851 free(directory_path);
3856 // skip directory entries which are not a directory
3857 if (!dir_entry->is_directory) // not a directory
3859 free(directory_path);
3864 free(directory_path);
3866 // check if this directory contains artwork with or without config file
3867 valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
3869 directory_name, type);
3872 closeDirectory(dir);
3874 // check if this directory directly contains artwork itself
3875 valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
3876 base_directory, ".",
3878 if (!valid_entry_found)
3879 Warn("cannot find any valid artwork in directory '%s'", base_directory);
3882 static TreeInfo *getDummyArtworkInfo(int type)
3884 // this is only needed when there is completely no artwork available
3885 TreeInfo *artwork_new = newTreeInfo();
3887 setTreeInfoToDefaults(artwork_new, type);
3889 setString(&artwork_new->subdir, UNDEFINED_FILENAME);
3890 setString(&artwork_new->fullpath, UNDEFINED_FILENAME);
3891 setString(&artwork_new->basepath, UNDEFINED_FILENAME);
3893 setString(&artwork_new->identifier, UNDEFINED_FILENAME);
3894 setString(&artwork_new->name, UNDEFINED_FILENAME);
3895 setString(&artwork_new->name_sorting, UNDEFINED_FILENAME);
3900 void SetCurrentArtwork(int type)
3902 ArtworkDirTree **current_ptr = ARTWORK_CURRENT_PTR(artwork, type);
3903 ArtworkDirTree *first_node = ARTWORK_FIRST_NODE(artwork, type);
3904 char *setup_set = SETUP_ARTWORK_SET(setup, type);
3905 char *default_subdir = ARTWORK_DEFAULT_SUBDIR(type);
3907 // set current artwork to artwork configured in setup menu
3908 *current_ptr = getTreeInfoFromIdentifier(first_node, setup_set);
3910 // if not found, set current artwork to default artwork
3911 if (*current_ptr == NULL)
3912 *current_ptr = getTreeInfoFromIdentifier(first_node, default_subdir);
3914 // if not found, set current artwork to first artwork in tree
3915 if (*current_ptr == NULL)
3916 *current_ptr = getFirstValidTreeInfoEntry(first_node);
3919 void ChangeCurrentArtworkIfNeeded(int type)
3921 char *current_identifier = ARTWORK_CURRENT_IDENTIFIER(artwork, type);
3922 char *setup_set = SETUP_ARTWORK_SET(setup, type);
3924 if (!strEqual(current_identifier, setup_set))
3925 SetCurrentArtwork(type);
3928 void LoadArtworkInfo(void)
3930 LoadArtworkInfoCache();
3932 DrawInitText("Looking for custom artwork", 120, FC_GREEN);
3934 LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
3935 options.graphics_directory,
3936 TREE_TYPE_GRAPHICS_DIR);
3937 LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
3938 getUserGraphicsDir(),
3939 TREE_TYPE_GRAPHICS_DIR);
3941 LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
3942 options.sounds_directory,
3943 TREE_TYPE_SOUNDS_DIR);
3944 LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
3946 TREE_TYPE_SOUNDS_DIR);
3948 LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
3949 options.music_directory,
3950 TREE_TYPE_MUSIC_DIR);
3951 LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
3953 TREE_TYPE_MUSIC_DIR);
3955 if (artwork.gfx_first == NULL)
3956 artwork.gfx_first = getDummyArtworkInfo(TREE_TYPE_GRAPHICS_DIR);
3957 if (artwork.snd_first == NULL)
3958 artwork.snd_first = getDummyArtworkInfo(TREE_TYPE_SOUNDS_DIR);
3959 if (artwork.mus_first == NULL)
3960 artwork.mus_first = getDummyArtworkInfo(TREE_TYPE_MUSIC_DIR);
3962 // before sorting, the first entries will be from the user directory
3963 SetCurrentArtwork(ARTWORK_TYPE_GRAPHICS);
3964 SetCurrentArtwork(ARTWORK_TYPE_SOUNDS);
3965 SetCurrentArtwork(ARTWORK_TYPE_MUSIC);
3967 artwork.gfx_current_identifier = artwork.gfx_current->identifier;
3968 artwork.snd_current_identifier = artwork.snd_current->identifier;
3969 artwork.mus_current_identifier = artwork.mus_current->identifier;
3971 #if ENABLE_UNUSED_CODE
3972 Debug("setup:LoadArtworkInfo", "graphics set == %s",
3973 artwork.gfx_current_identifier);
3974 Debug("setup:LoadArtworkInfo", "sounds set == %s",
3975 artwork.snd_current_identifier);
3976 Debug("setup:LoadArtworkInfo", "music set == %s",
3977 artwork.mus_current_identifier);
3980 sortTreeInfo(&artwork.gfx_first);
3981 sortTreeInfo(&artwork.snd_first);
3982 sortTreeInfo(&artwork.mus_first);
3984 #if ENABLE_UNUSED_CODE
3985 dumpTreeInfo(artwork.gfx_first, 0);
3986 dumpTreeInfo(artwork.snd_first, 0);
3987 dumpTreeInfo(artwork.mus_first, 0);
3991 static void MoveArtworkInfoIntoSubTree(ArtworkDirTree **artwork_node)
3993 ArtworkDirTree *artwork_new = newTreeInfo();
3994 char *top_node_name = "standalone artwork";
3996 setTreeInfoToDefaults(artwork_new, (*artwork_node)->type);
3998 artwork_new->level_group = TRUE;
4000 setString(&artwork_new->identifier, top_node_name);
4001 setString(&artwork_new->name, top_node_name);
4002 setString(&artwork_new->name_sorting, top_node_name);
4004 // create node to link back to current custom artwork directory
4005 createParentTreeInfoNode(artwork_new);
4007 // move existing custom artwork tree into newly created sub-tree
4008 artwork_new->node_group->next = *artwork_node;
4010 // change custom artwork tree to contain only newly created node
4011 *artwork_node = artwork_new;
4014 static void LoadArtworkInfoFromLevelInfoExt(ArtworkDirTree **artwork_node,
4015 ArtworkDirTree *node_parent,
4016 LevelDirTree *level_node,
4017 boolean empty_level_set_mode)
4019 int type = (*artwork_node)->type;
4021 // recursively check all level directories for artwork sub-directories
4025 boolean empty_level_set = (level_node->levels == 0);
4027 // check all tree entries for artwork, but skip parent link entries
4028 if (!level_node->parent_link && empty_level_set == empty_level_set_mode)
4030 TreeInfo *artwork_new = getArtworkInfoCacheEntry(level_node, type);
4031 boolean cached = (artwork_new != NULL);
4035 pushTreeInfo(artwork_node, artwork_new);
4039 TreeInfo *topnode_last = *artwork_node;
4040 char *path = getPath2(getLevelDirFromTreeInfo(level_node),
4041 ARTWORK_DIRECTORY(type));
4043 LoadArtworkInfoFromArtworkDir(artwork_node, NULL, path, type);
4045 if (topnode_last != *artwork_node) // check for newly added node
4047 artwork_new = *artwork_node;
4049 setString(&artwork_new->identifier, level_node->subdir);
4050 setString(&artwork_new->name, level_node->name);
4051 setString(&artwork_new->name_sorting, level_node->name_sorting);
4053 artwork_new->sort_priority = level_node->sort_priority;
4054 artwork_new->in_user_dir = level_node->in_user_dir;
4056 update_artworkinfo_cache = TRUE;
4062 // insert artwork info (from old cache or filesystem) into new cache
4063 if (artwork_new != NULL)
4064 setArtworkInfoCacheEntry(artwork_new, level_node, type);
4067 DrawInitText(level_node->name, 150, FC_YELLOW);
4069 if (level_node->node_group != NULL)
4071 TreeInfo *artwork_new = newTreeInfo();
4074 setTreeInfoToDefaultsFromParent(artwork_new, node_parent);
4076 setTreeInfoToDefaults(artwork_new, type);
4078 artwork_new->level_group = TRUE;
4080 setString(&artwork_new->identifier, level_node->subdir);
4082 if (node_parent == NULL) // check for top tree node
4084 char *top_node_name = (empty_level_set_mode ?
4085 "artwork for certain level sets" :
4086 "artwork included in level sets");
4088 setString(&artwork_new->name, top_node_name);
4089 setString(&artwork_new->name_sorting, top_node_name);
4093 setString(&artwork_new->name, level_node->name);
4094 setString(&artwork_new->name_sorting, level_node->name_sorting);
4097 pushTreeInfo(artwork_node, artwork_new);
4099 // create node to link back to current custom artwork directory
4100 createParentTreeInfoNode(artwork_new);
4102 // recursively step into sub-directory and look for more custom artwork
4103 LoadArtworkInfoFromLevelInfoExt(&artwork_new->node_group, artwork_new,
4104 level_node->node_group,
4105 empty_level_set_mode);
4107 // if sub-tree has no custom artwork at all, remove it
4108 if (artwork_new->node_group->next == NULL)
4109 removeTreeInfo(artwork_node);
4112 level_node = level_node->next;
4116 static void LoadArtworkInfoFromLevelInfo(ArtworkDirTree **artwork_node)
4118 // move peviously loaded artwork tree into separate sub-tree
4119 MoveArtworkInfoIntoSubTree(artwork_node);
4121 // load artwork from level sets into separate sub-trees
4122 LoadArtworkInfoFromLevelInfoExt(artwork_node, NULL, leveldir_first_all, TRUE);
4123 LoadArtworkInfoFromLevelInfoExt(artwork_node, NULL, leveldir_first_all, FALSE);
4125 // add top tree node over all three separate sub-trees
4126 *artwork_node = createTopTreeInfoNode(*artwork_node);
4128 // set all parent links (back links) in complete artwork tree
4129 setTreeInfoParentNodes(*artwork_node, NULL);
4132 void LoadLevelArtworkInfo(void)
4134 print_timestamp_init("LoadLevelArtworkInfo");
4136 DrawInitText("Looking for custom level artwork", 120, FC_GREEN);
4138 print_timestamp_time("DrawTimeText");
4140 LoadArtworkInfoFromLevelInfo(&artwork.gfx_first);
4141 print_timestamp_time("LoadArtworkInfoFromLevelInfo (gfx)");
4142 LoadArtworkInfoFromLevelInfo(&artwork.snd_first);
4143 print_timestamp_time("LoadArtworkInfoFromLevelInfo (snd)");
4144 LoadArtworkInfoFromLevelInfo(&artwork.mus_first);
4145 print_timestamp_time("LoadArtworkInfoFromLevelInfo (mus)");
4147 SaveArtworkInfoCache();
4149 print_timestamp_time("SaveArtworkInfoCache");
4151 // needed for reloading level artwork not known at ealier stage
4152 ChangeCurrentArtworkIfNeeded(ARTWORK_TYPE_GRAPHICS);
4153 ChangeCurrentArtworkIfNeeded(ARTWORK_TYPE_SOUNDS);
4154 ChangeCurrentArtworkIfNeeded(ARTWORK_TYPE_MUSIC);
4156 print_timestamp_time("getTreeInfoFromIdentifier");
4158 sortTreeInfo(&artwork.gfx_first);
4159 sortTreeInfo(&artwork.snd_first);
4160 sortTreeInfo(&artwork.mus_first);
4162 print_timestamp_time("sortTreeInfo");
4164 #if ENABLE_UNUSED_CODE
4165 dumpTreeInfo(artwork.gfx_first, 0);
4166 dumpTreeInfo(artwork.snd_first, 0);
4167 dumpTreeInfo(artwork.mus_first, 0);
4170 print_timestamp_done("LoadLevelArtworkInfo");
4173 static boolean AddTreeSetToTreeInfoExt(TreeInfo *tree_node_old, char *tree_dir,
4174 char *tree_subdir_new, int type)
4176 if (tree_node_old == NULL)
4178 if (type == TREE_TYPE_LEVEL_DIR)
4180 // get level info tree node of personal user level set
4181 tree_node_old = getTreeInfoFromIdentifier(leveldir_first, getLoginName());
4183 // this may happen if "setup.internal.create_user_levelset" is FALSE
4184 // or if file "levelinfo.conf" is missing in personal user level set
4185 if (tree_node_old == NULL)
4186 tree_node_old = leveldir_first->node_group;
4190 // get artwork info tree node of first artwork set
4191 tree_node_old = ARTWORK_FIRST_NODE(artwork, type);
4195 if (tree_dir == NULL)
4196 tree_dir = TREE_USERDIR(type);
4198 if (tree_node_old == NULL ||
4200 tree_subdir_new == NULL) // should not happen
4203 int draw_deactivation_mask = GetDrawDeactivationMask();
4205 // override draw deactivation mask (temporarily disable drawing)
4206 SetDrawDeactivationMask(REDRAW_ALL);
4208 if (type == TREE_TYPE_LEVEL_DIR)
4210 // load new level set config and add it next to first user level set
4211 LoadLevelInfoFromLevelConf(&tree_node_old->next,
4212 tree_node_old->node_parent,
4213 tree_dir, tree_subdir_new);
4217 // load new artwork set config and add it next to first artwork set
4218 LoadArtworkInfoFromArtworkConf(&tree_node_old->next,
4219 tree_node_old->node_parent,
4220 tree_dir, tree_subdir_new, type);
4223 // set draw deactivation mask to previous value
4224 SetDrawDeactivationMask(draw_deactivation_mask);
4226 // get first node of level or artwork info tree
4227 TreeInfo **tree_node_first = TREE_FIRST_NODE_PTR(type);
4229 // get tree info node of newly added level or artwork set
4230 TreeInfo *tree_node_new = getTreeInfoFromIdentifier(*tree_node_first,
4233 if (tree_node_new == NULL) // should not happen
4236 // correct top link and parent node link of newly created tree node
4237 tree_node_new->node_top = tree_node_old->node_top;
4238 tree_node_new->node_parent = tree_node_old->node_parent;
4240 // sort tree info to adjust position of newly added tree set
4241 sortTreeInfo(tree_node_first);
4246 void AddTreeSetToTreeInfo(TreeInfo *tree_node, char *tree_dir,
4247 char *tree_subdir_new, int type)
4249 if (!AddTreeSetToTreeInfoExt(tree_node, tree_dir, tree_subdir_new, type))
4250 Fail("internal tree info structure corrupted -- aborting");
4253 void AddUserLevelSetToLevelInfo(char *level_subdir_new)
4255 AddTreeSetToTreeInfo(NULL, NULL, level_subdir_new, TREE_TYPE_LEVEL_DIR);
4258 char *getArtworkIdentifierForUserLevelSet(int type)
4260 char *classic_artwork_set = getClassicArtworkSet(type);
4262 // check for custom artwork configured in "levelinfo.conf"
4263 char *leveldir_artwork_set =
4264 *LEVELDIR_ARTWORK_SET_PTR(leveldir_current, type);
4265 boolean has_leveldir_artwork_set =
4266 (leveldir_artwork_set != NULL && !strEqual(leveldir_artwork_set,
4267 classic_artwork_set));
4269 // check for custom artwork in sub-directory "graphics" etc.
4270 TreeInfo *artwork_first_node = ARTWORK_FIRST_NODE(artwork, type);
4271 char *leveldir_identifier = leveldir_current->identifier;
4272 boolean has_artwork_subdir =
4273 (getTreeInfoFromIdentifier(artwork_first_node,
4274 leveldir_identifier) != NULL);
4276 return (has_leveldir_artwork_set ? leveldir_artwork_set :
4277 has_artwork_subdir ? leveldir_identifier :
4278 classic_artwork_set);
4281 TreeInfo *getArtworkTreeInfoForUserLevelSet(int type)
4283 char *artwork_set = getArtworkIdentifierForUserLevelSet(type);
4284 TreeInfo *artwork_first_node = ARTWORK_FIRST_NODE(artwork, type);
4285 TreeInfo *ti = getTreeInfoFromIdentifier(artwork_first_node, artwork_set);
4289 ti = getTreeInfoFromIdentifier(artwork_first_node,
4290 ARTWORK_DEFAULT_SUBDIR(type));
4292 Fail("cannot find default graphics -- should not happen");
4298 boolean checkIfCustomArtworkExistsForCurrentLevelSet(void)
4300 char *graphics_set =
4301 getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_GRAPHICS);
4303 getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_SOUNDS);
4305 getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_MUSIC);
4307 return (!strEqual(graphics_set, GFX_CLASSIC_SUBDIR) ||
4308 !strEqual(sounds_set, SND_CLASSIC_SUBDIR) ||
4309 !strEqual(music_set, MUS_CLASSIC_SUBDIR));
4312 boolean UpdateUserLevelSet(char *level_subdir, char *level_name,
4313 char *level_author, int num_levels)
4315 char *filename = getPath2(getUserLevelDir(level_subdir), LEVELINFO_FILENAME);
4316 char *filename_tmp = getStringCat2(filename, ".tmp");
4318 FILE *file_tmp = NULL;
4319 char line[MAX_LINE_LEN];
4320 boolean success = FALSE;
4321 LevelDirTree *leveldir = getTreeInfoFromIdentifier(leveldir_first,
4323 // update values in level directory tree
4325 if (level_name != NULL)
4326 setString(&leveldir->name, level_name);
4328 if (level_author != NULL)
4329 setString(&leveldir->author, level_author);
4331 if (num_levels != -1)
4332 leveldir->levels = num_levels;
4334 // update values that depend on other values
4336 setString(&leveldir->name_sorting, leveldir->name);
4338 leveldir->last_level = leveldir->first_level + leveldir->levels - 1;
4340 // sort order of level sets may have changed
4341 sortTreeInfo(&leveldir_first);
4343 if ((file = fopen(filename, MODE_READ)) &&
4344 (file_tmp = fopen(filename_tmp, MODE_WRITE)))
4346 while (fgets(line, MAX_LINE_LEN, file))
4348 if (strPrefix(line, "name:") && level_name != NULL)
4349 fprintf(file_tmp, "%-32s%s\n", "name:", level_name);
4350 else if (strPrefix(line, "author:") && level_author != NULL)
4351 fprintf(file_tmp, "%-32s%s\n", "author:", level_author);
4352 else if (strPrefix(line, "levels:") && num_levels != -1)
4353 fprintf(file_tmp, "%-32s%d\n", "levels:", num_levels);
4355 fputs(line, file_tmp);
4368 success = (rename(filename_tmp, filename) == 0);
4376 boolean CreateUserLevelSet(char *level_subdir, char *level_name,
4377 char *level_author, int num_levels,
4378 boolean use_artwork_set)
4380 LevelDirTree *level_info;
4385 // create user level sub-directory, if needed
4386 createDirectory(getUserLevelDir(level_subdir), "user level", PERMS_PRIVATE);
4388 filename = getPath2(getUserLevelDir(level_subdir), LEVELINFO_FILENAME);
4390 if (!(file = fopen(filename, MODE_WRITE)))
4392 Warn("cannot write level info file '%s'", filename);
4399 level_info = newTreeInfo();
4401 // always start with reliable default values
4402 setTreeInfoToDefaults(level_info, TREE_TYPE_LEVEL_DIR);
4404 setString(&level_info->name, level_name);
4405 setString(&level_info->author, level_author);
4406 level_info->levels = num_levels;
4407 level_info->first_level = 1;
4408 level_info->sort_priority = LEVELCLASS_PRIVATE_START;
4409 level_info->readonly = FALSE;
4411 if (use_artwork_set)
4413 level_info->graphics_set =
4414 getStringCopy(getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_GRAPHICS));
4415 level_info->sounds_set =
4416 getStringCopy(getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_SOUNDS));
4417 level_info->music_set =
4418 getStringCopy(getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_MUSIC));
4421 token_value_position = TOKEN_VALUE_POSITION_SHORT;
4423 fprintFileHeader(file, LEVELINFO_FILENAME);
4426 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
4428 if (i == LEVELINFO_TOKEN_NAME ||
4429 i == LEVELINFO_TOKEN_AUTHOR ||
4430 i == LEVELINFO_TOKEN_LEVELS ||
4431 i == LEVELINFO_TOKEN_FIRST_LEVEL ||
4432 i == LEVELINFO_TOKEN_SORT_PRIORITY ||
4433 i == LEVELINFO_TOKEN_READONLY ||
4434 (use_artwork_set && (i == LEVELINFO_TOKEN_GRAPHICS_SET ||
4435 i == LEVELINFO_TOKEN_SOUNDS_SET ||
4436 i == LEVELINFO_TOKEN_MUSIC_SET)))
4437 fprintf(file, "%s\n", getSetupLine(levelinfo_tokens, "", i));
4439 // just to make things nicer :)
4440 if (i == LEVELINFO_TOKEN_AUTHOR ||
4441 i == LEVELINFO_TOKEN_FIRST_LEVEL ||
4442 (use_artwork_set && i == LEVELINFO_TOKEN_READONLY))
4443 fprintf(file, "\n");
4446 token_value_position = TOKEN_VALUE_POSITION_DEFAULT;
4450 SetFilePermissions(filename, PERMS_PRIVATE);
4452 freeTreeInfo(level_info);
4458 static void SaveUserLevelInfo(void)
4460 CreateUserLevelSet(getLoginName(), getLoginName(), getRealName(), 100, FALSE);
4463 char *getSetupValue(int type, void *value)
4465 static char value_string[MAX_LINE_LEN];
4473 strcpy(value_string, (*(boolean *)value ? "true" : "false"));
4477 strcpy(value_string, (*(boolean *)value ? "on" : "off"));
4481 strcpy(value_string, (*(int *)value == AUTO ? "auto" :
4482 *(int *)value == FALSE ? "off" : "on"));
4486 strcpy(value_string, (*(boolean *)value ? "yes" : "no"));
4489 case TYPE_YES_NO_AUTO:
4490 strcpy(value_string, (*(int *)value == AUTO ? "auto" :
4491 *(int *)value == FALSE ? "no" : "yes"));
4495 strcpy(value_string, (*(boolean *)value ? "AGA" : "ECS"));
4499 strcpy(value_string, getKeyNameFromKey(*(Key *)value));
4503 strcpy(value_string, getX11KeyNameFromKey(*(Key *)value));
4507 sprintf(value_string, "%d", *(int *)value);
4511 if (*(char **)value == NULL)
4514 strcpy(value_string, *(char **)value);
4518 sprintf(value_string, "player_%d", *(int *)value + 1);
4522 value_string[0] = '\0';
4526 if (type & TYPE_GHOSTED)
4527 strcpy(value_string, "n/a");
4529 return value_string;
4532 char *getSetupLine(struct TokenInfo *token_info, char *prefix, int token_nr)
4536 static char token_string[MAX_LINE_LEN];
4537 int token_type = token_info[token_nr].type;
4538 void *setup_value = token_info[token_nr].value;
4539 char *token_text = token_info[token_nr].text;
4540 char *value_string = getSetupValue(token_type, setup_value);
4542 // build complete token string
4543 sprintf(token_string, "%s%s", prefix, token_text);
4545 // build setup entry line
4546 line = getFormattedSetupEntry(token_string, value_string);
4548 if (token_type == TYPE_KEY_X11)
4550 Key key = *(Key *)setup_value;
4551 char *keyname = getKeyNameFromKey(key);
4553 // add comment, if useful
4554 if (!strEqual(keyname, "(undefined)") &&
4555 !strEqual(keyname, "(unknown)"))
4557 // add at least one whitespace
4559 for (i = strlen(line); i < token_comment_position; i++)
4563 strcat(line, keyname);
4570 static void InitLastPlayedLevels_ParentNode(void)
4572 LevelDirTree **leveldir_top = &leveldir_first->node_group->next;
4573 LevelDirTree *leveldir_new = NULL;
4575 // check if parent node for last played levels already exists
4576 if (strEqual((*leveldir_top)->identifier, TOKEN_STR_LAST_LEVEL_SERIES))
4579 leveldir_new = newTreeInfo();
4581 setTreeInfoToDefaultsFromParent(leveldir_new, leveldir_first);
4583 leveldir_new->level_group = TRUE;
4584 leveldir_new->sort_priority = LEVELCLASS_LAST_PLAYED_LEVEL;
4586 setString(&leveldir_new->identifier, TOKEN_STR_LAST_LEVEL_SERIES);
4587 setString(&leveldir_new->name, "<< (last played level sets)");
4588 setString(&leveldir_new->name_sorting, leveldir_new->name);
4590 pushTreeInfo(leveldir_top, leveldir_new);
4592 // create node to link back to current level directory
4593 createParentTreeInfoNode(leveldir_new);
4596 void UpdateLastPlayedLevels_TreeInfo(void)
4598 char **last_level_series = setup.level_setup.last_level_series;
4599 LevelDirTree *leveldir_last;
4600 TreeInfo **node_new = NULL;
4603 if (last_level_series[0] == NULL)
4606 InitLastPlayedLevels_ParentNode();
4608 leveldir_last = getTreeInfoFromIdentifierExt(leveldir_first,
4609 TOKEN_STR_LAST_LEVEL_SERIES,
4610 TREE_NODE_TYPE_GROUP);
4611 if (leveldir_last == NULL)
4614 node_new = &leveldir_last->node_group->next;
4616 freeTreeInfo(*node_new);
4620 for (i = 0; last_level_series[i] != NULL; i++)
4622 LevelDirTree *node_last = getTreeInfoFromIdentifier(leveldir_first,
4623 last_level_series[i]);
4624 if (node_last == NULL)
4627 *node_new = getTreeInfoCopy(node_last); // copy complete node
4629 (*node_new)->node_top = &leveldir_first; // correct top node link
4630 (*node_new)->node_parent = leveldir_last; // correct parent node link
4632 (*node_new)->is_copy = TRUE; // mark entry as node copy
4634 (*node_new)->node_group = NULL;
4635 (*node_new)->next = NULL;
4637 (*node_new)->cl_first = -1; // force setting tree cursor
4639 node_new = &((*node_new)->next);
4643 static void UpdateLastPlayedLevels_List(void)
4645 char **last_level_series = setup.level_setup.last_level_series;
4646 int pos = MAX_LEVELDIR_HISTORY - 1;
4649 // search for potentially already existing entry in list of level sets
4650 for (i = 0; last_level_series[i] != NULL; i++)
4651 if (strEqual(last_level_series[i], leveldir_current->identifier))
4654 // move list of level sets one entry down (using potentially free entry)
4655 for (i = pos; i > 0; i--)
4656 setString(&last_level_series[i], last_level_series[i - 1]);
4658 // put last played level set at top position
4659 setString(&last_level_series[0], leveldir_current->identifier);
4662 static TreeInfo *StoreOrRestoreLastPlayedLevels(TreeInfo *node, boolean store)
4664 static char *identifier = NULL;
4668 setString(&identifier, (node && node->is_copy ? node->identifier : NULL));
4670 return NULL; // not used
4674 TreeInfo *node_new = getTreeInfoFromIdentifierExt(leveldir_first,
4676 TREE_NODE_TYPE_COPY);
4677 return (node_new != NULL ? node_new : node);
4681 void StoreLastPlayedLevels(TreeInfo *node)
4683 StoreOrRestoreLastPlayedLevels(node, TRUE);
4686 void RestoreLastPlayedLevels(TreeInfo **node)
4688 *node = StoreOrRestoreLastPlayedLevels(*node, FALSE);
4691 void LoadLevelSetup_LastSeries(void)
4693 // --------------------------------------------------------------------------
4694 // ~/.<program>/levelsetup.conf
4695 // --------------------------------------------------------------------------
4697 char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
4698 SetupFileHash *level_setup_hash = NULL;
4702 // always start with reliable default values
4703 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
4705 // start with empty history of last played level sets
4706 setString(&setup.level_setup.last_level_series[0], NULL);
4708 if (!strEqual(DEFAULT_LEVELSET, UNDEFINED_LEVELSET))
4710 leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
4712 if (leveldir_current == NULL)
4713 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
4716 if ((level_setup_hash = loadSetupFileHash(filename)))
4718 char *last_level_series =
4719 getHashEntry(level_setup_hash, TOKEN_STR_LAST_LEVEL_SERIES);
4721 leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
4723 if (leveldir_current == NULL)
4724 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
4726 for (i = 0; i < MAX_LEVELDIR_HISTORY; i++)
4728 char token[strlen(TOKEN_STR_LAST_LEVEL_SERIES) + 10];
4729 LevelDirTree *leveldir_last;
4731 sprintf(token, "%s.%03d", TOKEN_STR_LAST_LEVEL_SERIES, i);
4733 last_level_series = getHashEntry(level_setup_hash, token);
4735 leveldir_last = getTreeInfoFromIdentifier(leveldir_first,
4737 if (leveldir_last != NULL)
4738 setString(&setup.level_setup.last_level_series[pos++],
4742 setString(&setup.level_setup.last_level_series[pos], NULL);
4744 freeSetupFileHash(level_setup_hash);
4748 Debug("setup", "using default setup values");
4754 static void SaveLevelSetup_LastSeries_Ext(boolean deactivate_last_level_series)
4756 // --------------------------------------------------------------------------
4757 // ~/.<program>/levelsetup.conf
4758 // --------------------------------------------------------------------------
4760 // check if the current level directory structure is available at this point
4761 if (leveldir_current == NULL)
4764 char **last_level_series = setup.level_setup.last_level_series;
4765 char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
4769 InitUserDataDirectory();
4771 UpdateLastPlayedLevels_List();
4773 if (!(file = fopen(filename, MODE_WRITE)))
4775 Warn("cannot write setup file '%s'", filename);
4782 fprintFileHeader(file, LEVELSETUP_FILENAME);
4784 if (deactivate_last_level_series)
4785 fprintf(file, "# %s\n# ", "the following level set may have caused a problem and was deactivated");
4787 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_LAST_LEVEL_SERIES,
4788 leveldir_current->identifier));
4790 for (i = 0; last_level_series[i] != NULL; i++)
4792 char token[strlen(TOKEN_STR_LAST_LEVEL_SERIES) + 10];
4794 sprintf(token, "%s.%03d", TOKEN_STR_LAST_LEVEL_SERIES, i);
4796 fprintf(file, "%s\n", getFormattedSetupEntry(token, last_level_series[i]));
4801 SetFilePermissions(filename, PERMS_PRIVATE);
4806 void SaveLevelSetup_LastSeries(void)
4808 SaveLevelSetup_LastSeries_Ext(FALSE);
4811 void SaveLevelSetup_LastSeries_Deactivate(void)
4813 SaveLevelSetup_LastSeries_Ext(TRUE);
4816 static void checkSeriesInfo(void)
4818 static char *level_directory = NULL;
4821 DirectoryEntry *dir_entry;
4824 checked_free(level_directory);
4826 // check for more levels besides the 'levels' field of 'levelinfo.conf'
4828 level_directory = getPath2((leveldir_current->in_user_dir ?
4829 getUserLevelDir(NULL) :
4830 options.level_directory),
4831 leveldir_current->fullpath);
4833 if ((dir = openDirectory(level_directory)) == NULL)
4835 Warn("cannot read level directory '%s'", level_directory);
4841 while ((dir_entry = readDirectory(dir)) != NULL) // loop all entries
4843 if (strlen(dir_entry->basename) > 4 &&
4844 dir_entry->basename[3] == '.' &&
4845 strEqual(&dir_entry->basename[4], LEVELFILE_EXTENSION))
4847 char levelnum_str[4];
4850 strncpy(levelnum_str, dir_entry->basename, 3);
4851 levelnum_str[3] = '\0';
4853 levelnum_value = atoi(levelnum_str);
4855 if (levelnum_value < leveldir_current->first_level)
4857 Warn("additional level %d found", levelnum_value);
4859 leveldir_current->first_level = levelnum_value;
4861 else if (levelnum_value > leveldir_current->last_level)
4863 Warn("additional level %d found", levelnum_value);
4865 leveldir_current->last_level = levelnum_value;
4871 closeDirectory(dir);
4874 void LoadLevelSetup_SeriesInfo(void)
4877 SetupFileHash *level_setup_hash = NULL;
4878 char *level_subdir = leveldir_current->subdir;
4881 // always start with reliable default values
4882 level_nr = leveldir_current->first_level;
4884 for (i = 0; i < MAX_LEVELS; i++)
4886 LevelStats_setPlayed(i, 0);
4887 LevelStats_setSolved(i, 0);
4892 // --------------------------------------------------------------------------
4893 // ~/.<program>/levelsetup/<level series>/levelsetup.conf
4894 // --------------------------------------------------------------------------
4896 level_subdir = leveldir_current->subdir;
4898 filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
4900 if ((level_setup_hash = loadSetupFileHash(filename)))
4904 // get last played level in this level set
4906 token_value = getHashEntry(level_setup_hash, TOKEN_STR_LAST_PLAYED_LEVEL);
4910 level_nr = atoi(token_value);
4912 if (level_nr < leveldir_current->first_level)
4913 level_nr = leveldir_current->first_level;
4914 if (level_nr > leveldir_current->last_level)
4915 level_nr = leveldir_current->last_level;
4918 // get handicap level in this level set
4920 token_value = getHashEntry(level_setup_hash, TOKEN_STR_HANDICAP_LEVEL);
4924 int level_nr = atoi(token_value);
4926 if (level_nr < leveldir_current->first_level)
4927 level_nr = leveldir_current->first_level;
4928 if (level_nr > leveldir_current->last_level + 1)
4929 level_nr = leveldir_current->last_level;
4931 if (leveldir_current->user_defined || !leveldir_current->handicap)
4932 level_nr = leveldir_current->last_level;
4934 leveldir_current->handicap_level = level_nr;
4937 // get number of played and solved levels in this level set
4939 BEGIN_HASH_ITERATION(level_setup_hash, itr)
4941 char *token = HASH_ITERATION_TOKEN(itr);
4942 char *value = HASH_ITERATION_VALUE(itr);
4944 if (strlen(token) == 3 &&
4945 token[0] >= '0' && token[0] <= '9' &&
4946 token[1] >= '0' && token[1] <= '9' &&
4947 token[2] >= '0' && token[2] <= '9')
4949 int level_nr = atoi(token);
4952 LevelStats_setPlayed(level_nr, atoi(value)); // read 1st column
4954 value = strchr(value, ' ');
4957 LevelStats_setSolved(level_nr, atoi(value)); // read 2nd column
4960 END_HASH_ITERATION(hash, itr)
4962 freeSetupFileHash(level_setup_hash);
4966 Debug("setup", "using default setup values");
4972 void SaveLevelSetup_SeriesInfo(void)
4975 char *level_subdir = leveldir_current->subdir;
4976 char *level_nr_str = int2str(level_nr, 0);
4977 char *handicap_level_str = int2str(leveldir_current->handicap_level, 0);
4981 // --------------------------------------------------------------------------
4982 // ~/.<program>/levelsetup/<level series>/levelsetup.conf
4983 // --------------------------------------------------------------------------
4985 InitLevelSetupDirectory(level_subdir);
4987 filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
4989 if (!(file = fopen(filename, MODE_WRITE)))
4991 Warn("cannot write setup file '%s'", filename);
4998 fprintFileHeader(file, LEVELSETUP_FILENAME);
5000 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_PLAYED_LEVEL,
5002 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_HANDICAP_LEVEL,
5003 handicap_level_str));
5005 for (i = leveldir_current->first_level; i <= leveldir_current->last_level;
5008 if (LevelStats_getPlayed(i) > 0 ||
5009 LevelStats_getSolved(i) > 0)
5014 sprintf(token, "%03d", i);
5015 sprintf(value, "%d %d", LevelStats_getPlayed(i), LevelStats_getSolved(i));
5017 fprintf(file, "%s\n", getFormattedSetupEntry(token, value));
5023 SetFilePermissions(filename, PERMS_PRIVATE);
5028 int LevelStats_getPlayed(int nr)
5030 return (nr >= 0 && nr < MAX_LEVELS ? level_stats[nr].played : 0);
5033 int LevelStats_getSolved(int nr)
5035 return (nr >= 0 && nr < MAX_LEVELS ? level_stats[nr].solved : 0);
5038 void LevelStats_setPlayed(int nr, int value)
5040 if (nr >= 0 && nr < MAX_LEVELS)
5041 level_stats[nr].played = value;
5044 void LevelStats_setSolved(int nr, int value)
5046 if (nr >= 0 && nr < MAX_LEVELS)
5047 level_stats[nr].solved = value;
5050 void LevelStats_incPlayed(int nr)
5052 if (nr >= 0 && nr < MAX_LEVELS)
5053 level_stats[nr].played++;
5056 void LevelStats_incSolved(int nr)
5058 if (nr >= 0 && nr < MAX_LEVELS)
5059 level_stats[nr].solved++;
5062 void LoadUserSetup(void)
5064 // --------------------------------------------------------------------------
5065 // ~/.<program>/usersetup.conf
5066 // --------------------------------------------------------------------------
5068 char *filename = getPath2(getMainUserGameDataDir(), USERSETUP_FILENAME);
5069 SetupFileHash *user_setup_hash = NULL;
5071 // always start with reliable default values
5074 if ((user_setup_hash = loadSetupFileHash(filename)))
5078 // get last selected user number
5079 token_value = getHashEntry(user_setup_hash, TOKEN_STR_LAST_USER);
5082 user.nr = MIN(MAX(0, atoi(token_value)), MAX_PLAYER_NAMES - 1);
5084 freeSetupFileHash(user_setup_hash);
5088 Debug("setup", "using default setup values");
5094 void SaveUserSetup(void)
5096 // --------------------------------------------------------------------------
5097 // ~/.<program>/usersetup.conf
5098 // --------------------------------------------------------------------------
5100 char *filename = getPath2(getMainUserGameDataDir(), USERSETUP_FILENAME);
5103 InitMainUserDataDirectory();
5105 if (!(file = fopen(filename, MODE_WRITE)))
5107 Warn("cannot write setup file '%s'", filename);
5114 fprintFileHeader(file, USERSETUP_FILENAME);
5116 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_USER,
5120 SetFilePermissions(filename, PERMS_PRIVATE);