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 SetupFileHash *missing_file_hash = NULL;
74 static boolean use_artworkinfo_cache = TRUE;
75 static boolean update_artworkinfo_cache = FALSE;
78 // ----------------------------------------------------------------------------
80 // ----------------------------------------------------------------------------
82 static void WarnUsingFallback(char *filename)
84 if (getHashEntry(missing_file_hash, filename) == NULL)
86 setHashEntry(missing_file_hash, filename, "");
88 Warn("cannot find artwork file '%s' (using fallback)", filename);
92 static char *getLevelClassDescription(TreeInfo *ti)
94 int position = ti->sort_priority / 100;
96 if (position >= 0 && position < NUM_LEVELCLASS_DESC)
97 return levelclass_desc[position];
99 return "Unknown Level Class";
102 static char *getCacheDir(void)
104 static char *cache_dir = NULL;
106 if (cache_dir == NULL)
107 cache_dir = getPath2(getMainUserGameDataDir(), CACHE_DIRECTORY);
112 static char *getScoreDir(char *level_subdir)
114 static char *score_dir = NULL;
115 static char *score_level_dir = NULL;
116 char *score_subdir = SCORES_DIRECTORY;
118 if (score_dir == NULL)
119 score_dir = getPath2(getMainUserGameDataDir(), score_subdir);
121 if (level_subdir != NULL)
123 checked_free(score_level_dir);
125 score_level_dir = getPath2(score_dir, level_subdir);
127 return score_level_dir;
133 static char *getScoreCacheDir(char *level_subdir)
135 static char *score_dir = NULL;
136 static char *score_level_dir = NULL;
137 char *score_subdir = SCORES_DIRECTORY;
139 if (score_dir == NULL)
140 score_dir = getPath2(getCacheDir(), score_subdir);
142 if (level_subdir != NULL)
144 checked_free(score_level_dir);
146 score_level_dir = getPath2(score_dir, level_subdir);
148 return score_level_dir;
154 static char *getScoreTapeDir(char *level_subdir, int nr)
156 static char *score_tape_dir = NULL;
157 char tape_subdir[MAX_FILENAME_LEN];
159 checked_free(score_tape_dir);
161 sprintf(tape_subdir, "%03d", nr);
162 score_tape_dir = getPath2(getScoreDir(level_subdir), tape_subdir);
164 return score_tape_dir;
167 static char *getUserSubdir(int nr)
169 static char user_subdir[16] = { 0 };
171 sprintf(user_subdir, "%03d", nr);
176 static char *getUserDir(int nr)
178 static char *user_dir = NULL;
179 char *main_data_dir = getMainUserGameDataDir();
180 char *users_subdir = USERS_DIRECTORY;
181 char *user_subdir = getUserSubdir(nr);
183 checked_free(user_dir);
186 user_dir = getPath3(main_data_dir, users_subdir, user_subdir);
188 user_dir = getPath2(main_data_dir, users_subdir);
193 static char *getLevelSetupDir(char *level_subdir)
195 static char *levelsetup_dir = NULL;
196 char *data_dir = getUserGameDataDir();
197 char *levelsetup_subdir = LEVELSETUP_DIRECTORY;
199 checked_free(levelsetup_dir);
201 if (level_subdir != NULL)
202 levelsetup_dir = getPath3(data_dir, levelsetup_subdir, level_subdir);
204 levelsetup_dir = getPath2(data_dir, levelsetup_subdir);
206 return levelsetup_dir;
209 static char *getNetworkDir(void)
211 static char *network_dir = NULL;
213 if (network_dir == NULL)
214 network_dir = getPath2(getMainUserGameDataDir(), NETWORK_DIRECTORY);
219 char *getLevelDirFromTreeInfo(TreeInfo *node)
221 static char *level_dir = NULL;
224 return options.level_directory;
226 checked_free(level_dir);
228 level_dir = getPath2((node->in_user_dir ? getUserLevelDir(NULL) :
229 options.level_directory), node->fullpath);
234 char *getUserLevelDir(char *level_subdir)
236 static char *userlevel_dir = NULL;
237 char *data_dir = getMainUserGameDataDir();
238 char *userlevel_subdir = LEVELS_DIRECTORY;
240 checked_free(userlevel_dir);
242 if (level_subdir != NULL)
243 userlevel_dir = getPath3(data_dir, userlevel_subdir, level_subdir);
245 userlevel_dir = getPath2(data_dir, userlevel_subdir);
247 return userlevel_dir;
250 char *getNetworkLevelDir(char *level_subdir)
252 static char *network_level_dir = NULL;
253 char *data_dir = getNetworkDir();
254 char *networklevel_subdir = LEVELS_DIRECTORY;
256 checked_free(network_level_dir);
258 if (level_subdir != NULL)
259 network_level_dir = getPath3(data_dir, networklevel_subdir, level_subdir);
261 network_level_dir = getPath2(data_dir, networklevel_subdir);
263 return network_level_dir;
266 char *getCurrentLevelDir(void)
268 return getLevelDirFromTreeInfo(leveldir_current);
271 char *getNewUserLevelSubdir(void)
273 static char *new_level_subdir = NULL;
274 char *subdir_prefix = getLoginName();
275 char subdir_suffix[10];
276 int max_suffix_number = 1000;
279 while (++i < max_suffix_number)
281 sprintf(subdir_suffix, "_%d", i);
283 checked_free(new_level_subdir);
284 new_level_subdir = getStringCat2(subdir_prefix, subdir_suffix);
286 if (!directoryExists(getUserLevelDir(new_level_subdir)))
290 return new_level_subdir;
293 char *getTapeDir(char *level_subdir)
295 static char *tape_dir = NULL;
296 char *data_dir = getUserGameDataDir();
297 char *tape_subdir = TAPES_DIRECTORY;
299 checked_free(tape_dir);
301 if (level_subdir != NULL)
302 tape_dir = getPath3(data_dir, tape_subdir, level_subdir);
304 tape_dir = getPath2(data_dir, tape_subdir);
309 static char *getSolutionTapeDir(void)
311 static char *tape_dir = NULL;
312 char *data_dir = getCurrentLevelDir();
313 char *tape_subdir = TAPES_DIRECTORY;
315 checked_free(tape_dir);
317 tape_dir = getPath2(data_dir, tape_subdir);
322 static char *getDefaultGraphicsDir(char *graphics_subdir)
324 static char *graphics_dir = NULL;
326 if (graphics_subdir == NULL)
327 return options.graphics_directory;
329 checked_free(graphics_dir);
331 graphics_dir = getPath2(options.graphics_directory, graphics_subdir);
336 static char *getDefaultSoundsDir(char *sounds_subdir)
338 static char *sounds_dir = NULL;
340 if (sounds_subdir == NULL)
341 return options.sounds_directory;
343 checked_free(sounds_dir);
345 sounds_dir = getPath2(options.sounds_directory, sounds_subdir);
350 static char *getDefaultMusicDir(char *music_subdir)
352 static char *music_dir = NULL;
354 if (music_subdir == NULL)
355 return options.music_directory;
357 checked_free(music_dir);
359 music_dir = getPath2(options.music_directory, music_subdir);
364 static char *getClassicArtworkSet(int type)
366 return (type == TREE_TYPE_GRAPHICS_DIR ? GFX_CLASSIC_SUBDIR :
367 type == TREE_TYPE_SOUNDS_DIR ? SND_CLASSIC_SUBDIR :
368 type == TREE_TYPE_MUSIC_DIR ? MUS_CLASSIC_SUBDIR : "");
371 static char *getClassicArtworkDir(int type)
373 return (type == TREE_TYPE_GRAPHICS_DIR ?
374 getDefaultGraphicsDir(GFX_CLASSIC_SUBDIR) :
375 type == TREE_TYPE_SOUNDS_DIR ?
376 getDefaultSoundsDir(SND_CLASSIC_SUBDIR) :
377 type == TREE_TYPE_MUSIC_DIR ?
378 getDefaultMusicDir(MUS_CLASSIC_SUBDIR) : "");
381 char *getUserGraphicsDir(void)
383 static char *usergraphics_dir = NULL;
385 if (usergraphics_dir == NULL)
386 usergraphics_dir = getPath2(getMainUserGameDataDir(), GRAPHICS_DIRECTORY);
388 return usergraphics_dir;
391 char *getUserSoundsDir(void)
393 static char *usersounds_dir = NULL;
395 if (usersounds_dir == NULL)
396 usersounds_dir = getPath2(getMainUserGameDataDir(), SOUNDS_DIRECTORY);
398 return usersounds_dir;
401 char *getUserMusicDir(void)
403 static char *usermusic_dir = NULL;
405 if (usermusic_dir == NULL)
406 usermusic_dir = getPath2(getMainUserGameDataDir(), MUSIC_DIRECTORY);
408 return usermusic_dir;
411 static char *getSetupArtworkDir(TreeInfo *ti)
413 static char *artwork_dir = NULL;
418 checked_free(artwork_dir);
420 artwork_dir = getPath2(ti->basepath, ti->fullpath);
425 char *setLevelArtworkDir(TreeInfo *ti)
427 char **artwork_path_ptr, **artwork_set_ptr;
428 TreeInfo *level_artwork;
430 if (ti == NULL || leveldir_current == NULL)
433 artwork_path_ptr = LEVELDIR_ARTWORK_PATH_PTR(leveldir_current, ti->type);
434 artwork_set_ptr = LEVELDIR_ARTWORK_SET_PTR( leveldir_current, ti->type);
436 checked_free(*artwork_path_ptr);
438 if ((level_artwork = getTreeInfoFromIdentifier(ti, *artwork_set_ptr)))
440 *artwork_path_ptr = getStringCopy(getSetupArtworkDir(level_artwork));
445 No (or non-existing) artwork configured in "levelinfo.conf". This would
446 normally result in using the artwork configured in the setup menu. But
447 if an artwork subdirectory exists (which might contain custom artwork
448 or an artwork configuration file), this level artwork must be treated
449 as relative to the default "classic" artwork, not to the artwork that
450 is currently configured in the setup menu.
452 Update: For "special" versions of R'n'D (like "R'n'D jue"), do not use
453 the "default" artwork (which would be "jue0" for "R'n'D jue"), but use
454 the real "classic" artwork from the original R'n'D (like "gfx_classic").
457 char *dir = getPath2(getCurrentLevelDir(), ARTWORK_DIRECTORY(ti->type));
459 checked_free(*artwork_set_ptr);
461 if (directoryExists(dir))
463 *artwork_path_ptr = getStringCopy(getClassicArtworkDir(ti->type));
464 *artwork_set_ptr = getStringCopy(getClassicArtworkSet(ti->type));
468 *artwork_path_ptr = getStringCopy(UNDEFINED_FILENAME);
469 *artwork_set_ptr = NULL;
475 return *artwork_set_ptr;
478 static char *getLevelArtworkSet(int type)
480 if (leveldir_current == NULL)
483 return LEVELDIR_ARTWORK_SET(leveldir_current, type);
486 static char *getLevelArtworkDir(int type)
488 if (leveldir_current == NULL)
489 return UNDEFINED_FILENAME;
491 return LEVELDIR_ARTWORK_PATH(leveldir_current, type);
494 char *getProgramMainDataPath(char *command_filename, char *base_path)
496 // check if the program's main data base directory is configured
497 if (!strEqual(base_path, "."))
498 return getStringCopy(base_path);
500 /* if the program is configured to start from current directory (default),
501 determine program package directory from program binary (some versions
502 of KDE/Konqueror and Mac OS X (especially "Mavericks") apparently do not
503 set the current working directory to the program package directory) */
504 char *main_data_path = getBasePath(command_filename);
506 #if defined(PLATFORM_MACOSX)
507 if (strSuffix(main_data_path, MAC_APP_BINARY_SUBDIR))
509 char *main_data_path_old = main_data_path;
511 // cut relative path to Mac OS X application binary directory from path
512 main_data_path[strlen(main_data_path) -
513 strlen(MAC_APP_BINARY_SUBDIR)] = '\0';
515 // cut trailing path separator from path (but not if path is root directory)
516 if (strSuffix(main_data_path, "/") && !strEqual(main_data_path, "/"))
517 main_data_path[strlen(main_data_path) - 1] = '\0';
519 // replace empty path with current directory
520 if (strEqual(main_data_path, ""))
521 main_data_path = ".";
523 // add relative path to Mac OS X application resources directory to path
524 main_data_path = getPath2(main_data_path, MAC_APP_FILES_SUBDIR);
526 free(main_data_path_old);
530 return main_data_path;
533 char *getProgramConfigFilename(char *command_filename)
535 static char *config_filename_1 = NULL;
536 static char *config_filename_2 = NULL;
537 static char *config_filename_3 = NULL;
538 static boolean initialized = FALSE;
542 char *command_filename_1 = getStringCopy(command_filename);
544 // strip trailing executable suffix from command filename
545 if (strSuffix(command_filename_1, ".exe"))
546 command_filename_1[strlen(command_filename_1) - 4] = '\0';
548 char *base_path = getProgramMainDataPath(command_filename, BASE_PATH);
549 char *conf_directory = getPath2(base_path, CONF_DIRECTORY);
551 char *command_basepath = getBasePath(command_filename);
552 char *command_basename = getBaseNameNoSuffix(command_filename);
553 char *command_filename_2 = getPath2(command_basepath, command_basename);
555 config_filename_1 = getStringCat2(command_filename_1, ".conf");
556 config_filename_2 = getStringCat2(command_filename_2, ".conf");
557 config_filename_3 = getPath2(conf_directory, SETUP_FILENAME);
559 checked_free(base_path);
560 checked_free(conf_directory);
562 checked_free(command_basepath);
563 checked_free(command_basename);
565 checked_free(command_filename_1);
566 checked_free(command_filename_2);
571 // 1st try: look for config file that exactly matches the binary filename
572 if (fileExists(config_filename_1))
573 return config_filename_1;
575 // 2nd try: look for config file that matches binary filename without suffix
576 if (fileExists(config_filename_2))
577 return config_filename_2;
579 // 3rd try: return setup config filename in global program config directory
580 return config_filename_3;
583 char *getTapeFilename(int nr)
585 static char *filename = NULL;
586 char basename[MAX_FILENAME_LEN];
588 checked_free(filename);
590 sprintf(basename, "%03d.%s", nr, TAPEFILE_EXTENSION);
591 filename = getPath2(getTapeDir(leveldir_current->subdir), basename);
596 char *getTemporaryTapeFilename(void)
598 static char *filename = NULL;
599 char basename[MAX_FILENAME_LEN];
601 checked_free(filename);
603 sprintf(basename, "tmp.%s", TAPEFILE_EXTENSION);
604 filename = getPath2(getTapeDir(NULL), basename);
609 char *getDefaultSolutionTapeFilename(int nr)
611 static char *filename = NULL;
612 char basename[MAX_FILENAME_LEN];
614 checked_free(filename);
616 sprintf(basename, "%03d.%s", nr, TAPEFILE_EXTENSION);
617 filename = getPath2(getSolutionTapeDir(), basename);
622 char *getSokobanSolutionTapeFilename(int nr)
624 static char *filename = NULL;
625 char basename[MAX_FILENAME_LEN];
627 checked_free(filename);
629 sprintf(basename, "%03d.sln", nr);
630 filename = getPath2(getSolutionTapeDir(), basename);
635 char *getSolutionTapeFilename(int nr)
637 char *filename = getDefaultSolutionTapeFilename(nr);
639 if (!fileExists(filename))
641 char *filename2 = getSokobanSolutionTapeFilename(nr);
643 if (fileExists(filename2))
650 char *getScoreFilename(int nr)
652 static char *filename = NULL;
653 char basename[MAX_FILENAME_LEN];
655 checked_free(filename);
657 sprintf(basename, "%03d.%s", nr, SCOREFILE_EXTENSION);
659 // used instead of "leveldir_current->subdir" (for network games)
660 filename = getPath2(getScoreDir(levelset.identifier), basename);
665 char *getScoreCacheFilename(int nr)
667 static char *filename = NULL;
668 char basename[MAX_FILENAME_LEN];
670 checked_free(filename);
672 sprintf(basename, "%03d.%s", nr, SCOREFILE_EXTENSION);
674 // used instead of "leveldir_current->subdir" (for network games)
675 filename = getPath2(getScoreCacheDir(levelset.identifier), basename);
680 char *getScoreTapeBasename(char *name)
682 static char basename[MAX_FILENAME_LEN];
683 char basename_raw[MAX_FILENAME_LEN];
686 sprintf(timestamp, "%s", getCurrentTimestamp());
687 sprintf(basename_raw, "%s-%s", timestamp, name);
688 sprintf(basename, "%s-%08x", timestamp, get_hash_from_key(basename_raw));
693 char *getScoreTapeFilename(char *basename_no_ext, int nr)
695 static char *filename = NULL;
696 char basename[MAX_FILENAME_LEN];
698 checked_free(filename);
700 sprintf(basename, "%s.%s", basename_no_ext, TAPEFILE_EXTENSION);
702 // used instead of "leveldir_current->subdir" (for network games)
703 filename = getPath2(getScoreTapeDir(levelset.identifier, nr), basename);
708 char *getSetupFilename(void)
710 static char *filename = NULL;
712 checked_free(filename);
714 filename = getPath2(getSetupDir(), SETUP_FILENAME);
719 char *getDefaultSetupFilename(void)
721 return program.config_filename;
724 char *getEditorSetupFilename(void)
726 static char *filename = NULL;
728 checked_free(filename);
729 filename = getPath2(getCurrentLevelDir(), EDITORSETUP_FILENAME);
731 if (fileExists(filename))
734 checked_free(filename);
735 filename = getPath2(getSetupDir(), EDITORSETUP_FILENAME);
740 char *getHelpAnimFilename(void)
742 static char *filename = NULL;
744 checked_free(filename);
746 filename = getPath2(getCurrentLevelDir(), HELPANIM_FILENAME);
751 char *getHelpTextFilename(void)
753 static char *filename = NULL;
755 checked_free(filename);
757 filename = getPath2(getCurrentLevelDir(), HELPTEXT_FILENAME);
762 char *getLevelSetInfoFilename(void)
764 static char *filename = NULL;
779 for (i = 0; basenames[i] != NULL; i++)
781 checked_free(filename);
782 filename = getPath2(getCurrentLevelDir(), basenames[i]);
784 if (fileExists(filename))
791 static char *getLevelSetTitleMessageBasename(int nr, boolean initial)
793 static char basename[32];
795 sprintf(basename, "%s_%d.txt",
796 (initial ? "titlemessage_initial" : "titlemessage"), nr + 1);
801 char *getLevelSetTitleMessageFilename(int nr, boolean initial)
803 static char *filename = NULL;
805 boolean skip_setup_artwork = FALSE;
807 checked_free(filename);
809 basename = getLevelSetTitleMessageBasename(nr, initial);
811 if (!gfx.override_level_graphics)
813 // 1st try: look for special artwork in current level series directory
814 filename = getPath3(getCurrentLevelDir(), GRAPHICS_DIRECTORY, basename);
815 if (fileExists(filename))
820 // 2nd try: look for message file in current level set directory
821 filename = getPath2(getCurrentLevelDir(), basename);
822 if (fileExists(filename))
827 // check if there is special artwork configured in level series config
828 if (getLevelArtworkSet(ARTWORK_TYPE_GRAPHICS) != NULL)
830 // 3rd try: look for special artwork configured in level series config
831 filename = getPath2(getLevelArtworkDir(ARTWORK_TYPE_GRAPHICS), basename);
832 if (fileExists(filename))
837 // take missing artwork configured in level set config from default
838 skip_setup_artwork = TRUE;
842 if (!skip_setup_artwork)
844 // 4th try: look for special artwork in configured artwork directory
845 filename = getPath2(getSetupArtworkDir(artwork.gfx_current), basename);
846 if (fileExists(filename))
852 // 5th try: look for default artwork in new default artwork directory
853 filename = getPath2(getDefaultGraphicsDir(GFX_DEFAULT_SUBDIR), basename);
854 if (fileExists(filename))
859 // 6th try: look for default artwork in old default artwork directory
860 filename = getPath2(options.graphics_directory, basename);
861 if (fileExists(filename))
864 return NULL; // cannot find specified artwork file anywhere
867 static char *getCreditsBasename(int nr)
869 static char basename[32];
871 sprintf(basename, "credits_%d.txt", nr + 1);
876 char *getCreditsFilename(int nr, boolean global)
878 char *basename = getCreditsBasename(nr);
879 char *basepath = NULL;
880 static char *credits_subdir = NULL;
881 static char *filename = NULL;
883 if (credits_subdir == NULL)
884 credits_subdir = getPath2(DOCS_DIRECTORY, CREDITS_DIRECTORY);
886 checked_free(filename);
888 // look for credits file in the game's base or current level set directory
889 basepath = (global ? options.base_directory : getCurrentLevelDir());
891 filename = getPath3(basepath, credits_subdir, basename);
892 if (fileExists(filename))
895 return NULL; // cannot find credits file
898 static char *getProgramInfoBasename(int nr)
900 static char basename[32];
902 sprintf(basename, "program_%d.txt", nr + 1);
907 char *getProgramInfoFilename(int nr)
909 char *basename = getProgramInfoBasename(nr);
910 static char *info_subdir = NULL;
911 static char *filename = NULL;
913 if (info_subdir == NULL)
914 info_subdir = getPath2(DOCS_DIRECTORY, INFO_DIRECTORY);
916 checked_free(filename);
918 // look for program info file in the game's base directory
919 filename = getPath3(options.base_directory, info_subdir, basename);
920 if (fileExists(filename))
923 return NULL; // cannot find program info file
926 static char *getCorrectedArtworkBasename(char *basename)
931 char *getCustomImageFilename(char *basename)
933 static char *filename = NULL;
934 boolean skip_setup_artwork = FALSE;
936 checked_free(filename);
938 basename = getCorrectedArtworkBasename(basename);
940 if (!gfx.override_level_graphics)
942 // 1st try: look for special artwork in current level series directory
943 filename = getImg3(getCurrentLevelDir(), GRAPHICS_DIRECTORY, basename);
944 if (fileExists(filename))
949 // check if there is special artwork configured in level series config
950 if (getLevelArtworkSet(ARTWORK_TYPE_GRAPHICS) != NULL)
952 // 2nd try: look for special artwork configured in level series config
953 filename = getImg2(getLevelArtworkDir(ARTWORK_TYPE_GRAPHICS), basename);
954 if (fileExists(filename))
959 // take missing artwork configured in level set config from default
960 skip_setup_artwork = TRUE;
964 if (!skip_setup_artwork)
966 // 3rd try: look for special artwork in configured artwork directory
967 filename = getImg2(getSetupArtworkDir(artwork.gfx_current), basename);
968 if (fileExists(filename))
974 // 4th try: look for default artwork in new default artwork directory
975 filename = getImg2(getDefaultGraphicsDir(GFX_DEFAULT_SUBDIR), basename);
976 if (fileExists(filename))
981 // 5th try: look for default artwork in old default artwork directory
982 filename = getImg2(options.graphics_directory, basename);
983 if (fileExists(filename))
986 if (!strEqual(GFX_FALLBACK_FILENAME, UNDEFINED_FILENAME))
990 WarnUsingFallback(basename);
992 // 6th try: look for fallback artwork in old default artwork directory
993 // (needed to prevent errors when trying to access unused artwork files)
994 filename = getImg2(options.graphics_directory, GFX_FALLBACK_FILENAME);
995 if (fileExists(filename))
999 return NULL; // cannot find specified artwork file anywhere
1002 char *getCustomSoundFilename(char *basename)
1004 static char *filename = NULL;
1005 boolean skip_setup_artwork = FALSE;
1007 checked_free(filename);
1009 basename = getCorrectedArtworkBasename(basename);
1011 if (!gfx.override_level_sounds)
1013 // 1st try: look for special artwork in current level series directory
1014 filename = getPath3(getCurrentLevelDir(), SOUNDS_DIRECTORY, basename);
1015 if (fileExists(filename))
1020 // check if there is special artwork configured in level series config
1021 if (getLevelArtworkSet(ARTWORK_TYPE_SOUNDS) != NULL)
1023 // 2nd try: look for special artwork configured in level series config
1024 filename = getPath2(getLevelArtworkDir(TREE_TYPE_SOUNDS_DIR), basename);
1025 if (fileExists(filename))
1030 // take missing artwork configured in level set config from default
1031 skip_setup_artwork = TRUE;
1035 if (!skip_setup_artwork)
1037 // 3rd try: look for special artwork in configured artwork directory
1038 filename = getPath2(getSetupArtworkDir(artwork.snd_current), basename);
1039 if (fileExists(filename))
1045 // 4th try: look for default artwork in new default artwork directory
1046 filename = getPath2(getDefaultSoundsDir(SND_DEFAULT_SUBDIR), basename);
1047 if (fileExists(filename))
1052 // 5th try: look for default artwork in old default artwork directory
1053 filename = getPath2(options.sounds_directory, basename);
1054 if (fileExists(filename))
1057 if (!strEqual(SND_FALLBACK_FILENAME, UNDEFINED_FILENAME))
1061 WarnUsingFallback(basename);
1063 // 6th try: look for fallback artwork in old default artwork directory
1064 // (needed to prevent errors when trying to access unused artwork files)
1065 filename = getPath2(options.sounds_directory, SND_FALLBACK_FILENAME);
1066 if (fileExists(filename))
1070 return NULL; // cannot find specified artwork file anywhere
1073 char *getCustomMusicFilename(char *basename)
1075 static char *filename = NULL;
1076 boolean skip_setup_artwork = FALSE;
1078 checked_free(filename);
1080 basename = getCorrectedArtworkBasename(basename);
1082 if (!gfx.override_level_music)
1084 // 1st try: look for special artwork in current level series directory
1085 filename = getPath3(getCurrentLevelDir(), MUSIC_DIRECTORY, basename);
1086 if (fileExists(filename))
1091 // check if there is special artwork configured in level series config
1092 if (getLevelArtworkSet(ARTWORK_TYPE_MUSIC) != NULL)
1094 // 2nd try: look for special artwork configured in level series config
1095 filename = getPath2(getLevelArtworkDir(TREE_TYPE_MUSIC_DIR), basename);
1096 if (fileExists(filename))
1101 // take missing artwork configured in level set config from default
1102 skip_setup_artwork = TRUE;
1106 if (!skip_setup_artwork)
1108 // 3rd try: look for special artwork in configured artwork directory
1109 filename = getPath2(getSetupArtworkDir(artwork.mus_current), basename);
1110 if (fileExists(filename))
1116 // 4th try: look for default artwork in new default artwork directory
1117 filename = getPath2(getDefaultMusicDir(MUS_DEFAULT_SUBDIR), basename);
1118 if (fileExists(filename))
1123 // 5th try: look for default artwork in old default artwork directory
1124 filename = getPath2(options.music_directory, basename);
1125 if (fileExists(filename))
1128 if (!strEqual(MUS_FALLBACK_FILENAME, UNDEFINED_FILENAME))
1132 WarnUsingFallback(basename);
1134 // 6th try: look for fallback artwork in old default artwork directory
1135 // (needed to prevent errors when trying to access unused artwork files)
1136 filename = getPath2(options.music_directory, MUS_FALLBACK_FILENAME);
1137 if (fileExists(filename))
1141 return NULL; // cannot find specified artwork file anywhere
1144 char *getCustomArtworkFilename(char *basename, int type)
1146 if (type == ARTWORK_TYPE_GRAPHICS)
1147 return getCustomImageFilename(basename);
1148 else if (type == ARTWORK_TYPE_SOUNDS)
1149 return getCustomSoundFilename(basename);
1150 else if (type == ARTWORK_TYPE_MUSIC)
1151 return getCustomMusicFilename(basename);
1153 return UNDEFINED_FILENAME;
1156 char *getCustomArtworkConfigFilename(int type)
1158 return getCustomArtworkFilename(ARTWORKINFO_FILENAME(type), type);
1161 char *getCustomArtworkLevelConfigFilename(int type)
1163 static char *filename = NULL;
1165 checked_free(filename);
1167 filename = getPath2(getLevelArtworkDir(type), ARTWORKINFO_FILENAME(type));
1172 char *getCustomMusicDirectory(void)
1174 static char *directory = NULL;
1175 boolean skip_setup_artwork = FALSE;
1177 checked_free(directory);
1179 if (!gfx.override_level_music)
1181 // 1st try: look for special artwork in current level series directory
1182 directory = getPath2(getCurrentLevelDir(), MUSIC_DIRECTORY);
1183 if (directoryExists(directory))
1188 // check if there is special artwork configured in level series config
1189 if (getLevelArtworkSet(ARTWORK_TYPE_MUSIC) != NULL)
1191 // 2nd try: look for special artwork configured in level series config
1192 directory = getStringCopy(getLevelArtworkDir(TREE_TYPE_MUSIC_DIR));
1193 if (directoryExists(directory))
1198 // take missing artwork configured in level set config from default
1199 skip_setup_artwork = TRUE;
1203 if (!skip_setup_artwork)
1205 // 3rd try: look for special artwork in configured artwork directory
1206 directory = getStringCopy(getSetupArtworkDir(artwork.mus_current));
1207 if (directoryExists(directory))
1213 // 4th try: look for default artwork in new default artwork directory
1214 directory = getStringCopy(getDefaultMusicDir(MUS_DEFAULT_SUBDIR));
1215 if (directoryExists(directory))
1220 // 5th try: look for default artwork in old default artwork directory
1221 directory = getStringCopy(options.music_directory);
1222 if (directoryExists(directory))
1225 return NULL; // cannot find specified artwork file anywhere
1228 void MarkTapeDirectoryUploadsAsComplete(char *level_subdir)
1230 char *filename = getPath2(getTapeDir(level_subdir), UPLOADED_FILENAME);
1232 touchFile(filename);
1234 checked_free(filename);
1237 void MarkTapeDirectoryUploadsAsIncomplete(char *level_subdir)
1239 char *filename = getPath2(getTapeDir(level_subdir), UPLOADED_FILENAME);
1243 checked_free(filename);
1246 boolean CheckTapeDirectoryUploadsComplete(char *level_subdir)
1248 char *filename = getPath2(getTapeDir(level_subdir), UPLOADED_FILENAME);
1249 boolean success = fileExists(filename);
1251 checked_free(filename);
1256 void InitMissingFileHash(void)
1258 if (missing_file_hash == NULL)
1259 freeSetupFileHash(missing_file_hash);
1261 missing_file_hash = newSetupFileHash();
1264 void InitTapeDirectory(char *level_subdir)
1266 boolean new_tape_dir = !directoryExists(getTapeDir(level_subdir));
1268 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
1269 createDirectory(getTapeDir(NULL), "main tape", PERMS_PRIVATE);
1270 createDirectory(getTapeDir(level_subdir), "level tape", PERMS_PRIVATE);
1273 MarkTapeDirectoryUploadsAsComplete(level_subdir);
1276 void InitScoreDirectory(char *level_subdir)
1278 createDirectory(getMainUserGameDataDir(), "main user data", PERMS_PRIVATE);
1279 createDirectory(getScoreDir(NULL), "main score", PERMS_PRIVATE);
1280 createDirectory(getScoreDir(level_subdir), "level score", PERMS_PRIVATE);
1283 void InitScoreCacheDirectory(char *level_subdir)
1285 createDirectory(getMainUserGameDataDir(), "main user data", PERMS_PRIVATE);
1286 createDirectory(getCacheDir(), "cache data", PERMS_PRIVATE);
1287 createDirectory(getScoreCacheDir(NULL), "main score", PERMS_PRIVATE);
1288 createDirectory(getScoreCacheDir(level_subdir), "level score", PERMS_PRIVATE);
1291 void InitScoreTapeDirectory(char *level_subdir, int nr)
1293 InitScoreDirectory(level_subdir);
1295 createDirectory(getScoreTapeDir(level_subdir, nr), "score tape", PERMS_PRIVATE);
1298 static void SaveUserLevelInfo(void);
1300 void InitUserLevelDirectory(char *level_subdir)
1302 if (!directoryExists(getUserLevelDir(level_subdir)))
1304 createDirectory(getMainUserGameDataDir(), "main user data", PERMS_PRIVATE);
1305 createDirectory(getUserLevelDir(NULL), "main user level", PERMS_PRIVATE);
1306 createDirectory(getUserLevelDir(level_subdir), "user level", PERMS_PRIVATE);
1308 if (setup.internal.create_user_levelset)
1309 SaveUserLevelInfo();
1313 void InitNetworkLevelDirectory(char *level_subdir)
1315 if (!directoryExists(getNetworkLevelDir(level_subdir)))
1317 createDirectory(getMainUserGameDataDir(), "main user data", PERMS_PRIVATE);
1318 createDirectory(getNetworkDir(), "network data", PERMS_PRIVATE);
1319 createDirectory(getNetworkLevelDir(NULL), "main network level", PERMS_PRIVATE);
1320 createDirectory(getNetworkLevelDir(level_subdir), "network level", PERMS_PRIVATE);
1324 void InitLevelSetupDirectory(char *level_subdir)
1326 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
1327 createDirectory(getLevelSetupDir(NULL), "main level setup", PERMS_PRIVATE);
1328 createDirectory(getLevelSetupDir(level_subdir), "level setup", PERMS_PRIVATE);
1331 static void InitCacheDirectory(void)
1333 createDirectory(getMainUserGameDataDir(), "main user data", PERMS_PRIVATE);
1334 createDirectory(getCacheDir(), "cache data", PERMS_PRIVATE);
1338 // ----------------------------------------------------------------------------
1339 // some functions to handle lists of level and artwork directories
1340 // ----------------------------------------------------------------------------
1342 TreeInfo *newTreeInfo(void)
1344 return checked_calloc(sizeof(TreeInfo));
1347 TreeInfo *newTreeInfo_setDefaults(int type)
1349 TreeInfo *ti = newTreeInfo();
1351 setTreeInfoToDefaults(ti, type);
1356 void pushTreeInfo(TreeInfo **node_first, TreeInfo *node_new)
1358 node_new->next = *node_first;
1359 *node_first = node_new;
1362 void removeTreeInfo(TreeInfo **node_first)
1364 TreeInfo *node_old = *node_first;
1366 *node_first = node_old->next;
1367 node_old->next = NULL;
1369 freeTreeInfo(node_old);
1372 int numTreeInfo(TreeInfo *node)
1385 boolean validLevelSeries(TreeInfo *node)
1387 // in a number of cases, tree node is no valid level set
1388 if (node == NULL || node->node_group || node->parent_link || node->is_copy)
1394 TreeInfo *getValidLevelSeries(TreeInfo *node, TreeInfo *default_node)
1396 if (validLevelSeries(node))
1398 else if (node->is_copy)
1399 return getTreeInfoFromIdentifier(leveldir_first, node->identifier);
1401 return getFirstValidTreeInfoEntry(default_node);
1404 static TreeInfo *getValidTreeInfoEntryExt(TreeInfo *node, boolean get_next_node)
1409 if (node->node_group) // enter node group (step down into tree)
1410 return getFirstValidTreeInfoEntry(node->node_group);
1412 if (node->parent_link) // skip first node (back link) of node group
1413 get_next_node = TRUE;
1415 if (!get_next_node) // get current regular tree node
1418 // get next regular tree node, or step up until one is found
1419 while (node->next == NULL && node->node_parent != NULL)
1420 node = node->node_parent;
1422 return getFirstValidTreeInfoEntry(node->next);
1425 TreeInfo *getFirstValidTreeInfoEntry(TreeInfo *node)
1427 return getValidTreeInfoEntryExt(node, FALSE);
1430 TreeInfo *getNextValidTreeInfoEntry(TreeInfo *node)
1432 return getValidTreeInfoEntryExt(node, TRUE);
1435 TreeInfo *getTreeInfoFirstGroupEntry(TreeInfo *node)
1440 if (node->node_parent == NULL) // top level group
1441 return *node->node_top;
1442 else // sub level group
1443 return node->node_parent->node_group;
1446 int numTreeInfoInGroup(TreeInfo *node)
1448 return numTreeInfo(getTreeInfoFirstGroupEntry(node));
1451 int getPosFromTreeInfo(TreeInfo *node)
1453 TreeInfo *node_cmp = getTreeInfoFirstGroupEntry(node);
1458 if (node_cmp == node)
1462 node_cmp = node_cmp->next;
1468 TreeInfo *getTreeInfoFromPos(TreeInfo *node, int pos)
1470 TreeInfo *node_default = node;
1482 return node_default;
1485 static TreeInfo *getTreeInfoFromIdentifierExt(TreeInfo *node, char *identifier,
1486 int node_type_wanted)
1488 if (identifier == NULL)
1493 if (TREE_NODE_TYPE(node) == node_type_wanted &&
1494 strEqual(identifier, node->identifier))
1497 if (node->node_group)
1499 TreeInfo *node_group = getTreeInfoFromIdentifierExt(node->node_group,
1512 TreeInfo *getTreeInfoFromIdentifier(TreeInfo *node, char *identifier)
1514 return getTreeInfoFromIdentifierExt(node, identifier, TREE_NODE_TYPE_DEFAULT);
1517 static TreeInfo *cloneTreeNode(TreeInfo **node_top, TreeInfo *node_parent,
1518 TreeInfo *node, boolean skip_sets_without_levels)
1525 if (!node->parent_link && !node->level_group &&
1526 skip_sets_without_levels && node->levels == 0)
1527 return cloneTreeNode(node_top, node_parent, node->next,
1528 skip_sets_without_levels);
1530 node_new = getTreeInfoCopy(node); // copy complete node
1532 node_new->node_top = node_top; // correct top node link
1533 node_new->node_parent = node_parent; // correct parent node link
1535 if (node->level_group)
1536 node_new->node_group = cloneTreeNode(node_top, node_new, node->node_group,
1537 skip_sets_without_levels);
1539 node_new->next = cloneTreeNode(node_top, node_parent, node->next,
1540 skip_sets_without_levels);
1545 static void cloneTree(TreeInfo **ti_new, TreeInfo *ti, boolean skip_empty_sets)
1547 TreeInfo *ti_cloned = cloneTreeNode(ti_new, NULL, ti, skip_empty_sets);
1549 *ti_new = ti_cloned;
1552 static boolean adjustTreeGraphicsForEMC(TreeInfo *node)
1554 boolean settings_changed = FALSE;
1558 boolean want_ecs = (setup.prefer_aga_graphics == FALSE);
1559 boolean want_aga = (setup.prefer_aga_graphics == TRUE);
1560 boolean has_only_ecs = (!node->graphics_set && !node->graphics_set_aga);
1561 boolean has_only_aga = (!node->graphics_set && !node->graphics_set_ecs);
1562 char *graphics_set = NULL;
1564 if (node->graphics_set_ecs && (want_ecs || has_only_ecs))
1565 graphics_set = node->graphics_set_ecs;
1567 if (node->graphics_set_aga && (want_aga || has_only_aga))
1568 graphics_set = node->graphics_set_aga;
1570 if (graphics_set && !strEqual(node->graphics_set, graphics_set))
1572 setString(&node->graphics_set, graphics_set);
1573 settings_changed = TRUE;
1576 if (node->node_group != NULL)
1577 settings_changed |= adjustTreeGraphicsForEMC(node->node_group);
1582 return settings_changed;
1585 static boolean adjustTreeSoundsForEMC(TreeInfo *node)
1587 boolean settings_changed = FALSE;
1591 boolean want_default = (setup.prefer_lowpass_sounds == FALSE);
1592 boolean want_lowpass = (setup.prefer_lowpass_sounds == TRUE);
1593 boolean has_only_default = (!node->sounds_set && !node->sounds_set_lowpass);
1594 boolean has_only_lowpass = (!node->sounds_set && !node->sounds_set_default);
1595 char *sounds_set = NULL;
1597 if (node->sounds_set_default && (want_default || has_only_default))
1598 sounds_set = node->sounds_set_default;
1600 if (node->sounds_set_lowpass && (want_lowpass || has_only_lowpass))
1601 sounds_set = node->sounds_set_lowpass;
1603 if (sounds_set && !strEqual(node->sounds_set, sounds_set))
1605 setString(&node->sounds_set, sounds_set);
1606 settings_changed = TRUE;
1609 if (node->node_group != NULL)
1610 settings_changed |= adjustTreeSoundsForEMC(node->node_group);
1615 return settings_changed;
1618 int dumpTreeInfo(TreeInfo *node, int depth)
1620 char bullet_list[] = { '-', '*', 'o' };
1621 int num_leaf_nodes = 0;
1625 Debug("tree", "Dumping TreeInfo:");
1629 char bullet = bullet_list[depth % ARRAY_SIZE(bullet_list)];
1631 for (i = 0; i < depth * 2; i++)
1632 DebugContinued("", " ");
1634 DebugContinued("tree", "%c '%s' ['%s] [PARENT: '%s'] %s\n",
1635 bullet, node->name, node->identifier,
1636 (node->node_parent ? node->node_parent->identifier : "-"),
1637 (node->node_group ? "[GROUP]" :
1638 node->is_copy ? "[COPY]" : ""));
1640 if (!node->node_group && !node->parent_link)
1644 // use for dumping artwork info tree
1645 Debug("tree", "subdir == '%s' ['%s', '%s'] [%d])",
1646 node->subdir, node->fullpath, node->basepath, node->in_user_dir);
1649 if (node->node_group != NULL)
1650 num_leaf_nodes += dumpTreeInfo(node->node_group, depth + 1);
1656 Debug("tree", "Summary: %d leaf nodes found", num_leaf_nodes);
1658 return num_leaf_nodes;
1661 void sortTreeInfoBySortFunction(TreeInfo **node_first,
1662 int (*compare_function)(const void *,
1665 int num_nodes = numTreeInfo(*node_first);
1666 TreeInfo **sort_array;
1667 TreeInfo *node = *node_first;
1673 // allocate array for sorting structure pointers
1674 sort_array = checked_calloc(num_nodes * sizeof(TreeInfo *));
1676 // writing structure pointers to sorting array
1677 while (i < num_nodes && node) // double boundary check...
1679 sort_array[i] = node;
1685 // sorting the structure pointers in the sorting array
1686 qsort(sort_array, num_nodes, sizeof(TreeInfo *),
1689 // update the linkage of list elements with the sorted node array
1690 for (i = 0; i < num_nodes - 1; i++)
1691 sort_array[i]->next = sort_array[i + 1];
1692 sort_array[num_nodes - 1]->next = NULL;
1694 // update the linkage of the main list anchor pointer
1695 *node_first = sort_array[0];
1699 // now recursively sort the level group structures
1703 if (node->node_group != NULL)
1704 sortTreeInfoBySortFunction(&node->node_group, compare_function);
1710 void sortTreeInfo(TreeInfo **node_first)
1712 sortTreeInfoBySortFunction(node_first, compareTreeInfoEntries);
1716 // ============================================================================
1717 // some stuff from "files.c"
1718 // ============================================================================
1720 #if defined(PLATFORM_WIN32)
1722 #define S_IRGRP S_IRUSR
1725 #define S_IROTH S_IRUSR
1728 #define S_IWGRP S_IWUSR
1731 #define S_IWOTH S_IWUSR
1734 #define S_IXGRP S_IXUSR
1737 #define S_IXOTH S_IXUSR
1740 #define S_IRWXG (S_IRGRP | S_IWGRP | S_IXGRP)
1745 #endif // PLATFORM_WIN32
1747 // file permissions for newly written files
1748 #define MODE_R_ALL (S_IRUSR | S_IRGRP | S_IROTH)
1749 #define MODE_W_ALL (S_IWUSR | S_IWGRP | S_IWOTH)
1750 #define MODE_X_ALL (S_IXUSR | S_IXGRP | S_IXOTH)
1752 #define MODE_W_PRIVATE (S_IWUSR)
1753 #define MODE_W_PUBLIC_FILE (S_IWUSR | S_IWGRP)
1754 #define MODE_W_PUBLIC_DIR (S_IWUSR | S_IWGRP | S_ISGID)
1756 #define DIR_PERMS_PRIVATE (MODE_R_ALL | MODE_X_ALL | MODE_W_PRIVATE)
1757 #define DIR_PERMS_PUBLIC (MODE_R_ALL | MODE_X_ALL | MODE_W_PUBLIC_DIR)
1758 #define DIR_PERMS_PUBLIC_ALL (MODE_R_ALL | MODE_X_ALL | MODE_W_ALL)
1760 #define FILE_PERMS_PRIVATE (MODE_R_ALL | MODE_W_PRIVATE)
1761 #define FILE_PERMS_PUBLIC (MODE_R_ALL | MODE_W_PUBLIC_FILE)
1762 #define FILE_PERMS_PUBLIC_ALL (MODE_R_ALL | MODE_W_ALL)
1765 char *getHomeDir(void)
1767 static char *dir = NULL;
1769 #if defined(PLATFORM_WIN32)
1772 dir = checked_malloc(MAX_PATH + 1);
1774 if (!SUCCEEDED(SHGetFolderPath(NULL, CSIDL_PERSONAL, NULL, 0, dir)))
1777 #elif defined(PLATFORM_EMSCRIPTEN)
1778 dir = PERSISTENT_DIRECTORY;
1779 #elif defined(PLATFORM_UNIX)
1782 if ((dir = getenv("HOME")) == NULL)
1784 dir = getUnixHomeDir();
1787 dir = getStringCopy(dir);
1799 char *getPersonalDataDir(void)
1801 static char *personal_data_dir = NULL;
1803 #if defined(PLATFORM_MACOSX)
1804 if (personal_data_dir == NULL)
1805 personal_data_dir = getPath2(getHomeDir(), "Documents");
1807 if (personal_data_dir == NULL)
1808 personal_data_dir = getHomeDir();
1811 return personal_data_dir;
1814 char *getMainUserGameDataDir(void)
1816 static char *main_user_data_dir = NULL;
1818 #if defined(PLATFORM_ANDROID)
1819 if (main_user_data_dir == NULL)
1820 main_user_data_dir = (char *)(SDL_AndroidGetExternalStorageState() &
1821 SDL_ANDROID_EXTERNAL_STORAGE_WRITE ?
1822 SDL_AndroidGetExternalStoragePath() :
1823 SDL_AndroidGetInternalStoragePath());
1825 if (main_user_data_dir == NULL)
1826 main_user_data_dir = getPath2(getPersonalDataDir(),
1827 program.userdata_subdir);
1830 return main_user_data_dir;
1833 char *getUserGameDataDir(void)
1836 return getMainUserGameDataDir();
1838 return getUserDir(user.nr);
1841 char *getSetupDir(void)
1843 return getUserGameDataDir();
1846 static mode_t posix_umask(mode_t mask)
1848 #if defined(PLATFORM_UNIX)
1855 static int posix_mkdir(const char *pathname, mode_t mode)
1857 #if defined(PLATFORM_WIN32)
1858 return mkdir(pathname);
1860 return mkdir(pathname, mode);
1864 static boolean posix_process_running_setgid(void)
1866 #if defined(PLATFORM_UNIX)
1867 return (getgid() != getegid());
1873 void createDirectory(char *dir, char *text, int permission_class)
1875 if (directoryExists(dir))
1878 // leave "other" permissions in umask untouched, but ensure group parts
1879 // of USERDATA_DIR_MODE are not masked
1880 mode_t dir_mode = (permission_class == PERMS_PRIVATE ?
1881 DIR_PERMS_PRIVATE : DIR_PERMS_PUBLIC);
1882 mode_t last_umask = posix_umask(0);
1883 mode_t group_umask = ~(dir_mode & S_IRWXG);
1884 int running_setgid = posix_process_running_setgid();
1886 if (permission_class == PERMS_PUBLIC)
1888 // if we're setgid, protect files against "other"
1889 // else keep umask(0) to make the dir world-writable
1892 posix_umask(last_umask & group_umask);
1894 dir_mode = DIR_PERMS_PUBLIC_ALL;
1897 if (posix_mkdir(dir, dir_mode) != 0)
1898 Warn("cannot create %s directory '%s': %s", text, dir, strerror(errno));
1900 if (permission_class == PERMS_PUBLIC && !running_setgid)
1901 chmod(dir, dir_mode);
1903 posix_umask(last_umask); // restore previous umask
1906 void InitMainUserDataDirectory(void)
1908 createDirectory(getMainUserGameDataDir(), "main user data", PERMS_PRIVATE);
1911 void InitUserDataDirectory(void)
1913 createDirectory(getMainUserGameDataDir(), "main user data", PERMS_PRIVATE);
1917 createDirectory(getUserDir(-1), "users", PERMS_PRIVATE);
1918 createDirectory(getUserDir(user.nr), "user data", PERMS_PRIVATE);
1922 void SetFilePermissions(char *filename, int permission_class)
1924 int running_setgid = posix_process_running_setgid();
1925 int perms = (permission_class == PERMS_PRIVATE ?
1926 FILE_PERMS_PRIVATE : FILE_PERMS_PUBLIC);
1928 if (permission_class == PERMS_PUBLIC && !running_setgid)
1929 perms = FILE_PERMS_PUBLIC_ALL;
1931 chmod(filename, perms);
1934 char *getCookie(char *file_type)
1936 static char cookie[MAX_COOKIE_LEN + 1];
1938 if (strlen(program.cookie_prefix) + 1 +
1939 strlen(file_type) + strlen("_FILE_VERSION_x.x") > MAX_COOKIE_LEN)
1940 return "[COOKIE ERROR]"; // should never happen
1942 sprintf(cookie, "%s_%s_FILE_VERSION_%d.%d",
1943 program.cookie_prefix, file_type,
1944 program.version_super, program.version_major);
1949 void fprintFileHeader(FILE *file, char *basename)
1951 char *prefix = "# ";
1954 fprintf_line_with_prefix(file, prefix, sep1, 77);
1955 fprintf(file, "%s%s\n", prefix, basename);
1956 fprintf_line_with_prefix(file, prefix, sep1, 77);
1957 fprintf(file, "\n");
1960 int getFileVersionFromCookieString(const char *cookie)
1962 const char *ptr_cookie1, *ptr_cookie2;
1963 const char *pattern1 = "_FILE_VERSION_";
1964 const char *pattern2 = "?.?";
1965 const int len_cookie = strlen(cookie);
1966 const int len_pattern1 = strlen(pattern1);
1967 const int len_pattern2 = strlen(pattern2);
1968 const int len_pattern = len_pattern1 + len_pattern2;
1969 int version_super, version_major;
1971 if (len_cookie <= len_pattern)
1974 ptr_cookie1 = &cookie[len_cookie - len_pattern];
1975 ptr_cookie2 = &cookie[len_cookie - len_pattern2];
1977 if (strncmp(ptr_cookie1, pattern1, len_pattern1) != 0)
1980 if (ptr_cookie2[0] < '0' || ptr_cookie2[0] > '9' ||
1981 ptr_cookie2[1] != '.' ||
1982 ptr_cookie2[2] < '0' || ptr_cookie2[2] > '9')
1985 version_super = ptr_cookie2[0] - '0';
1986 version_major = ptr_cookie2[2] - '0';
1988 return VERSION_IDENT(version_super, version_major, 0, 0);
1991 boolean checkCookieString(const char *cookie, const char *template)
1993 const char *pattern = "_FILE_VERSION_?.?";
1994 const int len_cookie = strlen(cookie);
1995 const int len_template = strlen(template);
1996 const int len_pattern = strlen(pattern);
1998 if (len_cookie != len_template)
2001 if (strncmp(cookie, template, len_cookie - len_pattern) != 0)
2008 // ----------------------------------------------------------------------------
2009 // setup file list and hash handling functions
2010 // ----------------------------------------------------------------------------
2012 char *getFormattedSetupEntry(char *token, char *value)
2015 static char entry[MAX_LINE_LEN];
2017 // if value is an empty string, just return token without value
2021 // start with the token and some spaces to format output line
2022 sprintf(entry, "%s:", token);
2023 for (i = strlen(entry); i < token_value_position; i++)
2026 // continue with the token's value
2027 strcat(entry, value);
2032 SetupFileList *newSetupFileList(char *token, char *value)
2034 SetupFileList *new = checked_malloc(sizeof(SetupFileList));
2036 new->token = getStringCopy(token);
2037 new->value = getStringCopy(value);
2044 void freeSetupFileList(SetupFileList *list)
2049 checked_free(list->token);
2050 checked_free(list->value);
2053 freeSetupFileList(list->next);
2058 char *getListEntry(SetupFileList *list, char *token)
2063 if (strEqual(list->token, token))
2066 return getListEntry(list->next, token);
2069 SetupFileList *setListEntry(SetupFileList *list, char *token, char *value)
2074 if (strEqual(list->token, token))
2076 checked_free(list->value);
2078 list->value = getStringCopy(value);
2082 else if (list->next == NULL)
2083 return (list->next = newSetupFileList(token, value));
2085 return setListEntry(list->next, token, value);
2088 SetupFileList *addListEntry(SetupFileList *list, char *token, char *value)
2093 if (list->next == NULL)
2094 return (list->next = newSetupFileList(token, value));
2096 return addListEntry(list->next, token, value);
2099 #if ENABLE_UNUSED_CODE
2101 static void printSetupFileList(SetupFileList *list)
2106 Debug("setup:printSetupFileList", "token: '%s'", list->token);
2107 Debug("setup:printSetupFileList", "value: '%s'", list->value);
2109 printSetupFileList(list->next);
2115 DEFINE_HASHTABLE_INSERT(insert_hash_entry, char, char);
2116 DEFINE_HASHTABLE_SEARCH(search_hash_entry, char, char);
2117 DEFINE_HASHTABLE_CHANGE(change_hash_entry, char, char);
2118 DEFINE_HASHTABLE_REMOVE(remove_hash_entry, char, char);
2120 #define insert_hash_entry hashtable_insert
2121 #define search_hash_entry hashtable_search
2122 #define change_hash_entry hashtable_change
2123 #define remove_hash_entry hashtable_remove
2126 unsigned int get_hash_from_key(void *key)
2131 This algorithm (k=33) was first reported by Dan Bernstein many years ago in
2132 'comp.lang.c'. Another version of this algorithm (now favored by Bernstein)
2133 uses XOR: hash(i) = hash(i - 1) * 33 ^ str[i]; the magic of number 33 (why
2134 it works better than many other constants, prime or not) has never been
2135 adequately explained.
2137 If you just want to have a good hash function, and cannot wait, djb2
2138 is one of the best string hash functions i know. It has excellent
2139 distribution and speed on many different sets of keys and table sizes.
2140 You are not likely to do better with one of the "well known" functions
2141 such as PJW, K&R, etc.
2143 Ozan (oz) Yigit [http://www.cs.yorku.ca/~oz/hash.html]
2146 char *str = (char *)key;
2147 unsigned int hash = 5381;
2150 while ((c = *str++))
2151 hash = ((hash << 5) + hash) + c; // hash * 33 + c
2156 int hash_keys_are_equal(void *key1, void *key2)
2158 return (strEqual((char *)key1, (char *)key2));
2161 SetupFileHash *newSetupFileHash(void)
2163 SetupFileHash *new_hash =
2164 create_hashtable(16, 0.75, get_hash_from_key, hash_keys_are_equal);
2166 if (new_hash == NULL)
2167 Fail("create_hashtable() failed -- out of memory");
2172 void freeSetupFileHash(SetupFileHash *hash)
2177 hashtable_destroy(hash, 1); // 1 == also free values stored in hash
2180 char *getHashEntry(SetupFileHash *hash, char *token)
2185 return search_hash_entry(hash, token);
2188 void setHashEntry(SetupFileHash *hash, char *token, char *value)
2195 value_copy = getStringCopy(value);
2197 // change value; if it does not exist, insert it as new
2198 if (!change_hash_entry(hash, token, value_copy))
2199 if (!insert_hash_entry(hash, getStringCopy(token), value_copy))
2200 Fail("cannot insert into hash -- aborting");
2203 char *removeHashEntry(SetupFileHash *hash, char *token)
2208 return remove_hash_entry(hash, token);
2211 #if ENABLE_UNUSED_CODE
2213 static void printSetupFileHash(SetupFileHash *hash)
2215 BEGIN_HASH_ITERATION(hash, itr)
2217 Debug("setup:printSetupFileHash", "token: '%s'", HASH_ITERATION_TOKEN(itr));
2218 Debug("setup:printSetupFileHash", "value: '%s'", HASH_ITERATION_VALUE(itr));
2220 END_HASH_ITERATION(hash, itr)
2225 #define ALLOW_TOKEN_VALUE_SEPARATOR_BEING_WHITESPACE 1
2226 #define CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING 0
2227 #define CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH 0
2229 static boolean token_value_separator_found = FALSE;
2230 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2231 static boolean token_value_separator_warning = FALSE;
2233 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2234 static boolean token_already_exists_warning = FALSE;
2237 static boolean getTokenValueFromSetupLineExt(char *line,
2238 char **token_ptr, char **value_ptr,
2239 char *filename, char *line_raw,
2241 boolean separator_required)
2243 static char line_copy[MAX_LINE_LEN + 1], line_raw_copy[MAX_LINE_LEN + 1];
2244 char *token, *value, *line_ptr;
2246 // when externally invoked via ReadTokenValueFromLine(), copy line buffers
2247 if (line_raw == NULL)
2249 strncpy(line_copy, line, MAX_LINE_LEN);
2250 line_copy[MAX_LINE_LEN] = '\0';
2253 strcpy(line_raw_copy, line_copy);
2254 line_raw = line_raw_copy;
2257 // cut trailing comment from input line
2258 for (line_ptr = line; *line_ptr; line_ptr++)
2260 if (*line_ptr == '#')
2267 // cut trailing whitespaces from input line
2268 for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
2269 if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
2272 // ignore empty lines
2276 // cut leading whitespaces from token
2277 for (token = line; *token; token++)
2278 if (*token != ' ' && *token != '\t')
2281 // start with empty value as reliable default
2284 token_value_separator_found = FALSE;
2286 // find end of token to determine start of value
2287 for (line_ptr = token; *line_ptr; line_ptr++)
2289 // first look for an explicit token/value separator, like ':' or '='
2290 if (*line_ptr == ':' || *line_ptr == '=')
2292 *line_ptr = '\0'; // terminate token string
2293 value = line_ptr + 1; // set beginning of value
2295 token_value_separator_found = TRUE;
2301 #if ALLOW_TOKEN_VALUE_SEPARATOR_BEING_WHITESPACE
2302 // fallback: if no token/value separator found, also allow whitespaces
2303 if (!token_value_separator_found && !separator_required)
2305 for (line_ptr = token; *line_ptr; line_ptr++)
2307 if (*line_ptr == ' ' || *line_ptr == '\t')
2309 *line_ptr = '\0'; // terminate token string
2310 value = line_ptr + 1; // set beginning of value
2312 token_value_separator_found = TRUE;
2318 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2319 if (token_value_separator_found)
2321 if (!token_value_separator_warning)
2323 Debug("setup", "---");
2325 if (filename != NULL)
2327 Debug("setup", "missing token/value separator(s) in config file:");
2328 Debug("setup", "- config file: '%s'", filename);
2332 Debug("setup", "missing token/value separator(s):");
2335 token_value_separator_warning = TRUE;
2338 if (filename != NULL)
2339 Debug("setup", "- line %d: '%s'", line_nr, line_raw);
2341 Debug("setup", "- line: '%s'", line_raw);
2347 // cut trailing whitespaces from token
2348 for (line_ptr = &token[strlen(token)]; line_ptr >= token; line_ptr--)
2349 if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
2352 // cut leading whitespaces from value
2353 for (; *value; value++)
2354 if (*value != ' ' && *value != '\t')
2363 boolean getTokenValueFromSetupLine(char *line, char **token, char **value)
2365 // while the internal (old) interface does not require a token/value
2366 // separator (for downwards compatibility with existing files which
2367 // don't use them), it is mandatory for the external (new) interface
2369 return getTokenValueFromSetupLineExt(line, token, value, NULL, NULL, 0, TRUE);
2372 static boolean loadSetupFileData(void *setup_file_data, char *filename,
2373 boolean top_recursion_level, boolean is_hash)
2375 static SetupFileHash *include_filename_hash = NULL;
2376 char line[MAX_LINE_LEN], line_raw[MAX_LINE_LEN], previous_line[MAX_LINE_LEN];
2377 char *token, *value, *line_ptr;
2378 void *insert_ptr = NULL;
2379 boolean read_continued_line = FALSE;
2381 int line_nr = 0, token_count = 0, include_count = 0;
2383 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2384 token_value_separator_warning = FALSE;
2387 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2388 token_already_exists_warning = FALSE;
2391 if (!(file = openFile(filename, MODE_READ)))
2393 #if DEBUG_NO_CONFIG_FILE
2394 Debug("setup", "cannot open configuration file '%s'", filename);
2400 // use "insert pointer" to store list end for constant insertion complexity
2402 insert_ptr = setup_file_data;
2404 // on top invocation, create hash to mark included files (to prevent loops)
2405 if (top_recursion_level)
2406 include_filename_hash = newSetupFileHash();
2408 // mark this file as already included (to prevent including it again)
2409 setHashEntry(include_filename_hash, getBaseNamePtr(filename), "true");
2411 while (!checkEndOfFile(file))
2413 // read next line of input file
2414 if (!getStringFromFile(file, line, MAX_LINE_LEN))
2417 // check if line was completely read and is terminated by line break
2418 if (strlen(line) > 0 && line[strlen(line) - 1] == '\n')
2421 // cut trailing line break (this can be newline and/or carriage return)
2422 for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
2423 if ((*line_ptr == '\n' || *line_ptr == '\r') && *(line_ptr + 1) == '\0')
2426 // copy raw input line for later use (mainly debugging output)
2427 strcpy(line_raw, line);
2429 if (read_continued_line)
2431 // append new line to existing line, if there is enough space
2432 if (strlen(previous_line) + strlen(line_ptr) < MAX_LINE_LEN)
2433 strcat(previous_line, line_ptr);
2435 strcpy(line, previous_line); // copy storage buffer to line
2437 read_continued_line = FALSE;
2440 // if the last character is '\', continue at next line
2441 if (strlen(line) > 0 && line[strlen(line) - 1] == '\\')
2443 line[strlen(line) - 1] = '\0'; // cut off trailing backslash
2444 strcpy(previous_line, line); // copy line to storage buffer
2446 read_continued_line = TRUE;
2451 if (!getTokenValueFromSetupLineExt(line, &token, &value, filename,
2452 line_raw, line_nr, FALSE))
2457 if (strEqual(token, "include"))
2459 if (getHashEntry(include_filename_hash, value) == NULL)
2461 char *basepath = getBasePath(filename);
2462 char *basename = getBaseName(value);
2463 char *filename_include = getPath2(basepath, basename);
2465 loadSetupFileData(setup_file_data, filename_include, FALSE, is_hash);
2469 free(filename_include);
2475 Warn("ignoring already processed file '%s'", value);
2482 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2484 getHashEntry((SetupFileHash *)setup_file_data, token);
2486 if (old_value != NULL)
2488 if (!token_already_exists_warning)
2490 Debug("setup", "---");
2491 Debug("setup", "duplicate token(s) found in config file:");
2492 Debug("setup", "- config file: '%s'", filename);
2494 token_already_exists_warning = TRUE;
2497 Debug("setup", "- token: '%s' (in line %d)", token, line_nr);
2498 Debug("setup", " old value: '%s'", old_value);
2499 Debug("setup", " new value: '%s'", value);
2503 setHashEntry((SetupFileHash *)setup_file_data, token, value);
2507 insert_ptr = addListEntry((SetupFileList *)insert_ptr, token, value);
2517 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2518 if (token_value_separator_warning)
2519 Debug("setup", "---");
2522 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2523 if (token_already_exists_warning)
2524 Debug("setup", "---");
2527 if (token_count == 0 && include_count == 0)
2528 Warn("configuration file '%s' is empty", filename);
2530 if (top_recursion_level)
2531 freeSetupFileHash(include_filename_hash);
2536 static int compareSetupFileData(const void *object1, const void *object2)
2538 const struct ConfigInfo *entry1 = (struct ConfigInfo *)object1;
2539 const struct ConfigInfo *entry2 = (struct ConfigInfo *)object2;
2541 return strcmp(entry1->token, entry2->token);
2544 static void saveSetupFileHash(SetupFileHash *hash, char *filename)
2546 int item_count = hashtable_count(hash);
2547 int item_size = sizeof(struct ConfigInfo);
2548 struct ConfigInfo *sort_array = checked_malloc(item_count * item_size);
2552 // copy string pointers from hash to array
2553 BEGIN_HASH_ITERATION(hash, itr)
2555 sort_array[i].token = HASH_ITERATION_TOKEN(itr);
2556 sort_array[i].value = HASH_ITERATION_VALUE(itr);
2560 if (i > item_count) // should never happen
2563 END_HASH_ITERATION(hash, itr)
2565 // sort string pointers from hash in array
2566 qsort(sort_array, item_count, item_size, compareSetupFileData);
2568 if (!(file = fopen(filename, MODE_WRITE)))
2570 Warn("cannot write configuration file '%s'", filename);
2575 fprintf(file, "%s\n\n", getFormattedSetupEntry("program.version",
2576 program.version_string));
2577 for (i = 0; i < item_count; i++)
2578 fprintf(file, "%s\n", getFormattedSetupEntry(sort_array[i].token,
2579 sort_array[i].value));
2582 checked_free(sort_array);
2585 SetupFileList *loadSetupFileList(char *filename)
2587 SetupFileList *setup_file_list = newSetupFileList("", "");
2588 SetupFileList *first_valid_list_entry;
2590 if (!loadSetupFileData(setup_file_list, filename, TRUE, FALSE))
2592 freeSetupFileList(setup_file_list);
2597 first_valid_list_entry = setup_file_list->next;
2599 // free empty list header
2600 setup_file_list->next = NULL;
2601 freeSetupFileList(setup_file_list);
2603 return first_valid_list_entry;
2606 SetupFileHash *loadSetupFileHash(char *filename)
2608 SetupFileHash *setup_file_hash = newSetupFileHash();
2610 if (!loadSetupFileData(setup_file_hash, filename, TRUE, TRUE))
2612 freeSetupFileHash(setup_file_hash);
2617 return setup_file_hash;
2621 // ============================================================================
2623 // ============================================================================
2625 #define TOKEN_STR_LAST_LEVEL_SERIES "last_level_series"
2626 #define TOKEN_STR_LAST_PLAYED_LEVEL "last_played_level"
2627 #define TOKEN_STR_HANDICAP_LEVEL "handicap_level"
2628 #define TOKEN_STR_LAST_USER "last_user"
2630 // level directory info
2631 #define LEVELINFO_TOKEN_IDENTIFIER 0
2632 #define LEVELINFO_TOKEN_NAME 1
2633 #define LEVELINFO_TOKEN_NAME_SORTING 2
2634 #define LEVELINFO_TOKEN_AUTHOR 3
2635 #define LEVELINFO_TOKEN_YEAR 4
2636 #define LEVELINFO_TOKEN_PROGRAM_TITLE 5
2637 #define LEVELINFO_TOKEN_PROGRAM_COPYRIGHT 6
2638 #define LEVELINFO_TOKEN_PROGRAM_COMPANY 7
2639 #define LEVELINFO_TOKEN_IMPORTED_FROM 8
2640 #define LEVELINFO_TOKEN_IMPORTED_BY 9
2641 #define LEVELINFO_TOKEN_TESTED_BY 10
2642 #define LEVELINFO_TOKEN_LEVELS 11
2643 #define LEVELINFO_TOKEN_FIRST_LEVEL 12
2644 #define LEVELINFO_TOKEN_SORT_PRIORITY 13
2645 #define LEVELINFO_TOKEN_LATEST_ENGINE 14
2646 #define LEVELINFO_TOKEN_LEVEL_GROUP 15
2647 #define LEVELINFO_TOKEN_READONLY 16
2648 #define LEVELINFO_TOKEN_GRAPHICS_SET_ECS 17
2649 #define LEVELINFO_TOKEN_GRAPHICS_SET_AGA 18
2650 #define LEVELINFO_TOKEN_GRAPHICS_SET 19
2651 #define LEVELINFO_TOKEN_SOUNDS_SET_DEFAULT 20
2652 #define LEVELINFO_TOKEN_SOUNDS_SET_LOWPASS 21
2653 #define LEVELINFO_TOKEN_SOUNDS_SET 22
2654 #define LEVELINFO_TOKEN_MUSIC_SET 23
2655 #define LEVELINFO_TOKEN_FILENAME 24
2656 #define LEVELINFO_TOKEN_FILETYPE 25
2657 #define LEVELINFO_TOKEN_SPECIAL_FLAGS 26
2658 #define LEVELINFO_TOKEN_EMPTY_LEVEL_NAME 27
2659 #define LEVELINFO_TOKEN_FORCE_LEVEL_NAME 28
2660 #define LEVELINFO_TOKEN_HANDICAP 29
2661 #define LEVELINFO_TOKEN_SKIP_LEVELS 30
2662 #define LEVELINFO_TOKEN_USE_EMC_TILES 31
2664 #define NUM_LEVELINFO_TOKENS 32
2666 static LevelDirTree ldi;
2668 static struct TokenInfo levelinfo_tokens[] =
2670 // level directory info
2671 { TYPE_STRING, &ldi.identifier, "identifier" },
2672 { TYPE_STRING, &ldi.name, "name" },
2673 { TYPE_STRING, &ldi.name_sorting, "name_sorting" },
2674 { TYPE_STRING, &ldi.author, "author" },
2675 { TYPE_STRING, &ldi.year, "year" },
2676 { TYPE_STRING, &ldi.program_title, "program_title" },
2677 { TYPE_STRING, &ldi.program_copyright, "program_copyright" },
2678 { TYPE_STRING, &ldi.program_company, "program_company" },
2679 { TYPE_STRING, &ldi.imported_from, "imported_from" },
2680 { TYPE_STRING, &ldi.imported_by, "imported_by" },
2681 { TYPE_STRING, &ldi.tested_by, "tested_by" },
2682 { TYPE_INTEGER, &ldi.levels, "levels" },
2683 { TYPE_INTEGER, &ldi.first_level, "first_level" },
2684 { TYPE_INTEGER, &ldi.sort_priority, "sort_priority" },
2685 { TYPE_BOOLEAN, &ldi.latest_engine, "latest_engine" },
2686 { TYPE_BOOLEAN, &ldi.level_group, "level_group" },
2687 { TYPE_BOOLEAN, &ldi.readonly, "readonly" },
2688 { TYPE_STRING, &ldi.graphics_set_ecs, "graphics_set.ecs" },
2689 { TYPE_STRING, &ldi.graphics_set_aga, "graphics_set.aga" },
2690 { TYPE_STRING, &ldi.graphics_set, "graphics_set" },
2691 { TYPE_STRING, &ldi.sounds_set_default,"sounds_set.default" },
2692 { TYPE_STRING, &ldi.sounds_set_lowpass,"sounds_set.lowpass" },
2693 { TYPE_STRING, &ldi.sounds_set, "sounds_set" },
2694 { TYPE_STRING, &ldi.music_set, "music_set" },
2695 { TYPE_STRING, &ldi.level_filename, "filename" },
2696 { TYPE_STRING, &ldi.level_filetype, "filetype" },
2697 { TYPE_STRING, &ldi.special_flags, "special_flags" },
2698 { TYPE_STRING, &ldi.empty_level_name, "empty_level_name" },
2699 { TYPE_BOOLEAN, &ldi.force_level_name, "force_level_name" },
2700 { TYPE_BOOLEAN, &ldi.handicap, "handicap" },
2701 { TYPE_BOOLEAN, &ldi.skip_levels, "skip_levels" },
2702 { TYPE_BOOLEAN, &ldi.use_emc_tiles, "use_emc_tiles" }
2705 static struct TokenInfo artworkinfo_tokens[] =
2707 // artwork directory info
2708 { TYPE_STRING, &ldi.identifier, "identifier" },
2709 { TYPE_STRING, &ldi.subdir, "subdir" },
2710 { TYPE_STRING, &ldi.name, "name" },
2711 { TYPE_STRING, &ldi.name_sorting, "name_sorting" },
2712 { TYPE_STRING, &ldi.author, "author" },
2713 { TYPE_STRING, &ldi.program_title, "program_title" },
2714 { TYPE_STRING, &ldi.program_copyright, "program_copyright" },
2715 { TYPE_STRING, &ldi.program_company, "program_company" },
2716 { TYPE_INTEGER, &ldi.sort_priority, "sort_priority" },
2717 { TYPE_STRING, &ldi.basepath, "basepath" },
2718 { TYPE_STRING, &ldi.fullpath, "fullpath" },
2719 { TYPE_BOOLEAN, &ldi.in_user_dir, "in_user_dir" },
2720 { TYPE_STRING, &ldi.class_desc, "class_desc" },
2725 static char *optional_tokens[] =
2728 "program_copyright",
2734 static void setTreeInfoToDefaults(TreeInfo *ti, int type)
2738 ti->node_top = (ti->type == TREE_TYPE_LEVEL_DIR ? &leveldir_first :
2739 ti->type == TREE_TYPE_GRAPHICS_DIR ? &artwork.gfx_first :
2740 ti->type == TREE_TYPE_SOUNDS_DIR ? &artwork.snd_first :
2741 ti->type == TREE_TYPE_MUSIC_DIR ? &artwork.mus_first :
2744 ti->node_parent = NULL;
2745 ti->node_group = NULL;
2752 ti->fullpath = NULL;
2753 ti->basepath = NULL;
2754 ti->identifier = NULL;
2755 ti->name = getStringCopy(ANONYMOUS_NAME);
2756 ti->name_sorting = NULL;
2757 ti->author = getStringCopy(ANONYMOUS_NAME);
2760 ti->program_title = NULL;
2761 ti->program_copyright = NULL;
2762 ti->program_company = NULL;
2764 ti->sort_priority = LEVELCLASS_UNDEFINED; // default: least priority
2765 ti->latest_engine = FALSE; // default: get from level
2766 ti->parent_link = FALSE;
2767 ti->is_copy = FALSE;
2768 ti->in_user_dir = FALSE;
2769 ti->user_defined = FALSE;
2771 ti->class_desc = NULL;
2773 ti->infotext = getStringCopy(TREE_INFOTEXT(ti->type));
2775 if (ti->type == TREE_TYPE_LEVEL_DIR)
2777 ti->imported_from = NULL;
2778 ti->imported_by = NULL;
2779 ti->tested_by = NULL;
2781 ti->graphics_set_ecs = NULL;
2782 ti->graphics_set_aga = NULL;
2783 ti->graphics_set = NULL;
2784 ti->sounds_set_default = NULL;
2785 ti->sounds_set_lowpass = NULL;
2786 ti->sounds_set = NULL;
2787 ti->music_set = NULL;
2788 ti->graphics_path = getStringCopy(UNDEFINED_FILENAME);
2789 ti->sounds_path = getStringCopy(UNDEFINED_FILENAME);
2790 ti->music_path = getStringCopy(UNDEFINED_FILENAME);
2792 ti->level_filename = NULL;
2793 ti->level_filetype = NULL;
2795 ti->special_flags = NULL;
2797 ti->empty_level_name = NULL;
2798 ti->force_level_name = FALSE;
2801 ti->first_level = 0;
2803 ti->level_group = FALSE;
2804 ti->handicap_level = 0;
2805 ti->readonly = TRUE;
2806 ti->handicap = TRUE;
2807 ti->skip_levels = FALSE;
2809 ti->use_emc_tiles = FALSE;
2813 static void setTreeInfoToDefaultsFromParent(TreeInfo *ti, TreeInfo *parent)
2817 Warn("setTreeInfoToDefaultsFromParent(): parent == NULL");
2819 setTreeInfoToDefaults(ti, TREE_TYPE_UNDEFINED);
2824 // copy all values from the parent structure
2826 ti->type = parent->type;
2828 ti->node_top = parent->node_top;
2829 ti->node_parent = parent;
2830 ti->node_group = NULL;
2837 ti->fullpath = NULL;
2838 ti->basepath = NULL;
2839 ti->identifier = NULL;
2840 ti->name = getStringCopy(ANONYMOUS_NAME);
2841 ti->name_sorting = NULL;
2842 ti->author = getStringCopy(parent->author);
2843 ti->year = getStringCopy(parent->year);
2845 ti->program_title = getStringCopy(parent->program_title);
2846 ti->program_copyright = getStringCopy(parent->program_copyright);
2847 ti->program_company = getStringCopy(parent->program_company);
2849 ti->sort_priority = parent->sort_priority;
2850 ti->latest_engine = parent->latest_engine;
2851 ti->parent_link = FALSE;
2852 ti->is_copy = FALSE;
2853 ti->in_user_dir = parent->in_user_dir;
2854 ti->user_defined = parent->user_defined;
2855 ti->color = parent->color;
2856 ti->class_desc = getStringCopy(parent->class_desc);
2858 ti->infotext = getStringCopy(parent->infotext);
2860 if (ti->type == TREE_TYPE_LEVEL_DIR)
2862 ti->imported_from = getStringCopy(parent->imported_from);
2863 ti->imported_by = getStringCopy(parent->imported_by);
2864 ti->tested_by = getStringCopy(parent->tested_by);
2866 ti->graphics_set_ecs = getStringCopy(parent->graphics_set_ecs);
2867 ti->graphics_set_aga = getStringCopy(parent->graphics_set_aga);
2868 ti->graphics_set = getStringCopy(parent->graphics_set);
2869 ti->sounds_set_default = getStringCopy(parent->sounds_set_default);
2870 ti->sounds_set_lowpass = getStringCopy(parent->sounds_set_lowpass);
2871 ti->sounds_set = getStringCopy(parent->sounds_set);
2872 ti->music_set = getStringCopy(parent->music_set);
2873 ti->graphics_path = getStringCopy(UNDEFINED_FILENAME);
2874 ti->sounds_path = getStringCopy(UNDEFINED_FILENAME);
2875 ti->music_path = getStringCopy(UNDEFINED_FILENAME);
2877 ti->level_filename = getStringCopy(parent->level_filename);
2878 ti->level_filetype = getStringCopy(parent->level_filetype);
2880 ti->special_flags = getStringCopy(parent->special_flags);
2882 ti->empty_level_name = getStringCopy(parent->empty_level_name);
2883 ti->force_level_name = parent->force_level_name;
2885 ti->levels = parent->levels;
2886 ti->first_level = parent->first_level;
2887 ti->last_level = parent->last_level;
2888 ti->level_group = FALSE;
2889 ti->handicap_level = parent->handicap_level;
2890 ti->readonly = parent->readonly;
2891 ti->handicap = parent->handicap;
2892 ti->skip_levels = parent->skip_levels;
2894 ti->use_emc_tiles = parent->use_emc_tiles;
2898 static TreeInfo *getTreeInfoCopy(TreeInfo *ti)
2900 TreeInfo *ti_copy = newTreeInfo();
2902 // copy all values from the original structure
2904 ti_copy->type = ti->type;
2906 ti_copy->node_top = ti->node_top;
2907 ti_copy->node_parent = ti->node_parent;
2908 ti_copy->node_group = ti->node_group;
2909 ti_copy->next = ti->next;
2911 ti_copy->cl_first = ti->cl_first;
2912 ti_copy->cl_cursor = ti->cl_cursor;
2914 ti_copy->subdir = getStringCopy(ti->subdir);
2915 ti_copy->fullpath = getStringCopy(ti->fullpath);
2916 ti_copy->basepath = getStringCopy(ti->basepath);
2917 ti_copy->identifier = getStringCopy(ti->identifier);
2918 ti_copy->name = getStringCopy(ti->name);
2919 ti_copy->name_sorting = getStringCopy(ti->name_sorting);
2920 ti_copy->author = getStringCopy(ti->author);
2921 ti_copy->year = getStringCopy(ti->year);
2923 ti_copy->program_title = getStringCopy(ti->program_title);
2924 ti_copy->program_copyright = getStringCopy(ti->program_copyright);
2925 ti_copy->program_company = getStringCopy(ti->program_company);
2927 ti_copy->imported_from = getStringCopy(ti->imported_from);
2928 ti_copy->imported_by = getStringCopy(ti->imported_by);
2929 ti_copy->tested_by = getStringCopy(ti->tested_by);
2931 ti_copy->graphics_set_ecs = getStringCopy(ti->graphics_set_ecs);
2932 ti_copy->graphics_set_aga = getStringCopy(ti->graphics_set_aga);
2933 ti_copy->graphics_set = getStringCopy(ti->graphics_set);
2934 ti_copy->sounds_set_default = getStringCopy(ti->sounds_set_default);
2935 ti_copy->sounds_set_lowpass = getStringCopy(ti->sounds_set_lowpass);
2936 ti_copy->sounds_set = getStringCopy(ti->sounds_set);
2937 ti_copy->music_set = getStringCopy(ti->music_set);
2938 ti_copy->graphics_path = getStringCopy(ti->graphics_path);
2939 ti_copy->sounds_path = getStringCopy(ti->sounds_path);
2940 ti_copy->music_path = getStringCopy(ti->music_path);
2942 ti_copy->level_filename = getStringCopy(ti->level_filename);
2943 ti_copy->level_filetype = getStringCopy(ti->level_filetype);
2945 ti_copy->special_flags = getStringCopy(ti->special_flags);
2947 ti_copy->empty_level_name = getStringCopy(ti->empty_level_name);
2948 ti_copy->force_level_name = ti->force_level_name;
2950 ti_copy->levels = ti->levels;
2951 ti_copy->first_level = ti->first_level;
2952 ti_copy->last_level = ti->last_level;
2953 ti_copy->sort_priority = ti->sort_priority;
2955 ti_copy->latest_engine = ti->latest_engine;
2957 ti_copy->level_group = ti->level_group;
2958 ti_copy->parent_link = ti->parent_link;
2959 ti_copy->is_copy = ti->is_copy;
2960 ti_copy->in_user_dir = ti->in_user_dir;
2961 ti_copy->user_defined = ti->user_defined;
2962 ti_copy->readonly = ti->readonly;
2963 ti_copy->handicap = ti->handicap;
2964 ti_copy->skip_levels = ti->skip_levels;
2966 ti_copy->use_emc_tiles = ti->use_emc_tiles;
2968 ti_copy->color = ti->color;
2969 ti_copy->class_desc = getStringCopy(ti->class_desc);
2970 ti_copy->handicap_level = ti->handicap_level;
2972 ti_copy->infotext = getStringCopy(ti->infotext);
2977 void freeTreeInfo(TreeInfo *ti)
2982 checked_free(ti->subdir);
2983 checked_free(ti->fullpath);
2984 checked_free(ti->basepath);
2985 checked_free(ti->identifier);
2987 checked_free(ti->name);
2988 checked_free(ti->name_sorting);
2989 checked_free(ti->author);
2990 checked_free(ti->year);
2992 checked_free(ti->program_title);
2993 checked_free(ti->program_copyright);
2994 checked_free(ti->program_company);
2996 checked_free(ti->class_desc);
2998 checked_free(ti->infotext);
3000 if (ti->type == TREE_TYPE_LEVEL_DIR)
3002 checked_free(ti->imported_from);
3003 checked_free(ti->imported_by);
3004 checked_free(ti->tested_by);
3006 checked_free(ti->graphics_set_ecs);
3007 checked_free(ti->graphics_set_aga);
3008 checked_free(ti->graphics_set);
3009 checked_free(ti->sounds_set_default);
3010 checked_free(ti->sounds_set_lowpass);
3011 checked_free(ti->sounds_set);
3012 checked_free(ti->music_set);
3014 checked_free(ti->graphics_path);
3015 checked_free(ti->sounds_path);
3016 checked_free(ti->music_path);
3018 checked_free(ti->level_filename);
3019 checked_free(ti->level_filetype);
3021 checked_free(ti->special_flags);
3024 // recursively free child node
3026 freeTreeInfo(ti->node_group);
3028 // recursively free next node
3030 freeTreeInfo(ti->next);
3035 void setSetupInfo(struct TokenInfo *token_info,
3036 int token_nr, char *token_value)
3038 int token_type = token_info[token_nr].type;
3039 void *setup_value = token_info[token_nr].value;
3041 if (token_value == NULL)
3044 // set setup field to corresponding token value
3049 *(boolean *)setup_value = get_boolean_from_string(token_value);
3053 *(int *)setup_value = get_switch3_from_string(token_value);
3057 *(Key *)setup_value = getKeyFromKeyName(token_value);
3061 *(Key *)setup_value = getKeyFromX11KeyName(token_value);
3065 *(int *)setup_value = get_integer_from_string(token_value);
3069 checked_free(*(char **)setup_value);
3070 *(char **)setup_value = getStringCopy(token_value);
3074 *(int *)setup_value = get_player_nr_from_string(token_value);
3082 static int compareTreeInfoEntries(const void *object1, const void *object2)
3084 const TreeInfo *entry1 = *((TreeInfo **)object1);
3085 const TreeInfo *entry2 = *((TreeInfo **)object2);
3086 int tree_sorting1 = TREE_SORTING(entry1);
3087 int tree_sorting2 = TREE_SORTING(entry2);
3089 if (tree_sorting1 != tree_sorting2)
3090 return (tree_sorting1 - tree_sorting2);
3092 return strcasecmp(entry1->name_sorting, entry2->name_sorting);
3095 static TreeInfo *createParentTreeInfoNode(TreeInfo *node_parent)
3099 if (node_parent == NULL)
3102 ti_new = newTreeInfo();
3103 setTreeInfoToDefaults(ti_new, node_parent->type);
3105 ti_new->node_parent = node_parent;
3106 ti_new->parent_link = TRUE;
3108 setString(&ti_new->identifier, node_parent->identifier);
3109 setString(&ti_new->name, BACKLINK_TEXT_PARENT);
3110 setString(&ti_new->name_sorting, ti_new->name);
3112 setString(&ti_new->subdir, STRING_PARENT_DIRECTORY);
3113 setString(&ti_new->fullpath, node_parent->fullpath);
3115 ti_new->sort_priority = LEVELCLASS_PARENT;
3116 ti_new->latest_engine = node_parent->latest_engine;
3118 setString(&ti_new->class_desc, getLevelClassDescription(ti_new));
3120 pushTreeInfo(&node_parent->node_group, ti_new);
3125 static TreeInfo *createTopTreeInfoNode(TreeInfo *node_first)
3127 if (node_first == NULL)
3130 TreeInfo *ti_new = newTreeInfo();
3131 int type = node_first->type;
3133 setTreeInfoToDefaults(ti_new, type);
3135 ti_new->node_parent = NULL;
3136 ti_new->parent_link = FALSE;
3138 setString(&ti_new->identifier, "top_tree_node");
3139 setString(&ti_new->name, TREE_INFOTEXT(type));
3140 setString(&ti_new->name_sorting, ti_new->name);
3142 setString(&ti_new->subdir, STRING_TOP_DIRECTORY);
3143 setString(&ti_new->fullpath, ".");
3145 ti_new->sort_priority = LEVELCLASS_TOP;
3146 ti_new->latest_engine = node_first->latest_engine;
3148 setString(&ti_new->class_desc, TREE_INFOTEXT(type));
3150 ti_new->node_group = node_first;
3151 ti_new->level_group = TRUE;
3153 TreeInfo *ti_new2 = createParentTreeInfoNode(ti_new);
3155 setString(&ti_new2->name, TREE_BACKLINK_TEXT(type));
3156 setString(&ti_new2->name_sorting, ti_new2->name);
3161 static void setTreeInfoParentNodes(TreeInfo *node, TreeInfo *node_parent)
3165 if (node->node_group)
3166 setTreeInfoParentNodes(node->node_group, node);
3168 node->node_parent = node_parent;
3174 TreeInfo *addTopTreeInfoNode(TreeInfo *node_first)
3176 // add top tree node with back link node in previous tree
3177 node_first = createTopTreeInfoNode(node_first);
3179 // set all parent links (back links) in complete tree
3180 setTreeInfoParentNodes(node_first, NULL);
3186 // ----------------------------------------------------------------------------
3187 // functions for handling level and custom artwork info cache
3188 // ----------------------------------------------------------------------------
3190 static void LoadArtworkInfoCache(void)
3192 InitCacheDirectory();
3194 if (artworkinfo_cache_old == NULL)
3196 char *filename = getPath2(getCacheDir(), ARTWORKINFO_CACHE_FILE);
3198 // try to load artwork info hash from already existing cache file
3199 artworkinfo_cache_old = loadSetupFileHash(filename);
3201 // try to get program version that artwork info cache was written with
3202 char *version = getHashEntry(artworkinfo_cache_old, "program.version");
3204 // check program version of artwork info cache against current version
3205 if (!strEqual(version, program.version_string))
3207 freeSetupFileHash(artworkinfo_cache_old);
3209 artworkinfo_cache_old = NULL;
3212 // if no artwork info cache file was found, start with empty hash
3213 if (artworkinfo_cache_old == NULL)
3214 artworkinfo_cache_old = newSetupFileHash();
3219 if (artworkinfo_cache_new == NULL)
3220 artworkinfo_cache_new = newSetupFileHash();
3222 update_artworkinfo_cache = FALSE;
3225 static void SaveArtworkInfoCache(void)
3227 if (!update_artworkinfo_cache)
3230 char *filename = getPath2(getCacheDir(), ARTWORKINFO_CACHE_FILE);
3232 InitCacheDirectory();
3234 saveSetupFileHash(artworkinfo_cache_new, filename);
3239 static char *getCacheTokenPrefix(char *prefix1, char *prefix2)
3241 static char *prefix = NULL;
3243 checked_free(prefix);
3245 prefix = getStringCat2WithSeparator(prefix1, prefix2, ".");
3250 // (identical to above function, but separate string buffer needed -- nasty)
3251 static char *getCacheToken(char *prefix, char *suffix)
3253 static char *token = NULL;
3255 checked_free(token);
3257 token = getStringCat2WithSeparator(prefix, suffix, ".");
3262 static char *getFileTimestampString(char *filename)
3264 return getStringCopy(i_to_a(getFileTimestampEpochSeconds(filename)));
3267 static boolean modifiedFileTimestamp(char *filename, char *timestamp_string)
3269 struct stat file_status;
3271 if (timestamp_string == NULL)
3274 if (!fileExists(filename)) // file does not exist
3275 return (atoi(timestamp_string) != 0);
3277 if (stat(filename, &file_status) != 0) // cannot stat file
3280 return (file_status.st_mtime != atoi(timestamp_string));
3283 static TreeInfo *getArtworkInfoCacheEntry(LevelDirTree *level_node, int type)
3285 char *identifier = level_node->subdir;
3286 char *type_string = ARTWORK_DIRECTORY(type);
3287 char *token_prefix = getCacheTokenPrefix(type_string, identifier);
3288 char *token_main = getCacheToken(token_prefix, "CACHED");
3289 char *cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
3290 boolean cached = (cache_entry != NULL && strEqual(cache_entry, "true"));
3291 TreeInfo *artwork_info = NULL;
3293 if (!use_artworkinfo_cache)
3296 if (optional_tokens_hash == NULL)
3300 // create hash from list of optional tokens (for quick access)
3301 optional_tokens_hash = newSetupFileHash();
3302 for (i = 0; optional_tokens[i] != NULL; i++)
3303 setHashEntry(optional_tokens_hash, optional_tokens[i], "");
3310 artwork_info = newTreeInfo();
3311 setTreeInfoToDefaults(artwork_info, type);
3313 // set all structure fields according to the token/value pairs
3314 ldi = *artwork_info;
3315 for (i = 0; artworkinfo_tokens[i].type != -1; i++)
3317 char *token_suffix = artworkinfo_tokens[i].text;
3318 char *token = getCacheToken(token_prefix, token_suffix);
3319 char *value = getHashEntry(artworkinfo_cache_old, token);
3321 (getHashEntry(optional_tokens_hash, token_suffix) != NULL);
3323 setSetupInfo(artworkinfo_tokens, i, value);
3325 // check if cache entry for this item is mandatory, but missing
3326 if (value == NULL && !optional)
3328 Warn("missing cache entry '%s'", token);
3334 *artwork_info = ldi;
3339 char *filename_levelinfo = getPath2(getLevelDirFromTreeInfo(level_node),
3340 LEVELINFO_FILENAME);
3341 char *filename_artworkinfo = getPath2(getSetupArtworkDir(artwork_info),
3342 ARTWORKINFO_FILENAME(type));
3344 // check if corresponding "levelinfo.conf" file has changed
3345 token_main = getCacheToken(token_prefix, "TIMESTAMP_LEVELINFO");
3346 cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
3348 if (modifiedFileTimestamp(filename_levelinfo, cache_entry))
3351 // check if corresponding "<artworkinfo>.conf" file has changed
3352 token_main = getCacheToken(token_prefix, "TIMESTAMP_ARTWORKINFO");
3353 cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
3355 if (modifiedFileTimestamp(filename_artworkinfo, cache_entry))
3358 checked_free(filename_levelinfo);
3359 checked_free(filename_artworkinfo);
3362 if (!cached && artwork_info != NULL)
3364 freeTreeInfo(artwork_info);
3369 return artwork_info;
3372 static void setArtworkInfoCacheEntry(TreeInfo *artwork_info,
3373 LevelDirTree *level_node, int type)
3375 char *identifier = level_node->subdir;
3376 char *type_string = ARTWORK_DIRECTORY(type);
3377 char *token_prefix = getCacheTokenPrefix(type_string, identifier);
3378 char *token_main = getCacheToken(token_prefix, "CACHED");
3379 boolean set_cache_timestamps = TRUE;
3382 setHashEntry(artworkinfo_cache_new, token_main, "true");
3384 if (set_cache_timestamps)
3386 char *filename_levelinfo = getPath2(getLevelDirFromTreeInfo(level_node),
3387 LEVELINFO_FILENAME);
3388 char *filename_artworkinfo = getPath2(getSetupArtworkDir(artwork_info),
3389 ARTWORKINFO_FILENAME(type));
3390 char *timestamp_levelinfo = getFileTimestampString(filename_levelinfo);
3391 char *timestamp_artworkinfo = getFileTimestampString(filename_artworkinfo);
3393 token_main = getCacheToken(token_prefix, "TIMESTAMP_LEVELINFO");
3394 setHashEntry(artworkinfo_cache_new, token_main, timestamp_levelinfo);
3396 token_main = getCacheToken(token_prefix, "TIMESTAMP_ARTWORKINFO");
3397 setHashEntry(artworkinfo_cache_new, token_main, timestamp_artworkinfo);
3399 checked_free(filename_levelinfo);
3400 checked_free(filename_artworkinfo);
3401 checked_free(timestamp_levelinfo);
3402 checked_free(timestamp_artworkinfo);
3405 ldi = *artwork_info;
3406 for (i = 0; artworkinfo_tokens[i].type != -1; i++)
3408 char *token = getCacheToken(token_prefix, artworkinfo_tokens[i].text);
3409 char *value = getSetupValue(artworkinfo_tokens[i].type,
3410 artworkinfo_tokens[i].value);
3412 setHashEntry(artworkinfo_cache_new, token, value);
3417 // ----------------------------------------------------------------------------
3418 // functions for loading level info and custom artwork info
3419 // ----------------------------------------------------------------------------
3421 int GetZipFileTreeType(char *zip_filename)
3423 static char *top_dir_path = NULL;
3424 static char *top_dir_conf_filename[NUM_BASE_TREE_TYPES] = { NULL };
3425 static char *conf_basename[NUM_BASE_TREE_TYPES] =
3427 GRAPHICSINFO_FILENAME,
3428 SOUNDSINFO_FILENAME,
3434 checked_free(top_dir_path);
3435 top_dir_path = NULL;
3437 for (j = 0; j < NUM_BASE_TREE_TYPES; j++)
3439 checked_free(top_dir_conf_filename[j]);
3440 top_dir_conf_filename[j] = NULL;
3443 char **zip_entries = zip_list(zip_filename);
3445 // check if zip file successfully opened
3446 if (zip_entries == NULL || zip_entries[0] == NULL)
3447 return TREE_TYPE_UNDEFINED;
3449 // first zip file entry is expected to be top level directory
3450 char *top_dir = zip_entries[0];
3452 // check if valid top level directory found in zip file
3453 if (!strSuffix(top_dir, "/"))
3454 return TREE_TYPE_UNDEFINED;
3456 // get filenames of valid configuration files in top level directory
3457 for (j = 0; j < NUM_BASE_TREE_TYPES; j++)
3458 top_dir_conf_filename[j] = getStringCat2(top_dir, conf_basename[j]);
3460 int tree_type = TREE_TYPE_UNDEFINED;
3463 while (zip_entries[e] != NULL)
3465 // check if every zip file entry is below top level directory
3466 if (!strPrefix(zip_entries[e], top_dir))
3467 return TREE_TYPE_UNDEFINED;
3469 // check if this zip file entry is a valid configuration filename
3470 for (j = 0; j < NUM_BASE_TREE_TYPES; j++)
3472 if (strEqual(zip_entries[e], top_dir_conf_filename[j]))
3474 // only exactly one valid configuration file allowed
3475 if (tree_type != TREE_TYPE_UNDEFINED)
3476 return TREE_TYPE_UNDEFINED;
3488 static boolean CheckZipFileForDirectory(char *zip_filename, char *directory,
3491 static char *top_dir_path = NULL;
3492 static char *top_dir_conf_filename = NULL;
3494 checked_free(top_dir_path);
3495 checked_free(top_dir_conf_filename);
3497 top_dir_path = NULL;
3498 top_dir_conf_filename = NULL;
3500 char *conf_basename = (tree_type == TREE_TYPE_LEVEL_DIR ? LEVELINFO_FILENAME :
3501 ARTWORKINFO_FILENAME(tree_type));
3503 // check if valid configuration filename determined
3504 if (conf_basename == NULL || strEqual(conf_basename, ""))
3507 char **zip_entries = zip_list(zip_filename);
3509 // check if zip file successfully opened
3510 if (zip_entries == NULL || zip_entries[0] == NULL)
3513 // first zip file entry is expected to be top level directory
3514 char *top_dir = zip_entries[0];
3516 // check if valid top level directory found in zip file
3517 if (!strSuffix(top_dir, "/"))
3520 // get path of extracted top level directory
3521 top_dir_path = getPath2(directory, top_dir);
3523 // remove trailing directory separator from top level directory path
3524 // (required to be able to check for file and directory in next step)
3525 top_dir_path[strlen(top_dir_path) - 1] = '\0';
3527 // check if zip file's top level directory already exists in target directory
3528 if (fileExists(top_dir_path)) // (checks for file and directory)
3531 // get filename of configuration file in top level directory
3532 top_dir_conf_filename = getStringCat2(top_dir, conf_basename);
3534 boolean found_top_dir_conf_filename = FALSE;
3537 while (zip_entries[i] != NULL)
3539 // check if every zip file entry is below top level directory
3540 if (!strPrefix(zip_entries[i], top_dir))
3543 // check if this zip file entry is the configuration filename
3544 if (strEqual(zip_entries[i], top_dir_conf_filename))
3545 found_top_dir_conf_filename = TRUE;
3550 // check if valid configuration filename was found in zip file
3551 if (!found_top_dir_conf_filename)
3557 char *ExtractZipFileIntoDirectory(char *zip_filename, char *directory,
3560 boolean zip_file_valid = CheckZipFileForDirectory(zip_filename, directory,
3563 if (!zip_file_valid)
3565 Warn("zip file '%s' rejected!", zip_filename);
3570 char **zip_entries = zip_extract(zip_filename, directory);
3572 if (zip_entries == NULL)
3574 Warn("zip file '%s' could not be extracted!", zip_filename);
3579 Info("zip file '%s' successfully extracted!", zip_filename);
3581 // first zip file entry contains top level directory
3582 char *top_dir = zip_entries[0];
3584 // remove trailing directory separator from top level directory
3585 top_dir[strlen(top_dir) - 1] = '\0';
3590 static void ProcessZipFilesInDirectory(char *directory, int tree_type)
3593 DirectoryEntry *dir_entry;
3595 if ((dir = openDirectory(directory)) == NULL)
3597 // display error if directory is main "options.graphics_directory" etc.
3598 if (tree_type == TREE_TYPE_LEVEL_DIR ||
3599 directory == OPTIONS_ARTWORK_DIRECTORY(tree_type))
3600 Warn("cannot read directory '%s'", directory);
3605 while ((dir_entry = readDirectory(dir)) != NULL) // loop all entries
3607 // skip non-zip files (and also directories with zip extension)
3608 if (!strSuffixLower(dir_entry->basename, ".zip") || dir_entry->is_directory)
3611 char *zip_filename = getPath2(directory, dir_entry->basename);
3612 char *zip_filename_extracted = getStringCat2(zip_filename, ".extracted");
3613 char *zip_filename_rejected = getStringCat2(zip_filename, ".rejected");
3615 // check if zip file hasn't already been extracted or rejected
3616 if (!fileExists(zip_filename_extracted) &&
3617 !fileExists(zip_filename_rejected))
3619 char *top_dir = ExtractZipFileIntoDirectory(zip_filename, directory,
3621 char *marker_filename = (top_dir != NULL ? zip_filename_extracted :
3622 zip_filename_rejected);
3625 // create empty file to mark zip file as extracted or rejected
3626 if ((marker_file = fopen(marker_filename, MODE_WRITE)))
3627 fclose(marker_file);
3630 free(zip_filename_extracted);
3631 free(zip_filename_rejected);
3635 closeDirectory(dir);
3638 // forward declaration for recursive call by "LoadLevelInfoFromLevelDir()"
3639 static void LoadLevelInfoFromLevelDir(TreeInfo **, TreeInfo *, char *);
3641 static boolean LoadLevelInfoFromLevelConf(TreeInfo **node_first,
3642 TreeInfo *node_parent,
3643 char *level_directory,
3644 char *directory_name)
3646 char *directory_path = getPath2(level_directory, directory_name);
3647 char *filename = getPath2(directory_path, LEVELINFO_FILENAME);
3648 SetupFileHash *setup_file_hash;
3649 LevelDirTree *leveldir_new = NULL;
3652 // unless debugging, silently ignore directories without "levelinfo.conf"
3653 if (!options.debug && !fileExists(filename))
3655 free(directory_path);
3661 setup_file_hash = loadSetupFileHash(filename);
3663 if (setup_file_hash == NULL)
3665 #if DEBUG_NO_CONFIG_FILE
3666 Debug("setup", "ignoring level directory '%s'", directory_path);
3669 free(directory_path);
3675 leveldir_new = newTreeInfo();
3678 setTreeInfoToDefaultsFromParent(leveldir_new, node_parent);
3680 setTreeInfoToDefaults(leveldir_new, TREE_TYPE_LEVEL_DIR);
3682 leveldir_new->subdir = getStringCopy(directory_name);
3684 // set all structure fields according to the token/value pairs
3685 ldi = *leveldir_new;
3686 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
3687 setSetupInfo(levelinfo_tokens, i,
3688 getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
3689 *leveldir_new = ldi;
3691 if (strEqual(leveldir_new->name, ANONYMOUS_NAME))
3692 setString(&leveldir_new->name, leveldir_new->subdir);
3694 if (leveldir_new->identifier == NULL)
3695 leveldir_new->identifier = getStringCopy(leveldir_new->subdir);
3697 if (leveldir_new->name_sorting == NULL)
3698 leveldir_new->name_sorting = getStringCopy(leveldir_new->name);
3700 if (node_parent == NULL) // top level group
3702 leveldir_new->basepath = getStringCopy(level_directory);
3703 leveldir_new->fullpath = getStringCopy(leveldir_new->subdir);
3705 else // sub level group
3707 leveldir_new->basepath = getStringCopy(node_parent->basepath);
3708 leveldir_new->fullpath = getPath2(node_parent->fullpath, directory_name);
3711 leveldir_new->last_level =
3712 leveldir_new->first_level + leveldir_new->levels - 1;
3714 leveldir_new->in_user_dir =
3715 (!strEqual(leveldir_new->basepath, options.level_directory));
3717 // adjust some settings if user's private level directory was detected
3718 if (leveldir_new->sort_priority == LEVELCLASS_UNDEFINED &&
3719 leveldir_new->in_user_dir &&
3720 (strEqual(leveldir_new->subdir, getLoginName()) ||
3721 strEqual(leveldir_new->name, getLoginName()) ||
3722 strEqual(leveldir_new->author, getRealName())))
3724 leveldir_new->sort_priority = LEVELCLASS_PRIVATE_START;
3725 leveldir_new->readonly = FALSE;
3728 leveldir_new->user_defined =
3729 (leveldir_new->in_user_dir && IS_LEVELCLASS_PRIVATE(leveldir_new));
3731 setString(&leveldir_new->class_desc, getLevelClassDescription(leveldir_new));
3733 leveldir_new->handicap_level = // set handicap to default value
3734 (leveldir_new->user_defined || !leveldir_new->handicap ?
3735 leveldir_new->last_level : leveldir_new->first_level);
3737 DrawInitTextItem(leveldir_new->name);
3739 pushTreeInfo(node_first, leveldir_new);
3741 freeSetupFileHash(setup_file_hash);
3743 if (leveldir_new->level_group)
3745 // create node to link back to current level directory
3746 createParentTreeInfoNode(leveldir_new);
3748 // recursively step into sub-directory and look for more level series
3749 LoadLevelInfoFromLevelDir(&leveldir_new->node_group,
3750 leveldir_new, directory_path);
3753 free(directory_path);
3759 static void LoadLevelInfoFromLevelDir(TreeInfo **node_first,
3760 TreeInfo *node_parent,
3761 char *level_directory)
3763 // ---------- 1st stage: process any level set zip files ----------
3765 ProcessZipFilesInDirectory(level_directory, TREE_TYPE_LEVEL_DIR);
3767 // ---------- 2nd stage: check for level set directories ----------
3770 DirectoryEntry *dir_entry;
3771 boolean valid_entry_found = FALSE;
3773 if ((dir = openDirectory(level_directory)) == NULL)
3775 Warn("cannot read level directory '%s'", level_directory);
3780 while ((dir_entry = readDirectory(dir)) != NULL) // loop all entries
3782 char *directory_name = dir_entry->basename;
3783 char *directory_path = getPath2(level_directory, directory_name);
3785 // skip entries for current and parent directory
3786 if (strEqual(directory_name, ".") ||
3787 strEqual(directory_name, ".."))
3789 free(directory_path);
3794 // find out if directory entry is itself a directory
3795 if (!dir_entry->is_directory) // not a directory
3797 free(directory_path);
3802 free(directory_path);
3804 if (strEqual(directory_name, GRAPHICS_DIRECTORY) ||
3805 strEqual(directory_name, SOUNDS_DIRECTORY) ||
3806 strEqual(directory_name, MUSIC_DIRECTORY))
3809 valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
3814 closeDirectory(dir);
3816 // special case: top level directory may directly contain "levelinfo.conf"
3817 if (node_parent == NULL && !valid_entry_found)
3819 // check if this directory directly contains a file "levelinfo.conf"
3820 valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
3821 level_directory, ".");
3824 if (!valid_entry_found)
3825 Warn("cannot find any valid level series in directory '%s'",
3829 boolean AdjustGraphicsForEMC(void)
3831 boolean settings_changed = FALSE;
3833 settings_changed |= adjustTreeGraphicsForEMC(leveldir_first_all);
3834 settings_changed |= adjustTreeGraphicsForEMC(leveldir_first);
3836 return settings_changed;
3839 boolean AdjustSoundsForEMC(void)
3841 boolean settings_changed = FALSE;
3843 settings_changed |= adjustTreeSoundsForEMC(leveldir_first_all);
3844 settings_changed |= adjustTreeSoundsForEMC(leveldir_first);
3846 return settings_changed;
3849 void LoadLevelInfo(void)
3851 InitUserLevelDirectory(getLoginName());
3853 DrawInitTextHead("Loading level series");
3855 LoadLevelInfoFromLevelDir(&leveldir_first, NULL, options.level_directory);
3856 LoadLevelInfoFromLevelDir(&leveldir_first, NULL, getUserLevelDir(NULL));
3858 leveldir_first = createTopTreeInfoNode(leveldir_first);
3860 /* after loading all level set information, clone the level directory tree
3861 and remove all level sets without levels (these may still contain artwork
3862 to be offered in the setup menu as "custom artwork", and are therefore
3863 checked for existing artwork in the function "LoadLevelArtworkInfo()") */
3864 leveldir_first_all = leveldir_first;
3865 cloneTree(&leveldir_first, leveldir_first_all, TRUE);
3867 AdjustGraphicsForEMC();
3868 AdjustSoundsForEMC();
3870 // before sorting, the first entries will be from the user directory
3871 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
3873 if (leveldir_first == NULL)
3874 Fail("cannot find any valid level series in any directory");
3876 sortTreeInfo(&leveldir_first);
3878 #if ENABLE_UNUSED_CODE
3879 dumpTreeInfo(leveldir_first, 0);
3883 static boolean LoadArtworkInfoFromArtworkConf(TreeInfo **node_first,
3884 TreeInfo *node_parent,
3885 char *base_directory,
3886 char *directory_name, int type)
3888 char *directory_path = getPath2(base_directory, directory_name);
3889 char *filename = getPath2(directory_path, ARTWORKINFO_FILENAME(type));
3890 SetupFileHash *setup_file_hash = NULL;
3891 TreeInfo *artwork_new = NULL;
3894 if (fileExists(filename))
3895 setup_file_hash = loadSetupFileHash(filename);
3897 if (setup_file_hash == NULL) // no config file -- look for artwork files
3900 DirectoryEntry *dir_entry;
3901 boolean valid_file_found = FALSE;
3903 if ((dir = openDirectory(directory_path)) != NULL)
3905 while ((dir_entry = readDirectory(dir)) != NULL)
3907 if (FileIsArtworkType(dir_entry->filename, type))
3909 valid_file_found = TRUE;
3915 closeDirectory(dir);
3918 if (!valid_file_found)
3920 #if DEBUG_NO_CONFIG_FILE
3921 if (!strEqual(directory_name, "."))
3922 Debug("setup", "ignoring artwork directory '%s'", directory_path);
3925 free(directory_path);
3932 artwork_new = newTreeInfo();
3935 setTreeInfoToDefaultsFromParent(artwork_new, node_parent);
3937 setTreeInfoToDefaults(artwork_new, type);
3939 artwork_new->subdir = getStringCopy(directory_name);
3941 if (setup_file_hash) // (before defining ".color" and ".class_desc")
3943 // set all structure fields according to the token/value pairs
3945 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
3946 setSetupInfo(levelinfo_tokens, i,
3947 getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
3950 if (strEqual(artwork_new->name, ANONYMOUS_NAME))
3951 setString(&artwork_new->name, artwork_new->subdir);
3953 if (artwork_new->identifier == NULL)
3954 artwork_new->identifier = getStringCopy(artwork_new->subdir);
3956 if (artwork_new->name_sorting == NULL)
3957 artwork_new->name_sorting = getStringCopy(artwork_new->name);
3960 if (node_parent == NULL) // top level group
3962 artwork_new->basepath = getStringCopy(base_directory);
3963 artwork_new->fullpath = getStringCopy(artwork_new->subdir);
3965 else // sub level group
3967 artwork_new->basepath = getStringCopy(node_parent->basepath);
3968 artwork_new->fullpath = getPath2(node_parent->fullpath, directory_name);
3971 artwork_new->in_user_dir =
3972 (!strEqual(artwork_new->basepath, OPTIONS_ARTWORK_DIRECTORY(type)));
3974 setString(&artwork_new->class_desc, getLevelClassDescription(artwork_new));
3976 if (setup_file_hash == NULL) // (after determining ".user_defined")
3978 if (strEqual(artwork_new->subdir, "."))
3980 if (artwork_new->user_defined)
3982 setString(&artwork_new->identifier, "private");
3983 artwork_new->sort_priority = ARTWORKCLASS_PRIVATE;
3987 setString(&artwork_new->identifier, "classic");
3988 artwork_new->sort_priority = ARTWORKCLASS_CLASSICS;
3991 setString(&artwork_new->class_desc,
3992 getLevelClassDescription(artwork_new));
3996 setString(&artwork_new->identifier, artwork_new->subdir);
3999 setString(&artwork_new->name, artwork_new->identifier);
4000 setString(&artwork_new->name_sorting, artwork_new->name);
4003 pushTreeInfo(node_first, artwork_new);
4005 freeSetupFileHash(setup_file_hash);
4007 free(directory_path);
4013 static void LoadArtworkInfoFromArtworkDir(TreeInfo **node_first,
4014 TreeInfo *node_parent,
4015 char *base_directory, int type)
4017 // ---------- 1st stage: process any artwork set zip files ----------
4019 ProcessZipFilesInDirectory(base_directory, type);
4021 // ---------- 2nd stage: check for artwork set directories ----------
4024 DirectoryEntry *dir_entry;
4025 boolean valid_entry_found = FALSE;
4027 if ((dir = openDirectory(base_directory)) == NULL)
4029 // display error if directory is main "options.graphics_directory" etc.
4030 if (base_directory == OPTIONS_ARTWORK_DIRECTORY(type))
4031 Warn("cannot read directory '%s'", base_directory);
4036 while ((dir_entry = readDirectory(dir)) != NULL) // loop all entries
4038 char *directory_name = dir_entry->basename;
4039 char *directory_path = getPath2(base_directory, directory_name);
4041 // skip directory entries for current and parent directory
4042 if (strEqual(directory_name, ".") ||
4043 strEqual(directory_name, ".."))
4045 free(directory_path);
4050 // skip directory entries which are not a directory
4051 if (!dir_entry->is_directory) // not a directory
4053 free(directory_path);
4058 free(directory_path);
4060 // check if this directory contains artwork with or without config file
4061 valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
4063 directory_name, type);
4066 closeDirectory(dir);
4068 // check if this directory directly contains artwork itself
4069 valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
4070 base_directory, ".",
4072 if (!valid_entry_found)
4073 Warn("cannot find any valid artwork in directory '%s'", base_directory);
4076 static TreeInfo *getDummyArtworkInfo(int type)
4078 // this is only needed when there is completely no artwork available
4079 TreeInfo *artwork_new = newTreeInfo();
4081 setTreeInfoToDefaults(artwork_new, type);
4083 setString(&artwork_new->subdir, UNDEFINED_FILENAME);
4084 setString(&artwork_new->fullpath, UNDEFINED_FILENAME);
4085 setString(&artwork_new->basepath, UNDEFINED_FILENAME);
4087 setString(&artwork_new->identifier, UNDEFINED_FILENAME);
4088 setString(&artwork_new->name, UNDEFINED_FILENAME);
4089 setString(&artwork_new->name_sorting, UNDEFINED_FILENAME);
4094 void SetCurrentArtwork(int type)
4096 ArtworkDirTree **current_ptr = ARTWORK_CURRENT_PTR(artwork, type);
4097 ArtworkDirTree *first_node = ARTWORK_FIRST_NODE(artwork, type);
4098 char *setup_set = SETUP_ARTWORK_SET(setup, type);
4099 char *default_subdir = ARTWORK_DEFAULT_SUBDIR(type);
4101 // set current artwork to artwork configured in setup menu
4102 *current_ptr = getTreeInfoFromIdentifier(first_node, setup_set);
4104 // if not found, set current artwork to default artwork
4105 if (*current_ptr == NULL)
4106 *current_ptr = getTreeInfoFromIdentifier(first_node, default_subdir);
4108 // if not found, set current artwork to first artwork in tree
4109 if (*current_ptr == NULL)
4110 *current_ptr = getFirstValidTreeInfoEntry(first_node);
4113 void ChangeCurrentArtworkIfNeeded(int type)
4115 char *current_identifier = ARTWORK_CURRENT_IDENTIFIER(artwork, type);
4116 char *setup_set = SETUP_ARTWORK_SET(setup, type);
4118 if (!strEqual(current_identifier, setup_set))
4119 SetCurrentArtwork(type);
4122 void LoadArtworkInfo(void)
4124 LoadArtworkInfoCache();
4126 DrawInitTextHead("Looking for custom artwork");
4128 LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
4129 options.graphics_directory,
4130 TREE_TYPE_GRAPHICS_DIR);
4131 LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
4132 getUserGraphicsDir(),
4133 TREE_TYPE_GRAPHICS_DIR);
4135 LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
4136 options.sounds_directory,
4137 TREE_TYPE_SOUNDS_DIR);
4138 LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
4140 TREE_TYPE_SOUNDS_DIR);
4142 LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
4143 options.music_directory,
4144 TREE_TYPE_MUSIC_DIR);
4145 LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
4147 TREE_TYPE_MUSIC_DIR);
4149 if (artwork.gfx_first == NULL)
4150 artwork.gfx_first = getDummyArtworkInfo(TREE_TYPE_GRAPHICS_DIR);
4151 if (artwork.snd_first == NULL)
4152 artwork.snd_first = getDummyArtworkInfo(TREE_TYPE_SOUNDS_DIR);
4153 if (artwork.mus_first == NULL)
4154 artwork.mus_first = getDummyArtworkInfo(TREE_TYPE_MUSIC_DIR);
4156 // before sorting, the first entries will be from the user directory
4157 SetCurrentArtwork(ARTWORK_TYPE_GRAPHICS);
4158 SetCurrentArtwork(ARTWORK_TYPE_SOUNDS);
4159 SetCurrentArtwork(ARTWORK_TYPE_MUSIC);
4161 artwork.gfx_current_identifier = artwork.gfx_current->identifier;
4162 artwork.snd_current_identifier = artwork.snd_current->identifier;
4163 artwork.mus_current_identifier = artwork.mus_current->identifier;
4165 #if ENABLE_UNUSED_CODE
4166 Debug("setup:LoadArtworkInfo", "graphics set == %s",
4167 artwork.gfx_current_identifier);
4168 Debug("setup:LoadArtworkInfo", "sounds set == %s",
4169 artwork.snd_current_identifier);
4170 Debug("setup:LoadArtworkInfo", "music set == %s",
4171 artwork.mus_current_identifier);
4174 sortTreeInfo(&artwork.gfx_first);
4175 sortTreeInfo(&artwork.snd_first);
4176 sortTreeInfo(&artwork.mus_first);
4178 #if ENABLE_UNUSED_CODE
4179 dumpTreeInfo(artwork.gfx_first, 0);
4180 dumpTreeInfo(artwork.snd_first, 0);
4181 dumpTreeInfo(artwork.mus_first, 0);
4185 static void MoveArtworkInfoIntoSubTree(ArtworkDirTree **artwork_node)
4187 ArtworkDirTree *artwork_new = newTreeInfo();
4188 char *top_node_name = "standalone artwork";
4190 setTreeInfoToDefaults(artwork_new, (*artwork_node)->type);
4192 artwork_new->level_group = TRUE;
4194 setString(&artwork_new->identifier, top_node_name);
4195 setString(&artwork_new->name, top_node_name);
4196 setString(&artwork_new->name_sorting, top_node_name);
4198 // create node to link back to current custom artwork directory
4199 createParentTreeInfoNode(artwork_new);
4201 // move existing custom artwork tree into newly created sub-tree
4202 artwork_new->node_group->next = *artwork_node;
4204 // change custom artwork tree to contain only newly created node
4205 *artwork_node = artwork_new;
4208 static void LoadArtworkInfoFromLevelInfoExt(ArtworkDirTree **artwork_node,
4209 ArtworkDirTree *node_parent,
4210 LevelDirTree *level_node,
4211 boolean empty_level_set_mode)
4213 int type = (*artwork_node)->type;
4215 // recursively check all level directories for artwork sub-directories
4219 boolean empty_level_set = (level_node->levels == 0);
4221 // check all tree entries for artwork, but skip parent link entries
4222 if (!level_node->parent_link && empty_level_set == empty_level_set_mode)
4224 TreeInfo *artwork_new = getArtworkInfoCacheEntry(level_node, type);
4225 boolean cached = (artwork_new != NULL);
4229 pushTreeInfo(artwork_node, artwork_new);
4233 TreeInfo *topnode_last = *artwork_node;
4234 char *path = getPath2(getLevelDirFromTreeInfo(level_node),
4235 ARTWORK_DIRECTORY(type));
4237 LoadArtworkInfoFromArtworkDir(artwork_node, NULL, path, type);
4239 if (topnode_last != *artwork_node) // check for newly added node
4241 artwork_new = *artwork_node;
4243 setString(&artwork_new->identifier, level_node->subdir);
4244 setString(&artwork_new->name, level_node->name);
4245 setString(&artwork_new->name_sorting, level_node->name_sorting);
4247 artwork_new->sort_priority = level_node->sort_priority;
4248 artwork_new->in_user_dir = level_node->in_user_dir;
4250 update_artworkinfo_cache = TRUE;
4256 // insert artwork info (from old cache or filesystem) into new cache
4257 if (artwork_new != NULL)
4258 setArtworkInfoCacheEntry(artwork_new, level_node, type);
4261 DrawInitTextItem(level_node->name);
4263 if (level_node->node_group != NULL)
4265 TreeInfo *artwork_new = newTreeInfo();
4268 setTreeInfoToDefaultsFromParent(artwork_new, node_parent);
4270 setTreeInfoToDefaults(artwork_new, type);
4272 artwork_new->level_group = TRUE;
4274 setString(&artwork_new->identifier, level_node->subdir);
4276 if (node_parent == NULL) // check for top tree node
4278 char *top_node_name = (empty_level_set_mode ?
4279 "artwork for certain level sets" :
4280 "artwork included in level sets");
4282 setString(&artwork_new->name, top_node_name);
4283 setString(&artwork_new->name_sorting, top_node_name);
4287 setString(&artwork_new->name, level_node->name);
4288 setString(&artwork_new->name_sorting, level_node->name_sorting);
4291 pushTreeInfo(artwork_node, artwork_new);
4293 // create node to link back to current custom artwork directory
4294 createParentTreeInfoNode(artwork_new);
4296 // recursively step into sub-directory and look for more custom artwork
4297 LoadArtworkInfoFromLevelInfoExt(&artwork_new->node_group, artwork_new,
4298 level_node->node_group,
4299 empty_level_set_mode);
4301 // if sub-tree has no custom artwork at all, remove it
4302 if (artwork_new->node_group->next == NULL)
4303 removeTreeInfo(artwork_node);
4306 level_node = level_node->next;
4310 static void LoadArtworkInfoFromLevelInfo(ArtworkDirTree **artwork_node)
4312 // move peviously loaded artwork tree into separate sub-tree
4313 MoveArtworkInfoIntoSubTree(artwork_node);
4315 // load artwork from level sets into separate sub-trees
4316 LoadArtworkInfoFromLevelInfoExt(artwork_node, NULL, leveldir_first_all, TRUE);
4317 LoadArtworkInfoFromLevelInfoExt(artwork_node, NULL, leveldir_first_all, FALSE);
4319 // add top tree node over all sub-trees and set parent links
4320 *artwork_node = addTopTreeInfoNode(*artwork_node);
4323 void LoadLevelArtworkInfo(void)
4325 print_timestamp_init("LoadLevelArtworkInfo");
4327 DrawInitTextHead("Looking for custom level artwork");
4329 print_timestamp_time("DrawTimeText");
4331 LoadArtworkInfoFromLevelInfo(&artwork.gfx_first);
4332 print_timestamp_time("LoadArtworkInfoFromLevelInfo (gfx)");
4333 LoadArtworkInfoFromLevelInfo(&artwork.snd_first);
4334 print_timestamp_time("LoadArtworkInfoFromLevelInfo (snd)");
4335 LoadArtworkInfoFromLevelInfo(&artwork.mus_first);
4336 print_timestamp_time("LoadArtworkInfoFromLevelInfo (mus)");
4338 SaveArtworkInfoCache();
4340 print_timestamp_time("SaveArtworkInfoCache");
4342 // needed for reloading level artwork not known at ealier stage
4343 ChangeCurrentArtworkIfNeeded(ARTWORK_TYPE_GRAPHICS);
4344 ChangeCurrentArtworkIfNeeded(ARTWORK_TYPE_SOUNDS);
4345 ChangeCurrentArtworkIfNeeded(ARTWORK_TYPE_MUSIC);
4347 print_timestamp_time("getTreeInfoFromIdentifier");
4349 sortTreeInfo(&artwork.gfx_first);
4350 sortTreeInfo(&artwork.snd_first);
4351 sortTreeInfo(&artwork.mus_first);
4353 print_timestamp_time("sortTreeInfo");
4355 #if ENABLE_UNUSED_CODE
4356 dumpTreeInfo(artwork.gfx_first, 0);
4357 dumpTreeInfo(artwork.snd_first, 0);
4358 dumpTreeInfo(artwork.mus_first, 0);
4361 print_timestamp_done("LoadLevelArtworkInfo");
4364 static boolean AddTreeSetToTreeInfoExt(TreeInfo *tree_node_old, char *tree_dir,
4365 char *tree_subdir_new, int type)
4367 if (tree_node_old == NULL)
4369 if (type == TREE_TYPE_LEVEL_DIR)
4371 // get level info tree node of personal user level set
4372 tree_node_old = getTreeInfoFromIdentifier(leveldir_first, getLoginName());
4374 // this may happen if "setup.internal.create_user_levelset" is FALSE
4375 // or if file "levelinfo.conf" is missing in personal user level set
4376 if (tree_node_old == NULL)
4377 tree_node_old = leveldir_first->node_group;
4381 // get artwork info tree node of first artwork set
4382 tree_node_old = ARTWORK_FIRST_NODE(artwork, type);
4386 if (tree_dir == NULL)
4387 tree_dir = TREE_USERDIR(type);
4389 if (tree_node_old == NULL ||
4391 tree_subdir_new == NULL) // should not happen
4394 int draw_deactivation_mask = GetDrawDeactivationMask();
4396 // override draw deactivation mask (temporarily disable drawing)
4397 SetDrawDeactivationMask(REDRAW_ALL);
4399 if (type == TREE_TYPE_LEVEL_DIR)
4401 // load new level set config and add it next to first user level set
4402 LoadLevelInfoFromLevelConf(&tree_node_old->next,
4403 tree_node_old->node_parent,
4404 tree_dir, tree_subdir_new);
4408 // load new artwork set config and add it next to first artwork set
4409 LoadArtworkInfoFromArtworkConf(&tree_node_old->next,
4410 tree_node_old->node_parent,
4411 tree_dir, tree_subdir_new, type);
4414 // set draw deactivation mask to previous value
4415 SetDrawDeactivationMask(draw_deactivation_mask);
4417 // get first node of level or artwork info tree
4418 TreeInfo **tree_node_first = TREE_FIRST_NODE_PTR(type);
4420 // get tree info node of newly added level or artwork set
4421 TreeInfo *tree_node_new = getTreeInfoFromIdentifier(*tree_node_first,
4424 if (tree_node_new == NULL) // should not happen
4427 // correct top link and parent node link of newly created tree node
4428 tree_node_new->node_top = tree_node_old->node_top;
4429 tree_node_new->node_parent = tree_node_old->node_parent;
4431 // sort tree info to adjust position of newly added tree set
4432 sortTreeInfo(tree_node_first);
4437 void AddTreeSetToTreeInfo(TreeInfo *tree_node, char *tree_dir,
4438 char *tree_subdir_new, int type)
4440 if (!AddTreeSetToTreeInfoExt(tree_node, tree_dir, tree_subdir_new, type))
4441 Fail("internal tree info structure corrupted -- aborting");
4444 void AddUserLevelSetToLevelInfo(char *level_subdir_new)
4446 AddTreeSetToTreeInfo(NULL, NULL, level_subdir_new, TREE_TYPE_LEVEL_DIR);
4449 char *getArtworkIdentifierForUserLevelSet(int type)
4451 char *classic_artwork_set = getClassicArtworkSet(type);
4453 // check for custom artwork configured in "levelinfo.conf"
4454 char *leveldir_artwork_set =
4455 *LEVELDIR_ARTWORK_SET_PTR(leveldir_current, type);
4456 boolean has_leveldir_artwork_set =
4457 (leveldir_artwork_set != NULL && !strEqual(leveldir_artwork_set,
4458 classic_artwork_set));
4460 // check for custom artwork in sub-directory "graphics" etc.
4461 TreeInfo *artwork_first_node = ARTWORK_FIRST_NODE(artwork, type);
4462 char *leveldir_identifier = leveldir_current->identifier;
4463 boolean has_artwork_subdir =
4464 (getTreeInfoFromIdentifier(artwork_first_node,
4465 leveldir_identifier) != NULL);
4467 return (has_leveldir_artwork_set ? leveldir_artwork_set :
4468 has_artwork_subdir ? leveldir_identifier :
4469 classic_artwork_set);
4472 TreeInfo *getArtworkTreeInfoForUserLevelSet(int type)
4474 char *artwork_set = getArtworkIdentifierForUserLevelSet(type);
4475 TreeInfo *artwork_first_node = ARTWORK_FIRST_NODE(artwork, type);
4476 TreeInfo *ti = getTreeInfoFromIdentifier(artwork_first_node, artwork_set);
4480 ti = getTreeInfoFromIdentifier(artwork_first_node,
4481 ARTWORK_DEFAULT_SUBDIR(type));
4483 Fail("cannot find default graphics -- should not happen");
4489 boolean checkIfCustomArtworkExistsForCurrentLevelSet(void)
4491 char *graphics_set =
4492 getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_GRAPHICS);
4494 getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_SOUNDS);
4496 getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_MUSIC);
4498 return (!strEqual(graphics_set, GFX_CLASSIC_SUBDIR) ||
4499 !strEqual(sounds_set, SND_CLASSIC_SUBDIR) ||
4500 !strEqual(music_set, MUS_CLASSIC_SUBDIR));
4503 boolean UpdateUserLevelSet(char *level_subdir, char *level_name,
4504 char *level_author, int num_levels)
4506 char *filename = getPath2(getUserLevelDir(level_subdir), LEVELINFO_FILENAME);
4507 char *filename_tmp = getStringCat2(filename, ".tmp");
4509 FILE *file_tmp = NULL;
4510 char line[MAX_LINE_LEN];
4511 boolean success = FALSE;
4512 LevelDirTree *leveldir = getTreeInfoFromIdentifier(leveldir_first,
4514 // update values in level directory tree
4516 if (level_name != NULL)
4517 setString(&leveldir->name, level_name);
4519 if (level_author != NULL)
4520 setString(&leveldir->author, level_author);
4522 if (num_levels != -1)
4523 leveldir->levels = num_levels;
4525 // update values that depend on other values
4527 setString(&leveldir->name_sorting, leveldir->name);
4529 leveldir->last_level = leveldir->first_level + leveldir->levels - 1;
4531 // sort order of level sets may have changed
4532 sortTreeInfo(&leveldir_first);
4534 if ((file = fopen(filename, MODE_READ)) &&
4535 (file_tmp = fopen(filename_tmp, MODE_WRITE)))
4537 while (fgets(line, MAX_LINE_LEN, file))
4539 if (strPrefix(line, "name:") && level_name != NULL)
4540 fprintf(file_tmp, "%-32s%s\n", "name:", level_name);
4541 else if (strPrefix(line, "author:") && level_author != NULL)
4542 fprintf(file_tmp, "%-32s%s\n", "author:", level_author);
4543 else if (strPrefix(line, "levels:") && num_levels != -1)
4544 fprintf(file_tmp, "%-32s%d\n", "levels:", num_levels);
4546 fputs(line, file_tmp);
4559 success = (rename(filename_tmp, filename) == 0);
4567 boolean CreateUserLevelSet(char *level_subdir, char *level_name,
4568 char *level_author, int num_levels,
4569 boolean use_artwork_set)
4571 LevelDirTree *level_info;
4576 // create user level sub-directory, if needed
4577 createDirectory(getUserLevelDir(level_subdir), "user level", PERMS_PRIVATE);
4579 filename = getPath2(getUserLevelDir(level_subdir), LEVELINFO_FILENAME);
4581 if (!(file = fopen(filename, MODE_WRITE)))
4583 Warn("cannot write level info file '%s'", filename);
4590 level_info = newTreeInfo();
4592 // always start with reliable default values
4593 setTreeInfoToDefaults(level_info, TREE_TYPE_LEVEL_DIR);
4595 setString(&level_info->name, level_name);
4596 setString(&level_info->author, level_author);
4597 level_info->levels = num_levels;
4598 level_info->first_level = 1;
4599 level_info->sort_priority = LEVELCLASS_PRIVATE_START;
4600 level_info->readonly = FALSE;
4602 if (use_artwork_set)
4604 level_info->graphics_set =
4605 getStringCopy(getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_GRAPHICS));
4606 level_info->sounds_set =
4607 getStringCopy(getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_SOUNDS));
4608 level_info->music_set =
4609 getStringCopy(getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_MUSIC));
4612 token_value_position = TOKEN_VALUE_POSITION_SHORT;
4614 fprintFileHeader(file, LEVELINFO_FILENAME);
4617 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
4619 if (i == LEVELINFO_TOKEN_NAME ||
4620 i == LEVELINFO_TOKEN_AUTHOR ||
4621 i == LEVELINFO_TOKEN_LEVELS ||
4622 i == LEVELINFO_TOKEN_FIRST_LEVEL ||
4623 i == LEVELINFO_TOKEN_SORT_PRIORITY ||
4624 i == LEVELINFO_TOKEN_READONLY ||
4625 (use_artwork_set && (i == LEVELINFO_TOKEN_GRAPHICS_SET ||
4626 i == LEVELINFO_TOKEN_SOUNDS_SET ||
4627 i == LEVELINFO_TOKEN_MUSIC_SET)))
4628 fprintf(file, "%s\n", getSetupLine(levelinfo_tokens, "", i));
4630 // just to make things nicer :)
4631 if (i == LEVELINFO_TOKEN_AUTHOR ||
4632 i == LEVELINFO_TOKEN_FIRST_LEVEL ||
4633 (use_artwork_set && i == LEVELINFO_TOKEN_READONLY))
4634 fprintf(file, "\n");
4637 token_value_position = TOKEN_VALUE_POSITION_DEFAULT;
4641 SetFilePermissions(filename, PERMS_PRIVATE);
4643 freeTreeInfo(level_info);
4649 static void SaveUserLevelInfo(void)
4651 CreateUserLevelSet(getLoginName(), getLoginName(), getRealName(), 100, FALSE);
4654 char *getSetupValue(int type, void *value)
4656 static char value_string[MAX_LINE_LEN];
4664 strcpy(value_string, (*(boolean *)value ? "true" : "false"));
4668 strcpy(value_string, (*(boolean *)value ? "on" : "off"));
4672 strcpy(value_string, (*(int *)value == AUTO ? "auto" :
4673 *(int *)value == FALSE ? "off" : "on"));
4677 strcpy(value_string, (*(boolean *)value ? "yes" : "no"));
4680 case TYPE_YES_NO_AUTO:
4681 strcpy(value_string, (*(int *)value == AUTO ? "auto" :
4682 *(int *)value == FALSE ? "no" : "yes"));
4686 strcpy(value_string, (*(boolean *)value ? "AGA" : "ECS"));
4690 strcpy(value_string, getKeyNameFromKey(*(Key *)value));
4694 strcpy(value_string, getX11KeyNameFromKey(*(Key *)value));
4698 sprintf(value_string, "%d", *(int *)value);
4702 if (*(char **)value == NULL)
4705 strcpy(value_string, *(char **)value);
4709 sprintf(value_string, "player_%d", *(int *)value + 1);
4713 value_string[0] = '\0';
4717 if (type & TYPE_GHOSTED)
4718 strcpy(value_string, "n/a");
4720 return value_string;
4723 char *getSetupLine(struct TokenInfo *token_info, char *prefix, int token_nr)
4727 static char token_string[MAX_LINE_LEN];
4728 int token_type = token_info[token_nr].type;
4729 void *setup_value = token_info[token_nr].value;
4730 char *token_text = token_info[token_nr].text;
4731 char *value_string = getSetupValue(token_type, setup_value);
4733 // build complete token string
4734 sprintf(token_string, "%s%s", prefix, token_text);
4736 // build setup entry line
4737 line = getFormattedSetupEntry(token_string, value_string);
4739 if (token_type == TYPE_KEY_X11)
4741 Key key = *(Key *)setup_value;
4742 char *keyname = getKeyNameFromKey(key);
4744 // add comment, if useful
4745 if (!strEqual(keyname, "(undefined)") &&
4746 !strEqual(keyname, "(unknown)"))
4748 // add at least one whitespace
4750 for (i = strlen(line); i < token_comment_position; i++)
4754 strcat(line, keyname);
4761 static void InitLastPlayedLevels_ParentNode(void)
4763 LevelDirTree **leveldir_top = &leveldir_first->node_group->next;
4764 LevelDirTree *leveldir_new = NULL;
4766 // check if parent node for last played levels already exists
4767 if (strEqual((*leveldir_top)->identifier, TOKEN_STR_LAST_LEVEL_SERIES))
4770 leveldir_new = newTreeInfo();
4772 setTreeInfoToDefaultsFromParent(leveldir_new, leveldir_first);
4774 leveldir_new->level_group = TRUE;
4775 leveldir_new->sort_priority = LEVELCLASS_LAST_PLAYED_LEVEL;
4777 setString(&leveldir_new->identifier, TOKEN_STR_LAST_LEVEL_SERIES);
4778 setString(&leveldir_new->name, "<< (last played level sets)");
4779 setString(&leveldir_new->name_sorting, leveldir_new->name);
4781 pushTreeInfo(leveldir_top, leveldir_new);
4783 // create node to link back to current level directory
4784 createParentTreeInfoNode(leveldir_new);
4787 void UpdateLastPlayedLevels_TreeInfo(void)
4789 char **last_level_series = setup.level_setup.last_level_series;
4790 LevelDirTree *leveldir_last;
4791 TreeInfo **node_new = NULL;
4794 if (last_level_series[0] == NULL)
4797 InitLastPlayedLevels_ParentNode();
4799 leveldir_last = getTreeInfoFromIdentifierExt(leveldir_first,
4800 TOKEN_STR_LAST_LEVEL_SERIES,
4801 TREE_NODE_TYPE_GROUP);
4802 if (leveldir_last == NULL)
4805 node_new = &leveldir_last->node_group->next;
4807 freeTreeInfo(*node_new);
4811 for (i = 0; last_level_series[i] != NULL; i++)
4813 LevelDirTree *node_last = getTreeInfoFromIdentifier(leveldir_first,
4814 last_level_series[i]);
4815 if (node_last == NULL)
4818 *node_new = getTreeInfoCopy(node_last); // copy complete node
4820 (*node_new)->node_top = &leveldir_first; // correct top node link
4821 (*node_new)->node_parent = leveldir_last; // correct parent node link
4823 (*node_new)->is_copy = TRUE; // mark entry as node copy
4825 (*node_new)->node_group = NULL;
4826 (*node_new)->next = NULL;
4828 (*node_new)->cl_first = -1; // force setting tree cursor
4830 node_new = &((*node_new)->next);
4834 static void UpdateLastPlayedLevels_List(void)
4836 char **last_level_series = setup.level_setup.last_level_series;
4837 int pos = MAX_LEVELDIR_HISTORY - 1;
4840 // search for potentially already existing entry in list of level sets
4841 for (i = 0; last_level_series[i] != NULL; i++)
4842 if (strEqual(last_level_series[i], leveldir_current->identifier))
4845 // move list of level sets one entry down (using potentially free entry)
4846 for (i = pos; i > 0; i--)
4847 setString(&last_level_series[i], last_level_series[i - 1]);
4849 // put last played level set at top position
4850 setString(&last_level_series[0], leveldir_current->identifier);
4853 static TreeInfo *StoreOrRestoreLastPlayedLevels(TreeInfo *node, boolean store)
4855 static char *identifier = NULL;
4859 setString(&identifier, (node && node->is_copy ? node->identifier : NULL));
4861 return NULL; // not used
4865 TreeInfo *node_new = getTreeInfoFromIdentifierExt(leveldir_first,
4867 TREE_NODE_TYPE_COPY);
4868 return (node_new != NULL ? node_new : node);
4872 void StoreLastPlayedLevels(TreeInfo *node)
4874 StoreOrRestoreLastPlayedLevels(node, TRUE);
4877 void RestoreLastPlayedLevels(TreeInfo **node)
4879 *node = StoreOrRestoreLastPlayedLevels(*node, FALSE);
4882 void LoadLevelSetup_LastSeries(void)
4884 // --------------------------------------------------------------------------
4885 // ~/.<program>/levelsetup.conf
4886 // --------------------------------------------------------------------------
4888 char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
4889 SetupFileHash *level_setup_hash = NULL;
4893 // always start with reliable default values
4894 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
4896 // start with empty history of last played level sets
4897 setString(&setup.level_setup.last_level_series[0], NULL);
4899 if (!strEqual(DEFAULT_LEVELSET, UNDEFINED_LEVELSET))
4901 leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
4903 if (leveldir_current == NULL)
4904 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
4907 if ((level_setup_hash = loadSetupFileHash(filename)))
4909 char *last_level_series =
4910 getHashEntry(level_setup_hash, TOKEN_STR_LAST_LEVEL_SERIES);
4912 leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
4914 if (leveldir_current == NULL)
4915 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
4917 for (i = 0; i < MAX_LEVELDIR_HISTORY; i++)
4919 char token[strlen(TOKEN_STR_LAST_LEVEL_SERIES) + 10];
4920 LevelDirTree *leveldir_last;
4922 sprintf(token, "%s.%03d", TOKEN_STR_LAST_LEVEL_SERIES, i);
4924 last_level_series = getHashEntry(level_setup_hash, token);
4926 leveldir_last = getTreeInfoFromIdentifier(leveldir_first,
4928 if (leveldir_last != NULL)
4929 setString(&setup.level_setup.last_level_series[pos++],
4933 setString(&setup.level_setup.last_level_series[pos], NULL);
4935 freeSetupFileHash(level_setup_hash);
4939 Debug("setup", "using default setup values");
4945 static void SaveLevelSetup_LastSeries_Ext(boolean deactivate_last_level_series)
4947 // --------------------------------------------------------------------------
4948 // ~/.<program>/levelsetup.conf
4949 // --------------------------------------------------------------------------
4951 // check if the current level directory structure is available at this point
4952 if (leveldir_current == NULL)
4955 char **last_level_series = setup.level_setup.last_level_series;
4956 char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
4960 InitUserDataDirectory();
4962 UpdateLastPlayedLevels_List();
4964 if (!(file = fopen(filename, MODE_WRITE)))
4966 Warn("cannot write setup file '%s'", filename);
4973 fprintFileHeader(file, LEVELSETUP_FILENAME);
4975 if (deactivate_last_level_series)
4976 fprintf(file, "# %s\n# ", "the following level set may have caused a problem and was deactivated");
4978 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_LAST_LEVEL_SERIES,
4979 leveldir_current->identifier));
4981 for (i = 0; last_level_series[i] != NULL; i++)
4983 char token[strlen(TOKEN_STR_LAST_LEVEL_SERIES) + 1 + 10 + 1];
4985 sprintf(token, "%s.%03d", TOKEN_STR_LAST_LEVEL_SERIES, i);
4987 fprintf(file, "%s\n", getFormattedSetupEntry(token, last_level_series[i]));
4992 SetFilePermissions(filename, PERMS_PRIVATE);
4997 void SaveLevelSetup_LastSeries(void)
4999 SaveLevelSetup_LastSeries_Ext(FALSE);
5002 void SaveLevelSetup_LastSeries_Deactivate(void)
5004 SaveLevelSetup_LastSeries_Ext(TRUE);
5007 static void checkSeriesInfo(void)
5009 static char *level_directory = NULL;
5012 DirectoryEntry *dir_entry;
5015 checked_free(level_directory);
5017 // check for more levels besides the 'levels' field of 'levelinfo.conf'
5019 level_directory = getPath2((leveldir_current->in_user_dir ?
5020 getUserLevelDir(NULL) :
5021 options.level_directory),
5022 leveldir_current->fullpath);
5024 if ((dir = openDirectory(level_directory)) == NULL)
5026 Warn("cannot read level directory '%s'", level_directory);
5032 while ((dir_entry = readDirectory(dir)) != NULL) // loop all entries
5034 if (strlen(dir_entry->basename) > 4 &&
5035 dir_entry->basename[3] == '.' &&
5036 strEqual(&dir_entry->basename[4], LEVELFILE_EXTENSION))
5038 char levelnum_str[4];
5041 strncpy(levelnum_str, dir_entry->basename, 3);
5042 levelnum_str[3] = '\0';
5044 levelnum_value = atoi(levelnum_str);
5046 if (levelnum_value < leveldir_current->first_level)
5048 Warn("additional level %d found", levelnum_value);
5050 leveldir_current->first_level = levelnum_value;
5052 else if (levelnum_value > leveldir_current->last_level)
5054 Warn("additional level %d found", levelnum_value);
5056 leveldir_current->last_level = levelnum_value;
5062 closeDirectory(dir);
5065 void LoadLevelSetup_SeriesInfo(void)
5068 SetupFileHash *level_setup_hash = NULL;
5069 char *level_subdir = leveldir_current->subdir;
5072 // always start with reliable default values
5073 level_nr = leveldir_current->first_level;
5075 for (i = 0; i < MAX_LEVELS; i++)
5077 LevelStats_setPlayed(i, 0);
5078 LevelStats_setSolved(i, 0);
5083 // --------------------------------------------------------------------------
5084 // ~/.<program>/levelsetup/<level series>/levelsetup.conf
5085 // --------------------------------------------------------------------------
5087 level_subdir = leveldir_current->subdir;
5089 filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
5091 if ((level_setup_hash = loadSetupFileHash(filename)))
5095 // get last played level in this level set
5097 token_value = getHashEntry(level_setup_hash, TOKEN_STR_LAST_PLAYED_LEVEL);
5101 level_nr = atoi(token_value);
5103 if (level_nr < leveldir_current->first_level)
5104 level_nr = leveldir_current->first_level;
5105 if (level_nr > leveldir_current->last_level)
5106 level_nr = leveldir_current->last_level;
5109 // get handicap level in this level set
5111 token_value = getHashEntry(level_setup_hash, TOKEN_STR_HANDICAP_LEVEL);
5115 int level_nr = atoi(token_value);
5117 if (level_nr < leveldir_current->first_level)
5118 level_nr = leveldir_current->first_level;
5119 if (level_nr > leveldir_current->last_level + 1)
5120 level_nr = leveldir_current->last_level;
5122 if (leveldir_current->user_defined || !leveldir_current->handicap)
5123 level_nr = leveldir_current->last_level;
5125 leveldir_current->handicap_level = level_nr;
5128 // get number of played and solved levels in this level set
5130 BEGIN_HASH_ITERATION(level_setup_hash, itr)
5132 char *token = HASH_ITERATION_TOKEN(itr);
5133 char *value = HASH_ITERATION_VALUE(itr);
5135 if (strlen(token) == 3 &&
5136 token[0] >= '0' && token[0] <= '9' &&
5137 token[1] >= '0' && token[1] <= '9' &&
5138 token[2] >= '0' && token[2] <= '9')
5140 int level_nr = atoi(token);
5143 LevelStats_setPlayed(level_nr, atoi(value)); // read 1st column
5145 value = strchr(value, ' ');
5148 LevelStats_setSolved(level_nr, atoi(value)); // read 2nd column
5151 END_HASH_ITERATION(hash, itr)
5153 freeSetupFileHash(level_setup_hash);
5157 Debug("setup", "using default setup values");
5163 void SaveLevelSetup_SeriesInfo(void)
5166 char *level_subdir = leveldir_current->subdir;
5167 char *level_nr_str = int2str(level_nr, 0);
5168 char *handicap_level_str = int2str(leveldir_current->handicap_level, 0);
5172 // --------------------------------------------------------------------------
5173 // ~/.<program>/levelsetup/<level series>/levelsetup.conf
5174 // --------------------------------------------------------------------------
5176 InitLevelSetupDirectory(level_subdir);
5178 filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
5180 if (!(file = fopen(filename, MODE_WRITE)))
5182 Warn("cannot write setup file '%s'", filename);
5189 fprintFileHeader(file, LEVELSETUP_FILENAME);
5191 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_PLAYED_LEVEL,
5193 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_HANDICAP_LEVEL,
5194 handicap_level_str));
5196 for (i = leveldir_current->first_level; i <= leveldir_current->last_level;
5199 if (LevelStats_getPlayed(i) > 0 ||
5200 LevelStats_getSolved(i) > 0)
5205 sprintf(token, "%03d", i);
5206 sprintf(value, "%d %d", LevelStats_getPlayed(i), LevelStats_getSolved(i));
5208 fprintf(file, "%s\n", getFormattedSetupEntry(token, value));
5214 SetFilePermissions(filename, PERMS_PRIVATE);
5219 int LevelStats_getPlayed(int nr)
5221 return (nr >= 0 && nr < MAX_LEVELS ? level_stats[nr].played : 0);
5224 int LevelStats_getSolved(int nr)
5226 return (nr >= 0 && nr < MAX_LEVELS ? level_stats[nr].solved : 0);
5229 void LevelStats_setPlayed(int nr, int value)
5231 if (nr >= 0 && nr < MAX_LEVELS)
5232 level_stats[nr].played = value;
5235 void LevelStats_setSolved(int nr, int value)
5237 if (nr >= 0 && nr < MAX_LEVELS)
5238 level_stats[nr].solved = value;
5241 void LevelStats_incPlayed(int nr)
5243 if (nr >= 0 && nr < MAX_LEVELS)
5244 level_stats[nr].played++;
5247 void LevelStats_incSolved(int nr)
5249 if (nr >= 0 && nr < MAX_LEVELS)
5250 level_stats[nr].solved++;
5253 void LoadUserSetup(void)
5255 // --------------------------------------------------------------------------
5256 // ~/.<program>/usersetup.conf
5257 // --------------------------------------------------------------------------
5259 char *filename = getPath2(getMainUserGameDataDir(), USERSETUP_FILENAME);
5260 SetupFileHash *user_setup_hash = NULL;
5262 // always start with reliable default values
5265 if ((user_setup_hash = loadSetupFileHash(filename)))
5269 // get last selected user number
5270 token_value = getHashEntry(user_setup_hash, TOKEN_STR_LAST_USER);
5273 user.nr = MIN(MAX(0, atoi(token_value)), MAX_PLAYER_NAMES - 1);
5275 freeSetupFileHash(user_setup_hash);
5279 Debug("setup", "using default setup values");
5285 void SaveUserSetup(void)
5287 // --------------------------------------------------------------------------
5288 // ~/.<program>/usersetup.conf
5289 // --------------------------------------------------------------------------
5291 char *filename = getPath2(getMainUserGameDataDir(), USERSETUP_FILENAME);
5294 InitMainUserDataDirectory();
5296 if (!(file = fopen(filename, MODE_WRITE)))
5298 Warn("cannot write setup file '%s'", filename);
5305 fprintFileHeader(file, USERSETUP_FILENAME);
5307 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_USER,
5311 SetFilePermissions(filename, PERMS_PRIVATE);