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 *getScoreCacheTapeDir(char *level_subdir, int nr)
169 static char *score_cache_tape_dir = NULL;
170 char tape_subdir[MAX_FILENAME_LEN];
172 checked_free(score_cache_tape_dir);
174 sprintf(tape_subdir, "%03d", nr);
175 score_cache_tape_dir = getPath2(getScoreCacheDir(level_subdir), tape_subdir);
177 return score_cache_tape_dir;
180 static char *getUserSubdir(int nr)
182 static char user_subdir[16] = { 0 };
184 sprintf(user_subdir, "%03d", nr);
189 static char *getUserDir(int nr)
191 static char *user_dir = NULL;
192 char *main_data_dir = getMainUserGameDataDir();
193 char *users_subdir = USERS_DIRECTORY;
194 char *user_subdir = getUserSubdir(nr);
196 checked_free(user_dir);
199 user_dir = getPath3(main_data_dir, users_subdir, user_subdir);
201 user_dir = getPath2(main_data_dir, users_subdir);
206 static char *getLevelSetupDir(char *level_subdir)
208 static char *levelsetup_dir = NULL;
209 char *data_dir = getUserGameDataDir();
210 char *levelsetup_subdir = LEVELSETUP_DIRECTORY;
212 checked_free(levelsetup_dir);
214 if (level_subdir != NULL)
215 levelsetup_dir = getPath3(data_dir, levelsetup_subdir, level_subdir);
217 levelsetup_dir = getPath2(data_dir, levelsetup_subdir);
219 return levelsetup_dir;
222 static char *getNetworkDir(void)
224 static char *network_dir = NULL;
226 if (network_dir == NULL)
227 network_dir = getPath2(getMainUserGameDataDir(), NETWORK_DIRECTORY);
232 char *getLevelDirFromTreeInfo(TreeInfo *node)
234 static char *level_dir = NULL;
237 return options.level_directory;
239 checked_free(level_dir);
241 level_dir = getPath2((node->in_user_dir ? getUserLevelDir(NULL) :
242 options.level_directory), node->fullpath);
247 char *getUserLevelDir(char *level_subdir)
249 static char *userlevel_dir = NULL;
250 char *data_dir = getMainUserGameDataDir();
251 char *userlevel_subdir = LEVELS_DIRECTORY;
253 checked_free(userlevel_dir);
255 if (level_subdir != NULL)
256 userlevel_dir = getPath3(data_dir, userlevel_subdir, level_subdir);
258 userlevel_dir = getPath2(data_dir, userlevel_subdir);
260 return userlevel_dir;
263 char *getNetworkLevelDir(char *level_subdir)
265 static char *network_level_dir = NULL;
266 char *data_dir = getNetworkDir();
267 char *networklevel_subdir = LEVELS_DIRECTORY;
269 checked_free(network_level_dir);
271 if (level_subdir != NULL)
272 network_level_dir = getPath3(data_dir, networklevel_subdir, level_subdir);
274 network_level_dir = getPath2(data_dir, networklevel_subdir);
276 return network_level_dir;
279 char *getCurrentLevelDir(void)
281 return getLevelDirFromTreeInfo(leveldir_current);
284 char *getNewUserLevelSubdir(void)
286 static char *new_level_subdir = NULL;
287 char *subdir_prefix = getLoginName();
288 char subdir_suffix[10];
289 int max_suffix_number = 1000;
292 while (++i < max_suffix_number)
294 sprintf(subdir_suffix, "_%d", i);
296 checked_free(new_level_subdir);
297 new_level_subdir = getStringCat2(subdir_prefix, subdir_suffix);
299 if (!directoryExists(getUserLevelDir(new_level_subdir)))
303 return new_level_subdir;
306 char *getTapeDir(char *level_subdir)
308 static char *tape_dir = NULL;
309 char *data_dir = getUserGameDataDir();
310 char *tape_subdir = TAPES_DIRECTORY;
312 checked_free(tape_dir);
314 if (level_subdir != NULL)
315 tape_dir = getPath3(data_dir, tape_subdir, level_subdir);
317 tape_dir = getPath2(data_dir, tape_subdir);
322 static char *getSolutionTapeDir(void)
324 static char *tape_dir = NULL;
325 char *data_dir = getCurrentLevelDir();
326 char *tape_subdir = TAPES_DIRECTORY;
328 checked_free(tape_dir);
330 tape_dir = getPath2(data_dir, tape_subdir);
335 static char *getDefaultGraphicsDir(char *graphics_subdir)
337 static char *graphics_dir = NULL;
339 if (graphics_subdir == NULL)
340 return options.graphics_directory;
342 checked_free(graphics_dir);
344 graphics_dir = getPath2(options.graphics_directory, graphics_subdir);
349 static char *getDefaultSoundsDir(char *sounds_subdir)
351 static char *sounds_dir = NULL;
353 if (sounds_subdir == NULL)
354 return options.sounds_directory;
356 checked_free(sounds_dir);
358 sounds_dir = getPath2(options.sounds_directory, sounds_subdir);
363 static char *getDefaultMusicDir(char *music_subdir)
365 static char *music_dir = NULL;
367 if (music_subdir == NULL)
368 return options.music_directory;
370 checked_free(music_dir);
372 music_dir = getPath2(options.music_directory, music_subdir);
377 static char *getClassicArtworkSet(int type)
379 return (type == TREE_TYPE_GRAPHICS_DIR ? GFX_CLASSIC_SUBDIR :
380 type == TREE_TYPE_SOUNDS_DIR ? SND_CLASSIC_SUBDIR :
381 type == TREE_TYPE_MUSIC_DIR ? MUS_CLASSIC_SUBDIR : "");
384 static char *getClassicArtworkDir(int type)
386 return (type == TREE_TYPE_GRAPHICS_DIR ?
387 getDefaultGraphicsDir(GFX_CLASSIC_SUBDIR) :
388 type == TREE_TYPE_SOUNDS_DIR ?
389 getDefaultSoundsDir(SND_CLASSIC_SUBDIR) :
390 type == TREE_TYPE_MUSIC_DIR ?
391 getDefaultMusicDir(MUS_CLASSIC_SUBDIR) : "");
394 char *getUserGraphicsDir(void)
396 static char *usergraphics_dir = NULL;
398 if (usergraphics_dir == NULL)
399 usergraphics_dir = getPath2(getMainUserGameDataDir(), GRAPHICS_DIRECTORY);
401 return usergraphics_dir;
404 char *getUserSoundsDir(void)
406 static char *usersounds_dir = NULL;
408 if (usersounds_dir == NULL)
409 usersounds_dir = getPath2(getMainUserGameDataDir(), SOUNDS_DIRECTORY);
411 return usersounds_dir;
414 char *getUserMusicDir(void)
416 static char *usermusic_dir = NULL;
418 if (usermusic_dir == NULL)
419 usermusic_dir = getPath2(getMainUserGameDataDir(), MUSIC_DIRECTORY);
421 return usermusic_dir;
424 static char *getSetupArtworkDir(TreeInfo *ti)
426 static char *artwork_dir = NULL;
431 checked_free(artwork_dir);
433 artwork_dir = getPath2(ti->basepath, ti->fullpath);
438 char *setLevelArtworkDir(TreeInfo *ti)
440 char **artwork_path_ptr, **artwork_set_ptr;
441 TreeInfo *level_artwork;
443 if (ti == NULL || leveldir_current == NULL)
446 artwork_path_ptr = LEVELDIR_ARTWORK_PATH_PTR(leveldir_current, ti->type);
447 artwork_set_ptr = LEVELDIR_ARTWORK_SET_PTR( leveldir_current, ti->type);
449 checked_free(*artwork_path_ptr);
451 if ((level_artwork = getTreeInfoFromIdentifier(ti, *artwork_set_ptr)))
453 *artwork_path_ptr = getStringCopy(getSetupArtworkDir(level_artwork));
458 No (or non-existing) artwork configured in "levelinfo.conf". This would
459 normally result in using the artwork configured in the setup menu. But
460 if an artwork subdirectory exists (which might contain custom artwork
461 or an artwork configuration file), this level artwork must be treated
462 as relative to the default "classic" artwork, not to the artwork that
463 is currently configured in the setup menu.
465 Update: For "special" versions of R'n'D (like "R'n'D jue"), do not use
466 the "default" artwork (which would be "jue0" for "R'n'D jue"), but use
467 the real "classic" artwork from the original R'n'D (like "gfx_classic").
470 char *dir = getPath2(getCurrentLevelDir(), ARTWORK_DIRECTORY(ti->type));
472 checked_free(*artwork_set_ptr);
474 if (directoryExists(dir))
476 *artwork_path_ptr = getStringCopy(getClassicArtworkDir(ti->type));
477 *artwork_set_ptr = getStringCopy(getClassicArtworkSet(ti->type));
481 *artwork_path_ptr = getStringCopy(UNDEFINED_FILENAME);
482 *artwork_set_ptr = NULL;
488 return *artwork_set_ptr;
491 static char *getLevelArtworkSet(int type)
493 if (leveldir_current == NULL)
496 return LEVELDIR_ARTWORK_SET(leveldir_current, type);
499 static char *getLevelArtworkDir(int type)
501 if (leveldir_current == NULL)
502 return UNDEFINED_FILENAME;
504 return LEVELDIR_ARTWORK_PATH(leveldir_current, type);
507 char *getProgramMainDataPath(char *command_filename, char *base_path)
509 // check if the program's main data base directory is configured
510 if (!strEqual(base_path, "."))
511 return getStringCopy(base_path);
513 /* if the program is configured to start from current directory (default),
514 determine program package directory from program binary (some versions
515 of KDE/Konqueror and Mac OS X (especially "Mavericks") apparently do not
516 set the current working directory to the program package directory) */
517 char *main_data_path = getBasePath(command_filename);
519 #if defined(PLATFORM_MAC)
520 if (strSuffix(main_data_path, MAC_APP_BINARY_SUBDIR))
522 char *main_data_path_old = main_data_path;
524 // cut relative path to Mac OS X application binary directory from path
525 main_data_path[strlen(main_data_path) -
526 strlen(MAC_APP_BINARY_SUBDIR)] = '\0';
528 // cut trailing path separator from path (but not if path is root directory)
529 if (strSuffix(main_data_path, "/") && !strEqual(main_data_path, "/"))
530 main_data_path[strlen(main_data_path) - 1] = '\0';
532 // replace empty path with current directory
533 if (strEqual(main_data_path, ""))
534 main_data_path = ".";
536 // add relative path to Mac OS X application resources directory to path
537 main_data_path = getPath2(main_data_path, MAC_APP_FILES_SUBDIR);
539 free(main_data_path_old);
543 return main_data_path;
546 char *getProgramConfigFilename(char *command_filename)
548 static char *config_filename_1 = NULL;
549 static char *config_filename_2 = NULL;
550 static char *config_filename_3 = NULL;
551 static boolean initialized = FALSE;
555 char *command_filename_1 = getStringCopy(command_filename);
557 // strip trailing executable suffix from command filename
558 if (strSuffix(command_filename_1, ".exe"))
559 command_filename_1[strlen(command_filename_1) - 4] = '\0';
561 char *base_path = getProgramMainDataPath(command_filename, BASE_PATH);
562 char *conf_directory = getPath2(base_path, CONF_DIRECTORY);
564 char *command_basepath = getBasePath(command_filename);
565 char *command_basename = getBaseNameNoSuffix(command_filename);
566 char *command_filename_2 = getPath2(command_basepath, command_basename);
568 config_filename_1 = getStringCat2(command_filename_1, ".conf");
569 config_filename_2 = getStringCat2(command_filename_2, ".conf");
570 config_filename_3 = getPath2(conf_directory, SETUP_FILENAME);
572 checked_free(base_path);
573 checked_free(conf_directory);
575 checked_free(command_basepath);
576 checked_free(command_basename);
578 checked_free(command_filename_1);
579 checked_free(command_filename_2);
584 // 1st try: look for config file that exactly matches the binary filename
585 if (fileExists(config_filename_1))
586 return config_filename_1;
588 // 2nd try: look for config file that matches binary filename without suffix
589 if (fileExists(config_filename_2))
590 return config_filename_2;
592 // 3rd try: return setup config filename in global program config directory
593 return config_filename_3;
596 static char *getPlatformConfigFilename(char *config_filename)
598 static char *platform_config_filename = NULL;
599 static boolean initialized = FALSE;
603 char *config_basepath = getBasePath(config_filename);
604 char *config_basename = getBaseNameNoSuffix(config_filename);
605 char *config_filename_prefix = getPath2(config_basepath, config_basename);
606 char *platform_string_lower = getStringToLower(PLATFORM_STRING);
607 char *platform_suffix = getStringCat2("-", platform_string_lower);
609 platform_config_filename = getStringCat3(config_filename_prefix,
610 platform_suffix, ".conf");
612 checked_free(config_basepath);
613 checked_free(config_basename);
614 checked_free(config_filename_prefix);
615 checked_free(platform_string_lower);
616 checked_free(platform_suffix);
621 return platform_config_filename;
624 char *getTapeFilename(int nr)
626 static char *filename = NULL;
627 char basename[MAX_FILENAME_LEN];
629 checked_free(filename);
631 sprintf(basename, "%03d.%s", nr, TAPEFILE_EXTENSION);
632 filename = getPath2(getTapeDir(leveldir_current->subdir), basename);
637 char *getTemporaryTapeFilename(void)
639 static char *filename = NULL;
640 char basename[MAX_FILENAME_LEN];
642 checked_free(filename);
644 sprintf(basename, "tmp.%s", TAPEFILE_EXTENSION);
645 filename = getPath2(getTapeDir(NULL), basename);
650 char *getDefaultSolutionTapeFilename(int nr)
652 static char *filename = NULL;
653 char basename[MAX_FILENAME_LEN];
655 checked_free(filename);
657 sprintf(basename, "%03d.%s", nr, TAPEFILE_EXTENSION);
658 filename = getPath2(getSolutionTapeDir(), basename);
663 char *getSokobanSolutionTapeFilename(int nr)
665 static char *filename = NULL;
666 char basename[MAX_FILENAME_LEN];
668 checked_free(filename);
670 sprintf(basename, "%03d.sln", nr);
671 filename = getPath2(getSolutionTapeDir(), basename);
676 char *getSolutionTapeFilename(int nr)
678 char *filename = getDefaultSolutionTapeFilename(nr);
680 if (!fileExists(filename))
682 char *filename2 = getSokobanSolutionTapeFilename(nr);
684 if (fileExists(filename2))
691 char *getScoreFilename(int nr)
693 static char *filename = NULL;
694 char basename[MAX_FILENAME_LEN];
696 checked_free(filename);
698 sprintf(basename, "%03d.%s", nr, SCOREFILE_EXTENSION);
700 // used instead of "leveldir_current->subdir" (for network games)
701 filename = getPath2(getScoreDir(levelset.identifier), basename);
706 char *getScoreCacheFilename(int nr)
708 static char *filename = NULL;
709 char basename[MAX_FILENAME_LEN];
711 checked_free(filename);
713 sprintf(basename, "%03d.%s", nr, SCOREFILE_EXTENSION);
715 // used instead of "leveldir_current->subdir" (for network games)
716 filename = getPath2(getScoreCacheDir(levelset.identifier), basename);
721 char *getScoreTapeBasename(char *name)
723 static char basename[MAX_FILENAME_LEN];
724 char basename_raw[MAX_FILENAME_LEN];
727 sprintf(timestamp, "%s", getCurrentTimestamp());
728 sprintf(basename_raw, "%s-%s", timestamp, name);
729 sprintf(basename, "%s-%08x", timestamp, get_hash_from_key(basename_raw));
734 char *getScoreTapeFilename(char *basename_no_ext, int nr)
736 static char *filename = NULL;
737 char basename[MAX_FILENAME_LEN];
739 checked_free(filename);
741 sprintf(basename, "%s.%s", basename_no_ext, TAPEFILE_EXTENSION);
743 // used instead of "leveldir_current->subdir" (for network games)
744 filename = getPath2(getScoreTapeDir(levelset.identifier, nr), basename);
749 char *getScoreCacheTapeFilename(char *basename_no_ext, int nr)
751 static char *filename = NULL;
752 char basename[MAX_FILENAME_LEN];
754 checked_free(filename);
756 sprintf(basename, "%s.%s", basename_no_ext, TAPEFILE_EXTENSION);
758 // used instead of "leveldir_current->subdir" (for network games)
759 filename = getPath2(getScoreCacheTapeDir(levelset.identifier, nr), basename);
764 char *getSetupFilename(void)
766 static char *filename = NULL;
768 checked_free(filename);
770 filename = getPath2(getSetupDir(), SETUP_FILENAME);
775 char *getDefaultSetupFilename(void)
777 return program.config_filename;
780 char *getPlatformSetupFilename(void)
782 return getPlatformConfigFilename(program.config_filename);
785 char *getEditorSetupFilename(void)
787 static char *filename = NULL;
789 checked_free(filename);
790 filename = getPath2(getCurrentLevelDir(), EDITORSETUP_FILENAME);
792 if (fileExists(filename))
795 checked_free(filename);
796 filename = getPath2(getSetupDir(), EDITORSETUP_FILENAME);
801 char *getHelpAnimFilename(void)
803 static char *filename = NULL;
805 checked_free(filename);
807 filename = getPath2(getCurrentLevelDir(), HELPANIM_FILENAME);
812 char *getHelpTextFilename(void)
814 static char *filename = NULL;
816 checked_free(filename);
818 filename = getPath2(getCurrentLevelDir(), HELPTEXT_FILENAME);
823 char *getLevelSetInfoFilename(void)
825 static char *filename = NULL;
840 for (i = 0; basenames[i] != NULL; i++)
842 checked_free(filename);
843 filename = getPath2(getCurrentLevelDir(), basenames[i]);
845 if (fileExists(filename))
852 static char *getLevelSetTitleMessageBasename(int nr, boolean initial)
854 static char basename[32];
856 sprintf(basename, "%s_%d.txt",
857 (initial ? "titlemessage_initial" : "titlemessage"), nr + 1);
862 char *getLevelSetTitleMessageFilename(int nr, boolean initial)
864 static char *filename = NULL;
866 boolean skip_setup_artwork = FALSE;
868 checked_free(filename);
870 basename = getLevelSetTitleMessageBasename(nr, initial);
872 if (!gfx.override_level_graphics)
874 // 1st try: look for special artwork in current level series directory
875 filename = getPath3(getCurrentLevelDir(), GRAPHICS_DIRECTORY, basename);
876 if (fileExists(filename))
881 // 2nd try: look for message file in current level set directory
882 filename = getPath2(getCurrentLevelDir(), basename);
883 if (fileExists(filename))
888 // check if there is special artwork configured in level series config
889 if (getLevelArtworkSet(ARTWORK_TYPE_GRAPHICS) != NULL)
891 // 3rd try: look for special artwork configured in level series config
892 filename = getPath2(getLevelArtworkDir(ARTWORK_TYPE_GRAPHICS), basename);
893 if (fileExists(filename))
898 // take missing artwork configured in level set config from default
899 skip_setup_artwork = TRUE;
903 if (!skip_setup_artwork)
905 // 4th try: look for special artwork in configured artwork directory
906 filename = getPath2(getSetupArtworkDir(artwork.gfx_current), basename);
907 if (fileExists(filename))
913 // 5th try: look for default artwork in new default artwork directory
914 filename = getPath2(getDefaultGraphicsDir(GFX_DEFAULT_SUBDIR), basename);
915 if (fileExists(filename))
920 // 6th try: look for default artwork in old default artwork directory
921 filename = getPath2(options.graphics_directory, basename);
922 if (fileExists(filename))
925 return NULL; // cannot find specified artwork file anywhere
928 static char *getCreditsBasename(int nr)
930 static char basename[32];
932 sprintf(basename, "credits_%d.txt", nr + 1);
937 char *getCreditsFilename(int nr, boolean global)
939 char *basename = getCreditsBasename(nr);
940 char *basepath = NULL;
941 static char *credits_subdir = NULL;
942 static char *filename = NULL;
944 if (credits_subdir == NULL)
945 credits_subdir = getPath2(DOCS_DIRECTORY, CREDITS_DIRECTORY);
947 checked_free(filename);
949 // look for credits file in the game's base or current level set directory
950 basepath = (global ? options.base_directory : getCurrentLevelDir());
952 filename = getPath3(basepath, credits_subdir, basename);
953 if (fileExists(filename))
956 return NULL; // cannot find credits file
959 static char *getProgramInfoBasename(int nr)
961 static char basename[32];
963 sprintf(basename, "program_%d.txt", nr + 1);
968 char *getProgramInfoFilename(int nr)
970 char *basename = getProgramInfoBasename(nr);
971 static char *info_subdir = NULL;
972 static char *filename = NULL;
974 if (info_subdir == NULL)
975 info_subdir = getPath2(DOCS_DIRECTORY, PROGRAM_INFO_DIRECTORY);
977 checked_free(filename);
979 // look for program info file in the game's base directory
980 filename = getPath3(options.base_directory, info_subdir, basename);
981 if (fileExists(filename))
984 return NULL; // cannot find program info file
987 static char *getCorrectedArtworkBasename(char *basename)
992 char *getCustomImageFilename(char *basename)
994 static char *filename = NULL;
995 boolean skip_setup_artwork = FALSE;
997 checked_free(filename);
999 basename = getCorrectedArtworkBasename(basename);
1001 if (!gfx.override_level_graphics)
1003 // 1st try: look for special artwork in current level series directory
1004 filename = getImg3(getCurrentLevelDir(), GRAPHICS_DIRECTORY, basename);
1005 if (fileExists(filename))
1010 // check if there is special artwork configured in level series config
1011 if (getLevelArtworkSet(ARTWORK_TYPE_GRAPHICS) != NULL)
1013 // 2nd try: look for special artwork configured in level series config
1014 filename = getImg2(getLevelArtworkDir(ARTWORK_TYPE_GRAPHICS), basename);
1015 if (fileExists(filename))
1020 // take missing artwork configured in level set config from default
1021 skip_setup_artwork = TRUE;
1025 if (!skip_setup_artwork)
1027 // 3rd try: look for special artwork in configured artwork directory
1028 filename = getImg2(getSetupArtworkDir(artwork.gfx_current), basename);
1029 if (fileExists(filename))
1035 // 4th try: look for default artwork in new default artwork directory
1036 filename = getImg2(getDefaultGraphicsDir(GFX_DEFAULT_SUBDIR), basename);
1037 if (fileExists(filename))
1042 // 5th try: look for default artwork in old default artwork directory
1043 filename = getImg2(options.graphics_directory, basename);
1044 if (fileExists(filename))
1047 if (!strEqual(GFX_FALLBACK_FILENAME, UNDEFINED_FILENAME))
1051 WarnUsingFallback(basename);
1053 // 6th try: look for fallback artwork in old default artwork directory
1054 // (needed to prevent errors when trying to access unused artwork files)
1055 filename = getImg2(options.graphics_directory, GFX_FALLBACK_FILENAME);
1056 if (fileExists(filename))
1060 return NULL; // cannot find specified artwork file anywhere
1063 char *getCustomSoundFilename(char *basename)
1065 static char *filename = NULL;
1066 boolean skip_setup_artwork = FALSE;
1068 checked_free(filename);
1070 basename = getCorrectedArtworkBasename(basename);
1072 if (!gfx.override_level_sounds)
1074 // 1st try: look for special artwork in current level series directory
1075 filename = getPath3(getCurrentLevelDir(), SOUNDS_DIRECTORY, basename);
1076 if (fileExists(filename))
1081 // check if there is special artwork configured in level series config
1082 if (getLevelArtworkSet(ARTWORK_TYPE_SOUNDS) != NULL)
1084 // 2nd try: look for special artwork configured in level series config
1085 filename = getPath2(getLevelArtworkDir(TREE_TYPE_SOUNDS_DIR), basename);
1086 if (fileExists(filename))
1091 // take missing artwork configured in level set config from default
1092 skip_setup_artwork = TRUE;
1096 if (!skip_setup_artwork)
1098 // 3rd try: look for special artwork in configured artwork directory
1099 filename = getPath2(getSetupArtworkDir(artwork.snd_current), basename);
1100 if (fileExists(filename))
1106 // 4th try: look for default artwork in new default artwork directory
1107 filename = getPath2(getDefaultSoundsDir(SND_DEFAULT_SUBDIR), basename);
1108 if (fileExists(filename))
1113 // 5th try: look for default artwork in old default artwork directory
1114 filename = getPath2(options.sounds_directory, basename);
1115 if (fileExists(filename))
1118 if (!strEqual(SND_FALLBACK_FILENAME, UNDEFINED_FILENAME))
1122 WarnUsingFallback(basename);
1124 // 6th try: look for fallback artwork in old default artwork directory
1125 // (needed to prevent errors when trying to access unused artwork files)
1126 filename = getPath2(options.sounds_directory, SND_FALLBACK_FILENAME);
1127 if (fileExists(filename))
1131 return NULL; // cannot find specified artwork file anywhere
1134 char *getCustomMusicFilename(char *basename)
1136 static char *filename = NULL;
1137 boolean skip_setup_artwork = FALSE;
1139 checked_free(filename);
1141 basename = getCorrectedArtworkBasename(basename);
1143 if (!gfx.override_level_music)
1145 // 1st try: look for special artwork in current level series directory
1146 filename = getPath3(getCurrentLevelDir(), MUSIC_DIRECTORY, basename);
1147 if (fileExists(filename))
1152 // check if there is special artwork configured in level series config
1153 if (getLevelArtworkSet(ARTWORK_TYPE_MUSIC) != NULL)
1155 // 2nd try: look for special artwork configured in level series config
1156 filename = getPath2(getLevelArtworkDir(TREE_TYPE_MUSIC_DIR), basename);
1157 if (fileExists(filename))
1162 // take missing artwork configured in level set config from default
1163 skip_setup_artwork = TRUE;
1167 if (!skip_setup_artwork)
1169 // 3rd try: look for special artwork in configured artwork directory
1170 filename = getPath2(getSetupArtworkDir(artwork.mus_current), basename);
1171 if (fileExists(filename))
1177 // 4th try: look for default artwork in new default artwork directory
1178 filename = getPath2(getDefaultMusicDir(MUS_DEFAULT_SUBDIR), basename);
1179 if (fileExists(filename))
1184 // 5th try: look for default artwork in old default artwork directory
1185 filename = getPath2(options.music_directory, basename);
1186 if (fileExists(filename))
1189 if (!strEqual(MUS_FALLBACK_FILENAME, UNDEFINED_FILENAME))
1193 WarnUsingFallback(basename);
1195 // 6th try: look for fallback artwork in old default artwork directory
1196 // (needed to prevent errors when trying to access unused artwork files)
1197 filename = getPath2(options.music_directory, MUS_FALLBACK_FILENAME);
1198 if (fileExists(filename))
1202 return NULL; // cannot find specified artwork file anywhere
1205 char *getCustomArtworkFilename(char *basename, int type)
1207 if (type == ARTWORK_TYPE_GRAPHICS)
1208 return getCustomImageFilename(basename);
1209 else if (type == ARTWORK_TYPE_SOUNDS)
1210 return getCustomSoundFilename(basename);
1211 else if (type == ARTWORK_TYPE_MUSIC)
1212 return getCustomMusicFilename(basename);
1214 return UNDEFINED_FILENAME;
1217 char *getCustomArtworkConfigFilename(int type)
1219 return getCustomArtworkFilename(ARTWORKINFO_FILENAME(type), type);
1222 char *getCustomArtworkLevelConfigFilename(int type)
1224 static char *filename = NULL;
1226 checked_free(filename);
1228 filename = getPath2(getLevelArtworkDir(type), ARTWORKINFO_FILENAME(type));
1233 char *getCustomMusicDirectory(void)
1235 static char *directory = NULL;
1236 boolean skip_setup_artwork = FALSE;
1238 checked_free(directory);
1240 if (!gfx.override_level_music)
1242 // 1st try: look for special artwork in current level series directory
1243 directory = getPath2(getCurrentLevelDir(), MUSIC_DIRECTORY);
1244 if (directoryExists(directory))
1249 // check if there is special artwork configured in level series config
1250 if (getLevelArtworkSet(ARTWORK_TYPE_MUSIC) != NULL)
1252 // 2nd try: look for special artwork configured in level series config
1253 directory = getStringCopy(getLevelArtworkDir(TREE_TYPE_MUSIC_DIR));
1254 if (directoryExists(directory))
1259 // take missing artwork configured in level set config from default
1260 skip_setup_artwork = TRUE;
1264 if (!skip_setup_artwork)
1266 // 3rd try: look for special artwork in configured artwork directory
1267 directory = getStringCopy(getSetupArtworkDir(artwork.mus_current));
1268 if (directoryExists(directory))
1274 // 4th try: look for default artwork in new default artwork directory
1275 directory = getStringCopy(getDefaultMusicDir(MUS_DEFAULT_SUBDIR));
1276 if (directoryExists(directory))
1281 // 5th try: look for default artwork in old default artwork directory
1282 directory = getStringCopy(options.music_directory);
1283 if (directoryExists(directory))
1286 return NULL; // cannot find specified artwork file anywhere
1289 void MarkTapeDirectoryUploadsAsComplete(char *level_subdir)
1291 char *filename = getPath2(getTapeDir(level_subdir), UPLOADED_FILENAME);
1293 touchFile(filename);
1295 checked_free(filename);
1298 void MarkTapeDirectoryUploadsAsIncomplete(char *level_subdir)
1300 char *filename = getPath2(getTapeDir(level_subdir), UPLOADED_FILENAME);
1304 checked_free(filename);
1307 boolean CheckTapeDirectoryUploadsComplete(char *level_subdir)
1309 char *filename = getPath2(getTapeDir(level_subdir), UPLOADED_FILENAME);
1310 boolean success = fileExists(filename);
1312 checked_free(filename);
1317 void InitMissingFileHash(void)
1319 if (missing_file_hash == NULL)
1320 freeSetupFileHash(missing_file_hash);
1322 missing_file_hash = newSetupFileHash();
1325 void InitTapeDirectory(char *level_subdir)
1327 boolean new_tape_dir = !directoryExists(getTapeDir(level_subdir));
1329 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
1330 createDirectory(getTapeDir(NULL), "main tape", PERMS_PRIVATE);
1331 createDirectory(getTapeDir(level_subdir), "level tape", PERMS_PRIVATE);
1334 MarkTapeDirectoryUploadsAsComplete(level_subdir);
1337 void InitScoreDirectory(char *level_subdir)
1339 createDirectory(getMainUserGameDataDir(), "main user data", PERMS_PRIVATE);
1340 createDirectory(getScoreDir(NULL), "main score", PERMS_PRIVATE);
1341 createDirectory(getScoreDir(level_subdir), "level score", PERMS_PRIVATE);
1344 void InitScoreCacheDirectory(char *level_subdir)
1346 createDirectory(getMainUserGameDataDir(), "main user data", PERMS_PRIVATE);
1347 createDirectory(getCacheDir(), "cache data", PERMS_PRIVATE);
1348 createDirectory(getScoreCacheDir(NULL), "main score", PERMS_PRIVATE);
1349 createDirectory(getScoreCacheDir(level_subdir), "level score", PERMS_PRIVATE);
1352 void InitScoreTapeDirectory(char *level_subdir, int nr)
1354 InitScoreDirectory(level_subdir);
1356 createDirectory(getScoreTapeDir(level_subdir, nr), "score tape", PERMS_PRIVATE);
1359 void InitScoreCacheTapeDirectory(char *level_subdir, int nr)
1361 InitScoreCacheDirectory(level_subdir);
1363 createDirectory(getScoreCacheTapeDir(level_subdir, nr), "score tape", PERMS_PRIVATE);
1366 static void SaveUserLevelInfo(void);
1368 void InitUserLevelDirectory(char *level_subdir)
1370 if (!directoryExists(getUserLevelDir(level_subdir)))
1372 createDirectory(getMainUserGameDataDir(), "main user data", PERMS_PRIVATE);
1373 createDirectory(getUserLevelDir(NULL), "main user level", PERMS_PRIVATE);
1374 createDirectory(getUserLevelDir(level_subdir), "user level", PERMS_PRIVATE);
1376 if (setup.internal.create_user_levelset)
1377 SaveUserLevelInfo();
1381 void InitNetworkLevelDirectory(char *level_subdir)
1383 if (!directoryExists(getNetworkLevelDir(level_subdir)))
1385 createDirectory(getMainUserGameDataDir(), "main user data", PERMS_PRIVATE);
1386 createDirectory(getNetworkDir(), "network data", PERMS_PRIVATE);
1387 createDirectory(getNetworkLevelDir(NULL), "main network level", PERMS_PRIVATE);
1388 createDirectory(getNetworkLevelDir(level_subdir), "network level", PERMS_PRIVATE);
1392 void InitLevelSetupDirectory(char *level_subdir)
1394 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
1395 createDirectory(getLevelSetupDir(NULL), "main level setup", PERMS_PRIVATE);
1396 createDirectory(getLevelSetupDir(level_subdir), "level setup", PERMS_PRIVATE);
1399 static void InitCacheDirectory(void)
1401 createDirectory(getMainUserGameDataDir(), "main user data", PERMS_PRIVATE);
1402 createDirectory(getCacheDir(), "cache data", PERMS_PRIVATE);
1406 // ----------------------------------------------------------------------------
1407 // some functions to handle lists of level and artwork directories
1408 // ----------------------------------------------------------------------------
1410 TreeInfo *newTreeInfo(void)
1412 return checked_calloc(sizeof(TreeInfo));
1415 TreeInfo *newTreeInfo_setDefaults(int type)
1417 TreeInfo *ti = newTreeInfo();
1419 setTreeInfoToDefaults(ti, type);
1424 void pushTreeInfo(TreeInfo **node_first, TreeInfo *node_new)
1426 node_new->next = *node_first;
1427 *node_first = node_new;
1430 void removeTreeInfo(TreeInfo **node_first)
1432 TreeInfo *node_old = *node_first;
1434 *node_first = node_old->next;
1435 node_old->next = NULL;
1437 freeTreeInfo(node_old);
1440 int numTreeInfo(TreeInfo *node)
1453 boolean validLevelSeries(TreeInfo *node)
1455 // in a number of cases, tree node is no valid level set
1456 if (node == NULL || node->node_group || node->parent_link || node->is_copy)
1462 TreeInfo *getValidLevelSeries(TreeInfo *node, TreeInfo *default_node)
1464 if (validLevelSeries(node))
1466 else if (node->is_copy)
1467 return getTreeInfoFromIdentifier(leveldir_first, node->identifier);
1469 return getFirstValidTreeInfoEntry(default_node);
1472 static TreeInfo *getValidTreeInfoEntryExt(TreeInfo *node, boolean get_next_node)
1477 if (node->node_group) // enter node group (step down into tree)
1478 return getFirstValidTreeInfoEntry(node->node_group);
1480 if (node->parent_link) // skip first node (back link) of node group
1481 get_next_node = TRUE;
1483 if (!get_next_node) // get current regular tree node
1486 // get next regular tree node, or step up until one is found
1487 while (node->next == NULL && node->node_parent != NULL)
1488 node = node->node_parent;
1490 return getFirstValidTreeInfoEntry(node->next);
1493 TreeInfo *getFirstValidTreeInfoEntry(TreeInfo *node)
1495 return getValidTreeInfoEntryExt(node, FALSE);
1498 TreeInfo *getNextValidTreeInfoEntry(TreeInfo *node)
1500 return getValidTreeInfoEntryExt(node, TRUE);
1503 TreeInfo *getTreeInfoFirstGroupEntry(TreeInfo *node)
1508 if (node->node_parent == NULL) // top level group
1509 return *node->node_top;
1510 else // sub level group
1511 return node->node_parent->node_group;
1514 int numTreeInfoInGroup(TreeInfo *node)
1516 return numTreeInfo(getTreeInfoFirstGroupEntry(node));
1519 int getPosFromTreeInfo(TreeInfo *node)
1521 TreeInfo *node_cmp = getTreeInfoFirstGroupEntry(node);
1526 if (node_cmp == node)
1530 node_cmp = node_cmp->next;
1536 TreeInfo *getTreeInfoFromPos(TreeInfo *node, int pos)
1538 TreeInfo *node_default = node;
1550 return node_default;
1553 static TreeInfo *getTreeInfoFromIdentifierExt(TreeInfo *node, char *identifier,
1554 int node_type_wanted)
1556 if (identifier == NULL)
1561 if (TREE_NODE_TYPE(node) == node_type_wanted &&
1562 strEqual(identifier, node->identifier))
1565 if (node->node_group)
1567 TreeInfo *node_group = getTreeInfoFromIdentifierExt(node->node_group,
1580 TreeInfo *getTreeInfoFromIdentifier(TreeInfo *node, char *identifier)
1582 return getTreeInfoFromIdentifierExt(node, identifier, TREE_NODE_TYPE_DEFAULT);
1585 static TreeInfo *cloneTreeNode(TreeInfo **node_top, TreeInfo *node_parent,
1586 TreeInfo *node, boolean skip_sets_without_levels)
1593 if (!node->parent_link && !node->level_group &&
1594 skip_sets_without_levels && node->levels == 0)
1595 return cloneTreeNode(node_top, node_parent, node->next,
1596 skip_sets_without_levels);
1598 node_new = getTreeInfoCopy(node); // copy complete node
1600 node_new->node_top = node_top; // correct top node link
1601 node_new->node_parent = node_parent; // correct parent node link
1603 if (node->level_group)
1604 node_new->node_group = cloneTreeNode(node_top, node_new, node->node_group,
1605 skip_sets_without_levels);
1607 node_new->next = cloneTreeNode(node_top, node_parent, node->next,
1608 skip_sets_without_levels);
1613 static void cloneTree(TreeInfo **ti_new, TreeInfo *ti, boolean skip_empty_sets)
1615 TreeInfo *ti_cloned = cloneTreeNode(ti_new, NULL, ti, skip_empty_sets);
1617 *ti_new = ti_cloned;
1620 static boolean adjustTreeGraphicsForEMC(TreeInfo *node)
1622 boolean settings_changed = FALSE;
1626 boolean want_ecs = (setup.prefer_aga_graphics == FALSE);
1627 boolean want_aga = (setup.prefer_aga_graphics == TRUE);
1628 boolean has_only_ecs = (!node->graphics_set && !node->graphics_set_aga);
1629 boolean has_only_aga = (!node->graphics_set && !node->graphics_set_ecs);
1630 char *graphics_set = NULL;
1632 if (node->graphics_set_ecs && (want_ecs || has_only_ecs))
1633 graphics_set = node->graphics_set_ecs;
1635 if (node->graphics_set_aga && (want_aga || has_only_aga))
1636 graphics_set = node->graphics_set_aga;
1638 if (graphics_set && !strEqual(node->graphics_set, graphics_set))
1640 setString(&node->graphics_set, graphics_set);
1641 settings_changed = TRUE;
1644 if (node->node_group != NULL)
1645 settings_changed |= adjustTreeGraphicsForEMC(node->node_group);
1650 return settings_changed;
1653 static boolean adjustTreeSoundsForEMC(TreeInfo *node)
1655 boolean settings_changed = FALSE;
1659 boolean want_default = (setup.prefer_lowpass_sounds == FALSE);
1660 boolean want_lowpass = (setup.prefer_lowpass_sounds == TRUE);
1661 boolean has_only_default = (!node->sounds_set && !node->sounds_set_lowpass);
1662 boolean has_only_lowpass = (!node->sounds_set && !node->sounds_set_default);
1663 char *sounds_set = NULL;
1665 if (node->sounds_set_default && (want_default || has_only_default))
1666 sounds_set = node->sounds_set_default;
1668 if (node->sounds_set_lowpass && (want_lowpass || has_only_lowpass))
1669 sounds_set = node->sounds_set_lowpass;
1671 if (sounds_set && !strEqual(node->sounds_set, sounds_set))
1673 setString(&node->sounds_set, sounds_set);
1674 settings_changed = TRUE;
1677 if (node->node_group != NULL)
1678 settings_changed |= adjustTreeSoundsForEMC(node->node_group);
1683 return settings_changed;
1686 int dumpTreeInfo(TreeInfo *node, int depth)
1688 char bullet_list[] = { '-', '*', 'o' };
1689 int num_leaf_nodes = 0;
1693 Debug("tree", "Dumping TreeInfo:");
1697 char bullet = bullet_list[depth % ARRAY_SIZE(bullet_list)];
1699 for (i = 0; i < depth * 2; i++)
1700 DebugContinued("", " ");
1702 DebugContinued("tree", "%c '%s' ['%s] [PARENT: '%s'] %s\n",
1703 bullet, node->name, node->identifier,
1704 (node->node_parent ? node->node_parent->identifier : "-"),
1705 (node->node_group ? "[GROUP]" :
1706 node->is_copy ? "[COPY]" : ""));
1708 if (!node->node_group && !node->parent_link)
1712 // use for dumping artwork info tree
1713 Debug("tree", "subdir == '%s' ['%s', '%s'] [%d])",
1714 node->subdir, node->fullpath, node->basepath, node->in_user_dir);
1717 if (node->node_group != NULL)
1718 num_leaf_nodes += dumpTreeInfo(node->node_group, depth + 1);
1724 Debug("tree", "Summary: %d leaf nodes found", num_leaf_nodes);
1726 return num_leaf_nodes;
1729 void sortTreeInfoBySortFunction(TreeInfo **node_first,
1730 int (*compare_function)(const void *,
1733 int num_nodes = numTreeInfo(*node_first);
1734 TreeInfo **sort_array;
1735 TreeInfo *node = *node_first;
1741 // allocate array for sorting structure pointers
1742 sort_array = checked_calloc(num_nodes * sizeof(TreeInfo *));
1744 // writing structure pointers to sorting array
1745 while (i < num_nodes && node) // double boundary check...
1747 sort_array[i] = node;
1753 // sorting the structure pointers in the sorting array
1754 qsort(sort_array, num_nodes, sizeof(TreeInfo *),
1757 // update the linkage of list elements with the sorted node array
1758 for (i = 0; i < num_nodes - 1; i++)
1759 sort_array[i]->next = sort_array[i + 1];
1760 sort_array[num_nodes - 1]->next = NULL;
1762 // update the linkage of the main list anchor pointer
1763 *node_first = sort_array[0];
1767 // now recursively sort the level group structures
1771 if (node->node_group != NULL)
1772 sortTreeInfoBySortFunction(&node->node_group, compare_function);
1778 void sortTreeInfo(TreeInfo **node_first)
1780 sortTreeInfoBySortFunction(node_first, compareTreeInfoEntries);
1784 // ============================================================================
1785 // some stuff from "files.c"
1786 // ============================================================================
1788 #if defined(PLATFORM_WINDOWS)
1790 #define S_IRGRP S_IRUSR
1793 #define S_IROTH S_IRUSR
1796 #define S_IWGRP S_IWUSR
1799 #define S_IWOTH S_IWUSR
1802 #define S_IXGRP S_IXUSR
1805 #define S_IXOTH S_IXUSR
1808 #define S_IRWXG (S_IRGRP | S_IWGRP | S_IXGRP)
1813 #endif // PLATFORM_WINDOWS
1815 // file permissions for newly written files
1816 #define MODE_R_ALL (S_IRUSR | S_IRGRP | S_IROTH)
1817 #define MODE_W_ALL (S_IWUSR | S_IWGRP | S_IWOTH)
1818 #define MODE_X_ALL (S_IXUSR | S_IXGRP | S_IXOTH)
1820 #define MODE_W_PRIVATE (S_IWUSR)
1821 #define MODE_W_PUBLIC_FILE (S_IWUSR | S_IWGRP)
1822 #define MODE_W_PUBLIC_DIR (S_IWUSR | S_IWGRP | S_ISGID)
1824 #define DIR_PERMS_PRIVATE (MODE_R_ALL | MODE_X_ALL | MODE_W_PRIVATE)
1825 #define DIR_PERMS_PUBLIC (MODE_R_ALL | MODE_X_ALL | MODE_W_PUBLIC_DIR)
1826 #define DIR_PERMS_PUBLIC_ALL (MODE_R_ALL | MODE_X_ALL | MODE_W_ALL)
1828 #define FILE_PERMS_PRIVATE (MODE_R_ALL | MODE_W_PRIVATE)
1829 #define FILE_PERMS_PUBLIC (MODE_R_ALL | MODE_W_PUBLIC_FILE)
1830 #define FILE_PERMS_PUBLIC_ALL (MODE_R_ALL | MODE_W_ALL)
1833 char *getHomeDir(void)
1835 static char *dir = NULL;
1837 #if defined(PLATFORM_WINDOWS)
1840 dir = checked_malloc(MAX_PATH + 1);
1842 if (!SUCCEEDED(SHGetFolderPath(NULL, CSIDL_PERSONAL, NULL, 0, dir)))
1845 #elif defined(PLATFORM_EMSCRIPTEN)
1846 dir = PERSISTENT_DIRECTORY;
1847 #elif defined(PLATFORM_UNIX)
1850 if ((dir = getenv("HOME")) == NULL)
1852 dir = getUnixHomeDir();
1855 dir = getStringCopy(dir);
1867 char *getPersonalDataDir(void)
1869 static char *personal_data_dir = NULL;
1871 #if defined(PLATFORM_MAC)
1872 if (personal_data_dir == NULL)
1873 personal_data_dir = getPath2(getHomeDir(), "Documents");
1875 if (personal_data_dir == NULL)
1876 personal_data_dir = getHomeDir();
1879 return personal_data_dir;
1882 char *getMainUserGameDataDir(void)
1884 static char *main_user_data_dir = NULL;
1886 #if defined(PLATFORM_ANDROID)
1887 if (main_user_data_dir == NULL)
1888 main_user_data_dir = (char *)(SDL_AndroidGetExternalStorageState() &
1889 SDL_ANDROID_EXTERNAL_STORAGE_WRITE ?
1890 SDL_AndroidGetExternalStoragePath() :
1891 SDL_AndroidGetInternalStoragePath());
1893 if (main_user_data_dir == NULL)
1894 main_user_data_dir = getPath2(getPersonalDataDir(),
1895 program.userdata_subdir);
1898 return main_user_data_dir;
1901 char *getUserGameDataDir(void)
1904 return getMainUserGameDataDir();
1906 return getUserDir(user.nr);
1909 char *getSetupDir(void)
1911 return getUserGameDataDir();
1914 static mode_t posix_umask(mode_t mask)
1916 #if defined(PLATFORM_UNIX)
1923 static int posix_mkdir(const char *pathname, mode_t mode)
1925 #if defined(PLATFORM_WINDOWS)
1926 return mkdir(pathname);
1928 return mkdir(pathname, mode);
1932 static boolean posix_process_running_setgid(void)
1934 #if defined(PLATFORM_UNIX)
1935 return (getgid() != getegid());
1941 void createDirectory(char *dir, char *text, int permission_class)
1943 if (directoryExists(dir))
1946 // leave "other" permissions in umask untouched, but ensure group parts
1947 // of USERDATA_DIR_MODE are not masked
1948 mode_t dir_mode = (permission_class == PERMS_PRIVATE ?
1949 DIR_PERMS_PRIVATE : DIR_PERMS_PUBLIC);
1950 mode_t last_umask = posix_umask(0);
1951 mode_t group_umask = ~(dir_mode & S_IRWXG);
1952 int running_setgid = posix_process_running_setgid();
1954 if (permission_class == PERMS_PUBLIC)
1956 // if we're setgid, protect files against "other"
1957 // else keep umask(0) to make the dir world-writable
1960 posix_umask(last_umask & group_umask);
1962 dir_mode = DIR_PERMS_PUBLIC_ALL;
1965 if (posix_mkdir(dir, dir_mode) != 0)
1966 Warn("cannot create %s directory '%s': %s", text, dir, strerror(errno));
1968 if (permission_class == PERMS_PUBLIC && !running_setgid)
1969 chmod(dir, dir_mode);
1971 posix_umask(last_umask); // restore previous umask
1974 void InitMainUserDataDirectory(void)
1976 createDirectory(getMainUserGameDataDir(), "main user data", PERMS_PRIVATE);
1979 void InitUserDataDirectory(void)
1981 createDirectory(getMainUserGameDataDir(), "main user data", PERMS_PRIVATE);
1985 createDirectory(getUserDir(-1), "users", PERMS_PRIVATE);
1986 createDirectory(getUserDir(user.nr), "user data", PERMS_PRIVATE);
1990 void SetFilePermissions(char *filename, int permission_class)
1992 int running_setgid = posix_process_running_setgid();
1993 int perms = (permission_class == PERMS_PRIVATE ?
1994 FILE_PERMS_PRIVATE : FILE_PERMS_PUBLIC);
1996 if (permission_class == PERMS_PUBLIC && !running_setgid)
1997 perms = FILE_PERMS_PUBLIC_ALL;
1999 chmod(filename, perms);
2002 char *getCookie(char *file_type)
2004 static char cookie[MAX_COOKIE_LEN + 1];
2006 if (strlen(program.cookie_prefix) + 1 +
2007 strlen(file_type) + strlen("_FILE_VERSION_x.x") > MAX_COOKIE_LEN)
2008 return "[COOKIE ERROR]"; // should never happen
2010 sprintf(cookie, "%s_%s_FILE_VERSION_%d.%d",
2011 program.cookie_prefix, file_type,
2012 program.version_super, program.version_major);
2017 void fprintFileHeader(FILE *file, char *basename)
2019 char *prefix = "# ";
2022 fprintf_line_with_prefix(file, prefix, sep1, 77);
2023 fprintf(file, "%s%s\n", prefix, basename);
2024 fprintf_line_with_prefix(file, prefix, sep1, 77);
2025 fprintf(file, "\n");
2028 int getFileVersionFromCookieString(const char *cookie)
2030 const char *ptr_cookie1, *ptr_cookie2;
2031 const char *pattern1 = "_FILE_VERSION_";
2032 const char *pattern2 = "?.?";
2033 const int len_cookie = strlen(cookie);
2034 const int len_pattern1 = strlen(pattern1);
2035 const int len_pattern2 = strlen(pattern2);
2036 const int len_pattern = len_pattern1 + len_pattern2;
2037 int version_super, version_major;
2039 if (len_cookie <= len_pattern)
2042 ptr_cookie1 = &cookie[len_cookie - len_pattern];
2043 ptr_cookie2 = &cookie[len_cookie - len_pattern2];
2045 if (strncmp(ptr_cookie1, pattern1, len_pattern1) != 0)
2048 if (ptr_cookie2[0] < '0' || ptr_cookie2[0] > '9' ||
2049 ptr_cookie2[1] != '.' ||
2050 ptr_cookie2[2] < '0' || ptr_cookie2[2] > '9')
2053 version_super = ptr_cookie2[0] - '0';
2054 version_major = ptr_cookie2[2] - '0';
2056 return VERSION_IDENT(version_super, version_major, 0, 0);
2059 boolean checkCookieString(const char *cookie, const char *template)
2061 const char *pattern = "_FILE_VERSION_?.?";
2062 const int len_cookie = strlen(cookie);
2063 const int len_template = strlen(template);
2064 const int len_pattern = strlen(pattern);
2066 if (len_cookie != len_template)
2069 if (strncmp(cookie, template, len_cookie - len_pattern) != 0)
2076 // ----------------------------------------------------------------------------
2077 // setup file list and hash handling functions
2078 // ----------------------------------------------------------------------------
2080 char *getFormattedSetupEntry(char *token, char *value)
2083 static char entry[MAX_LINE_LEN];
2085 // if value is an empty string, just return token without value
2089 // start with the token and some spaces to format output line
2090 sprintf(entry, "%s:", token);
2091 for (i = strlen(entry); i < token_value_position; i++)
2094 // continue with the token's value
2095 strcat(entry, value);
2100 SetupFileList *newSetupFileList(char *token, char *value)
2102 SetupFileList *new = checked_malloc(sizeof(SetupFileList));
2104 new->token = getStringCopy(token);
2105 new->value = getStringCopy(value);
2112 void freeSetupFileList(SetupFileList *list)
2117 checked_free(list->token);
2118 checked_free(list->value);
2121 freeSetupFileList(list->next);
2126 char *getListEntry(SetupFileList *list, char *token)
2131 if (strEqual(list->token, token))
2134 return getListEntry(list->next, token);
2137 SetupFileList *setListEntry(SetupFileList *list, char *token, char *value)
2142 if (strEqual(list->token, token))
2144 checked_free(list->value);
2146 list->value = getStringCopy(value);
2150 else if (list->next == NULL)
2151 return (list->next = newSetupFileList(token, value));
2153 return setListEntry(list->next, token, value);
2156 SetupFileList *addListEntry(SetupFileList *list, char *token, char *value)
2161 if (list->next == NULL)
2162 return (list->next = newSetupFileList(token, value));
2164 return addListEntry(list->next, token, value);
2167 #if ENABLE_UNUSED_CODE
2169 static void printSetupFileList(SetupFileList *list)
2174 Debug("setup:printSetupFileList", "token: '%s'", list->token);
2175 Debug("setup:printSetupFileList", "value: '%s'", list->value);
2177 printSetupFileList(list->next);
2183 DEFINE_HASHTABLE_INSERT(insert_hash_entry, char, char);
2184 DEFINE_HASHTABLE_SEARCH(search_hash_entry, char, char);
2185 DEFINE_HASHTABLE_CHANGE(change_hash_entry, char, char);
2186 DEFINE_HASHTABLE_REMOVE(remove_hash_entry, char, char);
2188 #define insert_hash_entry hashtable_insert
2189 #define search_hash_entry hashtable_search
2190 #define change_hash_entry hashtable_change
2191 #define remove_hash_entry hashtable_remove
2194 unsigned int get_hash_from_key(void *key)
2199 This algorithm (k=33) was first reported by Dan Bernstein many years ago in
2200 'comp.lang.c'. Another version of this algorithm (now favored by Bernstein)
2201 uses XOR: hash(i) = hash(i - 1) * 33 ^ str[i]; the magic of number 33 (why
2202 it works better than many other constants, prime or not) has never been
2203 adequately explained.
2205 If you just want to have a good hash function, and cannot wait, djb2
2206 is one of the best string hash functions i know. It has excellent
2207 distribution and speed on many different sets of keys and table sizes.
2208 You are not likely to do better with one of the "well known" functions
2209 such as PJW, K&R, etc.
2211 Ozan (oz) Yigit [http://www.cs.yorku.ca/~oz/hash.html]
2214 char *str = (char *)key;
2215 unsigned int hash = 5381;
2218 while ((c = *str++))
2219 hash = ((hash << 5) + hash) + c; // hash * 33 + c
2224 int hash_keys_are_equal(void *key1, void *key2)
2226 return (strEqual((char *)key1, (char *)key2));
2229 SetupFileHash *newSetupFileHash(void)
2231 SetupFileHash *new_hash =
2232 create_hashtable(16, 0.75, get_hash_from_key, hash_keys_are_equal);
2234 if (new_hash == NULL)
2235 Fail("create_hashtable() failed -- out of memory");
2240 void freeSetupFileHash(SetupFileHash *hash)
2245 hashtable_destroy(hash, 1); // 1 == also free values stored in hash
2248 char *getHashEntry(SetupFileHash *hash, char *token)
2253 return search_hash_entry(hash, token);
2256 void setHashEntry(SetupFileHash *hash, char *token, char *value)
2263 value_copy = getStringCopy(value);
2265 // change value; if it does not exist, insert it as new
2266 if (!change_hash_entry(hash, token, value_copy))
2267 if (!insert_hash_entry(hash, getStringCopy(token), value_copy))
2268 Fail("cannot insert into hash -- aborting");
2271 char *removeHashEntry(SetupFileHash *hash, char *token)
2276 return remove_hash_entry(hash, token);
2279 #if ENABLE_UNUSED_CODE
2281 static void printSetupFileHash(SetupFileHash *hash)
2283 BEGIN_HASH_ITERATION(hash, itr)
2285 Debug("setup:printSetupFileHash", "token: '%s'", HASH_ITERATION_TOKEN(itr));
2286 Debug("setup:printSetupFileHash", "value: '%s'", HASH_ITERATION_VALUE(itr));
2288 END_HASH_ITERATION(hash, itr)
2293 #define ALLOW_TOKEN_VALUE_SEPARATOR_BEING_WHITESPACE 1
2294 #define CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING 0
2295 #define CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH 0
2297 static boolean token_value_separator_found = FALSE;
2298 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2299 static boolean token_value_separator_warning = FALSE;
2301 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2302 static boolean token_already_exists_warning = FALSE;
2305 static boolean getTokenValueFromSetupLineExt(char *line,
2306 char **token_ptr, char **value_ptr,
2307 char *filename, char *line_raw,
2309 boolean separator_required)
2311 static char line_copy[MAX_LINE_LEN + 1], line_raw_copy[MAX_LINE_LEN + 1];
2312 char *token, *value, *line_ptr;
2314 // when externally invoked via ReadTokenValueFromLine(), copy line buffers
2315 if (line_raw == NULL)
2317 strncpy(line_copy, line, MAX_LINE_LEN);
2318 line_copy[MAX_LINE_LEN] = '\0';
2321 strcpy(line_raw_copy, line_copy);
2322 line_raw = line_raw_copy;
2325 // cut trailing comment from input line
2326 for (line_ptr = line; *line_ptr; line_ptr++)
2328 if (*line_ptr == '#')
2335 // cut trailing whitespaces from input line
2336 for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
2337 if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
2340 // ignore empty lines
2344 // cut leading whitespaces from token
2345 for (token = line; *token; token++)
2346 if (*token != ' ' && *token != '\t')
2349 // start with empty value as reliable default
2352 token_value_separator_found = FALSE;
2354 // find end of token to determine start of value
2355 for (line_ptr = token; *line_ptr; line_ptr++)
2357 // first look for an explicit token/value separator, like ':' or '='
2358 if (*line_ptr == ':' || *line_ptr == '=')
2360 *line_ptr = '\0'; // terminate token string
2361 value = line_ptr + 1; // set beginning of value
2363 token_value_separator_found = TRUE;
2369 #if ALLOW_TOKEN_VALUE_SEPARATOR_BEING_WHITESPACE
2370 // fallback: if no token/value separator found, also allow whitespaces
2371 if (!token_value_separator_found && !separator_required)
2373 for (line_ptr = token; *line_ptr; line_ptr++)
2375 if (*line_ptr == ' ' || *line_ptr == '\t')
2377 *line_ptr = '\0'; // terminate token string
2378 value = line_ptr + 1; // set beginning of value
2380 token_value_separator_found = TRUE;
2386 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2387 if (token_value_separator_found)
2389 if (!token_value_separator_warning)
2391 Debug("setup", "---");
2393 if (filename != NULL)
2395 Debug("setup", "missing token/value separator(s) in config file:");
2396 Debug("setup", "- config file: '%s'", filename);
2400 Debug("setup", "missing token/value separator(s):");
2403 token_value_separator_warning = TRUE;
2406 if (filename != NULL)
2407 Debug("setup", "- line %d: '%s'", line_nr, line_raw);
2409 Debug("setup", "- line: '%s'", line_raw);
2415 // cut trailing whitespaces from token
2416 for (line_ptr = &token[strlen(token)]; line_ptr >= token; line_ptr--)
2417 if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
2420 // cut leading whitespaces from value
2421 for (; *value; value++)
2422 if (*value != ' ' && *value != '\t')
2431 boolean getTokenValueFromSetupLine(char *line, char **token, char **value)
2433 // while the internal (old) interface does not require a token/value
2434 // separator (for downwards compatibility with existing files which
2435 // don't use them), it is mandatory for the external (new) interface
2437 return getTokenValueFromSetupLineExt(line, token, value, NULL, NULL, 0, TRUE);
2440 static boolean loadSetupFileData(void *setup_file_data, char *filename,
2441 boolean top_recursion_level, boolean is_hash)
2443 static SetupFileHash *include_filename_hash = NULL;
2444 char line[MAX_LINE_LEN], line_raw[MAX_LINE_LEN], previous_line[MAX_LINE_LEN];
2445 char *token, *value, *line_ptr;
2446 void *insert_ptr = NULL;
2447 boolean read_continued_line = FALSE;
2449 int line_nr = 0, token_count = 0, include_count = 0;
2451 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2452 token_value_separator_warning = FALSE;
2455 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2456 token_already_exists_warning = FALSE;
2459 if (!(file = openFile(filename, MODE_READ)))
2461 #if DEBUG_NO_CONFIG_FILE
2462 Debug("setup", "cannot open configuration file '%s'", filename);
2468 // use "insert pointer" to store list end for constant insertion complexity
2470 insert_ptr = setup_file_data;
2472 // on top invocation, create hash to mark included files (to prevent loops)
2473 if (top_recursion_level)
2474 include_filename_hash = newSetupFileHash();
2476 // mark this file as already included (to prevent including it again)
2477 setHashEntry(include_filename_hash, getBaseNamePtr(filename), "true");
2479 while (!checkEndOfFile(file))
2481 // read next line of input file
2482 if (!getStringFromFile(file, line, MAX_LINE_LEN))
2485 // check if line was completely read and is terminated by line break
2486 if (strlen(line) > 0 && line[strlen(line) - 1] == '\n')
2489 // cut trailing line break (this can be newline and/or carriage return)
2490 for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
2491 if ((*line_ptr == '\n' || *line_ptr == '\r') && *(line_ptr + 1) == '\0')
2494 // copy raw input line for later use (mainly debugging output)
2495 strcpy(line_raw, line);
2497 if (read_continued_line)
2499 // append new line to existing line, if there is enough space
2500 if (strlen(previous_line) + strlen(line_ptr) < MAX_LINE_LEN)
2501 strcat(previous_line, line_ptr);
2503 strcpy(line, previous_line); // copy storage buffer to line
2505 read_continued_line = FALSE;
2508 // if the last character is '\', continue at next line
2509 if (strlen(line) > 0 && line[strlen(line) - 1] == '\\')
2511 line[strlen(line) - 1] = '\0'; // cut off trailing backslash
2512 strcpy(previous_line, line); // copy line to storage buffer
2514 read_continued_line = TRUE;
2519 if (!getTokenValueFromSetupLineExt(line, &token, &value, filename,
2520 line_raw, line_nr, FALSE))
2525 if (strEqual(token, "include"))
2527 if (getHashEntry(include_filename_hash, value) == NULL)
2529 char *basepath = getBasePath(filename);
2530 char *basename = getBaseName(value);
2531 char *filename_include = getPath2(basepath, basename);
2533 loadSetupFileData(setup_file_data, filename_include, FALSE, is_hash);
2537 free(filename_include);
2543 Warn("ignoring already processed file '%s'", value);
2550 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2552 getHashEntry((SetupFileHash *)setup_file_data, token);
2554 if (old_value != NULL)
2556 if (!token_already_exists_warning)
2558 Debug("setup", "---");
2559 Debug("setup", "duplicate token(s) found in config file:");
2560 Debug("setup", "- config file: '%s'", filename);
2562 token_already_exists_warning = TRUE;
2565 Debug("setup", "- token: '%s' (in line %d)", token, line_nr);
2566 Debug("setup", " old value: '%s'", old_value);
2567 Debug("setup", " new value: '%s'", value);
2571 setHashEntry((SetupFileHash *)setup_file_data, token, value);
2575 insert_ptr = addListEntry((SetupFileList *)insert_ptr, token, value);
2585 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2586 if (token_value_separator_warning)
2587 Debug("setup", "---");
2590 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2591 if (token_already_exists_warning)
2592 Debug("setup", "---");
2595 if (token_count == 0 && include_count == 0)
2596 Warn("configuration file '%s' is empty", filename);
2598 if (top_recursion_level)
2599 freeSetupFileHash(include_filename_hash);
2604 static int compareSetupFileData(const void *object1, const void *object2)
2606 const struct ConfigInfo *entry1 = (struct ConfigInfo *)object1;
2607 const struct ConfigInfo *entry2 = (struct ConfigInfo *)object2;
2609 return strcmp(entry1->token, entry2->token);
2612 static void saveSetupFileHash(SetupFileHash *hash, char *filename)
2614 int item_count = hashtable_count(hash);
2615 int item_size = sizeof(struct ConfigInfo);
2616 struct ConfigInfo *sort_array = checked_malloc(item_count * item_size);
2620 // copy string pointers from hash to array
2621 BEGIN_HASH_ITERATION(hash, itr)
2623 sort_array[i].token = HASH_ITERATION_TOKEN(itr);
2624 sort_array[i].value = HASH_ITERATION_VALUE(itr);
2628 if (i > item_count) // should never happen
2631 END_HASH_ITERATION(hash, itr)
2633 // sort string pointers from hash in array
2634 qsort(sort_array, item_count, item_size, compareSetupFileData);
2636 if (!(file = fopen(filename, MODE_WRITE)))
2638 Warn("cannot write configuration file '%s'", filename);
2643 fprintf(file, "%s\n\n", getFormattedSetupEntry("program.version",
2644 program.version_string));
2645 for (i = 0; i < item_count; i++)
2646 fprintf(file, "%s\n", getFormattedSetupEntry(sort_array[i].token,
2647 sort_array[i].value));
2650 checked_free(sort_array);
2653 SetupFileList *loadSetupFileList(char *filename)
2655 SetupFileList *setup_file_list = newSetupFileList("", "");
2656 SetupFileList *first_valid_list_entry;
2658 if (!loadSetupFileData(setup_file_list, filename, TRUE, FALSE))
2660 freeSetupFileList(setup_file_list);
2665 first_valid_list_entry = setup_file_list->next;
2667 // free empty list header
2668 setup_file_list->next = NULL;
2669 freeSetupFileList(setup_file_list);
2671 return first_valid_list_entry;
2674 SetupFileHash *loadSetupFileHash(char *filename)
2676 SetupFileHash *setup_file_hash = newSetupFileHash();
2678 if (!loadSetupFileData(setup_file_hash, filename, TRUE, TRUE))
2680 freeSetupFileHash(setup_file_hash);
2685 return setup_file_hash;
2689 // ============================================================================
2691 // ============================================================================
2693 #define TOKEN_STR_LAST_LEVEL_SERIES "last_level_series"
2694 #define TOKEN_STR_LAST_PLAYED_LEVEL "last_played_level"
2695 #define TOKEN_STR_HANDICAP_LEVEL "handicap_level"
2696 #define TOKEN_STR_LAST_USER "last_user"
2698 // level directory info
2699 #define LEVELINFO_TOKEN_IDENTIFIER 0
2700 #define LEVELINFO_TOKEN_NAME 1
2701 #define LEVELINFO_TOKEN_NAME_SORTING 2
2702 #define LEVELINFO_TOKEN_AUTHOR 3
2703 #define LEVELINFO_TOKEN_YEAR 4
2704 #define LEVELINFO_TOKEN_PROGRAM_TITLE 5
2705 #define LEVELINFO_TOKEN_PROGRAM_COPYRIGHT 6
2706 #define LEVELINFO_TOKEN_PROGRAM_COMPANY 7
2707 #define LEVELINFO_TOKEN_IMPORTED_FROM 8
2708 #define LEVELINFO_TOKEN_IMPORTED_BY 9
2709 #define LEVELINFO_TOKEN_TESTED_BY 10
2710 #define LEVELINFO_TOKEN_LEVELS 11
2711 #define LEVELINFO_TOKEN_FIRST_LEVEL 12
2712 #define LEVELINFO_TOKEN_SORT_PRIORITY 13
2713 #define LEVELINFO_TOKEN_LATEST_ENGINE 14
2714 #define LEVELINFO_TOKEN_LEVEL_GROUP 15
2715 #define LEVELINFO_TOKEN_READONLY 16
2716 #define LEVELINFO_TOKEN_GRAPHICS_SET_ECS 17
2717 #define LEVELINFO_TOKEN_GRAPHICS_SET_AGA 18
2718 #define LEVELINFO_TOKEN_GRAPHICS_SET 19
2719 #define LEVELINFO_TOKEN_SOUNDS_SET_DEFAULT 20
2720 #define LEVELINFO_TOKEN_SOUNDS_SET_LOWPASS 21
2721 #define LEVELINFO_TOKEN_SOUNDS_SET 22
2722 #define LEVELINFO_TOKEN_MUSIC_SET 23
2723 #define LEVELINFO_TOKEN_FILENAME 24
2724 #define LEVELINFO_TOKEN_FILETYPE 25
2725 #define LEVELINFO_TOKEN_SPECIAL_FLAGS 26
2726 #define LEVELINFO_TOKEN_EMPTY_LEVEL_NAME 27
2727 #define LEVELINFO_TOKEN_FORCE_LEVEL_NAME 28
2728 #define LEVELINFO_TOKEN_HANDICAP 29
2729 #define LEVELINFO_TOKEN_SKIP_LEVELS 30
2730 #define LEVELINFO_TOKEN_USE_EMC_TILES 31
2732 #define NUM_LEVELINFO_TOKENS 32
2734 static LevelDirTree ldi;
2736 static struct TokenInfo levelinfo_tokens[] =
2738 // level directory info
2739 { TYPE_STRING, &ldi.identifier, "identifier" },
2740 { TYPE_STRING, &ldi.name, "name" },
2741 { TYPE_STRING, &ldi.name_sorting, "name_sorting" },
2742 { TYPE_STRING, &ldi.author, "author" },
2743 { TYPE_STRING, &ldi.year, "year" },
2744 { TYPE_STRING, &ldi.program_title, "program_title" },
2745 { TYPE_STRING, &ldi.program_copyright, "program_copyright" },
2746 { TYPE_STRING, &ldi.program_company, "program_company" },
2747 { TYPE_STRING, &ldi.imported_from, "imported_from" },
2748 { TYPE_STRING, &ldi.imported_by, "imported_by" },
2749 { TYPE_STRING, &ldi.tested_by, "tested_by" },
2750 { TYPE_INTEGER, &ldi.levels, "levels" },
2751 { TYPE_INTEGER, &ldi.first_level, "first_level" },
2752 { TYPE_INTEGER, &ldi.sort_priority, "sort_priority" },
2753 { TYPE_BOOLEAN, &ldi.latest_engine, "latest_engine" },
2754 { TYPE_BOOLEAN, &ldi.level_group, "level_group" },
2755 { TYPE_BOOLEAN, &ldi.readonly, "readonly" },
2756 { TYPE_STRING, &ldi.graphics_set_ecs, "graphics_set.ecs" },
2757 { TYPE_STRING, &ldi.graphics_set_aga, "graphics_set.aga" },
2758 { TYPE_STRING, &ldi.graphics_set, "graphics_set" },
2759 { TYPE_STRING, &ldi.sounds_set_default,"sounds_set.default" },
2760 { TYPE_STRING, &ldi.sounds_set_lowpass,"sounds_set.lowpass" },
2761 { TYPE_STRING, &ldi.sounds_set, "sounds_set" },
2762 { TYPE_STRING, &ldi.music_set, "music_set" },
2763 { TYPE_STRING, &ldi.level_filename, "filename" },
2764 { TYPE_STRING, &ldi.level_filetype, "filetype" },
2765 { TYPE_STRING, &ldi.special_flags, "special_flags" },
2766 { TYPE_STRING, &ldi.empty_level_name, "empty_level_name" },
2767 { TYPE_BOOLEAN, &ldi.force_level_name, "force_level_name" },
2768 { TYPE_BOOLEAN, &ldi.handicap, "handicap" },
2769 { TYPE_BOOLEAN, &ldi.skip_levels, "skip_levels" },
2770 { TYPE_BOOLEAN, &ldi.use_emc_tiles, "use_emc_tiles" }
2773 static struct TokenInfo artworkinfo_tokens[] =
2775 // artwork directory info
2776 { TYPE_STRING, &ldi.identifier, "identifier" },
2777 { TYPE_STRING, &ldi.subdir, "subdir" },
2778 { TYPE_STRING, &ldi.name, "name" },
2779 { TYPE_STRING, &ldi.name_sorting, "name_sorting" },
2780 { TYPE_STRING, &ldi.author, "author" },
2781 { TYPE_STRING, &ldi.program_title, "program_title" },
2782 { TYPE_STRING, &ldi.program_copyright, "program_copyright" },
2783 { TYPE_STRING, &ldi.program_company, "program_company" },
2784 { TYPE_INTEGER, &ldi.sort_priority, "sort_priority" },
2785 { TYPE_STRING, &ldi.basepath, "basepath" },
2786 { TYPE_STRING, &ldi.fullpath, "fullpath" },
2787 { TYPE_BOOLEAN, &ldi.in_user_dir, "in_user_dir" },
2788 { TYPE_STRING, &ldi.class_desc, "class_desc" },
2793 static char *optional_tokens[] =
2796 "program_copyright",
2802 static void setTreeInfoToDefaults(TreeInfo *ti, int type)
2806 ti->node_top = (ti->type == TREE_TYPE_LEVEL_DIR ? &leveldir_first :
2807 ti->type == TREE_TYPE_GRAPHICS_DIR ? &artwork.gfx_first :
2808 ti->type == TREE_TYPE_SOUNDS_DIR ? &artwork.snd_first :
2809 ti->type == TREE_TYPE_MUSIC_DIR ? &artwork.mus_first :
2812 ti->node_parent = NULL;
2813 ti->node_group = NULL;
2820 ti->fullpath = NULL;
2821 ti->basepath = NULL;
2822 ti->identifier = NULL;
2823 ti->name = getStringCopy(ANONYMOUS_NAME);
2824 ti->name_sorting = NULL;
2825 ti->author = getStringCopy(ANONYMOUS_NAME);
2828 ti->program_title = NULL;
2829 ti->program_copyright = NULL;
2830 ti->program_company = NULL;
2832 ti->sort_priority = LEVELCLASS_UNDEFINED; // default: least priority
2833 ti->latest_engine = FALSE; // default: get from level
2834 ti->parent_link = FALSE;
2835 ti->is_copy = FALSE;
2836 ti->in_user_dir = FALSE;
2837 ti->user_defined = FALSE;
2839 ti->class_desc = NULL;
2841 ti->infotext = getStringCopy(TREE_INFOTEXT(ti->type));
2843 if (ti->type == TREE_TYPE_LEVEL_DIR)
2845 ti->imported_from = NULL;
2846 ti->imported_by = NULL;
2847 ti->tested_by = NULL;
2849 ti->graphics_set_ecs = NULL;
2850 ti->graphics_set_aga = NULL;
2851 ti->graphics_set = NULL;
2852 ti->sounds_set_default = NULL;
2853 ti->sounds_set_lowpass = NULL;
2854 ti->sounds_set = NULL;
2855 ti->music_set = NULL;
2856 ti->graphics_path = getStringCopy(UNDEFINED_FILENAME);
2857 ti->sounds_path = getStringCopy(UNDEFINED_FILENAME);
2858 ti->music_path = getStringCopy(UNDEFINED_FILENAME);
2860 ti->level_filename = NULL;
2861 ti->level_filetype = NULL;
2863 ti->special_flags = NULL;
2865 ti->empty_level_name = NULL;
2866 ti->force_level_name = FALSE;
2869 ti->first_level = 0;
2871 ti->level_group = FALSE;
2872 ti->handicap_level = 0;
2873 ti->readonly = TRUE;
2874 ti->handicap = TRUE;
2875 ti->skip_levels = FALSE;
2877 ti->use_emc_tiles = FALSE;
2881 static void setTreeInfoToDefaultsFromParent(TreeInfo *ti, TreeInfo *parent)
2885 Warn("setTreeInfoToDefaultsFromParent(): parent == NULL");
2887 setTreeInfoToDefaults(ti, TREE_TYPE_UNDEFINED);
2892 // copy all values from the parent structure
2894 ti->type = parent->type;
2896 ti->node_top = parent->node_top;
2897 ti->node_parent = parent;
2898 ti->node_group = NULL;
2905 ti->fullpath = NULL;
2906 ti->basepath = NULL;
2907 ti->identifier = NULL;
2908 ti->name = getStringCopy(ANONYMOUS_NAME);
2909 ti->name_sorting = NULL;
2910 ti->author = getStringCopy(parent->author);
2911 ti->year = getStringCopy(parent->year);
2913 ti->program_title = getStringCopy(parent->program_title);
2914 ti->program_copyright = getStringCopy(parent->program_copyright);
2915 ti->program_company = getStringCopy(parent->program_company);
2917 ti->sort_priority = parent->sort_priority;
2918 ti->latest_engine = parent->latest_engine;
2919 ti->parent_link = FALSE;
2920 ti->is_copy = FALSE;
2921 ti->in_user_dir = parent->in_user_dir;
2922 ti->user_defined = parent->user_defined;
2923 ti->color = parent->color;
2924 ti->class_desc = getStringCopy(parent->class_desc);
2926 ti->infotext = getStringCopy(parent->infotext);
2928 if (ti->type == TREE_TYPE_LEVEL_DIR)
2930 ti->imported_from = getStringCopy(parent->imported_from);
2931 ti->imported_by = getStringCopy(parent->imported_by);
2932 ti->tested_by = getStringCopy(parent->tested_by);
2934 ti->graphics_set_ecs = getStringCopy(parent->graphics_set_ecs);
2935 ti->graphics_set_aga = getStringCopy(parent->graphics_set_aga);
2936 ti->graphics_set = getStringCopy(parent->graphics_set);
2937 ti->sounds_set_default = getStringCopy(parent->sounds_set_default);
2938 ti->sounds_set_lowpass = getStringCopy(parent->sounds_set_lowpass);
2939 ti->sounds_set = getStringCopy(parent->sounds_set);
2940 ti->music_set = getStringCopy(parent->music_set);
2941 ti->graphics_path = getStringCopy(UNDEFINED_FILENAME);
2942 ti->sounds_path = getStringCopy(UNDEFINED_FILENAME);
2943 ti->music_path = getStringCopy(UNDEFINED_FILENAME);
2945 ti->level_filename = getStringCopy(parent->level_filename);
2946 ti->level_filetype = getStringCopy(parent->level_filetype);
2948 ti->special_flags = getStringCopy(parent->special_flags);
2950 ti->empty_level_name = getStringCopy(parent->empty_level_name);
2951 ti->force_level_name = parent->force_level_name;
2953 ti->levels = parent->levels;
2954 ti->first_level = parent->first_level;
2955 ti->last_level = parent->last_level;
2956 ti->level_group = FALSE;
2957 ti->handicap_level = parent->handicap_level;
2958 ti->readonly = parent->readonly;
2959 ti->handicap = parent->handicap;
2960 ti->skip_levels = parent->skip_levels;
2962 ti->use_emc_tiles = parent->use_emc_tiles;
2966 static TreeInfo *getTreeInfoCopy(TreeInfo *ti)
2968 TreeInfo *ti_copy = newTreeInfo();
2970 // copy all values from the original structure
2972 ti_copy->type = ti->type;
2974 ti_copy->node_top = ti->node_top;
2975 ti_copy->node_parent = ti->node_parent;
2976 ti_copy->node_group = ti->node_group;
2977 ti_copy->next = ti->next;
2979 ti_copy->cl_first = ti->cl_first;
2980 ti_copy->cl_cursor = ti->cl_cursor;
2982 ti_copy->subdir = getStringCopy(ti->subdir);
2983 ti_copy->fullpath = getStringCopy(ti->fullpath);
2984 ti_copy->basepath = getStringCopy(ti->basepath);
2985 ti_copy->identifier = getStringCopy(ti->identifier);
2986 ti_copy->name = getStringCopy(ti->name);
2987 ti_copy->name_sorting = getStringCopy(ti->name_sorting);
2988 ti_copy->author = getStringCopy(ti->author);
2989 ti_copy->year = getStringCopy(ti->year);
2991 ti_copy->program_title = getStringCopy(ti->program_title);
2992 ti_copy->program_copyright = getStringCopy(ti->program_copyright);
2993 ti_copy->program_company = getStringCopy(ti->program_company);
2995 ti_copy->imported_from = getStringCopy(ti->imported_from);
2996 ti_copy->imported_by = getStringCopy(ti->imported_by);
2997 ti_copy->tested_by = getStringCopy(ti->tested_by);
2999 ti_copy->graphics_set_ecs = getStringCopy(ti->graphics_set_ecs);
3000 ti_copy->graphics_set_aga = getStringCopy(ti->graphics_set_aga);
3001 ti_copy->graphics_set = getStringCopy(ti->graphics_set);
3002 ti_copy->sounds_set_default = getStringCopy(ti->sounds_set_default);
3003 ti_copy->sounds_set_lowpass = getStringCopy(ti->sounds_set_lowpass);
3004 ti_copy->sounds_set = getStringCopy(ti->sounds_set);
3005 ti_copy->music_set = getStringCopy(ti->music_set);
3006 ti_copy->graphics_path = getStringCopy(ti->graphics_path);
3007 ti_copy->sounds_path = getStringCopy(ti->sounds_path);
3008 ti_copy->music_path = getStringCopy(ti->music_path);
3010 ti_copy->level_filename = getStringCopy(ti->level_filename);
3011 ti_copy->level_filetype = getStringCopy(ti->level_filetype);
3013 ti_copy->special_flags = getStringCopy(ti->special_flags);
3015 ti_copy->empty_level_name = getStringCopy(ti->empty_level_name);
3016 ti_copy->force_level_name = ti->force_level_name;
3018 ti_copy->levels = ti->levels;
3019 ti_copy->first_level = ti->first_level;
3020 ti_copy->last_level = ti->last_level;
3021 ti_copy->sort_priority = ti->sort_priority;
3023 ti_copy->latest_engine = ti->latest_engine;
3025 ti_copy->level_group = ti->level_group;
3026 ti_copy->parent_link = ti->parent_link;
3027 ti_copy->is_copy = ti->is_copy;
3028 ti_copy->in_user_dir = ti->in_user_dir;
3029 ti_copy->user_defined = ti->user_defined;
3030 ti_copy->readonly = ti->readonly;
3031 ti_copy->handicap = ti->handicap;
3032 ti_copy->skip_levels = ti->skip_levels;
3034 ti_copy->use_emc_tiles = ti->use_emc_tiles;
3036 ti_copy->color = ti->color;
3037 ti_copy->class_desc = getStringCopy(ti->class_desc);
3038 ti_copy->handicap_level = ti->handicap_level;
3040 ti_copy->infotext = getStringCopy(ti->infotext);
3045 void freeTreeInfo(TreeInfo *ti)
3050 checked_free(ti->subdir);
3051 checked_free(ti->fullpath);
3052 checked_free(ti->basepath);
3053 checked_free(ti->identifier);
3055 checked_free(ti->name);
3056 checked_free(ti->name_sorting);
3057 checked_free(ti->author);
3058 checked_free(ti->year);
3060 checked_free(ti->program_title);
3061 checked_free(ti->program_copyright);
3062 checked_free(ti->program_company);
3064 checked_free(ti->class_desc);
3066 checked_free(ti->infotext);
3068 if (ti->type == TREE_TYPE_LEVEL_DIR)
3070 checked_free(ti->imported_from);
3071 checked_free(ti->imported_by);
3072 checked_free(ti->tested_by);
3074 checked_free(ti->graphics_set_ecs);
3075 checked_free(ti->graphics_set_aga);
3076 checked_free(ti->graphics_set);
3077 checked_free(ti->sounds_set_default);
3078 checked_free(ti->sounds_set_lowpass);
3079 checked_free(ti->sounds_set);
3080 checked_free(ti->music_set);
3082 checked_free(ti->graphics_path);
3083 checked_free(ti->sounds_path);
3084 checked_free(ti->music_path);
3086 checked_free(ti->level_filename);
3087 checked_free(ti->level_filetype);
3089 checked_free(ti->special_flags);
3092 // recursively free child node
3094 freeTreeInfo(ti->node_group);
3096 // recursively free next node
3098 freeTreeInfo(ti->next);
3103 void setSetupInfo(struct TokenInfo *token_info,
3104 int token_nr, char *token_value)
3106 int token_type = token_info[token_nr].type;
3107 void *setup_value = token_info[token_nr].value;
3109 if (token_value == NULL)
3112 // set setup field to corresponding token value
3117 *(boolean *)setup_value = get_boolean_from_string(token_value);
3121 *(int *)setup_value = get_switch3_from_string(token_value);
3125 *(Key *)setup_value = getKeyFromKeyName(token_value);
3129 *(Key *)setup_value = getKeyFromX11KeyName(token_value);
3133 *(int *)setup_value = get_integer_from_string(token_value);
3137 checked_free(*(char **)setup_value);
3138 *(char **)setup_value = getStringCopy(token_value);
3142 *(int *)setup_value = get_player_nr_from_string(token_value);
3150 static int compareTreeInfoEntries(const void *object1, const void *object2)
3152 const TreeInfo *entry1 = *((TreeInfo **)object1);
3153 const TreeInfo *entry2 = *((TreeInfo **)object2);
3154 int tree_sorting1 = TREE_SORTING(entry1);
3155 int tree_sorting2 = TREE_SORTING(entry2);
3157 if (tree_sorting1 != tree_sorting2)
3158 return (tree_sorting1 - tree_sorting2);
3160 return strcasecmp(entry1->name_sorting, entry2->name_sorting);
3163 static TreeInfo *createParentTreeInfoNode(TreeInfo *node_parent)
3167 if (node_parent == NULL)
3170 ti_new = newTreeInfo();
3171 setTreeInfoToDefaults(ti_new, node_parent->type);
3173 ti_new->node_parent = node_parent;
3174 ti_new->parent_link = TRUE;
3176 setString(&ti_new->identifier, node_parent->identifier);
3177 setString(&ti_new->name, BACKLINK_TEXT_PARENT);
3178 setString(&ti_new->name_sorting, ti_new->name);
3180 setString(&ti_new->subdir, STRING_PARENT_DIRECTORY);
3181 setString(&ti_new->fullpath, node_parent->fullpath);
3183 ti_new->sort_priority = LEVELCLASS_PARENT;
3184 ti_new->latest_engine = node_parent->latest_engine;
3186 setString(&ti_new->class_desc, getLevelClassDescription(ti_new));
3188 pushTreeInfo(&node_parent->node_group, ti_new);
3193 static TreeInfo *createTopTreeInfoNode(TreeInfo *node_first)
3195 if (node_first == NULL)
3198 TreeInfo *ti_new = newTreeInfo();
3199 int type = node_first->type;
3201 setTreeInfoToDefaults(ti_new, type);
3203 ti_new->node_parent = NULL;
3204 ti_new->parent_link = FALSE;
3206 setString(&ti_new->identifier, "top_tree_node");
3207 setString(&ti_new->name, TREE_INFOTEXT(type));
3208 setString(&ti_new->name_sorting, ti_new->name);
3210 setString(&ti_new->subdir, STRING_TOP_DIRECTORY);
3211 setString(&ti_new->fullpath, ".");
3213 ti_new->sort_priority = LEVELCLASS_TOP;
3214 ti_new->latest_engine = node_first->latest_engine;
3216 setString(&ti_new->class_desc, TREE_INFOTEXT(type));
3218 ti_new->node_group = node_first;
3219 ti_new->level_group = TRUE;
3221 TreeInfo *ti_new2 = createParentTreeInfoNode(ti_new);
3223 setString(&ti_new2->name, TREE_BACKLINK_TEXT(type));
3224 setString(&ti_new2->name_sorting, ti_new2->name);
3229 static void setTreeInfoParentNodes(TreeInfo *node, TreeInfo *node_parent)
3233 if (node->node_group)
3234 setTreeInfoParentNodes(node->node_group, node);
3236 node->node_parent = node_parent;
3242 TreeInfo *addTopTreeInfoNode(TreeInfo *node_first)
3244 // add top tree node with back link node in previous tree
3245 node_first = createTopTreeInfoNode(node_first);
3247 // set all parent links (back links) in complete tree
3248 setTreeInfoParentNodes(node_first, NULL);
3254 // ----------------------------------------------------------------------------
3255 // functions for handling level and custom artwork info cache
3256 // ----------------------------------------------------------------------------
3258 static void LoadArtworkInfoCache(void)
3260 InitCacheDirectory();
3262 if (artworkinfo_cache_old == NULL)
3264 char *filename = getPath2(getCacheDir(), ARTWORKINFO_CACHE_FILE);
3266 // try to load artwork info hash from already existing cache file
3267 artworkinfo_cache_old = loadSetupFileHash(filename);
3269 // try to get program version that artwork info cache was written with
3270 char *version = getHashEntry(artworkinfo_cache_old, "program.version");
3272 // check program version of artwork info cache against current version
3273 if (!strEqual(version, program.version_string))
3275 freeSetupFileHash(artworkinfo_cache_old);
3277 artworkinfo_cache_old = NULL;
3280 // if no artwork info cache file was found, start with empty hash
3281 if (artworkinfo_cache_old == NULL)
3282 artworkinfo_cache_old = newSetupFileHash();
3287 if (artworkinfo_cache_new == NULL)
3288 artworkinfo_cache_new = newSetupFileHash();
3290 update_artworkinfo_cache = FALSE;
3293 static void SaveArtworkInfoCache(void)
3295 if (!update_artworkinfo_cache)
3298 char *filename = getPath2(getCacheDir(), ARTWORKINFO_CACHE_FILE);
3300 InitCacheDirectory();
3302 saveSetupFileHash(artworkinfo_cache_new, filename);
3307 static char *getCacheTokenPrefix(char *prefix1, char *prefix2)
3309 static char *prefix = NULL;
3311 checked_free(prefix);
3313 prefix = getStringCat2WithSeparator(prefix1, prefix2, ".");
3318 // (identical to above function, but separate string buffer needed -- nasty)
3319 static char *getCacheToken(char *prefix, char *suffix)
3321 static char *token = NULL;
3323 checked_free(token);
3325 token = getStringCat2WithSeparator(prefix, suffix, ".");
3330 static char *getFileTimestampString(char *filename)
3332 return getStringCopy(i_to_a(getFileTimestampEpochSeconds(filename)));
3335 static boolean modifiedFileTimestamp(char *filename, char *timestamp_string)
3337 struct stat file_status;
3339 if (timestamp_string == NULL)
3342 if (!fileExists(filename)) // file does not exist
3343 return (atoi(timestamp_string) != 0);
3345 if (stat(filename, &file_status) != 0) // cannot stat file
3348 return (file_status.st_mtime != atoi(timestamp_string));
3351 static TreeInfo *getArtworkInfoCacheEntry(LevelDirTree *level_node, int type)
3353 char *identifier = level_node->subdir;
3354 char *type_string = ARTWORK_DIRECTORY(type);
3355 char *token_prefix = getCacheTokenPrefix(type_string, identifier);
3356 char *token_main = getCacheToken(token_prefix, "CACHED");
3357 char *cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
3358 boolean cached = (cache_entry != NULL && strEqual(cache_entry, "true"));
3359 TreeInfo *artwork_info = NULL;
3361 if (!use_artworkinfo_cache)
3364 if (optional_tokens_hash == NULL)
3368 // create hash from list of optional tokens (for quick access)
3369 optional_tokens_hash = newSetupFileHash();
3370 for (i = 0; optional_tokens[i] != NULL; i++)
3371 setHashEntry(optional_tokens_hash, optional_tokens[i], "");
3378 artwork_info = newTreeInfo();
3379 setTreeInfoToDefaults(artwork_info, type);
3381 // set all structure fields according to the token/value pairs
3382 ldi = *artwork_info;
3383 for (i = 0; artworkinfo_tokens[i].type != -1; i++)
3385 char *token_suffix = artworkinfo_tokens[i].text;
3386 char *token = getCacheToken(token_prefix, token_suffix);
3387 char *value = getHashEntry(artworkinfo_cache_old, token);
3389 (getHashEntry(optional_tokens_hash, token_suffix) != NULL);
3391 setSetupInfo(artworkinfo_tokens, i, value);
3393 // check if cache entry for this item is mandatory, but missing
3394 if (value == NULL && !optional)
3396 Warn("missing cache entry '%s'", token);
3402 *artwork_info = ldi;
3407 char *filename_levelinfo = getPath2(getLevelDirFromTreeInfo(level_node),
3408 LEVELINFO_FILENAME);
3409 char *filename_artworkinfo = getPath2(getSetupArtworkDir(artwork_info),
3410 ARTWORKINFO_FILENAME(type));
3412 // check if corresponding "levelinfo.conf" file has changed
3413 token_main = getCacheToken(token_prefix, "TIMESTAMP_LEVELINFO");
3414 cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
3416 if (modifiedFileTimestamp(filename_levelinfo, cache_entry))
3419 // check if corresponding "<artworkinfo>.conf" file has changed
3420 token_main = getCacheToken(token_prefix, "TIMESTAMP_ARTWORKINFO");
3421 cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
3423 if (modifiedFileTimestamp(filename_artworkinfo, cache_entry))
3426 checked_free(filename_levelinfo);
3427 checked_free(filename_artworkinfo);
3430 if (!cached && artwork_info != NULL)
3432 freeTreeInfo(artwork_info);
3437 return artwork_info;
3440 static void setArtworkInfoCacheEntry(TreeInfo *artwork_info,
3441 LevelDirTree *level_node, int type)
3443 char *identifier = level_node->subdir;
3444 char *type_string = ARTWORK_DIRECTORY(type);
3445 char *token_prefix = getCacheTokenPrefix(type_string, identifier);
3446 char *token_main = getCacheToken(token_prefix, "CACHED");
3447 boolean set_cache_timestamps = TRUE;
3450 setHashEntry(artworkinfo_cache_new, token_main, "true");
3452 if (set_cache_timestamps)
3454 char *filename_levelinfo = getPath2(getLevelDirFromTreeInfo(level_node),
3455 LEVELINFO_FILENAME);
3456 char *filename_artworkinfo = getPath2(getSetupArtworkDir(artwork_info),
3457 ARTWORKINFO_FILENAME(type));
3458 char *timestamp_levelinfo = getFileTimestampString(filename_levelinfo);
3459 char *timestamp_artworkinfo = getFileTimestampString(filename_artworkinfo);
3461 token_main = getCacheToken(token_prefix, "TIMESTAMP_LEVELINFO");
3462 setHashEntry(artworkinfo_cache_new, token_main, timestamp_levelinfo);
3464 token_main = getCacheToken(token_prefix, "TIMESTAMP_ARTWORKINFO");
3465 setHashEntry(artworkinfo_cache_new, token_main, timestamp_artworkinfo);
3467 checked_free(filename_levelinfo);
3468 checked_free(filename_artworkinfo);
3469 checked_free(timestamp_levelinfo);
3470 checked_free(timestamp_artworkinfo);
3473 ldi = *artwork_info;
3474 for (i = 0; artworkinfo_tokens[i].type != -1; i++)
3476 char *token = getCacheToken(token_prefix, artworkinfo_tokens[i].text);
3477 char *value = getSetupValue(artworkinfo_tokens[i].type,
3478 artworkinfo_tokens[i].value);
3480 setHashEntry(artworkinfo_cache_new, token, value);
3485 // ----------------------------------------------------------------------------
3486 // functions for loading level info and custom artwork info
3487 // ----------------------------------------------------------------------------
3489 int GetZipFileTreeType(char *zip_filename)
3491 static char *top_dir_path = NULL;
3492 static char *top_dir_conf_filename[NUM_BASE_TREE_TYPES] = { NULL };
3493 static char *conf_basename[NUM_BASE_TREE_TYPES] =
3495 GRAPHICSINFO_FILENAME,
3496 SOUNDSINFO_FILENAME,
3502 checked_free(top_dir_path);
3503 top_dir_path = NULL;
3505 for (j = 0; j < NUM_BASE_TREE_TYPES; j++)
3507 checked_free(top_dir_conf_filename[j]);
3508 top_dir_conf_filename[j] = NULL;
3511 char **zip_entries = zip_list(zip_filename);
3513 // check if zip file successfully opened
3514 if (zip_entries == NULL || zip_entries[0] == NULL)
3515 return TREE_TYPE_UNDEFINED;
3517 // first zip file entry is expected to be top level directory
3518 char *top_dir = zip_entries[0];
3520 // check if valid top level directory found in zip file
3521 if (!strSuffix(top_dir, "/"))
3522 return TREE_TYPE_UNDEFINED;
3524 // get filenames of valid configuration files in top level directory
3525 for (j = 0; j < NUM_BASE_TREE_TYPES; j++)
3526 top_dir_conf_filename[j] = getStringCat2(top_dir, conf_basename[j]);
3528 int tree_type = TREE_TYPE_UNDEFINED;
3531 while (zip_entries[e] != NULL)
3533 // check if every zip file entry is below top level directory
3534 if (!strPrefix(zip_entries[e], top_dir))
3535 return TREE_TYPE_UNDEFINED;
3537 // check if this zip file entry is a valid configuration filename
3538 for (j = 0; j < NUM_BASE_TREE_TYPES; j++)
3540 if (strEqual(zip_entries[e], top_dir_conf_filename[j]))
3542 // only exactly one valid configuration file allowed
3543 if (tree_type != TREE_TYPE_UNDEFINED)
3544 return TREE_TYPE_UNDEFINED;
3556 static boolean CheckZipFileForDirectory(char *zip_filename, char *directory,
3559 static char *top_dir_path = NULL;
3560 static char *top_dir_conf_filename = NULL;
3562 checked_free(top_dir_path);
3563 checked_free(top_dir_conf_filename);
3565 top_dir_path = NULL;
3566 top_dir_conf_filename = NULL;
3568 char *conf_basename = (tree_type == TREE_TYPE_LEVEL_DIR ? LEVELINFO_FILENAME :
3569 ARTWORKINFO_FILENAME(tree_type));
3571 // check if valid configuration filename determined
3572 if (conf_basename == NULL || strEqual(conf_basename, ""))
3575 char **zip_entries = zip_list(zip_filename);
3577 // check if zip file successfully opened
3578 if (zip_entries == NULL || zip_entries[0] == NULL)
3581 // first zip file entry is expected to be top level directory
3582 char *top_dir = zip_entries[0];
3584 // check if valid top level directory found in zip file
3585 if (!strSuffix(top_dir, "/"))
3588 // get path of extracted top level directory
3589 top_dir_path = getPath2(directory, top_dir);
3591 // remove trailing directory separator from top level directory path
3592 // (required to be able to check for file and directory in next step)
3593 top_dir_path[strlen(top_dir_path) - 1] = '\0';
3595 // check if zip file's top level directory already exists in target directory
3596 if (fileExists(top_dir_path)) // (checks for file and directory)
3599 // get filename of configuration file in top level directory
3600 top_dir_conf_filename = getStringCat2(top_dir, conf_basename);
3602 boolean found_top_dir_conf_filename = FALSE;
3605 while (zip_entries[i] != NULL)
3607 // check if every zip file entry is below top level directory
3608 if (!strPrefix(zip_entries[i], top_dir))
3611 // check if this zip file entry is the configuration filename
3612 if (strEqual(zip_entries[i], top_dir_conf_filename))
3613 found_top_dir_conf_filename = TRUE;
3618 // check if valid configuration filename was found in zip file
3619 if (!found_top_dir_conf_filename)
3625 char *ExtractZipFileIntoDirectory(char *zip_filename, char *directory,
3628 boolean zip_file_valid = CheckZipFileForDirectory(zip_filename, directory,
3631 if (!zip_file_valid)
3633 Warn("zip file '%s' rejected!", zip_filename);
3638 char **zip_entries = zip_extract(zip_filename, directory);
3640 if (zip_entries == NULL)
3642 Warn("zip file '%s' could not be extracted!", zip_filename);
3647 Info("zip file '%s' successfully extracted!", zip_filename);
3649 // first zip file entry contains top level directory
3650 char *top_dir = zip_entries[0];
3652 // remove trailing directory separator from top level directory
3653 top_dir[strlen(top_dir) - 1] = '\0';
3658 static void ProcessZipFilesInDirectory(char *directory, int tree_type)
3661 DirectoryEntry *dir_entry;
3663 if ((dir = openDirectory(directory)) == NULL)
3665 // display error if directory is main "options.graphics_directory" etc.
3666 if (tree_type == TREE_TYPE_LEVEL_DIR ||
3667 directory == OPTIONS_ARTWORK_DIRECTORY(tree_type))
3668 Warn("cannot read directory '%s'", directory);
3673 while ((dir_entry = readDirectory(dir)) != NULL) // loop all entries
3675 // skip non-zip files (and also directories with zip extension)
3676 if (!strSuffixLower(dir_entry->basename, ".zip") || dir_entry->is_directory)
3679 char *zip_filename = getPath2(directory, dir_entry->basename);
3680 char *zip_filename_extracted = getStringCat2(zip_filename, ".extracted");
3681 char *zip_filename_rejected = getStringCat2(zip_filename, ".rejected");
3683 // check if zip file hasn't already been extracted or rejected
3684 if (!fileExists(zip_filename_extracted) &&
3685 !fileExists(zip_filename_rejected))
3687 char *top_dir = ExtractZipFileIntoDirectory(zip_filename, directory,
3689 char *marker_filename = (top_dir != NULL ? zip_filename_extracted :
3690 zip_filename_rejected);
3693 // create empty file to mark zip file as extracted or rejected
3694 if ((marker_file = fopen(marker_filename, MODE_WRITE)))
3695 fclose(marker_file);
3698 free(zip_filename_extracted);
3699 free(zip_filename_rejected);
3703 closeDirectory(dir);
3706 // forward declaration for recursive call by "LoadLevelInfoFromLevelDir()"
3707 static void LoadLevelInfoFromLevelDir(TreeInfo **, TreeInfo *, char *);
3709 static boolean LoadLevelInfoFromLevelConf(TreeInfo **node_first,
3710 TreeInfo *node_parent,
3711 char *level_directory,
3712 char *directory_name)
3714 char *directory_path = getPath2(level_directory, directory_name);
3715 char *filename = getPath2(directory_path, LEVELINFO_FILENAME);
3716 SetupFileHash *setup_file_hash;
3717 LevelDirTree *leveldir_new = NULL;
3720 // unless debugging, silently ignore directories without "levelinfo.conf"
3721 if (!options.debug && !fileExists(filename))
3723 free(directory_path);
3729 setup_file_hash = loadSetupFileHash(filename);
3731 if (setup_file_hash == NULL)
3733 #if DEBUG_NO_CONFIG_FILE
3734 Debug("setup", "ignoring level directory '%s'", directory_path);
3737 free(directory_path);
3743 leveldir_new = newTreeInfo();
3746 setTreeInfoToDefaultsFromParent(leveldir_new, node_parent);
3748 setTreeInfoToDefaults(leveldir_new, TREE_TYPE_LEVEL_DIR);
3750 leveldir_new->subdir = getStringCopy(directory_name);
3752 // set all structure fields according to the token/value pairs
3753 ldi = *leveldir_new;
3754 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
3755 setSetupInfo(levelinfo_tokens, i,
3756 getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
3757 *leveldir_new = ldi;
3759 if (strEqual(leveldir_new->name, ANONYMOUS_NAME))
3760 setString(&leveldir_new->name, leveldir_new->subdir);
3762 if (leveldir_new->identifier == NULL)
3763 leveldir_new->identifier = getStringCopy(leveldir_new->subdir);
3765 if (leveldir_new->name_sorting == NULL)
3766 leveldir_new->name_sorting = getStringCopy(leveldir_new->name);
3768 if (node_parent == NULL) // top level group
3770 leveldir_new->basepath = getStringCopy(level_directory);
3771 leveldir_new->fullpath = getStringCopy(leveldir_new->subdir);
3773 else // sub level group
3775 leveldir_new->basepath = getStringCopy(node_parent->basepath);
3776 leveldir_new->fullpath = getPath2(node_parent->fullpath, directory_name);
3779 leveldir_new->last_level =
3780 leveldir_new->first_level + leveldir_new->levels - 1;
3782 leveldir_new->in_user_dir =
3783 (!strEqual(leveldir_new->basepath, options.level_directory));
3785 // adjust some settings if user's private level directory was detected
3786 if (leveldir_new->sort_priority == LEVELCLASS_UNDEFINED &&
3787 leveldir_new->in_user_dir &&
3788 (strEqual(leveldir_new->subdir, getLoginName()) ||
3789 strEqual(leveldir_new->name, getLoginName()) ||
3790 strEqual(leveldir_new->author, getRealName())))
3792 leveldir_new->sort_priority = LEVELCLASS_PRIVATE_START;
3793 leveldir_new->readonly = FALSE;
3796 leveldir_new->user_defined =
3797 (leveldir_new->in_user_dir && IS_LEVELCLASS_PRIVATE(leveldir_new));
3799 setString(&leveldir_new->class_desc, getLevelClassDescription(leveldir_new));
3801 leveldir_new->handicap_level = // set handicap to default value
3802 (leveldir_new->user_defined || !leveldir_new->handicap ?
3803 leveldir_new->last_level : leveldir_new->first_level);
3805 DrawInitTextItem(leveldir_new->name);
3807 pushTreeInfo(node_first, leveldir_new);
3809 freeSetupFileHash(setup_file_hash);
3811 if (leveldir_new->level_group)
3813 // create node to link back to current level directory
3814 createParentTreeInfoNode(leveldir_new);
3816 // recursively step into sub-directory and look for more level series
3817 LoadLevelInfoFromLevelDir(&leveldir_new->node_group,
3818 leveldir_new, directory_path);
3821 free(directory_path);
3827 static void LoadLevelInfoFromLevelDir(TreeInfo **node_first,
3828 TreeInfo *node_parent,
3829 char *level_directory)
3831 // ---------- 1st stage: process any level set zip files ----------
3833 ProcessZipFilesInDirectory(level_directory, TREE_TYPE_LEVEL_DIR);
3835 // ---------- 2nd stage: check for level set directories ----------
3838 DirectoryEntry *dir_entry;
3839 boolean valid_entry_found = FALSE;
3841 if ((dir = openDirectory(level_directory)) == NULL)
3843 Warn("cannot read level directory '%s'", level_directory);
3848 while ((dir_entry = readDirectory(dir)) != NULL) // loop all entries
3850 char *directory_name = dir_entry->basename;
3851 char *directory_path = getPath2(level_directory, directory_name);
3853 // skip entries for current and parent directory
3854 if (strEqual(directory_name, ".") ||
3855 strEqual(directory_name, ".."))
3857 free(directory_path);
3862 // find out if directory entry is itself a directory
3863 if (!dir_entry->is_directory) // not a directory
3865 free(directory_path);
3870 free(directory_path);
3872 if (strEqual(directory_name, GRAPHICS_DIRECTORY) ||
3873 strEqual(directory_name, SOUNDS_DIRECTORY) ||
3874 strEqual(directory_name, MUSIC_DIRECTORY))
3877 valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
3882 closeDirectory(dir);
3884 // special case: top level directory may directly contain "levelinfo.conf"
3885 if (node_parent == NULL && !valid_entry_found)
3887 // check if this directory directly contains a file "levelinfo.conf"
3888 valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
3889 level_directory, ".");
3892 if (!valid_entry_found)
3893 Warn("cannot find any valid level series in directory '%s'",
3897 boolean AdjustGraphicsForEMC(void)
3899 boolean settings_changed = FALSE;
3901 settings_changed |= adjustTreeGraphicsForEMC(leveldir_first_all);
3902 settings_changed |= adjustTreeGraphicsForEMC(leveldir_first);
3904 return settings_changed;
3907 boolean AdjustSoundsForEMC(void)
3909 boolean settings_changed = FALSE;
3911 settings_changed |= adjustTreeSoundsForEMC(leveldir_first_all);
3912 settings_changed |= adjustTreeSoundsForEMC(leveldir_first);
3914 return settings_changed;
3917 void LoadLevelInfo(void)
3919 InitUserLevelDirectory(getLoginName());
3921 DrawInitTextHead("Loading level series");
3923 LoadLevelInfoFromLevelDir(&leveldir_first, NULL, options.level_directory);
3924 LoadLevelInfoFromLevelDir(&leveldir_first, NULL, getUserLevelDir(NULL));
3926 leveldir_first = createTopTreeInfoNode(leveldir_first);
3928 /* after loading all level set information, clone the level directory tree
3929 and remove all level sets without levels (these may still contain artwork
3930 to be offered in the setup menu as "custom artwork", and are therefore
3931 checked for existing artwork in the function "LoadLevelArtworkInfo()") */
3932 leveldir_first_all = leveldir_first;
3933 cloneTree(&leveldir_first, leveldir_first_all, TRUE);
3935 AdjustGraphicsForEMC();
3936 AdjustSoundsForEMC();
3938 // before sorting, the first entries will be from the user directory
3939 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
3941 if (leveldir_first == NULL)
3942 Fail("cannot find any valid level series in any directory");
3944 sortTreeInfo(&leveldir_first);
3946 #if ENABLE_UNUSED_CODE
3947 dumpTreeInfo(leveldir_first, 0);
3951 static boolean LoadArtworkInfoFromArtworkConf(TreeInfo **node_first,
3952 TreeInfo *node_parent,
3953 char *base_directory,
3954 char *directory_name, int type)
3956 char *directory_path = getPath2(base_directory, directory_name);
3957 char *filename = getPath2(directory_path, ARTWORKINFO_FILENAME(type));
3958 SetupFileHash *setup_file_hash = NULL;
3959 TreeInfo *artwork_new = NULL;
3962 if (fileExists(filename))
3963 setup_file_hash = loadSetupFileHash(filename);
3965 if (setup_file_hash == NULL) // no config file -- look for artwork files
3968 DirectoryEntry *dir_entry;
3969 boolean valid_file_found = FALSE;
3971 if ((dir = openDirectory(directory_path)) != NULL)
3973 while ((dir_entry = readDirectory(dir)) != NULL)
3975 if (FileIsArtworkType(dir_entry->filename, type))
3977 valid_file_found = TRUE;
3983 closeDirectory(dir);
3986 if (!valid_file_found)
3988 #if DEBUG_NO_CONFIG_FILE
3989 if (!strEqual(directory_name, "."))
3990 Debug("setup", "ignoring artwork directory '%s'", directory_path);
3993 free(directory_path);
4000 artwork_new = newTreeInfo();
4003 setTreeInfoToDefaultsFromParent(artwork_new, node_parent);
4005 setTreeInfoToDefaults(artwork_new, type);
4007 artwork_new->subdir = getStringCopy(directory_name);
4009 if (setup_file_hash) // (before defining ".color" and ".class_desc")
4011 // set all structure fields according to the token/value pairs
4013 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
4014 setSetupInfo(levelinfo_tokens, i,
4015 getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
4018 if (strEqual(artwork_new->name, ANONYMOUS_NAME))
4019 setString(&artwork_new->name, artwork_new->subdir);
4021 if (artwork_new->identifier == NULL)
4022 artwork_new->identifier = getStringCopy(artwork_new->subdir);
4024 if (artwork_new->name_sorting == NULL)
4025 artwork_new->name_sorting = getStringCopy(artwork_new->name);
4028 if (node_parent == NULL) // top level group
4030 artwork_new->basepath = getStringCopy(base_directory);
4031 artwork_new->fullpath = getStringCopy(artwork_new->subdir);
4033 else // sub level group
4035 artwork_new->basepath = getStringCopy(node_parent->basepath);
4036 artwork_new->fullpath = getPath2(node_parent->fullpath, directory_name);
4039 artwork_new->in_user_dir =
4040 (!strEqual(artwork_new->basepath, OPTIONS_ARTWORK_DIRECTORY(type)));
4042 setString(&artwork_new->class_desc, getLevelClassDescription(artwork_new));
4044 if (setup_file_hash == NULL) // (after determining ".user_defined")
4046 if (strEqual(artwork_new->subdir, "."))
4048 if (artwork_new->user_defined)
4050 setString(&artwork_new->identifier, "private");
4051 artwork_new->sort_priority = ARTWORKCLASS_PRIVATE;
4055 setString(&artwork_new->identifier, "classic");
4056 artwork_new->sort_priority = ARTWORKCLASS_CLASSICS;
4059 setString(&artwork_new->class_desc,
4060 getLevelClassDescription(artwork_new));
4064 setString(&artwork_new->identifier, artwork_new->subdir);
4067 setString(&artwork_new->name, artwork_new->identifier);
4068 setString(&artwork_new->name_sorting, artwork_new->name);
4071 pushTreeInfo(node_first, artwork_new);
4073 freeSetupFileHash(setup_file_hash);
4075 free(directory_path);
4081 static void LoadArtworkInfoFromArtworkDir(TreeInfo **node_first,
4082 TreeInfo *node_parent,
4083 char *base_directory, int type)
4085 // ---------- 1st stage: process any artwork set zip files ----------
4087 ProcessZipFilesInDirectory(base_directory, type);
4089 // ---------- 2nd stage: check for artwork set directories ----------
4092 DirectoryEntry *dir_entry;
4093 boolean valid_entry_found = FALSE;
4095 if ((dir = openDirectory(base_directory)) == NULL)
4097 // display error if directory is main "options.graphics_directory" etc.
4098 if (base_directory == OPTIONS_ARTWORK_DIRECTORY(type))
4099 Warn("cannot read directory '%s'", base_directory);
4104 while ((dir_entry = readDirectory(dir)) != NULL) // loop all entries
4106 char *directory_name = dir_entry->basename;
4107 char *directory_path = getPath2(base_directory, directory_name);
4109 // skip directory entries for current and parent directory
4110 if (strEqual(directory_name, ".") ||
4111 strEqual(directory_name, ".."))
4113 free(directory_path);
4118 // skip directory entries which are not a directory
4119 if (!dir_entry->is_directory) // not a directory
4121 free(directory_path);
4126 free(directory_path);
4128 // check if this directory contains artwork with or without config file
4129 valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
4131 directory_name, type);
4134 closeDirectory(dir);
4136 // check if this directory directly contains artwork itself
4137 valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
4138 base_directory, ".",
4140 if (!valid_entry_found)
4141 Warn("cannot find any valid artwork in directory '%s'", base_directory);
4144 static TreeInfo *getDummyArtworkInfo(int type)
4146 // this is only needed when there is completely no artwork available
4147 TreeInfo *artwork_new = newTreeInfo();
4149 setTreeInfoToDefaults(artwork_new, type);
4151 setString(&artwork_new->subdir, UNDEFINED_FILENAME);
4152 setString(&artwork_new->fullpath, UNDEFINED_FILENAME);
4153 setString(&artwork_new->basepath, UNDEFINED_FILENAME);
4155 setString(&artwork_new->identifier, UNDEFINED_FILENAME);
4156 setString(&artwork_new->name, UNDEFINED_FILENAME);
4157 setString(&artwork_new->name_sorting, UNDEFINED_FILENAME);
4162 void SetCurrentArtwork(int type)
4164 ArtworkDirTree **current_ptr = ARTWORK_CURRENT_PTR(artwork, type);
4165 ArtworkDirTree *first_node = ARTWORK_FIRST_NODE(artwork, type);
4166 char *setup_set = SETUP_ARTWORK_SET(setup, type);
4167 char *default_subdir = ARTWORK_DEFAULT_SUBDIR(type);
4169 // set current artwork to artwork configured in setup menu
4170 *current_ptr = getTreeInfoFromIdentifier(first_node, setup_set);
4172 // if not found, set current artwork to default artwork
4173 if (*current_ptr == NULL)
4174 *current_ptr = getTreeInfoFromIdentifier(first_node, default_subdir);
4176 // if not found, set current artwork to first artwork in tree
4177 if (*current_ptr == NULL)
4178 *current_ptr = getFirstValidTreeInfoEntry(first_node);
4181 void ChangeCurrentArtworkIfNeeded(int type)
4183 char *current_identifier = ARTWORK_CURRENT_IDENTIFIER(artwork, type);
4184 char *setup_set = SETUP_ARTWORK_SET(setup, type);
4186 if (!strEqual(current_identifier, setup_set))
4187 SetCurrentArtwork(type);
4190 void LoadArtworkInfo(void)
4192 LoadArtworkInfoCache();
4194 DrawInitTextHead("Looking for custom artwork");
4196 LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
4197 options.graphics_directory,
4198 TREE_TYPE_GRAPHICS_DIR);
4199 LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
4200 getUserGraphicsDir(),
4201 TREE_TYPE_GRAPHICS_DIR);
4203 LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
4204 options.sounds_directory,
4205 TREE_TYPE_SOUNDS_DIR);
4206 LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
4208 TREE_TYPE_SOUNDS_DIR);
4210 LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
4211 options.music_directory,
4212 TREE_TYPE_MUSIC_DIR);
4213 LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
4215 TREE_TYPE_MUSIC_DIR);
4217 if (artwork.gfx_first == NULL)
4218 artwork.gfx_first = getDummyArtworkInfo(TREE_TYPE_GRAPHICS_DIR);
4219 if (artwork.snd_first == NULL)
4220 artwork.snd_first = getDummyArtworkInfo(TREE_TYPE_SOUNDS_DIR);
4221 if (artwork.mus_first == NULL)
4222 artwork.mus_first = getDummyArtworkInfo(TREE_TYPE_MUSIC_DIR);
4224 // before sorting, the first entries will be from the user directory
4225 SetCurrentArtwork(ARTWORK_TYPE_GRAPHICS);
4226 SetCurrentArtwork(ARTWORK_TYPE_SOUNDS);
4227 SetCurrentArtwork(ARTWORK_TYPE_MUSIC);
4229 artwork.gfx_current_identifier = artwork.gfx_current->identifier;
4230 artwork.snd_current_identifier = artwork.snd_current->identifier;
4231 artwork.mus_current_identifier = artwork.mus_current->identifier;
4233 #if ENABLE_UNUSED_CODE
4234 Debug("setup:LoadArtworkInfo", "graphics set == %s",
4235 artwork.gfx_current_identifier);
4236 Debug("setup:LoadArtworkInfo", "sounds set == %s",
4237 artwork.snd_current_identifier);
4238 Debug("setup:LoadArtworkInfo", "music set == %s",
4239 artwork.mus_current_identifier);
4242 sortTreeInfo(&artwork.gfx_first);
4243 sortTreeInfo(&artwork.snd_first);
4244 sortTreeInfo(&artwork.mus_first);
4246 #if ENABLE_UNUSED_CODE
4247 dumpTreeInfo(artwork.gfx_first, 0);
4248 dumpTreeInfo(artwork.snd_first, 0);
4249 dumpTreeInfo(artwork.mus_first, 0);
4253 static void MoveArtworkInfoIntoSubTree(ArtworkDirTree **artwork_node)
4255 ArtworkDirTree *artwork_new = newTreeInfo();
4256 char *top_node_name = "standalone artwork";
4258 setTreeInfoToDefaults(artwork_new, (*artwork_node)->type);
4260 artwork_new->level_group = TRUE;
4262 setString(&artwork_new->identifier, top_node_name);
4263 setString(&artwork_new->name, top_node_name);
4264 setString(&artwork_new->name_sorting, top_node_name);
4266 // create node to link back to current custom artwork directory
4267 createParentTreeInfoNode(artwork_new);
4269 // move existing custom artwork tree into newly created sub-tree
4270 artwork_new->node_group->next = *artwork_node;
4272 // change custom artwork tree to contain only newly created node
4273 *artwork_node = artwork_new;
4276 static void LoadArtworkInfoFromLevelInfoExt(ArtworkDirTree **artwork_node,
4277 ArtworkDirTree *node_parent,
4278 LevelDirTree *level_node,
4279 boolean empty_level_set_mode)
4281 int type = (*artwork_node)->type;
4283 // recursively check all level directories for artwork sub-directories
4287 boolean empty_level_set = (level_node->levels == 0);
4289 // check all tree entries for artwork, but skip parent link entries
4290 if (!level_node->parent_link && empty_level_set == empty_level_set_mode)
4292 TreeInfo *artwork_new = getArtworkInfoCacheEntry(level_node, type);
4293 boolean cached = (artwork_new != NULL);
4297 pushTreeInfo(artwork_node, artwork_new);
4301 TreeInfo *topnode_last = *artwork_node;
4302 char *path = getPath2(getLevelDirFromTreeInfo(level_node),
4303 ARTWORK_DIRECTORY(type));
4305 LoadArtworkInfoFromArtworkDir(artwork_node, NULL, path, type);
4307 if (topnode_last != *artwork_node) // check for newly added node
4309 artwork_new = *artwork_node;
4311 setString(&artwork_new->identifier, level_node->subdir);
4312 setString(&artwork_new->name, level_node->name);
4313 setString(&artwork_new->name_sorting, level_node->name_sorting);
4315 artwork_new->sort_priority = level_node->sort_priority;
4316 artwork_new->in_user_dir = level_node->in_user_dir;
4318 update_artworkinfo_cache = TRUE;
4324 // insert artwork info (from old cache or filesystem) into new cache
4325 if (artwork_new != NULL)
4326 setArtworkInfoCacheEntry(artwork_new, level_node, type);
4329 DrawInitTextItem(level_node->name);
4331 if (level_node->node_group != NULL)
4333 TreeInfo *artwork_new = newTreeInfo();
4336 setTreeInfoToDefaultsFromParent(artwork_new, node_parent);
4338 setTreeInfoToDefaults(artwork_new, type);
4340 artwork_new->level_group = TRUE;
4342 setString(&artwork_new->identifier, level_node->subdir);
4344 if (node_parent == NULL) // check for top tree node
4346 char *top_node_name = (empty_level_set_mode ?
4347 "artwork for certain level sets" :
4348 "artwork included in level sets");
4350 setString(&artwork_new->name, top_node_name);
4351 setString(&artwork_new->name_sorting, top_node_name);
4355 setString(&artwork_new->name, level_node->name);
4356 setString(&artwork_new->name_sorting, level_node->name_sorting);
4359 pushTreeInfo(artwork_node, artwork_new);
4361 // create node to link back to current custom artwork directory
4362 createParentTreeInfoNode(artwork_new);
4364 // recursively step into sub-directory and look for more custom artwork
4365 LoadArtworkInfoFromLevelInfoExt(&artwork_new->node_group, artwork_new,
4366 level_node->node_group,
4367 empty_level_set_mode);
4369 // if sub-tree has no custom artwork at all, remove it
4370 if (artwork_new->node_group->next == NULL)
4371 removeTreeInfo(artwork_node);
4374 level_node = level_node->next;
4378 static void LoadArtworkInfoFromLevelInfo(ArtworkDirTree **artwork_node)
4380 // move peviously loaded artwork tree into separate sub-tree
4381 MoveArtworkInfoIntoSubTree(artwork_node);
4383 // load artwork from level sets into separate sub-trees
4384 LoadArtworkInfoFromLevelInfoExt(artwork_node, NULL, leveldir_first_all, TRUE);
4385 LoadArtworkInfoFromLevelInfoExt(artwork_node, NULL, leveldir_first_all, FALSE);
4387 // add top tree node over all sub-trees and set parent links
4388 *artwork_node = addTopTreeInfoNode(*artwork_node);
4391 void LoadLevelArtworkInfo(void)
4393 print_timestamp_init("LoadLevelArtworkInfo");
4395 DrawInitTextHead("Looking for custom level artwork");
4397 print_timestamp_time("DrawTimeText");
4399 LoadArtworkInfoFromLevelInfo(&artwork.gfx_first);
4400 print_timestamp_time("LoadArtworkInfoFromLevelInfo (gfx)");
4401 LoadArtworkInfoFromLevelInfo(&artwork.snd_first);
4402 print_timestamp_time("LoadArtworkInfoFromLevelInfo (snd)");
4403 LoadArtworkInfoFromLevelInfo(&artwork.mus_first);
4404 print_timestamp_time("LoadArtworkInfoFromLevelInfo (mus)");
4406 SaveArtworkInfoCache();
4408 print_timestamp_time("SaveArtworkInfoCache");
4410 // needed for reloading level artwork not known at ealier stage
4411 ChangeCurrentArtworkIfNeeded(ARTWORK_TYPE_GRAPHICS);
4412 ChangeCurrentArtworkIfNeeded(ARTWORK_TYPE_SOUNDS);
4413 ChangeCurrentArtworkIfNeeded(ARTWORK_TYPE_MUSIC);
4415 print_timestamp_time("getTreeInfoFromIdentifier");
4417 sortTreeInfo(&artwork.gfx_first);
4418 sortTreeInfo(&artwork.snd_first);
4419 sortTreeInfo(&artwork.mus_first);
4421 print_timestamp_time("sortTreeInfo");
4423 #if ENABLE_UNUSED_CODE
4424 dumpTreeInfo(artwork.gfx_first, 0);
4425 dumpTreeInfo(artwork.snd_first, 0);
4426 dumpTreeInfo(artwork.mus_first, 0);
4429 print_timestamp_done("LoadLevelArtworkInfo");
4432 static boolean AddTreeSetToTreeInfoExt(TreeInfo *tree_node_old, char *tree_dir,
4433 char *tree_subdir_new, int type)
4435 if (tree_node_old == NULL)
4437 if (type == TREE_TYPE_LEVEL_DIR)
4439 // get level info tree node of personal user level set
4440 tree_node_old = getTreeInfoFromIdentifier(leveldir_first, getLoginName());
4442 // this may happen if "setup.internal.create_user_levelset" is FALSE
4443 // or if file "levelinfo.conf" is missing in personal user level set
4444 if (tree_node_old == NULL)
4445 tree_node_old = leveldir_first->node_group;
4449 // get artwork info tree node of first artwork set
4450 tree_node_old = ARTWORK_FIRST_NODE(artwork, type);
4454 if (tree_dir == NULL)
4455 tree_dir = TREE_USERDIR(type);
4457 if (tree_node_old == NULL ||
4459 tree_subdir_new == NULL) // should not happen
4462 int draw_deactivation_mask = GetDrawDeactivationMask();
4464 // override draw deactivation mask (temporarily disable drawing)
4465 SetDrawDeactivationMask(REDRAW_ALL);
4467 if (type == TREE_TYPE_LEVEL_DIR)
4469 // load new level set config and add it next to first user level set
4470 LoadLevelInfoFromLevelConf(&tree_node_old->next,
4471 tree_node_old->node_parent,
4472 tree_dir, tree_subdir_new);
4476 // load new artwork set config and add it next to first artwork set
4477 LoadArtworkInfoFromArtworkConf(&tree_node_old->next,
4478 tree_node_old->node_parent,
4479 tree_dir, tree_subdir_new, type);
4482 // set draw deactivation mask to previous value
4483 SetDrawDeactivationMask(draw_deactivation_mask);
4485 // get first node of level or artwork info tree
4486 TreeInfo **tree_node_first = TREE_FIRST_NODE_PTR(type);
4488 // get tree info node of newly added level or artwork set
4489 TreeInfo *tree_node_new = getTreeInfoFromIdentifier(*tree_node_first,
4492 if (tree_node_new == NULL) // should not happen
4495 // correct top link and parent node link of newly created tree node
4496 tree_node_new->node_top = tree_node_old->node_top;
4497 tree_node_new->node_parent = tree_node_old->node_parent;
4499 // sort tree info to adjust position of newly added tree set
4500 sortTreeInfo(tree_node_first);
4505 void AddTreeSetToTreeInfo(TreeInfo *tree_node, char *tree_dir,
4506 char *tree_subdir_new, int type)
4508 if (!AddTreeSetToTreeInfoExt(tree_node, tree_dir, tree_subdir_new, type))
4509 Fail("internal tree info structure corrupted -- aborting");
4512 void AddUserLevelSetToLevelInfo(char *level_subdir_new)
4514 AddTreeSetToTreeInfo(NULL, NULL, level_subdir_new, TREE_TYPE_LEVEL_DIR);
4517 char *getArtworkIdentifierForUserLevelSet(int type)
4519 char *classic_artwork_set = getClassicArtworkSet(type);
4521 // check for custom artwork configured in "levelinfo.conf"
4522 char *leveldir_artwork_set =
4523 *LEVELDIR_ARTWORK_SET_PTR(leveldir_current, type);
4524 boolean has_leveldir_artwork_set =
4525 (leveldir_artwork_set != NULL && !strEqual(leveldir_artwork_set,
4526 classic_artwork_set));
4528 // check for custom artwork in sub-directory "graphics" etc.
4529 TreeInfo *artwork_first_node = ARTWORK_FIRST_NODE(artwork, type);
4530 char *leveldir_identifier = leveldir_current->identifier;
4531 boolean has_artwork_subdir =
4532 (getTreeInfoFromIdentifier(artwork_first_node,
4533 leveldir_identifier) != NULL);
4535 return (has_leveldir_artwork_set ? leveldir_artwork_set :
4536 has_artwork_subdir ? leveldir_identifier :
4537 classic_artwork_set);
4540 TreeInfo *getArtworkTreeInfoForUserLevelSet(int type)
4542 char *artwork_set = getArtworkIdentifierForUserLevelSet(type);
4543 TreeInfo *artwork_first_node = ARTWORK_FIRST_NODE(artwork, type);
4544 TreeInfo *ti = getTreeInfoFromIdentifier(artwork_first_node, artwork_set);
4548 ti = getTreeInfoFromIdentifier(artwork_first_node,
4549 ARTWORK_DEFAULT_SUBDIR(type));
4551 Fail("cannot find default graphics -- should not happen");
4557 boolean checkIfCustomArtworkExistsForCurrentLevelSet(void)
4559 char *graphics_set =
4560 getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_GRAPHICS);
4562 getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_SOUNDS);
4564 getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_MUSIC);
4566 return (!strEqual(graphics_set, GFX_CLASSIC_SUBDIR) ||
4567 !strEqual(sounds_set, SND_CLASSIC_SUBDIR) ||
4568 !strEqual(music_set, MUS_CLASSIC_SUBDIR));
4571 boolean UpdateUserLevelSet(char *level_subdir, char *level_name,
4572 char *level_author, int num_levels)
4574 char *filename = getPath2(getUserLevelDir(level_subdir), LEVELINFO_FILENAME);
4575 char *filename_tmp = getStringCat2(filename, ".tmp");
4577 FILE *file_tmp = NULL;
4578 char line[MAX_LINE_LEN];
4579 boolean success = FALSE;
4580 LevelDirTree *leveldir = getTreeInfoFromIdentifier(leveldir_first,
4582 // update values in level directory tree
4584 if (level_name != NULL)
4585 setString(&leveldir->name, level_name);
4587 if (level_author != NULL)
4588 setString(&leveldir->author, level_author);
4590 if (num_levels != -1)
4591 leveldir->levels = num_levels;
4593 // update values that depend on other values
4595 setString(&leveldir->name_sorting, leveldir->name);
4597 leveldir->last_level = leveldir->first_level + leveldir->levels - 1;
4599 // sort order of level sets may have changed
4600 sortTreeInfo(&leveldir_first);
4602 if ((file = fopen(filename, MODE_READ)) &&
4603 (file_tmp = fopen(filename_tmp, MODE_WRITE)))
4605 while (fgets(line, MAX_LINE_LEN, file))
4607 if (strPrefix(line, "name:") && level_name != NULL)
4608 fprintf(file_tmp, "%-32s%s\n", "name:", level_name);
4609 else if (strPrefix(line, "author:") && level_author != NULL)
4610 fprintf(file_tmp, "%-32s%s\n", "author:", level_author);
4611 else if (strPrefix(line, "levels:") && num_levels != -1)
4612 fprintf(file_tmp, "%-32s%d\n", "levels:", num_levels);
4614 fputs(line, file_tmp);
4627 success = (rename(filename_tmp, filename) == 0);
4635 boolean CreateUserLevelSet(char *level_subdir, char *level_name,
4636 char *level_author, int num_levels,
4637 boolean use_artwork_set)
4639 LevelDirTree *level_info;
4644 // create user level sub-directory, if needed
4645 createDirectory(getUserLevelDir(level_subdir), "user level", PERMS_PRIVATE);
4647 filename = getPath2(getUserLevelDir(level_subdir), LEVELINFO_FILENAME);
4649 if (!(file = fopen(filename, MODE_WRITE)))
4651 Warn("cannot write level info file '%s'", filename);
4658 level_info = newTreeInfo();
4660 // always start with reliable default values
4661 setTreeInfoToDefaults(level_info, TREE_TYPE_LEVEL_DIR);
4663 setString(&level_info->name, level_name);
4664 setString(&level_info->author, level_author);
4665 level_info->levels = num_levels;
4666 level_info->first_level = 1;
4667 level_info->sort_priority = LEVELCLASS_PRIVATE_START;
4668 level_info->readonly = FALSE;
4670 if (use_artwork_set)
4672 level_info->graphics_set =
4673 getStringCopy(getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_GRAPHICS));
4674 level_info->sounds_set =
4675 getStringCopy(getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_SOUNDS));
4676 level_info->music_set =
4677 getStringCopy(getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_MUSIC));
4680 token_value_position = TOKEN_VALUE_POSITION_SHORT;
4682 fprintFileHeader(file, LEVELINFO_FILENAME);
4685 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
4687 if (i == LEVELINFO_TOKEN_NAME ||
4688 i == LEVELINFO_TOKEN_AUTHOR ||
4689 i == LEVELINFO_TOKEN_LEVELS ||
4690 i == LEVELINFO_TOKEN_FIRST_LEVEL ||
4691 i == LEVELINFO_TOKEN_SORT_PRIORITY ||
4692 i == LEVELINFO_TOKEN_READONLY ||
4693 (use_artwork_set && (i == LEVELINFO_TOKEN_GRAPHICS_SET ||
4694 i == LEVELINFO_TOKEN_SOUNDS_SET ||
4695 i == LEVELINFO_TOKEN_MUSIC_SET)))
4696 fprintf(file, "%s\n", getSetupLine(levelinfo_tokens, "", i));
4698 // just to make things nicer :)
4699 if (i == LEVELINFO_TOKEN_AUTHOR ||
4700 i == LEVELINFO_TOKEN_FIRST_LEVEL ||
4701 (use_artwork_set && i == LEVELINFO_TOKEN_READONLY))
4702 fprintf(file, "\n");
4705 token_value_position = TOKEN_VALUE_POSITION_DEFAULT;
4709 SetFilePermissions(filename, PERMS_PRIVATE);
4711 freeTreeInfo(level_info);
4717 static void SaveUserLevelInfo(void)
4719 CreateUserLevelSet(getLoginName(), getLoginName(), getRealName(), 100, FALSE);
4722 char *getSetupValue(int type, void *value)
4724 static char value_string[MAX_LINE_LEN];
4732 strcpy(value_string, (*(boolean *)value ? "true" : "false"));
4736 strcpy(value_string, (*(boolean *)value ? "on" : "off"));
4740 strcpy(value_string, (*(int *)value == AUTO ? "auto" :
4741 *(int *)value == FALSE ? "off" : "on"));
4745 strcpy(value_string, (*(boolean *)value ? "yes" : "no"));
4748 case TYPE_YES_NO_AUTO:
4749 strcpy(value_string, (*(int *)value == AUTO ? "auto" :
4750 *(int *)value == FALSE ? "no" : "yes"));
4754 strcpy(value_string, (*(boolean *)value ? "AGA" : "ECS"));
4758 strcpy(value_string, getKeyNameFromKey(*(Key *)value));
4762 strcpy(value_string, getX11KeyNameFromKey(*(Key *)value));
4766 sprintf(value_string, "%d", *(int *)value);
4770 if (*(char **)value == NULL)
4773 strcpy(value_string, *(char **)value);
4777 sprintf(value_string, "player_%d", *(int *)value + 1);
4781 value_string[0] = '\0';
4785 if (type & TYPE_GHOSTED)
4786 strcpy(value_string, "n/a");
4788 return value_string;
4791 char *getSetupLine(struct TokenInfo *token_info, char *prefix, int token_nr)
4795 static char token_string[MAX_LINE_LEN];
4796 int token_type = token_info[token_nr].type;
4797 void *setup_value = token_info[token_nr].value;
4798 char *token_text = token_info[token_nr].text;
4799 char *value_string = getSetupValue(token_type, setup_value);
4801 // build complete token string
4802 sprintf(token_string, "%s%s", prefix, token_text);
4804 // build setup entry line
4805 line = getFormattedSetupEntry(token_string, value_string);
4807 if (token_type == TYPE_KEY_X11)
4809 Key key = *(Key *)setup_value;
4810 char *keyname = getKeyNameFromKey(key);
4812 // add comment, if useful
4813 if (!strEqual(keyname, "(undefined)") &&
4814 !strEqual(keyname, "(unknown)"))
4816 // add at least one whitespace
4818 for (i = strlen(line); i < token_comment_position; i++)
4822 strcat(line, keyname);
4829 static void InitLastPlayedLevels_ParentNode(void)
4831 LevelDirTree **leveldir_top = &leveldir_first->node_group->next;
4832 LevelDirTree *leveldir_new = NULL;
4834 // check if parent node for last played levels already exists
4835 if (strEqual((*leveldir_top)->identifier, TOKEN_STR_LAST_LEVEL_SERIES))
4838 leveldir_new = newTreeInfo();
4840 setTreeInfoToDefaultsFromParent(leveldir_new, leveldir_first);
4842 leveldir_new->level_group = TRUE;
4843 leveldir_new->sort_priority = LEVELCLASS_LAST_PLAYED_LEVEL;
4845 setString(&leveldir_new->identifier, TOKEN_STR_LAST_LEVEL_SERIES);
4846 setString(&leveldir_new->name, "<< (last played level sets)");
4847 setString(&leveldir_new->name_sorting, leveldir_new->name);
4849 pushTreeInfo(leveldir_top, leveldir_new);
4851 // create node to link back to current level directory
4852 createParentTreeInfoNode(leveldir_new);
4855 void UpdateLastPlayedLevels_TreeInfo(void)
4857 char **last_level_series = setup.level_setup.last_level_series;
4858 LevelDirTree *leveldir_last;
4859 TreeInfo **node_new = NULL;
4862 if (last_level_series[0] == NULL)
4865 InitLastPlayedLevels_ParentNode();
4867 leveldir_last = getTreeInfoFromIdentifierExt(leveldir_first,
4868 TOKEN_STR_LAST_LEVEL_SERIES,
4869 TREE_NODE_TYPE_GROUP);
4870 if (leveldir_last == NULL)
4873 node_new = &leveldir_last->node_group->next;
4875 freeTreeInfo(*node_new);
4879 for (i = 0; last_level_series[i] != NULL; i++)
4881 LevelDirTree *node_last = getTreeInfoFromIdentifier(leveldir_first,
4882 last_level_series[i]);
4883 if (node_last == NULL)
4886 *node_new = getTreeInfoCopy(node_last); // copy complete node
4888 (*node_new)->node_top = &leveldir_first; // correct top node link
4889 (*node_new)->node_parent = leveldir_last; // correct parent node link
4891 (*node_new)->is_copy = TRUE; // mark entry as node copy
4893 (*node_new)->node_group = NULL;
4894 (*node_new)->next = NULL;
4896 (*node_new)->cl_first = -1; // force setting tree cursor
4898 node_new = &((*node_new)->next);
4902 static void UpdateLastPlayedLevels_List(void)
4904 char **last_level_series = setup.level_setup.last_level_series;
4905 int pos = MAX_LEVELDIR_HISTORY - 1;
4908 // search for potentially already existing entry in list of level sets
4909 for (i = 0; last_level_series[i] != NULL; i++)
4910 if (strEqual(last_level_series[i], leveldir_current->identifier))
4913 // move list of level sets one entry down (using potentially free entry)
4914 for (i = pos; i > 0; i--)
4915 setString(&last_level_series[i], last_level_series[i - 1]);
4917 // put last played level set at top position
4918 setString(&last_level_series[0], leveldir_current->identifier);
4921 static TreeInfo *StoreOrRestoreLastPlayedLevels(TreeInfo *node, boolean store)
4923 static char *identifier = NULL;
4927 setString(&identifier, (node && node->is_copy ? node->identifier : NULL));
4929 return NULL; // not used
4933 TreeInfo *node_new = getTreeInfoFromIdentifierExt(leveldir_first,
4935 TREE_NODE_TYPE_COPY);
4936 return (node_new != NULL ? node_new : node);
4940 void StoreLastPlayedLevels(TreeInfo *node)
4942 StoreOrRestoreLastPlayedLevels(node, TRUE);
4945 void RestoreLastPlayedLevels(TreeInfo **node)
4947 *node = StoreOrRestoreLastPlayedLevels(*node, FALSE);
4950 void LoadLevelSetup_LastSeries(void)
4952 // --------------------------------------------------------------------------
4953 // ~/.<program>/levelsetup.conf
4954 // --------------------------------------------------------------------------
4956 char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
4957 SetupFileHash *level_setup_hash = NULL;
4961 // always start with reliable default values
4962 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
4964 // start with empty history of last played level sets
4965 setString(&setup.level_setup.last_level_series[0], NULL);
4967 if (!strEqual(DEFAULT_LEVELSET, UNDEFINED_LEVELSET))
4969 leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
4971 if (leveldir_current == NULL)
4972 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
4975 if ((level_setup_hash = loadSetupFileHash(filename)))
4977 char *last_level_series =
4978 getHashEntry(level_setup_hash, TOKEN_STR_LAST_LEVEL_SERIES);
4980 leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
4982 if (leveldir_current == NULL)
4983 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
4985 for (i = 0; i < MAX_LEVELDIR_HISTORY; i++)
4987 char token[strlen(TOKEN_STR_LAST_LEVEL_SERIES) + 10];
4988 LevelDirTree *leveldir_last;
4990 sprintf(token, "%s.%03d", TOKEN_STR_LAST_LEVEL_SERIES, i);
4992 last_level_series = getHashEntry(level_setup_hash, token);
4994 leveldir_last = getTreeInfoFromIdentifier(leveldir_first,
4996 if (leveldir_last != NULL)
4997 setString(&setup.level_setup.last_level_series[pos++],
5001 setString(&setup.level_setup.last_level_series[pos], NULL);
5003 freeSetupFileHash(level_setup_hash);
5007 Debug("setup", "using default setup values");
5013 static void SaveLevelSetup_LastSeries_Ext(boolean deactivate_last_level_series)
5015 // --------------------------------------------------------------------------
5016 // ~/.<program>/levelsetup.conf
5017 // --------------------------------------------------------------------------
5019 // check if the current level directory structure is available at this point
5020 if (leveldir_current == NULL)
5023 char **last_level_series = setup.level_setup.last_level_series;
5024 char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
5028 InitUserDataDirectory();
5030 UpdateLastPlayedLevels_List();
5032 if (!(file = fopen(filename, MODE_WRITE)))
5034 Warn("cannot write setup file '%s'", filename);
5041 fprintFileHeader(file, LEVELSETUP_FILENAME);
5043 if (deactivate_last_level_series)
5044 fprintf(file, "# %s\n# ", "the following level set may have caused a problem and was deactivated");
5046 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_LAST_LEVEL_SERIES,
5047 leveldir_current->identifier));
5049 for (i = 0; last_level_series[i] != NULL; i++)
5051 char token[strlen(TOKEN_STR_LAST_LEVEL_SERIES) + 1 + 10 + 1];
5053 sprintf(token, "%s.%03d", TOKEN_STR_LAST_LEVEL_SERIES, i);
5055 fprintf(file, "%s\n", getFormattedSetupEntry(token, last_level_series[i]));
5060 SetFilePermissions(filename, PERMS_PRIVATE);
5065 void SaveLevelSetup_LastSeries(void)
5067 SaveLevelSetup_LastSeries_Ext(FALSE);
5070 void SaveLevelSetup_LastSeries_Deactivate(void)
5072 SaveLevelSetup_LastSeries_Ext(TRUE);
5075 static void checkSeriesInfo(void)
5077 static char *level_directory = NULL;
5080 DirectoryEntry *dir_entry;
5083 checked_free(level_directory);
5085 // check for more levels besides the 'levels' field of 'levelinfo.conf'
5087 level_directory = getPath2((leveldir_current->in_user_dir ?
5088 getUserLevelDir(NULL) :
5089 options.level_directory),
5090 leveldir_current->fullpath);
5092 if ((dir = openDirectory(level_directory)) == NULL)
5094 Warn("cannot read level directory '%s'", level_directory);
5100 while ((dir_entry = readDirectory(dir)) != NULL) // loop all entries
5102 if (strlen(dir_entry->basename) > 4 &&
5103 dir_entry->basename[3] == '.' &&
5104 strEqual(&dir_entry->basename[4], LEVELFILE_EXTENSION))
5106 char levelnum_str[4];
5109 strncpy(levelnum_str, dir_entry->basename, 3);
5110 levelnum_str[3] = '\0';
5112 levelnum_value = atoi(levelnum_str);
5114 if (levelnum_value < leveldir_current->first_level)
5116 Warn("additional level %d found", levelnum_value);
5118 leveldir_current->first_level = levelnum_value;
5120 else if (levelnum_value > leveldir_current->last_level)
5122 Warn("additional level %d found", levelnum_value);
5124 leveldir_current->last_level = levelnum_value;
5130 closeDirectory(dir);
5133 void LoadLevelSetup_SeriesInfo(void)
5136 SetupFileHash *level_setup_hash = NULL;
5137 char *level_subdir = leveldir_current->subdir;
5140 // always start with reliable default values
5141 level_nr = leveldir_current->first_level;
5143 for (i = 0; i < MAX_LEVELS; i++)
5145 LevelStats_setPlayed(i, 0);
5146 LevelStats_setSolved(i, 0);
5151 // --------------------------------------------------------------------------
5152 // ~/.<program>/levelsetup/<level series>/levelsetup.conf
5153 // --------------------------------------------------------------------------
5155 level_subdir = leveldir_current->subdir;
5157 filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
5159 if ((level_setup_hash = loadSetupFileHash(filename)))
5163 // get last played level in this level set
5165 token_value = getHashEntry(level_setup_hash, TOKEN_STR_LAST_PLAYED_LEVEL);
5169 level_nr = atoi(token_value);
5171 if (level_nr < leveldir_current->first_level)
5172 level_nr = leveldir_current->first_level;
5173 if (level_nr > leveldir_current->last_level)
5174 level_nr = leveldir_current->last_level;
5177 // get handicap level in this level set
5179 token_value = getHashEntry(level_setup_hash, TOKEN_STR_HANDICAP_LEVEL);
5183 int level_nr = atoi(token_value);
5185 if (level_nr < leveldir_current->first_level)
5186 level_nr = leveldir_current->first_level;
5187 if (level_nr > leveldir_current->last_level + 1)
5188 level_nr = leveldir_current->last_level;
5190 if (leveldir_current->user_defined || !leveldir_current->handicap)
5191 level_nr = leveldir_current->last_level;
5193 leveldir_current->handicap_level = level_nr;
5196 // get number of played and solved levels in this level set
5198 BEGIN_HASH_ITERATION(level_setup_hash, itr)
5200 char *token = HASH_ITERATION_TOKEN(itr);
5201 char *value = HASH_ITERATION_VALUE(itr);
5203 if (strlen(token) == 3 &&
5204 token[0] >= '0' && token[0] <= '9' &&
5205 token[1] >= '0' && token[1] <= '9' &&
5206 token[2] >= '0' && token[2] <= '9')
5208 int level_nr = atoi(token);
5211 LevelStats_setPlayed(level_nr, atoi(value)); // read 1st column
5213 value = strchr(value, ' ');
5216 LevelStats_setSolved(level_nr, atoi(value)); // read 2nd column
5219 END_HASH_ITERATION(hash, itr)
5221 freeSetupFileHash(level_setup_hash);
5225 Debug("setup", "using default setup values");
5231 void SaveLevelSetup_SeriesInfo(void)
5234 char *level_subdir = leveldir_current->subdir;
5235 char *level_nr_str = int2str(level_nr, 0);
5236 char *handicap_level_str = int2str(leveldir_current->handicap_level, 0);
5240 // --------------------------------------------------------------------------
5241 // ~/.<program>/levelsetup/<level series>/levelsetup.conf
5242 // --------------------------------------------------------------------------
5244 InitLevelSetupDirectory(level_subdir);
5246 filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
5248 if (!(file = fopen(filename, MODE_WRITE)))
5250 Warn("cannot write setup file '%s'", filename);
5257 fprintFileHeader(file, LEVELSETUP_FILENAME);
5259 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_PLAYED_LEVEL,
5261 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_HANDICAP_LEVEL,
5262 handicap_level_str));
5264 for (i = leveldir_current->first_level; i <= leveldir_current->last_level;
5267 if (LevelStats_getPlayed(i) > 0 ||
5268 LevelStats_getSolved(i) > 0)
5273 sprintf(token, "%03d", i);
5274 sprintf(value, "%d %d", LevelStats_getPlayed(i), LevelStats_getSolved(i));
5276 fprintf(file, "%s\n", getFormattedSetupEntry(token, value));
5282 SetFilePermissions(filename, PERMS_PRIVATE);
5287 int LevelStats_getPlayed(int nr)
5289 return (nr >= 0 && nr < MAX_LEVELS ? level_stats[nr].played : 0);
5292 int LevelStats_getSolved(int nr)
5294 return (nr >= 0 && nr < MAX_LEVELS ? level_stats[nr].solved : 0);
5297 void LevelStats_setPlayed(int nr, int value)
5299 if (nr >= 0 && nr < MAX_LEVELS)
5300 level_stats[nr].played = value;
5303 void LevelStats_setSolved(int nr, int value)
5305 if (nr >= 0 && nr < MAX_LEVELS)
5306 level_stats[nr].solved = value;
5309 void LevelStats_incPlayed(int nr)
5311 if (nr >= 0 && nr < MAX_LEVELS)
5312 level_stats[nr].played++;
5315 void LevelStats_incSolved(int nr)
5317 if (nr >= 0 && nr < MAX_LEVELS)
5318 level_stats[nr].solved++;
5321 void LoadUserSetup(void)
5323 // --------------------------------------------------------------------------
5324 // ~/.<program>/usersetup.conf
5325 // --------------------------------------------------------------------------
5327 char *filename = getPath2(getMainUserGameDataDir(), USERSETUP_FILENAME);
5328 SetupFileHash *user_setup_hash = NULL;
5330 // always start with reliable default values
5333 if ((user_setup_hash = loadSetupFileHash(filename)))
5337 // get last selected user number
5338 token_value = getHashEntry(user_setup_hash, TOKEN_STR_LAST_USER);
5341 user.nr = MIN(MAX(0, atoi(token_value)), MAX_PLAYER_NAMES - 1);
5343 freeSetupFileHash(user_setup_hash);
5347 Debug("setup", "using default setup values");
5353 void SaveUserSetup(void)
5355 // --------------------------------------------------------------------------
5356 // ~/.<program>/usersetup.conf
5357 // --------------------------------------------------------------------------
5359 char *filename = getPath2(getMainUserGameDataDir(), USERSETUP_FILENAME);
5362 InitMainUserDataDirectory();
5364 if (!(file = fopen(filename, MODE_WRITE)))
5366 Warn("cannot write setup file '%s'", filename);
5373 fprintFileHeader(file, USERSETUP_FILENAME);
5375 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_USER,
5379 SetFilePermissions(filename, PERMS_PRIVATE);