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 Debug("setup", "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");
1330 createDirectory(getTapeDir(NULL), "main tape");
1331 createDirectory(getTapeDir(level_subdir), "level tape");
1334 MarkTapeDirectoryUploadsAsComplete(level_subdir);
1337 void InitScoreDirectory(char *level_subdir)
1339 createDirectory(getMainUserGameDataDir(), "main user data");
1340 createDirectory(getScoreDir(NULL), "main score");
1341 createDirectory(getScoreDir(level_subdir), "level score");
1344 void InitScoreCacheDirectory(char *level_subdir)
1346 createDirectory(getMainUserGameDataDir(), "main user data");
1347 createDirectory(getCacheDir(), "cache data");
1348 createDirectory(getScoreCacheDir(NULL), "main score");
1349 createDirectory(getScoreCacheDir(level_subdir), "level score");
1352 void InitScoreTapeDirectory(char *level_subdir, int nr)
1354 InitScoreDirectory(level_subdir);
1356 createDirectory(getScoreTapeDir(level_subdir, nr), "score tape");
1359 void InitScoreCacheTapeDirectory(char *level_subdir, int nr)
1361 InitScoreCacheDirectory(level_subdir);
1363 createDirectory(getScoreCacheTapeDir(level_subdir, nr), "score tape");
1366 static void SaveUserLevelInfo(void);
1368 void InitUserLevelDirectory(char *level_subdir)
1370 if (!directoryExists(getUserLevelDir(level_subdir)))
1372 createDirectory(getMainUserGameDataDir(), "main user data");
1373 createDirectory(getUserLevelDir(NULL), "main user level");
1375 if (setup.internal.create_user_levelset)
1377 createDirectory(getUserLevelDir(level_subdir), "user level");
1379 SaveUserLevelInfo();
1384 void InitNetworkLevelDirectory(char *level_subdir)
1386 if (!directoryExists(getNetworkLevelDir(level_subdir)))
1388 createDirectory(getMainUserGameDataDir(), "main user data");
1389 createDirectory(getNetworkDir(), "network data");
1390 createDirectory(getNetworkLevelDir(NULL), "main network level");
1391 createDirectory(getNetworkLevelDir(level_subdir), "network level");
1395 void InitLevelSetupDirectory(char *level_subdir)
1397 createDirectory(getUserGameDataDir(), "user data");
1398 createDirectory(getLevelSetupDir(NULL), "main level setup");
1399 createDirectory(getLevelSetupDir(level_subdir), "level setup");
1402 static void InitCacheDirectory(void)
1404 createDirectory(getMainUserGameDataDir(), "main user data");
1405 createDirectory(getCacheDir(), "cache data");
1409 // ----------------------------------------------------------------------------
1410 // some functions to handle lists of level and artwork directories
1411 // ----------------------------------------------------------------------------
1413 TreeInfo *newTreeInfo(void)
1415 return checked_calloc(sizeof(TreeInfo));
1418 TreeInfo *newTreeInfo_setDefaults(int type)
1420 TreeInfo *ti = newTreeInfo();
1422 setTreeInfoToDefaults(ti, type);
1427 void pushTreeInfo(TreeInfo **node_first, TreeInfo *node_new)
1429 node_new->next = *node_first;
1430 *node_first = node_new;
1433 void removeTreeInfo(TreeInfo **node_first)
1435 TreeInfo *node_old = *node_first;
1437 *node_first = node_old->next;
1438 node_old->next = NULL;
1440 freeTreeInfo(node_old);
1443 int numTreeInfo(TreeInfo *node)
1456 boolean validLevelSeries(TreeInfo *node)
1458 // in a number of cases, tree node is no valid level set
1459 if (node == NULL || node->node_group || node->parent_link || node->is_copy)
1465 TreeInfo *getValidLevelSeries(TreeInfo *node, TreeInfo *default_node)
1467 if (validLevelSeries(node))
1469 else if (node->is_copy)
1470 return getTreeInfoFromIdentifier(leveldir_first, node->identifier);
1472 return getFirstValidTreeInfoEntry(default_node);
1475 static TreeInfo *getValidTreeInfoEntryExt(TreeInfo *node, boolean get_next_node)
1480 if (node->node_group) // enter node group (step down into tree)
1481 return getFirstValidTreeInfoEntry(node->node_group);
1483 if (node->parent_link) // skip first node (back link) of node group
1484 get_next_node = TRUE;
1486 if (!get_next_node) // get current regular tree node
1489 // get next regular tree node, or step up until one is found
1490 while (node->next == NULL && node->node_parent != NULL)
1491 node = node->node_parent;
1493 return getFirstValidTreeInfoEntry(node->next);
1496 TreeInfo *getFirstValidTreeInfoEntry(TreeInfo *node)
1498 return getValidTreeInfoEntryExt(node, FALSE);
1501 TreeInfo *getNextValidTreeInfoEntry(TreeInfo *node)
1503 return getValidTreeInfoEntryExt(node, TRUE);
1506 TreeInfo *getTreeInfoFirstGroupEntry(TreeInfo *node)
1511 if (node->node_parent == NULL) // top level group
1512 return *node->node_top;
1513 else // sub level group
1514 return node->node_parent->node_group;
1517 int numTreeInfoInGroup(TreeInfo *node)
1519 return numTreeInfo(getTreeInfoFirstGroupEntry(node));
1522 int getPosFromTreeInfo(TreeInfo *node)
1524 TreeInfo *node_cmp = getTreeInfoFirstGroupEntry(node);
1529 if (node_cmp == node)
1533 node_cmp = node_cmp->next;
1539 TreeInfo *getTreeInfoFromPos(TreeInfo *node, int pos)
1541 TreeInfo *node_default = node;
1553 return node_default;
1556 static TreeInfo *getTreeInfoFromIdentifierExt(TreeInfo *node, char *identifier,
1557 int node_type_wanted)
1559 if (identifier == NULL)
1564 if (TREE_NODE_TYPE(node) == node_type_wanted &&
1565 strEqual(identifier, node->identifier))
1568 if (node->node_group)
1570 TreeInfo *node_group = getTreeInfoFromIdentifierExt(node->node_group,
1583 TreeInfo *getTreeInfoFromIdentifier(TreeInfo *node, char *identifier)
1585 return getTreeInfoFromIdentifierExt(node, identifier, TREE_NODE_TYPE_DEFAULT);
1588 static TreeInfo *cloneTreeNode(TreeInfo **node_top, TreeInfo *node_parent,
1589 TreeInfo *node, boolean skip_sets_without_levels)
1596 if (!node->parent_link && !node->level_group &&
1597 skip_sets_without_levels && node->levels == 0)
1598 return cloneTreeNode(node_top, node_parent, node->next,
1599 skip_sets_without_levels);
1601 node_new = getTreeInfoCopy(node); // copy complete node
1603 node_new->node_top = node_top; // correct top node link
1604 node_new->node_parent = node_parent; // correct parent node link
1606 if (node->level_group)
1607 node_new->node_group = cloneTreeNode(node_top, node_new, node->node_group,
1608 skip_sets_without_levels);
1610 node_new->next = cloneTreeNode(node_top, node_parent, node->next,
1611 skip_sets_without_levels);
1616 static void cloneTree(TreeInfo **ti_new, TreeInfo *ti, boolean skip_empty_sets)
1618 TreeInfo *ti_cloned = cloneTreeNode(ti_new, NULL, ti, skip_empty_sets);
1620 *ti_new = ti_cloned;
1623 static boolean adjustTreeGraphicsForEMC(TreeInfo *node)
1625 boolean settings_changed = FALSE;
1629 boolean want_ecs = (setup.prefer_aga_graphics == FALSE);
1630 boolean want_aga = (setup.prefer_aga_graphics == TRUE);
1631 boolean has_only_ecs = (!node->graphics_set && !node->graphics_set_aga);
1632 boolean has_only_aga = (!node->graphics_set && !node->graphics_set_ecs);
1633 char *graphics_set = NULL;
1635 if (node->graphics_set_ecs && (want_ecs || has_only_ecs))
1636 graphics_set = node->graphics_set_ecs;
1638 if (node->graphics_set_aga && (want_aga || has_only_aga))
1639 graphics_set = node->graphics_set_aga;
1641 if (graphics_set && !strEqual(node->graphics_set, graphics_set))
1643 setString(&node->graphics_set, graphics_set);
1644 settings_changed = TRUE;
1647 if (node->node_group != NULL)
1648 settings_changed |= adjustTreeGraphicsForEMC(node->node_group);
1653 return settings_changed;
1656 static boolean adjustTreeSoundsForEMC(TreeInfo *node)
1658 boolean settings_changed = FALSE;
1662 boolean want_default = (setup.prefer_lowpass_sounds == FALSE);
1663 boolean want_lowpass = (setup.prefer_lowpass_sounds == TRUE);
1664 boolean has_only_default = (!node->sounds_set && !node->sounds_set_lowpass);
1665 boolean has_only_lowpass = (!node->sounds_set && !node->sounds_set_default);
1666 char *sounds_set = NULL;
1668 if (node->sounds_set_default && (want_default || has_only_default))
1669 sounds_set = node->sounds_set_default;
1671 if (node->sounds_set_lowpass && (want_lowpass || has_only_lowpass))
1672 sounds_set = node->sounds_set_lowpass;
1674 if (sounds_set && !strEqual(node->sounds_set, sounds_set))
1676 setString(&node->sounds_set, sounds_set);
1677 settings_changed = TRUE;
1680 if (node->node_group != NULL)
1681 settings_changed |= adjustTreeSoundsForEMC(node->node_group);
1686 return settings_changed;
1689 int dumpTreeInfo(TreeInfo *node, int depth)
1691 char bullet_list[] = { '-', '*', 'o' };
1692 int num_leaf_nodes = 0;
1696 Debug("tree", "Dumping TreeInfo:");
1700 char bullet = bullet_list[depth % ARRAY_SIZE(bullet_list)];
1702 for (i = 0; i < depth * 2; i++)
1703 DebugContinued("", " ");
1705 DebugContinued("tree", "%c '%s' ['%s] [PARENT: '%s'] %s\n",
1706 bullet, node->name, node->identifier,
1707 (node->node_parent ? node->node_parent->identifier : "-"),
1708 (node->node_group ? "[GROUP]" :
1709 node->is_copy ? "[COPY]" : ""));
1711 if (!node->node_group && !node->parent_link)
1715 // use for dumping artwork info tree
1716 Debug("tree", "subdir == '%s' ['%s', '%s'] [%d])",
1717 node->subdir, node->fullpath, node->basepath, node->in_user_dir);
1720 if (node->node_group != NULL)
1721 num_leaf_nodes += dumpTreeInfo(node->node_group, depth + 1);
1727 Debug("tree", "Summary: %d leaf nodes found", num_leaf_nodes);
1729 return num_leaf_nodes;
1732 void sortTreeInfoBySortFunction(TreeInfo **node_first,
1733 int (*compare_function)(const void *,
1736 int num_nodes = numTreeInfo(*node_first);
1737 TreeInfo **sort_array;
1738 TreeInfo *node = *node_first;
1744 // allocate array for sorting structure pointers
1745 sort_array = checked_calloc(num_nodes * sizeof(TreeInfo *));
1747 // writing structure pointers to sorting array
1748 while (i < num_nodes && node) // double boundary check...
1750 sort_array[i] = node;
1756 // sorting the structure pointers in the sorting array
1757 qsort(sort_array, num_nodes, sizeof(TreeInfo *),
1760 // update the linkage of list elements with the sorted node array
1761 for (i = 0; i < num_nodes - 1; i++)
1762 sort_array[i]->next = sort_array[i + 1];
1763 sort_array[num_nodes - 1]->next = NULL;
1765 // update the linkage of the main list anchor pointer
1766 *node_first = sort_array[0];
1770 // now recursively sort the level group structures
1774 if (node->node_group != NULL)
1775 sortTreeInfoBySortFunction(&node->node_group, compare_function);
1781 void sortTreeInfo(TreeInfo **node_first)
1783 sortTreeInfoBySortFunction(node_first, compareTreeInfoEntries);
1787 // ============================================================================
1788 // some stuff from "files.c"
1789 // ============================================================================
1791 #if defined(PLATFORM_WINDOWS)
1793 #define S_IRGRP S_IRUSR
1796 #define S_IROTH S_IRUSR
1799 #define S_IWGRP S_IWUSR
1802 #define S_IWOTH S_IWUSR
1805 #define S_IXGRP S_IXUSR
1808 #define S_IXOTH S_IXUSR
1811 #define S_IRWXG (S_IRGRP | S_IWGRP | S_IXGRP)
1816 #endif // PLATFORM_WINDOWS
1818 // file permissions for newly written files
1819 #define MODE_R_ALL (S_IRUSR | S_IRGRP | S_IROTH)
1820 #define MODE_W_ALL (S_IWUSR | S_IWGRP | S_IWOTH)
1821 #define MODE_X_ALL (S_IXUSR | S_IXGRP | S_IXOTH)
1823 #define MODE_W_PRIVATE (S_IWUSR)
1824 #define MODE_W_PUBLIC_FILE (S_IWUSR | S_IWGRP)
1825 #define MODE_W_PUBLIC_DIR (S_IWUSR | S_IWGRP | S_ISGID)
1827 #define DIR_PERMS_PRIVATE (MODE_R_ALL | MODE_X_ALL | MODE_W_PRIVATE)
1828 #define DIR_PERMS_PUBLIC (MODE_R_ALL | MODE_X_ALL | MODE_W_PUBLIC_DIR)
1829 #define DIR_PERMS_PUBLIC_ALL (MODE_R_ALL | MODE_X_ALL | MODE_W_ALL)
1831 #define FILE_PERMS_PRIVATE (MODE_R_ALL | MODE_W_PRIVATE)
1832 #define FILE_PERMS_PUBLIC (MODE_R_ALL | MODE_W_PUBLIC_FILE)
1833 #define FILE_PERMS_PUBLIC_ALL (MODE_R_ALL | MODE_W_ALL)
1836 char *getHomeDir(void)
1838 static char *dir = NULL;
1840 #if defined(PLATFORM_WINDOWS)
1843 dir = checked_malloc(MAX_PATH + 1);
1845 if (!SUCCEEDED(SHGetFolderPath(NULL, CSIDL_PERSONAL, NULL, 0, dir)))
1848 #elif defined(PLATFORM_EMSCRIPTEN)
1849 dir = PERSISTENT_DIRECTORY;
1850 #elif defined(PLATFORM_UNIX)
1853 if ((dir = getenv("HOME")) == NULL)
1855 dir = getUnixHomeDir();
1858 dir = getStringCopy(dir);
1870 char *getPersonalDataDir(void)
1872 static char *personal_data_dir = NULL;
1874 #if defined(PLATFORM_MAC)
1875 if (personal_data_dir == NULL)
1876 personal_data_dir = getPath2(getHomeDir(), "Documents");
1878 if (personal_data_dir == NULL)
1879 personal_data_dir = getHomeDir();
1882 return personal_data_dir;
1885 char *getMainUserGameDataDir(void)
1887 static char *main_user_data_dir = NULL;
1889 #if defined(PLATFORM_ANDROID)
1890 if (main_user_data_dir == NULL)
1891 main_user_data_dir = (char *)(SDL_AndroidGetExternalStorageState() &
1892 SDL_ANDROID_EXTERNAL_STORAGE_WRITE ?
1893 SDL_AndroidGetExternalStoragePath() :
1894 SDL_AndroidGetInternalStoragePath());
1896 if (main_user_data_dir == NULL)
1897 main_user_data_dir = getPath2(getPersonalDataDir(),
1898 program.userdata_subdir);
1901 return main_user_data_dir;
1904 char *getUserGameDataDir(void)
1907 return getMainUserGameDataDir();
1909 return getUserDir(user.nr);
1912 char *getSetupDir(void)
1914 return getUserGameDataDir();
1917 static mode_t posix_umask(mode_t mask)
1919 #if defined(PLATFORM_UNIX)
1926 static int posix_mkdir(const char *pathname, mode_t mode)
1928 #if defined(PLATFORM_WINDOWS)
1929 return mkdir(pathname);
1931 return mkdir(pathname, mode);
1935 static boolean posix_process_running_setgid(void)
1937 #if defined(PLATFORM_UNIX)
1938 return (getgid() != getegid());
1944 void createDirectory(char *dir, char *text)
1946 if (directoryExists(dir))
1949 // leave "other" permissions in umask untouched, but ensure group parts
1950 // of USERDATA_DIR_MODE are not masked
1951 int permission_class = PERMS_PRIVATE;
1952 mode_t dir_mode = (permission_class == PERMS_PRIVATE ?
1953 DIR_PERMS_PRIVATE : DIR_PERMS_PUBLIC);
1954 mode_t last_umask = posix_umask(0);
1955 mode_t group_umask = ~(dir_mode & S_IRWXG);
1956 int running_setgid = posix_process_running_setgid();
1958 if (permission_class == PERMS_PUBLIC)
1960 // if we're setgid, protect files against "other"
1961 // else keep umask(0) to make the dir world-writable
1964 posix_umask(last_umask & group_umask);
1966 dir_mode = DIR_PERMS_PUBLIC_ALL;
1969 if (posix_mkdir(dir, dir_mode) != 0)
1970 Warn("cannot create %s directory '%s': %s", text, dir, strerror(errno));
1972 if (permission_class == PERMS_PUBLIC && !running_setgid)
1973 chmod(dir, dir_mode);
1975 posix_umask(last_umask); // restore previous umask
1978 void InitMainUserDataDirectory(void)
1980 createDirectory(getMainUserGameDataDir(), "main user data");
1983 void InitUserDataDirectory(void)
1985 createDirectory(getMainUserGameDataDir(), "main user data");
1989 createDirectory(getUserDir(-1), "users");
1990 createDirectory(getUserDir(user.nr), "user data");
1994 void SetFilePermissions(char *filename, int permission_class)
1996 int running_setgid = posix_process_running_setgid();
1997 int perms = (permission_class == PERMS_PRIVATE ?
1998 FILE_PERMS_PRIVATE : FILE_PERMS_PUBLIC);
2000 if (permission_class == PERMS_PUBLIC && !running_setgid)
2001 perms = FILE_PERMS_PUBLIC_ALL;
2003 chmod(filename, perms);
2006 char *getCookie(char *file_type)
2008 static char cookie[MAX_COOKIE_LEN + 1];
2010 if (strlen(program.cookie_prefix) + 1 +
2011 strlen(file_type) + strlen("_FILE_VERSION_x.x") > MAX_COOKIE_LEN)
2012 return "[COOKIE ERROR]"; // should never happen
2014 sprintf(cookie, "%s_%s_FILE_VERSION_%d.%d",
2015 program.cookie_prefix, file_type,
2016 program.version_super, program.version_major);
2021 void fprintFileHeader(FILE *file, char *basename)
2023 char *prefix = "# ";
2026 fprintf_line_with_prefix(file, prefix, sep1, 77);
2027 fprintf(file, "%s%s\n", prefix, basename);
2028 fprintf_line_with_prefix(file, prefix, sep1, 77);
2029 fprintf(file, "\n");
2032 int getFileVersionFromCookieString(const char *cookie)
2034 const char *ptr_cookie1, *ptr_cookie2;
2035 const char *pattern1 = "_FILE_VERSION_";
2036 const char *pattern2 = "?.?";
2037 const int len_cookie = strlen(cookie);
2038 const int len_pattern1 = strlen(pattern1);
2039 const int len_pattern2 = strlen(pattern2);
2040 const int len_pattern = len_pattern1 + len_pattern2;
2041 int version_super, version_major;
2043 if (len_cookie <= len_pattern)
2046 ptr_cookie1 = &cookie[len_cookie - len_pattern];
2047 ptr_cookie2 = &cookie[len_cookie - len_pattern2];
2049 if (strncmp(ptr_cookie1, pattern1, len_pattern1) != 0)
2052 if (ptr_cookie2[0] < '0' || ptr_cookie2[0] > '9' ||
2053 ptr_cookie2[1] != '.' ||
2054 ptr_cookie2[2] < '0' || ptr_cookie2[2] > '9')
2057 version_super = ptr_cookie2[0] - '0';
2058 version_major = ptr_cookie2[2] - '0';
2060 return VERSION_IDENT(version_super, version_major, 0, 0);
2063 boolean checkCookieString(const char *cookie, const char *template)
2065 const char *pattern = "_FILE_VERSION_?.?";
2066 const int len_cookie = strlen(cookie);
2067 const int len_template = strlen(template);
2068 const int len_pattern = strlen(pattern);
2070 if (len_cookie != len_template)
2073 if (strncmp(cookie, template, len_cookie - len_pattern) != 0)
2080 // ----------------------------------------------------------------------------
2081 // setup file list and hash handling functions
2082 // ----------------------------------------------------------------------------
2084 char *getFormattedSetupEntry(char *token, char *value)
2087 static char entry[MAX_LINE_LEN];
2089 // if value is an empty string, just return token without value
2093 // start with the token and some spaces to format output line
2094 sprintf(entry, "%s:", token);
2095 for (i = strlen(entry); i < token_value_position; i++)
2098 // continue with the token's value
2099 strcat(entry, value);
2104 SetupFileList *newSetupFileList(char *token, char *value)
2106 SetupFileList *new = checked_malloc(sizeof(SetupFileList));
2108 new->token = getStringCopy(token);
2109 new->value = getStringCopy(value);
2116 void freeSetupFileList(SetupFileList *list)
2121 checked_free(list->token);
2122 checked_free(list->value);
2125 freeSetupFileList(list->next);
2130 char *getListEntry(SetupFileList *list, char *token)
2135 if (strEqual(list->token, token))
2138 return getListEntry(list->next, token);
2141 SetupFileList *setListEntry(SetupFileList *list, char *token, char *value)
2146 if (strEqual(list->token, token))
2148 checked_free(list->value);
2150 list->value = getStringCopy(value);
2154 else if (list->next == NULL)
2155 return (list->next = newSetupFileList(token, value));
2157 return setListEntry(list->next, token, value);
2160 SetupFileList *addListEntry(SetupFileList *list, char *token, char *value)
2165 if (list->next == NULL)
2166 return (list->next = newSetupFileList(token, value));
2168 return addListEntry(list->next, token, value);
2171 #if ENABLE_UNUSED_CODE
2173 static void printSetupFileList(SetupFileList *list)
2178 Debug("setup:printSetupFileList", "token: '%s'", list->token);
2179 Debug("setup:printSetupFileList", "value: '%s'", list->value);
2181 printSetupFileList(list->next);
2187 DEFINE_HASHTABLE_INSERT(insert_hash_entry, char, char);
2188 DEFINE_HASHTABLE_SEARCH(search_hash_entry, char, char);
2189 DEFINE_HASHTABLE_CHANGE(change_hash_entry, char, char);
2190 DEFINE_HASHTABLE_REMOVE(remove_hash_entry, char, char);
2192 #define insert_hash_entry hashtable_insert
2193 #define search_hash_entry hashtable_search
2194 #define change_hash_entry hashtable_change
2195 #define remove_hash_entry hashtable_remove
2198 unsigned int get_hash_from_key(void *key)
2203 This algorithm (k=33) was first reported by Dan Bernstein many years ago in
2204 'comp.lang.c'. Another version of this algorithm (now favored by Bernstein)
2205 uses XOR: hash(i) = hash(i - 1) * 33 ^ str[i]; the magic of number 33 (why
2206 it works better than many other constants, prime or not) has never been
2207 adequately explained.
2209 If you just want to have a good hash function, and cannot wait, djb2
2210 is one of the best string hash functions i know. It has excellent
2211 distribution and speed on many different sets of keys and table sizes.
2212 You are not likely to do better with one of the "well known" functions
2213 such as PJW, K&R, etc.
2215 Ozan (oz) Yigit [http://www.cs.yorku.ca/~oz/hash.html]
2218 char *str = (char *)key;
2219 unsigned int hash = 5381;
2222 while ((c = *str++))
2223 hash = ((hash << 5) + hash) + c; // hash * 33 + c
2228 int hash_keys_are_equal(void *key1, void *key2)
2230 return (strEqual((char *)key1, (char *)key2));
2233 SetupFileHash *newSetupFileHash(void)
2235 SetupFileHash *new_hash =
2236 create_hashtable(16, 0.75, get_hash_from_key, hash_keys_are_equal);
2238 if (new_hash == NULL)
2239 Fail("create_hashtable() failed -- out of memory");
2244 void freeSetupFileHash(SetupFileHash *hash)
2249 hashtable_destroy(hash, 1); // 1 == also free values stored in hash
2252 char *getHashEntry(SetupFileHash *hash, char *token)
2257 return search_hash_entry(hash, token);
2260 void setHashEntry(SetupFileHash *hash, char *token, char *value)
2267 value_copy = getStringCopy(value);
2269 // change value; if it does not exist, insert it as new
2270 if (!change_hash_entry(hash, token, value_copy))
2271 if (!insert_hash_entry(hash, getStringCopy(token), value_copy))
2272 Fail("cannot insert into hash -- aborting");
2275 char *removeHashEntry(SetupFileHash *hash, char *token)
2280 return remove_hash_entry(hash, token);
2283 #if ENABLE_UNUSED_CODE
2285 static void printSetupFileHash(SetupFileHash *hash)
2287 BEGIN_HASH_ITERATION(hash, itr)
2289 Debug("setup:printSetupFileHash", "token: '%s'", HASH_ITERATION_TOKEN(itr));
2290 Debug("setup:printSetupFileHash", "value: '%s'", HASH_ITERATION_VALUE(itr));
2292 END_HASH_ITERATION(hash, itr)
2297 #define ALLOW_TOKEN_VALUE_SEPARATOR_BEING_WHITESPACE 1
2298 #define CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING 0
2299 #define CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH 0
2301 static boolean token_value_separator_found = FALSE;
2302 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2303 static boolean token_value_separator_warning = FALSE;
2305 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2306 static boolean token_already_exists_warning = FALSE;
2309 static boolean getTokenValueFromSetupLineExt(char *line,
2310 char **token_ptr, char **value_ptr,
2311 char *filename, char *line_raw,
2313 boolean separator_required)
2315 static char line_copy[MAX_LINE_LEN + 1], line_raw_copy[MAX_LINE_LEN + 1];
2316 char *token, *value, *line_ptr;
2318 // when externally invoked via ReadTokenValueFromLine(), copy line buffers
2319 if (line_raw == NULL)
2321 strncpy(line_copy, line, MAX_LINE_LEN);
2322 line_copy[MAX_LINE_LEN] = '\0';
2325 strcpy(line_raw_copy, line_copy);
2326 line_raw = line_raw_copy;
2329 // cut trailing comment from input line
2330 for (line_ptr = line; *line_ptr; line_ptr++)
2332 if (*line_ptr == '#')
2339 // cut trailing whitespaces from input line
2340 for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
2341 if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
2344 // ignore empty lines
2348 // cut leading whitespaces from token
2349 for (token = line; *token; token++)
2350 if (*token != ' ' && *token != '\t')
2353 // start with empty value as reliable default
2356 token_value_separator_found = FALSE;
2358 // find end of token to determine start of value
2359 for (line_ptr = token; *line_ptr; line_ptr++)
2361 // first look for an explicit token/value separator, like ':' or '='
2362 if (*line_ptr == ':' || *line_ptr == '=')
2364 *line_ptr = '\0'; // terminate token string
2365 value = line_ptr + 1; // set beginning of value
2367 token_value_separator_found = TRUE;
2373 #if ALLOW_TOKEN_VALUE_SEPARATOR_BEING_WHITESPACE
2374 // fallback: if no token/value separator found, also allow whitespaces
2375 if (!token_value_separator_found && !separator_required)
2377 for (line_ptr = token; *line_ptr; line_ptr++)
2379 if (*line_ptr == ' ' || *line_ptr == '\t')
2381 *line_ptr = '\0'; // terminate token string
2382 value = line_ptr + 1; // set beginning of value
2384 token_value_separator_found = TRUE;
2390 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2391 if (token_value_separator_found)
2393 if (!token_value_separator_warning)
2395 Debug("setup", "---");
2397 if (filename != NULL)
2399 Debug("setup", "missing token/value separator(s) in config file:");
2400 Debug("setup", "- config file: '%s'", filename);
2404 Debug("setup", "missing token/value separator(s):");
2407 token_value_separator_warning = TRUE;
2410 if (filename != NULL)
2411 Debug("setup", "- line %d: '%s'", line_nr, line_raw);
2413 Debug("setup", "- line: '%s'", line_raw);
2419 // cut trailing whitespaces from token
2420 for (line_ptr = &token[strlen(token)]; line_ptr >= token; line_ptr--)
2421 if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
2424 // cut leading whitespaces from value
2425 for (; *value; value++)
2426 if (*value != ' ' && *value != '\t')
2435 boolean getTokenValueFromSetupLine(char *line, char **token, char **value)
2437 // while the internal (old) interface does not require a token/value
2438 // separator (for downwards compatibility with existing files which
2439 // don't use them), it is mandatory for the external (new) interface
2441 return getTokenValueFromSetupLineExt(line, token, value, NULL, NULL, 0, TRUE);
2444 static boolean loadSetupFileData(void *setup_file_data, char *filename,
2445 boolean top_recursion_level, boolean is_hash)
2447 static SetupFileHash *include_filename_hash = NULL;
2448 char line[MAX_LINE_LEN], line_raw[MAX_LINE_LEN], previous_line[MAX_LINE_LEN];
2449 char *token, *value, *line_ptr;
2450 void *insert_ptr = NULL;
2451 boolean read_continued_line = FALSE;
2453 int line_nr = 0, token_count = 0, include_count = 0;
2455 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2456 token_value_separator_warning = FALSE;
2459 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2460 token_already_exists_warning = FALSE;
2463 if (!(file = openFile(filename, MODE_READ)))
2465 #if DEBUG_NO_CONFIG_FILE
2466 Debug("setup", "cannot open configuration file '%s'", filename);
2472 // use "insert pointer" to store list end for constant insertion complexity
2474 insert_ptr = setup_file_data;
2476 // on top invocation, create hash to mark included files (to prevent loops)
2477 if (top_recursion_level)
2478 include_filename_hash = newSetupFileHash();
2480 // mark this file as already included (to prevent including it again)
2481 setHashEntry(include_filename_hash, getBaseNamePtr(filename), "true");
2483 while (!checkEndOfFile(file))
2485 // read next line of input file
2486 if (!getStringFromFile(file, line, MAX_LINE_LEN))
2489 // check if line was completely read and is terminated by line break
2490 if (strlen(line) > 0 && line[strlen(line) - 1] == '\n')
2493 // cut trailing line break (this can be newline and/or carriage return)
2494 for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
2495 if ((*line_ptr == '\n' || *line_ptr == '\r') && *(line_ptr + 1) == '\0')
2498 // copy raw input line for later use (mainly debugging output)
2499 strcpy(line_raw, line);
2501 if (read_continued_line)
2503 // append new line to existing line, if there is enough space
2504 if (strlen(previous_line) + strlen(line_ptr) < MAX_LINE_LEN)
2505 strcat(previous_line, line_ptr);
2507 strcpy(line, previous_line); // copy storage buffer to line
2509 read_continued_line = FALSE;
2512 // if the last character is '\', continue at next line
2513 if (strlen(line) > 0 && line[strlen(line) - 1] == '\\')
2515 line[strlen(line) - 1] = '\0'; // cut off trailing backslash
2516 strcpy(previous_line, line); // copy line to storage buffer
2518 read_continued_line = TRUE;
2523 if (!getTokenValueFromSetupLineExt(line, &token, &value, filename,
2524 line_raw, line_nr, FALSE))
2529 if (strEqual(token, "include"))
2531 if (getHashEntry(include_filename_hash, value) == NULL)
2533 char *basepath = getBasePath(filename);
2534 char *basename = getBaseName(value);
2535 char *filename_include = getPath2(basepath, basename);
2537 loadSetupFileData(setup_file_data, filename_include, FALSE, is_hash);
2541 free(filename_include);
2547 Warn("ignoring already processed file '%s'", value);
2554 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2556 getHashEntry((SetupFileHash *)setup_file_data, token);
2558 if (old_value != NULL)
2560 if (!token_already_exists_warning)
2562 Debug("setup", "---");
2563 Debug("setup", "duplicate token(s) found in config file:");
2564 Debug("setup", "- config file: '%s'", filename);
2566 token_already_exists_warning = TRUE;
2569 Debug("setup", "- token: '%s' (in line %d)", token, line_nr);
2570 Debug("setup", " old value: '%s'", old_value);
2571 Debug("setup", " new value: '%s'", value);
2575 setHashEntry((SetupFileHash *)setup_file_data, token, value);
2579 insert_ptr = addListEntry((SetupFileList *)insert_ptr, token, value);
2589 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2590 if (token_value_separator_warning)
2591 Debug("setup", "---");
2594 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2595 if (token_already_exists_warning)
2596 Debug("setup", "---");
2599 if (token_count == 0 && include_count == 0)
2600 Warn("configuration file '%s' is empty", filename);
2602 if (top_recursion_level)
2603 freeSetupFileHash(include_filename_hash);
2608 static int compareSetupFileData(const void *object1, const void *object2)
2610 const struct ConfigInfo *entry1 = (struct ConfigInfo *)object1;
2611 const struct ConfigInfo *entry2 = (struct ConfigInfo *)object2;
2613 return strcmp(entry1->token, entry2->token);
2616 static void saveSetupFileHash(SetupFileHash *hash, char *filename)
2618 int item_count = hashtable_count(hash);
2619 int item_size = sizeof(struct ConfigInfo);
2620 struct ConfigInfo *sort_array = checked_malloc(item_count * item_size);
2624 // copy string pointers from hash to array
2625 BEGIN_HASH_ITERATION(hash, itr)
2627 sort_array[i].token = HASH_ITERATION_TOKEN(itr);
2628 sort_array[i].value = HASH_ITERATION_VALUE(itr);
2632 if (i > item_count) // should never happen
2635 END_HASH_ITERATION(hash, itr)
2637 // sort string pointers from hash in array
2638 qsort(sort_array, item_count, item_size, compareSetupFileData);
2640 if (!(file = fopen(filename, MODE_WRITE)))
2642 Warn("cannot write configuration file '%s'", filename);
2647 fprintf(file, "%s\n\n", getFormattedSetupEntry("program.version",
2648 program.version_string));
2649 for (i = 0; i < item_count; i++)
2650 fprintf(file, "%s\n", getFormattedSetupEntry(sort_array[i].token,
2651 sort_array[i].value));
2654 checked_free(sort_array);
2657 SetupFileList *loadSetupFileList(char *filename)
2659 SetupFileList *setup_file_list = newSetupFileList("", "");
2660 SetupFileList *first_valid_list_entry;
2662 if (!loadSetupFileData(setup_file_list, filename, TRUE, FALSE))
2664 freeSetupFileList(setup_file_list);
2669 first_valid_list_entry = setup_file_list->next;
2671 // free empty list header
2672 setup_file_list->next = NULL;
2673 freeSetupFileList(setup_file_list);
2675 return first_valid_list_entry;
2678 SetupFileHash *loadSetupFileHash(char *filename)
2680 SetupFileHash *setup_file_hash = newSetupFileHash();
2682 if (!loadSetupFileData(setup_file_hash, filename, TRUE, TRUE))
2684 freeSetupFileHash(setup_file_hash);
2689 return setup_file_hash;
2693 // ============================================================================
2695 // ============================================================================
2697 #define TOKEN_STR_LAST_LEVEL_SERIES "last_level_series"
2698 #define TOKEN_STR_LAST_PLAYED_LEVEL "last_played_level"
2699 #define TOKEN_STR_HANDICAP_LEVEL "handicap_level"
2700 #define TOKEN_STR_LAST_USER "last_user"
2702 // level directory info
2703 #define LEVELINFO_TOKEN_IDENTIFIER 0
2704 #define LEVELINFO_TOKEN_NAME 1
2705 #define LEVELINFO_TOKEN_NAME_SORTING 2
2706 #define LEVELINFO_TOKEN_AUTHOR 3
2707 #define LEVELINFO_TOKEN_YEAR 4
2708 #define LEVELINFO_TOKEN_PROGRAM_TITLE 5
2709 #define LEVELINFO_TOKEN_PROGRAM_COPYRIGHT 6
2710 #define LEVELINFO_TOKEN_PROGRAM_COMPANY 7
2711 #define LEVELINFO_TOKEN_IMPORTED_FROM 8
2712 #define LEVELINFO_TOKEN_IMPORTED_BY 9
2713 #define LEVELINFO_TOKEN_TESTED_BY 10
2714 #define LEVELINFO_TOKEN_LEVELS 11
2715 #define LEVELINFO_TOKEN_FIRST_LEVEL 12
2716 #define LEVELINFO_TOKEN_SORT_PRIORITY 13
2717 #define LEVELINFO_TOKEN_LATEST_ENGINE 14
2718 #define LEVELINFO_TOKEN_LEVEL_GROUP 15
2719 #define LEVELINFO_TOKEN_READONLY 16
2720 #define LEVELINFO_TOKEN_GRAPHICS_SET_ECS 17
2721 #define LEVELINFO_TOKEN_GRAPHICS_SET_AGA 18
2722 #define LEVELINFO_TOKEN_GRAPHICS_SET 19
2723 #define LEVELINFO_TOKEN_SOUNDS_SET_DEFAULT 20
2724 #define LEVELINFO_TOKEN_SOUNDS_SET_LOWPASS 21
2725 #define LEVELINFO_TOKEN_SOUNDS_SET 22
2726 #define LEVELINFO_TOKEN_MUSIC_SET 23
2727 #define LEVELINFO_TOKEN_FILENAME 24
2728 #define LEVELINFO_TOKEN_FILETYPE 25
2729 #define LEVELINFO_TOKEN_SPECIAL_FLAGS 26
2730 #define LEVELINFO_TOKEN_EMPTY_LEVEL_NAME 27
2731 #define LEVELINFO_TOKEN_FORCE_LEVEL_NAME 28
2732 #define LEVELINFO_TOKEN_HANDICAP 29
2733 #define LEVELINFO_TOKEN_SKIP_LEVELS 30
2734 #define LEVELINFO_TOKEN_USE_EMC_TILES 31
2736 #define NUM_LEVELINFO_TOKENS 32
2738 static LevelDirTree ldi;
2740 static struct TokenInfo levelinfo_tokens[] =
2742 // level directory info
2743 { TYPE_STRING, &ldi.identifier, "identifier" },
2744 { TYPE_STRING, &ldi.name, "name" },
2745 { TYPE_STRING, &ldi.name_sorting, "name_sorting" },
2746 { TYPE_STRING, &ldi.author, "author" },
2747 { TYPE_STRING, &ldi.year, "year" },
2748 { TYPE_STRING, &ldi.program_title, "program_title" },
2749 { TYPE_STRING, &ldi.program_copyright, "program_copyright" },
2750 { TYPE_STRING, &ldi.program_company, "program_company" },
2751 { TYPE_STRING, &ldi.imported_from, "imported_from" },
2752 { TYPE_STRING, &ldi.imported_by, "imported_by" },
2753 { TYPE_STRING, &ldi.tested_by, "tested_by" },
2754 { TYPE_INTEGER, &ldi.levels, "levels" },
2755 { TYPE_INTEGER, &ldi.first_level, "first_level" },
2756 { TYPE_INTEGER, &ldi.sort_priority, "sort_priority" },
2757 { TYPE_BOOLEAN, &ldi.latest_engine, "latest_engine" },
2758 { TYPE_BOOLEAN, &ldi.level_group, "level_group" },
2759 { TYPE_BOOLEAN, &ldi.readonly, "readonly" },
2760 { TYPE_STRING, &ldi.graphics_set_ecs, "graphics_set.ecs" },
2761 { TYPE_STRING, &ldi.graphics_set_aga, "graphics_set.aga" },
2762 { TYPE_STRING, &ldi.graphics_set, "graphics_set" },
2763 { TYPE_STRING, &ldi.sounds_set_default,"sounds_set.default" },
2764 { TYPE_STRING, &ldi.sounds_set_lowpass,"sounds_set.lowpass" },
2765 { TYPE_STRING, &ldi.sounds_set, "sounds_set" },
2766 { TYPE_STRING, &ldi.music_set, "music_set" },
2767 { TYPE_STRING, &ldi.level_filename, "filename" },
2768 { TYPE_STRING, &ldi.level_filetype, "filetype" },
2769 { TYPE_STRING, &ldi.special_flags, "special_flags" },
2770 { TYPE_STRING, &ldi.empty_level_name, "empty_level_name" },
2771 { TYPE_BOOLEAN, &ldi.force_level_name, "force_level_name" },
2772 { TYPE_BOOLEAN, &ldi.handicap, "handicap" },
2773 { TYPE_BOOLEAN, &ldi.skip_levels, "skip_levels" },
2774 { TYPE_BOOLEAN, &ldi.use_emc_tiles, "use_emc_tiles" }
2777 static struct TokenInfo artworkinfo_tokens[] =
2779 // artwork directory info
2780 { TYPE_STRING, &ldi.identifier, "identifier" },
2781 { TYPE_STRING, &ldi.subdir, "subdir" },
2782 { TYPE_STRING, &ldi.name, "name" },
2783 { TYPE_STRING, &ldi.name_sorting, "name_sorting" },
2784 { TYPE_STRING, &ldi.author, "author" },
2785 { TYPE_STRING, &ldi.program_title, "program_title" },
2786 { TYPE_STRING, &ldi.program_copyright, "program_copyright" },
2787 { TYPE_STRING, &ldi.program_company, "program_company" },
2788 { TYPE_INTEGER, &ldi.sort_priority, "sort_priority" },
2789 { TYPE_STRING, &ldi.basepath, "basepath" },
2790 { TYPE_STRING, &ldi.fullpath, "fullpath" },
2791 { TYPE_BOOLEAN, &ldi.in_user_dir, "in_user_dir" },
2792 { TYPE_STRING, &ldi.class_desc, "class_desc" },
2797 static char *optional_tokens[] =
2800 "program_copyright",
2806 static void setTreeInfoToDefaults(TreeInfo *ti, int type)
2810 ti->node_top = (ti->type == TREE_TYPE_LEVEL_DIR ? &leveldir_first :
2811 ti->type == TREE_TYPE_GRAPHICS_DIR ? &artwork.gfx_first :
2812 ti->type == TREE_TYPE_SOUNDS_DIR ? &artwork.snd_first :
2813 ti->type == TREE_TYPE_MUSIC_DIR ? &artwork.mus_first :
2816 ti->node_parent = NULL;
2817 ti->node_group = NULL;
2824 ti->fullpath = NULL;
2825 ti->basepath = NULL;
2826 ti->identifier = NULL;
2827 ti->name = getStringCopy(ANONYMOUS_NAME);
2828 ti->name_sorting = NULL;
2829 ti->author = getStringCopy(ANONYMOUS_NAME);
2832 ti->program_title = NULL;
2833 ti->program_copyright = NULL;
2834 ti->program_company = NULL;
2836 ti->sort_priority = LEVELCLASS_UNDEFINED; // default: least priority
2837 ti->latest_engine = FALSE; // default: get from level
2838 ti->parent_link = FALSE;
2839 ti->is_copy = FALSE;
2840 ti->in_user_dir = FALSE;
2841 ti->user_defined = FALSE;
2843 ti->class_desc = NULL;
2845 ti->infotext = getStringCopy(TREE_INFOTEXT(ti->type));
2847 if (ti->type == TREE_TYPE_LEVEL_DIR)
2849 ti->imported_from = NULL;
2850 ti->imported_by = NULL;
2851 ti->tested_by = NULL;
2853 ti->graphics_set_ecs = NULL;
2854 ti->graphics_set_aga = NULL;
2855 ti->graphics_set = NULL;
2856 ti->sounds_set_default = NULL;
2857 ti->sounds_set_lowpass = NULL;
2858 ti->sounds_set = NULL;
2859 ti->music_set = NULL;
2860 ti->graphics_path = getStringCopy(UNDEFINED_FILENAME);
2861 ti->sounds_path = getStringCopy(UNDEFINED_FILENAME);
2862 ti->music_path = getStringCopy(UNDEFINED_FILENAME);
2864 ti->level_filename = NULL;
2865 ti->level_filetype = NULL;
2867 ti->special_flags = NULL;
2869 ti->empty_level_name = NULL;
2870 ti->force_level_name = FALSE;
2873 ti->first_level = 0;
2875 ti->level_group = FALSE;
2876 ti->handicap_level = 0;
2877 ti->readonly = TRUE;
2878 ti->handicap = TRUE;
2879 ti->skip_levels = FALSE;
2881 ti->use_emc_tiles = FALSE;
2885 static void setTreeInfoToDefaultsFromParent(TreeInfo *ti, TreeInfo *parent)
2889 Warn("setTreeInfoToDefaultsFromParent(): parent == NULL");
2891 setTreeInfoToDefaults(ti, TREE_TYPE_UNDEFINED);
2896 // copy all values from the parent structure
2898 ti->type = parent->type;
2900 ti->node_top = parent->node_top;
2901 ti->node_parent = parent;
2902 ti->node_group = NULL;
2909 ti->fullpath = NULL;
2910 ti->basepath = NULL;
2911 ti->identifier = NULL;
2912 ti->name = getStringCopy(ANONYMOUS_NAME);
2913 ti->name_sorting = NULL;
2914 ti->author = getStringCopy(parent->author);
2915 ti->year = getStringCopy(parent->year);
2917 ti->program_title = getStringCopy(parent->program_title);
2918 ti->program_copyright = getStringCopy(parent->program_copyright);
2919 ti->program_company = getStringCopy(parent->program_company);
2921 ti->sort_priority = parent->sort_priority;
2922 ti->latest_engine = parent->latest_engine;
2923 ti->parent_link = FALSE;
2924 ti->is_copy = FALSE;
2925 ti->in_user_dir = parent->in_user_dir;
2926 ti->user_defined = parent->user_defined;
2927 ti->color = parent->color;
2928 ti->class_desc = getStringCopy(parent->class_desc);
2930 ti->infotext = getStringCopy(parent->infotext);
2932 if (ti->type == TREE_TYPE_LEVEL_DIR)
2934 ti->imported_from = getStringCopy(parent->imported_from);
2935 ti->imported_by = getStringCopy(parent->imported_by);
2936 ti->tested_by = getStringCopy(parent->tested_by);
2938 ti->graphics_set_ecs = getStringCopy(parent->graphics_set_ecs);
2939 ti->graphics_set_aga = getStringCopy(parent->graphics_set_aga);
2940 ti->graphics_set = getStringCopy(parent->graphics_set);
2941 ti->sounds_set_default = getStringCopy(parent->sounds_set_default);
2942 ti->sounds_set_lowpass = getStringCopy(parent->sounds_set_lowpass);
2943 ti->sounds_set = getStringCopy(parent->sounds_set);
2944 ti->music_set = getStringCopy(parent->music_set);
2945 ti->graphics_path = getStringCopy(UNDEFINED_FILENAME);
2946 ti->sounds_path = getStringCopy(UNDEFINED_FILENAME);
2947 ti->music_path = getStringCopy(UNDEFINED_FILENAME);
2949 ti->level_filename = getStringCopy(parent->level_filename);
2950 ti->level_filetype = getStringCopy(parent->level_filetype);
2952 ti->special_flags = getStringCopy(parent->special_flags);
2954 ti->empty_level_name = getStringCopy(parent->empty_level_name);
2955 ti->force_level_name = parent->force_level_name;
2957 ti->levels = parent->levels;
2958 ti->first_level = parent->first_level;
2959 ti->last_level = parent->last_level;
2960 ti->level_group = FALSE;
2961 ti->handicap_level = parent->handicap_level;
2962 ti->readonly = parent->readonly;
2963 ti->handicap = parent->handicap;
2964 ti->skip_levels = parent->skip_levels;
2966 ti->use_emc_tiles = parent->use_emc_tiles;
2970 static TreeInfo *getTreeInfoCopy(TreeInfo *ti)
2972 TreeInfo *ti_copy = newTreeInfo();
2974 // copy all values from the original structure
2976 ti_copy->type = ti->type;
2978 ti_copy->node_top = ti->node_top;
2979 ti_copy->node_parent = ti->node_parent;
2980 ti_copy->node_group = ti->node_group;
2981 ti_copy->next = ti->next;
2983 ti_copy->cl_first = ti->cl_first;
2984 ti_copy->cl_cursor = ti->cl_cursor;
2986 ti_copy->subdir = getStringCopy(ti->subdir);
2987 ti_copy->fullpath = getStringCopy(ti->fullpath);
2988 ti_copy->basepath = getStringCopy(ti->basepath);
2989 ti_copy->identifier = getStringCopy(ti->identifier);
2990 ti_copy->name = getStringCopy(ti->name);
2991 ti_copy->name_sorting = getStringCopy(ti->name_sorting);
2992 ti_copy->author = getStringCopy(ti->author);
2993 ti_copy->year = getStringCopy(ti->year);
2995 ti_copy->program_title = getStringCopy(ti->program_title);
2996 ti_copy->program_copyright = getStringCopy(ti->program_copyright);
2997 ti_copy->program_company = getStringCopy(ti->program_company);
2999 ti_copy->imported_from = getStringCopy(ti->imported_from);
3000 ti_copy->imported_by = getStringCopy(ti->imported_by);
3001 ti_copy->tested_by = getStringCopy(ti->tested_by);
3003 ti_copy->graphics_set_ecs = getStringCopy(ti->graphics_set_ecs);
3004 ti_copy->graphics_set_aga = getStringCopy(ti->graphics_set_aga);
3005 ti_copy->graphics_set = getStringCopy(ti->graphics_set);
3006 ti_copy->sounds_set_default = getStringCopy(ti->sounds_set_default);
3007 ti_copy->sounds_set_lowpass = getStringCopy(ti->sounds_set_lowpass);
3008 ti_copy->sounds_set = getStringCopy(ti->sounds_set);
3009 ti_copy->music_set = getStringCopy(ti->music_set);
3010 ti_copy->graphics_path = getStringCopy(ti->graphics_path);
3011 ti_copy->sounds_path = getStringCopy(ti->sounds_path);
3012 ti_copy->music_path = getStringCopy(ti->music_path);
3014 ti_copy->level_filename = getStringCopy(ti->level_filename);
3015 ti_copy->level_filetype = getStringCopy(ti->level_filetype);
3017 ti_copy->special_flags = getStringCopy(ti->special_flags);
3019 ti_copy->empty_level_name = getStringCopy(ti->empty_level_name);
3020 ti_copy->force_level_name = ti->force_level_name;
3022 ti_copy->levels = ti->levels;
3023 ti_copy->first_level = ti->first_level;
3024 ti_copy->last_level = ti->last_level;
3025 ti_copy->sort_priority = ti->sort_priority;
3027 ti_copy->latest_engine = ti->latest_engine;
3029 ti_copy->level_group = ti->level_group;
3030 ti_copy->parent_link = ti->parent_link;
3031 ti_copy->is_copy = ti->is_copy;
3032 ti_copy->in_user_dir = ti->in_user_dir;
3033 ti_copy->user_defined = ti->user_defined;
3034 ti_copy->readonly = ti->readonly;
3035 ti_copy->handicap = ti->handicap;
3036 ti_copy->skip_levels = ti->skip_levels;
3038 ti_copy->use_emc_tiles = ti->use_emc_tiles;
3040 ti_copy->color = ti->color;
3041 ti_copy->class_desc = getStringCopy(ti->class_desc);
3042 ti_copy->handicap_level = ti->handicap_level;
3044 ti_copy->infotext = getStringCopy(ti->infotext);
3049 void freeTreeInfo(TreeInfo *ti)
3054 checked_free(ti->subdir);
3055 checked_free(ti->fullpath);
3056 checked_free(ti->basepath);
3057 checked_free(ti->identifier);
3059 checked_free(ti->name);
3060 checked_free(ti->name_sorting);
3061 checked_free(ti->author);
3062 checked_free(ti->year);
3064 checked_free(ti->program_title);
3065 checked_free(ti->program_copyright);
3066 checked_free(ti->program_company);
3068 checked_free(ti->class_desc);
3070 checked_free(ti->infotext);
3072 if (ti->type == TREE_TYPE_LEVEL_DIR)
3074 checked_free(ti->imported_from);
3075 checked_free(ti->imported_by);
3076 checked_free(ti->tested_by);
3078 checked_free(ti->graphics_set_ecs);
3079 checked_free(ti->graphics_set_aga);
3080 checked_free(ti->graphics_set);
3081 checked_free(ti->sounds_set_default);
3082 checked_free(ti->sounds_set_lowpass);
3083 checked_free(ti->sounds_set);
3084 checked_free(ti->music_set);
3086 checked_free(ti->graphics_path);
3087 checked_free(ti->sounds_path);
3088 checked_free(ti->music_path);
3090 checked_free(ti->level_filename);
3091 checked_free(ti->level_filetype);
3093 checked_free(ti->special_flags);
3096 // recursively free child node
3098 freeTreeInfo(ti->node_group);
3100 // recursively free next node
3102 freeTreeInfo(ti->next);
3107 void setSetupInfo(struct TokenInfo *token_info,
3108 int token_nr, char *token_value)
3110 int token_type = token_info[token_nr].type;
3111 void *setup_value = token_info[token_nr].value;
3113 if (token_value == NULL)
3116 // set setup field to corresponding token value
3121 *(boolean *)setup_value = get_boolean_from_string(token_value);
3125 *(int *)setup_value = get_switch3_from_string(token_value);
3129 *(Key *)setup_value = getKeyFromKeyName(token_value);
3133 *(Key *)setup_value = getKeyFromX11KeyName(token_value);
3137 *(int *)setup_value = get_integer_from_string(token_value);
3141 checked_free(*(char **)setup_value);
3142 *(char **)setup_value = getStringCopy(token_value);
3146 *(int *)setup_value = get_player_nr_from_string(token_value);
3154 static int compareTreeInfoEntries(const void *object1, const void *object2)
3156 const TreeInfo *entry1 = *((TreeInfo **)object1);
3157 const TreeInfo *entry2 = *((TreeInfo **)object2);
3158 int tree_sorting1 = TREE_SORTING(entry1);
3159 int tree_sorting2 = TREE_SORTING(entry2);
3161 if (tree_sorting1 != tree_sorting2)
3162 return (tree_sorting1 - tree_sorting2);
3164 return strcasecmp(entry1->name_sorting, entry2->name_sorting);
3167 static TreeInfo *createParentTreeInfoNode(TreeInfo *node_parent)
3171 if (node_parent == NULL)
3174 ti_new = newTreeInfo();
3175 setTreeInfoToDefaults(ti_new, node_parent->type);
3177 ti_new->node_parent = node_parent;
3178 ti_new->parent_link = TRUE;
3180 setString(&ti_new->identifier, node_parent->identifier);
3181 setString(&ti_new->name, BACKLINK_TEXT_PARENT);
3182 setString(&ti_new->name_sorting, ti_new->name);
3184 setString(&ti_new->subdir, STRING_PARENT_DIRECTORY);
3185 setString(&ti_new->fullpath, node_parent->fullpath);
3187 ti_new->sort_priority = LEVELCLASS_PARENT;
3188 ti_new->latest_engine = node_parent->latest_engine;
3190 setString(&ti_new->class_desc, getLevelClassDescription(ti_new));
3192 pushTreeInfo(&node_parent->node_group, ti_new);
3197 static TreeInfo *createTopTreeInfoNode(TreeInfo *node_first)
3199 if (node_first == NULL)
3202 TreeInfo *ti_new = newTreeInfo();
3203 int type = node_first->type;
3205 setTreeInfoToDefaults(ti_new, type);
3207 ti_new->node_parent = NULL;
3208 ti_new->parent_link = FALSE;
3210 setString(&ti_new->identifier, "top_tree_node");
3211 setString(&ti_new->name, TREE_INFOTEXT(type));
3212 setString(&ti_new->name_sorting, ti_new->name);
3214 setString(&ti_new->subdir, STRING_TOP_DIRECTORY);
3215 setString(&ti_new->fullpath, ".");
3217 ti_new->sort_priority = LEVELCLASS_TOP;
3218 ti_new->latest_engine = node_first->latest_engine;
3220 setString(&ti_new->class_desc, TREE_INFOTEXT(type));
3222 ti_new->node_group = node_first;
3223 ti_new->level_group = TRUE;
3225 TreeInfo *ti_new2 = createParentTreeInfoNode(ti_new);
3227 setString(&ti_new2->name, TREE_BACKLINK_TEXT(type));
3228 setString(&ti_new2->name_sorting, ti_new2->name);
3233 static void setTreeInfoParentNodes(TreeInfo *node, TreeInfo *node_parent)
3237 if (node->node_group)
3238 setTreeInfoParentNodes(node->node_group, node);
3240 node->node_parent = node_parent;
3246 TreeInfo *addTopTreeInfoNode(TreeInfo *node_first)
3248 // add top tree node with back link node in previous tree
3249 node_first = createTopTreeInfoNode(node_first);
3251 // set all parent links (back links) in complete tree
3252 setTreeInfoParentNodes(node_first, NULL);
3258 // ----------------------------------------------------------------------------
3259 // functions for handling level and custom artwork info cache
3260 // ----------------------------------------------------------------------------
3262 static void LoadArtworkInfoCache(void)
3264 InitCacheDirectory();
3266 if (artworkinfo_cache_old == NULL)
3268 char *filename = getPath2(getCacheDir(), ARTWORKINFO_CACHE_FILE);
3270 // try to load artwork info hash from already existing cache file
3271 artworkinfo_cache_old = loadSetupFileHash(filename);
3273 // try to get program version that artwork info cache was written with
3274 char *version = getHashEntry(artworkinfo_cache_old, "program.version");
3276 // check program version of artwork info cache against current version
3277 if (!strEqual(version, program.version_string))
3279 freeSetupFileHash(artworkinfo_cache_old);
3281 artworkinfo_cache_old = NULL;
3284 // if no artwork info cache file was found, start with empty hash
3285 if (artworkinfo_cache_old == NULL)
3286 artworkinfo_cache_old = newSetupFileHash();
3291 if (artworkinfo_cache_new == NULL)
3292 artworkinfo_cache_new = newSetupFileHash();
3294 update_artworkinfo_cache = FALSE;
3297 static void SaveArtworkInfoCache(void)
3299 if (!update_artworkinfo_cache)
3302 char *filename = getPath2(getCacheDir(), ARTWORKINFO_CACHE_FILE);
3304 InitCacheDirectory();
3306 saveSetupFileHash(artworkinfo_cache_new, filename);
3311 static char *getCacheTokenPrefix(char *prefix1, char *prefix2)
3313 static char *prefix = NULL;
3315 checked_free(prefix);
3317 prefix = getStringCat2WithSeparator(prefix1, prefix2, ".");
3322 // (identical to above function, but separate string buffer needed -- nasty)
3323 static char *getCacheToken(char *prefix, char *suffix)
3325 static char *token = NULL;
3327 checked_free(token);
3329 token = getStringCat2WithSeparator(prefix, suffix, ".");
3334 static char *getFileTimestampString(char *filename)
3336 return getStringCopy(i_to_a(getFileTimestampEpochSeconds(filename)));
3339 static boolean modifiedFileTimestamp(char *filename, char *timestamp_string)
3341 struct stat file_status;
3343 if (timestamp_string == NULL)
3346 if (!fileExists(filename)) // file does not exist
3347 return (atoi(timestamp_string) != 0);
3349 if (stat(filename, &file_status) != 0) // cannot stat file
3352 return (file_status.st_mtime != atoi(timestamp_string));
3355 static TreeInfo *getArtworkInfoCacheEntry(LevelDirTree *level_node, int type)
3357 char *identifier = level_node->subdir;
3358 char *type_string = ARTWORK_DIRECTORY(type);
3359 char *token_prefix = getCacheTokenPrefix(type_string, identifier);
3360 char *token_main = getCacheToken(token_prefix, "CACHED");
3361 char *cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
3362 boolean cached = (cache_entry != NULL && strEqual(cache_entry, "true"));
3363 TreeInfo *artwork_info = NULL;
3365 if (!use_artworkinfo_cache)
3368 if (optional_tokens_hash == NULL)
3372 // create hash from list of optional tokens (for quick access)
3373 optional_tokens_hash = newSetupFileHash();
3374 for (i = 0; optional_tokens[i] != NULL; i++)
3375 setHashEntry(optional_tokens_hash, optional_tokens[i], "");
3382 artwork_info = newTreeInfo();
3383 setTreeInfoToDefaults(artwork_info, type);
3385 // set all structure fields according to the token/value pairs
3386 ldi = *artwork_info;
3387 for (i = 0; artworkinfo_tokens[i].type != -1; i++)
3389 char *token_suffix = artworkinfo_tokens[i].text;
3390 char *token = getCacheToken(token_prefix, token_suffix);
3391 char *value = getHashEntry(artworkinfo_cache_old, token);
3393 (getHashEntry(optional_tokens_hash, token_suffix) != NULL);
3395 setSetupInfo(artworkinfo_tokens, i, value);
3397 // check if cache entry for this item is mandatory, but missing
3398 if (value == NULL && !optional)
3400 Warn("missing cache entry '%s'", token);
3406 *artwork_info = ldi;
3411 char *filename_levelinfo = getPath2(getLevelDirFromTreeInfo(level_node),
3412 LEVELINFO_FILENAME);
3413 char *filename_artworkinfo = getPath2(getSetupArtworkDir(artwork_info),
3414 ARTWORKINFO_FILENAME(type));
3416 // check if corresponding "levelinfo.conf" file has changed
3417 token_main = getCacheToken(token_prefix, "TIMESTAMP_LEVELINFO");
3418 cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
3420 if (modifiedFileTimestamp(filename_levelinfo, cache_entry))
3423 // check if corresponding "<artworkinfo>.conf" file has changed
3424 token_main = getCacheToken(token_prefix, "TIMESTAMP_ARTWORKINFO");
3425 cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
3427 if (modifiedFileTimestamp(filename_artworkinfo, cache_entry))
3430 checked_free(filename_levelinfo);
3431 checked_free(filename_artworkinfo);
3434 if (!cached && artwork_info != NULL)
3436 freeTreeInfo(artwork_info);
3441 return artwork_info;
3444 static void setArtworkInfoCacheEntry(TreeInfo *artwork_info,
3445 LevelDirTree *level_node, int type)
3447 char *identifier = level_node->subdir;
3448 char *type_string = ARTWORK_DIRECTORY(type);
3449 char *token_prefix = getCacheTokenPrefix(type_string, identifier);
3450 char *token_main = getCacheToken(token_prefix, "CACHED");
3451 boolean set_cache_timestamps = TRUE;
3454 setHashEntry(artworkinfo_cache_new, token_main, "true");
3456 if (set_cache_timestamps)
3458 char *filename_levelinfo = getPath2(getLevelDirFromTreeInfo(level_node),
3459 LEVELINFO_FILENAME);
3460 char *filename_artworkinfo = getPath2(getSetupArtworkDir(artwork_info),
3461 ARTWORKINFO_FILENAME(type));
3462 char *timestamp_levelinfo = getFileTimestampString(filename_levelinfo);
3463 char *timestamp_artworkinfo = getFileTimestampString(filename_artworkinfo);
3465 token_main = getCacheToken(token_prefix, "TIMESTAMP_LEVELINFO");
3466 setHashEntry(artworkinfo_cache_new, token_main, timestamp_levelinfo);
3468 token_main = getCacheToken(token_prefix, "TIMESTAMP_ARTWORKINFO");
3469 setHashEntry(artworkinfo_cache_new, token_main, timestamp_artworkinfo);
3471 checked_free(filename_levelinfo);
3472 checked_free(filename_artworkinfo);
3473 checked_free(timestamp_levelinfo);
3474 checked_free(timestamp_artworkinfo);
3477 ldi = *artwork_info;
3478 for (i = 0; artworkinfo_tokens[i].type != -1; i++)
3480 char *token = getCacheToken(token_prefix, artworkinfo_tokens[i].text);
3481 char *value = getSetupValue(artworkinfo_tokens[i].type,
3482 artworkinfo_tokens[i].value);
3484 setHashEntry(artworkinfo_cache_new, token, value);
3489 // ----------------------------------------------------------------------------
3490 // functions for loading level info and custom artwork info
3491 // ----------------------------------------------------------------------------
3493 int GetZipFileTreeType(char *zip_filename)
3495 static char *top_dir_path = NULL;
3496 static char *top_dir_conf_filename[NUM_BASE_TREE_TYPES] = { NULL };
3497 static char *conf_basename[NUM_BASE_TREE_TYPES] =
3499 GRAPHICSINFO_FILENAME,
3500 SOUNDSINFO_FILENAME,
3506 checked_free(top_dir_path);
3507 top_dir_path = NULL;
3509 for (j = 0; j < NUM_BASE_TREE_TYPES; j++)
3511 checked_free(top_dir_conf_filename[j]);
3512 top_dir_conf_filename[j] = NULL;
3515 char **zip_entries = zip_list(zip_filename);
3517 // check if zip file successfully opened
3518 if (zip_entries == NULL || zip_entries[0] == NULL)
3519 return TREE_TYPE_UNDEFINED;
3521 // first zip file entry is expected to be top level directory
3522 char *top_dir = zip_entries[0];
3524 // check if valid top level directory found in zip file
3525 if (!strSuffix(top_dir, "/"))
3526 return TREE_TYPE_UNDEFINED;
3528 // get filenames of valid configuration files in top level directory
3529 for (j = 0; j < NUM_BASE_TREE_TYPES; j++)
3530 top_dir_conf_filename[j] = getStringCat2(top_dir, conf_basename[j]);
3532 int tree_type = TREE_TYPE_UNDEFINED;
3535 while (zip_entries[e] != NULL)
3537 // check if every zip file entry is below top level directory
3538 if (!strPrefix(zip_entries[e], top_dir))
3539 return TREE_TYPE_UNDEFINED;
3541 // check if this zip file entry is a valid configuration filename
3542 for (j = 0; j < NUM_BASE_TREE_TYPES; j++)
3544 if (strEqual(zip_entries[e], top_dir_conf_filename[j]))
3546 // only exactly one valid configuration file allowed
3547 if (tree_type != TREE_TYPE_UNDEFINED)
3548 return TREE_TYPE_UNDEFINED;
3560 static boolean CheckZipFileForDirectory(char *zip_filename, char *directory,
3563 static char *top_dir_path = NULL;
3564 static char *top_dir_conf_filename = NULL;
3566 checked_free(top_dir_path);
3567 checked_free(top_dir_conf_filename);
3569 top_dir_path = NULL;
3570 top_dir_conf_filename = NULL;
3572 char *conf_basename = (tree_type == TREE_TYPE_LEVEL_DIR ? LEVELINFO_FILENAME :
3573 ARTWORKINFO_FILENAME(tree_type));
3575 // check if valid configuration filename determined
3576 if (conf_basename == NULL || strEqual(conf_basename, ""))
3579 char **zip_entries = zip_list(zip_filename);
3581 // check if zip file successfully opened
3582 if (zip_entries == NULL || zip_entries[0] == NULL)
3585 // first zip file entry is expected to be top level directory
3586 char *top_dir = zip_entries[0];
3588 // check if valid top level directory found in zip file
3589 if (!strSuffix(top_dir, "/"))
3592 // get path of extracted top level directory
3593 top_dir_path = getPath2(directory, top_dir);
3595 // remove trailing directory separator from top level directory path
3596 // (required to be able to check for file and directory in next step)
3597 top_dir_path[strlen(top_dir_path) - 1] = '\0';
3599 // check if zip file's top level directory already exists in target directory
3600 if (fileExists(top_dir_path)) // (checks for file and directory)
3603 // get filename of configuration file in top level directory
3604 top_dir_conf_filename = getStringCat2(top_dir, conf_basename);
3606 boolean found_top_dir_conf_filename = FALSE;
3609 while (zip_entries[i] != NULL)
3611 // check if every zip file entry is below top level directory
3612 if (!strPrefix(zip_entries[i], top_dir))
3615 // check if this zip file entry is the configuration filename
3616 if (strEqual(zip_entries[i], top_dir_conf_filename))
3617 found_top_dir_conf_filename = TRUE;
3622 // check if valid configuration filename was found in zip file
3623 if (!found_top_dir_conf_filename)
3629 char *ExtractZipFileIntoDirectory(char *zip_filename, char *directory,
3632 boolean zip_file_valid = CheckZipFileForDirectory(zip_filename, directory,
3635 if (!zip_file_valid)
3637 Warn("zip file '%s' rejected!", zip_filename);
3642 char **zip_entries = zip_extract(zip_filename, directory);
3644 if (zip_entries == NULL)
3646 Warn("zip file '%s' could not be extracted!", zip_filename);
3651 Info("zip file '%s' successfully extracted!", zip_filename);
3653 // first zip file entry contains top level directory
3654 char *top_dir = zip_entries[0];
3656 // remove trailing directory separator from top level directory
3657 top_dir[strlen(top_dir) - 1] = '\0';
3662 static void ProcessZipFilesInDirectory(char *directory, int tree_type)
3665 DirectoryEntry *dir_entry;
3667 if ((dir = openDirectory(directory)) == NULL)
3669 // display error if directory is main "options.graphics_directory" etc.
3670 if (tree_type == TREE_TYPE_LEVEL_DIR ||
3671 directory == OPTIONS_ARTWORK_DIRECTORY(tree_type))
3672 Warn("cannot read directory '%s'", directory);
3677 while ((dir_entry = readDirectory(dir)) != NULL) // loop all entries
3679 // skip non-zip files (and also directories with zip extension)
3680 if (!strSuffixLower(dir_entry->basename, ".zip") || dir_entry->is_directory)
3683 char *zip_filename = getPath2(directory, dir_entry->basename);
3684 char *zip_filename_extracted = getStringCat2(zip_filename, ".extracted");
3685 char *zip_filename_rejected = getStringCat2(zip_filename, ".rejected");
3687 // check if zip file hasn't already been extracted or rejected
3688 if (!fileExists(zip_filename_extracted) &&
3689 !fileExists(zip_filename_rejected))
3691 char *top_dir = ExtractZipFileIntoDirectory(zip_filename, directory,
3693 char *marker_filename = (top_dir != NULL ? zip_filename_extracted :
3694 zip_filename_rejected);
3697 // create empty file to mark zip file as extracted or rejected
3698 if ((marker_file = fopen(marker_filename, MODE_WRITE)))
3699 fclose(marker_file);
3702 free(zip_filename_extracted);
3703 free(zip_filename_rejected);
3707 closeDirectory(dir);
3710 // forward declaration for recursive call by "LoadLevelInfoFromLevelDir()"
3711 static void LoadLevelInfoFromLevelDir(TreeInfo **, TreeInfo *, char *);
3713 static boolean LoadLevelInfoFromLevelConf(TreeInfo **node_first,
3714 TreeInfo *node_parent,
3715 char *level_directory,
3716 char *directory_name)
3718 char *directory_path = getPath2(level_directory, directory_name);
3719 char *filename = getPath2(directory_path, LEVELINFO_FILENAME);
3720 SetupFileHash *setup_file_hash;
3721 LevelDirTree *leveldir_new = NULL;
3724 // unless debugging, silently ignore directories without "levelinfo.conf"
3725 if (!options.debug && !fileExists(filename))
3727 free(directory_path);
3733 setup_file_hash = loadSetupFileHash(filename);
3735 if (setup_file_hash == NULL)
3737 #if DEBUG_NO_CONFIG_FILE
3738 Debug("setup", "ignoring level directory '%s'", directory_path);
3741 free(directory_path);
3747 leveldir_new = newTreeInfo();
3750 setTreeInfoToDefaultsFromParent(leveldir_new, node_parent);
3752 setTreeInfoToDefaults(leveldir_new, TREE_TYPE_LEVEL_DIR);
3754 leveldir_new->subdir = getStringCopy(directory_name);
3756 // set all structure fields according to the token/value pairs
3757 ldi = *leveldir_new;
3758 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
3759 setSetupInfo(levelinfo_tokens, i,
3760 getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
3761 *leveldir_new = ldi;
3763 if (strEqual(leveldir_new->name, ANONYMOUS_NAME))
3764 setString(&leveldir_new->name, leveldir_new->subdir);
3766 if (leveldir_new->identifier == NULL)
3767 leveldir_new->identifier = getStringCopy(leveldir_new->subdir);
3769 if (leveldir_new->name_sorting == NULL)
3770 leveldir_new->name_sorting = getStringCopy(leveldir_new->name);
3772 if (node_parent == NULL) // top level group
3774 leveldir_new->basepath = getStringCopy(level_directory);
3775 leveldir_new->fullpath = getStringCopy(leveldir_new->subdir);
3777 else // sub level group
3779 leveldir_new->basepath = getStringCopy(node_parent->basepath);
3780 leveldir_new->fullpath = getPath2(node_parent->fullpath, directory_name);
3783 leveldir_new->last_level =
3784 leveldir_new->first_level + leveldir_new->levels - 1;
3786 leveldir_new->in_user_dir =
3787 (!strEqual(leveldir_new->basepath, options.level_directory));
3789 // adjust some settings if user's private level directory was detected
3790 if (leveldir_new->sort_priority == LEVELCLASS_UNDEFINED &&
3791 leveldir_new->in_user_dir &&
3792 (strEqual(leveldir_new->subdir, getLoginName()) ||
3793 strEqual(leveldir_new->name, getLoginName()) ||
3794 strEqual(leveldir_new->author, getRealName())))
3796 leveldir_new->sort_priority = LEVELCLASS_PRIVATE_START;
3797 leveldir_new->readonly = FALSE;
3800 leveldir_new->user_defined =
3801 (leveldir_new->in_user_dir && IS_LEVELCLASS_PRIVATE(leveldir_new));
3803 setString(&leveldir_new->class_desc, getLevelClassDescription(leveldir_new));
3805 leveldir_new->handicap_level = // set handicap to default value
3806 (leveldir_new->user_defined || !leveldir_new->handicap ?
3807 leveldir_new->last_level : leveldir_new->first_level);
3809 DrawInitTextItem(leveldir_new->name);
3811 pushTreeInfo(node_first, leveldir_new);
3813 freeSetupFileHash(setup_file_hash);
3815 if (leveldir_new->level_group)
3817 // create node to link back to current level directory
3818 createParentTreeInfoNode(leveldir_new);
3820 // recursively step into sub-directory and look for more level series
3821 LoadLevelInfoFromLevelDir(&leveldir_new->node_group,
3822 leveldir_new, directory_path);
3825 free(directory_path);
3831 static void LoadLevelInfoFromLevelDir(TreeInfo **node_first,
3832 TreeInfo *node_parent,
3833 char *level_directory)
3835 // ---------- 1st stage: process any level set zip files ----------
3837 ProcessZipFilesInDirectory(level_directory, TREE_TYPE_LEVEL_DIR);
3839 // ---------- 2nd stage: check for level set directories ----------
3842 DirectoryEntry *dir_entry;
3843 boolean valid_entry_found = FALSE;
3845 if ((dir = openDirectory(level_directory)) == NULL)
3847 Warn("cannot read level directory '%s'", level_directory);
3852 while ((dir_entry = readDirectory(dir)) != NULL) // loop all entries
3854 char *directory_name = dir_entry->basename;
3855 char *directory_path = getPath2(level_directory, directory_name);
3857 // skip entries for current and parent directory
3858 if (strEqual(directory_name, ".") ||
3859 strEqual(directory_name, ".."))
3861 free(directory_path);
3866 // find out if directory entry is itself a directory
3867 if (!dir_entry->is_directory) // not a directory
3869 free(directory_path);
3874 free(directory_path);
3876 if (strEqual(directory_name, GRAPHICS_DIRECTORY) ||
3877 strEqual(directory_name, SOUNDS_DIRECTORY) ||
3878 strEqual(directory_name, MUSIC_DIRECTORY))
3881 valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
3886 closeDirectory(dir);
3888 // special case: top level directory may directly contain "levelinfo.conf"
3889 if (node_parent == NULL && !valid_entry_found)
3891 // check if this directory directly contains a file "levelinfo.conf"
3892 valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
3893 level_directory, ".");
3896 boolean valid_entry_expected =
3897 (strEqual(level_directory, options.level_directory) ||
3898 setup.internal.create_user_levelset);
3900 if (valid_entry_expected && !valid_entry_found)
3901 Warn("cannot find any valid level series in directory '%s'",
3905 boolean AdjustGraphicsForEMC(void)
3907 boolean settings_changed = FALSE;
3909 settings_changed |= adjustTreeGraphicsForEMC(leveldir_first_all);
3910 settings_changed |= adjustTreeGraphicsForEMC(leveldir_first);
3912 return settings_changed;
3915 boolean AdjustSoundsForEMC(void)
3917 boolean settings_changed = FALSE;
3919 settings_changed |= adjustTreeSoundsForEMC(leveldir_first_all);
3920 settings_changed |= adjustTreeSoundsForEMC(leveldir_first);
3922 return settings_changed;
3925 void LoadLevelInfo(void)
3927 InitUserLevelDirectory(getLoginName());
3929 DrawInitTextHead("Loading level series");
3931 LoadLevelInfoFromLevelDir(&leveldir_first, NULL, options.level_directory);
3932 LoadLevelInfoFromLevelDir(&leveldir_first, NULL, getUserLevelDir(NULL));
3934 leveldir_first = createTopTreeInfoNode(leveldir_first);
3936 /* after loading all level set information, clone the level directory tree
3937 and remove all level sets without levels (these may still contain artwork
3938 to be offered in the setup menu as "custom artwork", and are therefore
3939 checked for existing artwork in the function "LoadLevelArtworkInfo()") */
3940 leveldir_first_all = leveldir_first;
3941 cloneTree(&leveldir_first, leveldir_first_all, TRUE);
3943 AdjustGraphicsForEMC();
3944 AdjustSoundsForEMC();
3946 // before sorting, the first entries will be from the user directory
3947 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
3949 if (leveldir_first == NULL)
3950 Fail("cannot find any valid level series in any directory");
3952 sortTreeInfo(&leveldir_first);
3954 #if ENABLE_UNUSED_CODE
3955 dumpTreeInfo(leveldir_first, 0);
3959 static boolean LoadArtworkInfoFromArtworkConf(TreeInfo **node_first,
3960 TreeInfo *node_parent,
3961 char *base_directory,
3962 char *directory_name, int type)
3964 char *directory_path = getPath2(base_directory, directory_name);
3965 char *filename = getPath2(directory_path, ARTWORKINFO_FILENAME(type));
3966 SetupFileHash *setup_file_hash = NULL;
3967 TreeInfo *artwork_new = NULL;
3970 if (fileExists(filename))
3971 setup_file_hash = loadSetupFileHash(filename);
3973 if (setup_file_hash == NULL) // no config file -- look for artwork files
3976 DirectoryEntry *dir_entry;
3977 boolean valid_file_found = FALSE;
3979 if ((dir = openDirectory(directory_path)) != NULL)
3981 while ((dir_entry = readDirectory(dir)) != NULL)
3983 if (FileIsArtworkType(dir_entry->filename, type))
3985 valid_file_found = TRUE;
3991 closeDirectory(dir);
3994 if (!valid_file_found)
3996 #if DEBUG_NO_CONFIG_FILE
3997 if (!strEqual(directory_name, "."))
3998 Debug("setup", "ignoring artwork directory '%s'", directory_path);
4001 free(directory_path);
4008 artwork_new = newTreeInfo();
4011 setTreeInfoToDefaultsFromParent(artwork_new, node_parent);
4013 setTreeInfoToDefaults(artwork_new, type);
4015 artwork_new->subdir = getStringCopy(directory_name);
4017 if (setup_file_hash) // (before defining ".color" and ".class_desc")
4019 // set all structure fields according to the token/value pairs
4021 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
4022 setSetupInfo(levelinfo_tokens, i,
4023 getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
4026 if (strEqual(artwork_new->name, ANONYMOUS_NAME))
4027 setString(&artwork_new->name, artwork_new->subdir);
4029 if (artwork_new->identifier == NULL)
4030 artwork_new->identifier = getStringCopy(artwork_new->subdir);
4032 if (artwork_new->name_sorting == NULL)
4033 artwork_new->name_sorting = getStringCopy(artwork_new->name);
4036 if (node_parent == NULL) // top level group
4038 artwork_new->basepath = getStringCopy(base_directory);
4039 artwork_new->fullpath = getStringCopy(artwork_new->subdir);
4041 else // sub level group
4043 artwork_new->basepath = getStringCopy(node_parent->basepath);
4044 artwork_new->fullpath = getPath2(node_parent->fullpath, directory_name);
4047 artwork_new->in_user_dir =
4048 (!strEqual(artwork_new->basepath, OPTIONS_ARTWORK_DIRECTORY(type)));
4050 setString(&artwork_new->class_desc, getLevelClassDescription(artwork_new));
4052 if (setup_file_hash == NULL) // (after determining ".user_defined")
4054 if (strEqual(artwork_new->subdir, "."))
4056 if (artwork_new->user_defined)
4058 setString(&artwork_new->identifier, "private");
4059 artwork_new->sort_priority = ARTWORKCLASS_PRIVATE;
4063 setString(&artwork_new->identifier, "classic");
4064 artwork_new->sort_priority = ARTWORKCLASS_CLASSICS;
4067 setString(&artwork_new->class_desc,
4068 getLevelClassDescription(artwork_new));
4072 setString(&artwork_new->identifier, artwork_new->subdir);
4075 setString(&artwork_new->name, artwork_new->identifier);
4076 setString(&artwork_new->name_sorting, artwork_new->name);
4079 pushTreeInfo(node_first, artwork_new);
4081 freeSetupFileHash(setup_file_hash);
4083 free(directory_path);
4089 static void LoadArtworkInfoFromArtworkDir(TreeInfo **node_first,
4090 TreeInfo *node_parent,
4091 char *base_directory, int type)
4093 // ---------- 1st stage: process any artwork set zip files ----------
4095 ProcessZipFilesInDirectory(base_directory, type);
4097 // ---------- 2nd stage: check for artwork set directories ----------
4100 DirectoryEntry *dir_entry;
4101 boolean valid_entry_found = FALSE;
4103 if ((dir = openDirectory(base_directory)) == NULL)
4105 // display error if directory is main "options.graphics_directory" etc.
4106 if (base_directory == OPTIONS_ARTWORK_DIRECTORY(type))
4107 Warn("cannot read directory '%s'", base_directory);
4112 while ((dir_entry = readDirectory(dir)) != NULL) // loop all entries
4114 char *directory_name = dir_entry->basename;
4115 char *directory_path = getPath2(base_directory, directory_name);
4117 // skip directory entries for current and parent directory
4118 if (strEqual(directory_name, ".") ||
4119 strEqual(directory_name, ".."))
4121 free(directory_path);
4126 // skip directory entries which are not a directory
4127 if (!dir_entry->is_directory) // not a directory
4129 free(directory_path);
4134 free(directory_path);
4136 // check if this directory contains artwork with or without config file
4137 valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
4139 directory_name, type);
4142 closeDirectory(dir);
4144 // check if this directory directly contains artwork itself
4145 valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
4146 base_directory, ".",
4148 if (!valid_entry_found)
4149 Warn("cannot find any valid artwork in directory '%s'", base_directory);
4152 static TreeInfo *getDummyArtworkInfo(int type)
4154 // this is only needed when there is completely no artwork available
4155 TreeInfo *artwork_new = newTreeInfo();
4157 setTreeInfoToDefaults(artwork_new, type);
4159 setString(&artwork_new->subdir, UNDEFINED_FILENAME);
4160 setString(&artwork_new->fullpath, UNDEFINED_FILENAME);
4161 setString(&artwork_new->basepath, UNDEFINED_FILENAME);
4163 setString(&artwork_new->identifier, UNDEFINED_FILENAME);
4164 setString(&artwork_new->name, UNDEFINED_FILENAME);
4165 setString(&artwork_new->name_sorting, UNDEFINED_FILENAME);
4170 void SetCurrentArtwork(int type)
4172 ArtworkDirTree **current_ptr = ARTWORK_CURRENT_PTR(artwork, type);
4173 ArtworkDirTree *first_node = ARTWORK_FIRST_NODE(artwork, type);
4174 char *setup_set = SETUP_ARTWORK_SET(setup, type);
4175 char *default_subdir = ARTWORK_DEFAULT_SUBDIR(type);
4177 // set current artwork to artwork configured in setup menu
4178 *current_ptr = getTreeInfoFromIdentifier(first_node, setup_set);
4180 // if not found, set current artwork to default artwork
4181 if (*current_ptr == NULL)
4182 *current_ptr = getTreeInfoFromIdentifier(first_node, default_subdir);
4184 // if not found, set current artwork to first artwork in tree
4185 if (*current_ptr == NULL)
4186 *current_ptr = getFirstValidTreeInfoEntry(first_node);
4189 void ChangeCurrentArtworkIfNeeded(int type)
4191 char *current_identifier = ARTWORK_CURRENT_IDENTIFIER(artwork, type);
4192 char *setup_set = SETUP_ARTWORK_SET(setup, type);
4194 if (!strEqual(current_identifier, setup_set))
4195 SetCurrentArtwork(type);
4198 void LoadArtworkInfo(void)
4200 LoadArtworkInfoCache();
4202 DrawInitTextHead("Looking for custom artwork");
4204 LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
4205 options.graphics_directory,
4206 TREE_TYPE_GRAPHICS_DIR);
4207 LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
4208 getUserGraphicsDir(),
4209 TREE_TYPE_GRAPHICS_DIR);
4211 LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
4212 options.sounds_directory,
4213 TREE_TYPE_SOUNDS_DIR);
4214 LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
4216 TREE_TYPE_SOUNDS_DIR);
4218 LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
4219 options.music_directory,
4220 TREE_TYPE_MUSIC_DIR);
4221 LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
4223 TREE_TYPE_MUSIC_DIR);
4225 if (artwork.gfx_first == NULL)
4226 artwork.gfx_first = getDummyArtworkInfo(TREE_TYPE_GRAPHICS_DIR);
4227 if (artwork.snd_first == NULL)
4228 artwork.snd_first = getDummyArtworkInfo(TREE_TYPE_SOUNDS_DIR);
4229 if (artwork.mus_first == NULL)
4230 artwork.mus_first = getDummyArtworkInfo(TREE_TYPE_MUSIC_DIR);
4232 // before sorting, the first entries will be from the user directory
4233 SetCurrentArtwork(ARTWORK_TYPE_GRAPHICS);
4234 SetCurrentArtwork(ARTWORK_TYPE_SOUNDS);
4235 SetCurrentArtwork(ARTWORK_TYPE_MUSIC);
4237 artwork.gfx_current_identifier = artwork.gfx_current->identifier;
4238 artwork.snd_current_identifier = artwork.snd_current->identifier;
4239 artwork.mus_current_identifier = artwork.mus_current->identifier;
4241 #if ENABLE_UNUSED_CODE
4242 Debug("setup:LoadArtworkInfo", "graphics set == %s",
4243 artwork.gfx_current_identifier);
4244 Debug("setup:LoadArtworkInfo", "sounds set == %s",
4245 artwork.snd_current_identifier);
4246 Debug("setup:LoadArtworkInfo", "music set == %s",
4247 artwork.mus_current_identifier);
4250 sortTreeInfo(&artwork.gfx_first);
4251 sortTreeInfo(&artwork.snd_first);
4252 sortTreeInfo(&artwork.mus_first);
4254 #if ENABLE_UNUSED_CODE
4255 dumpTreeInfo(artwork.gfx_first, 0);
4256 dumpTreeInfo(artwork.snd_first, 0);
4257 dumpTreeInfo(artwork.mus_first, 0);
4261 static void MoveArtworkInfoIntoSubTree(ArtworkDirTree **artwork_node)
4263 ArtworkDirTree *artwork_new = newTreeInfo();
4264 char *top_node_name = "standalone artwork";
4266 setTreeInfoToDefaults(artwork_new, (*artwork_node)->type);
4268 artwork_new->level_group = TRUE;
4270 setString(&artwork_new->identifier, top_node_name);
4271 setString(&artwork_new->name, top_node_name);
4272 setString(&artwork_new->name_sorting, top_node_name);
4274 // create node to link back to current custom artwork directory
4275 createParentTreeInfoNode(artwork_new);
4277 // move existing custom artwork tree into newly created sub-tree
4278 artwork_new->node_group->next = *artwork_node;
4280 // change custom artwork tree to contain only newly created node
4281 *artwork_node = artwork_new;
4284 static void LoadArtworkInfoFromLevelInfoExt(ArtworkDirTree **artwork_node,
4285 ArtworkDirTree *node_parent,
4286 LevelDirTree *level_node,
4287 boolean empty_level_set_mode)
4289 int type = (*artwork_node)->type;
4291 // recursively check all level directories for artwork sub-directories
4295 boolean empty_level_set = (level_node->levels == 0);
4297 // check all tree entries for artwork, but skip parent link entries
4298 if (!level_node->parent_link && empty_level_set == empty_level_set_mode)
4300 TreeInfo *artwork_new = getArtworkInfoCacheEntry(level_node, type);
4301 boolean cached = (artwork_new != NULL);
4305 pushTreeInfo(artwork_node, artwork_new);
4309 TreeInfo *topnode_last = *artwork_node;
4310 char *path = getPath2(getLevelDirFromTreeInfo(level_node),
4311 ARTWORK_DIRECTORY(type));
4313 LoadArtworkInfoFromArtworkDir(artwork_node, NULL, path, type);
4315 if (topnode_last != *artwork_node) // check for newly added node
4317 artwork_new = *artwork_node;
4319 setString(&artwork_new->identifier, level_node->subdir);
4320 setString(&artwork_new->name, level_node->name);
4321 setString(&artwork_new->name_sorting, level_node->name_sorting);
4323 artwork_new->sort_priority = level_node->sort_priority;
4324 artwork_new->in_user_dir = level_node->in_user_dir;
4326 update_artworkinfo_cache = TRUE;
4332 // insert artwork info (from old cache or filesystem) into new cache
4333 if (artwork_new != NULL)
4334 setArtworkInfoCacheEntry(artwork_new, level_node, type);
4337 DrawInitTextItem(level_node->name);
4339 if (level_node->node_group != NULL)
4341 TreeInfo *artwork_new = newTreeInfo();
4344 setTreeInfoToDefaultsFromParent(artwork_new, node_parent);
4346 setTreeInfoToDefaults(artwork_new, type);
4348 artwork_new->level_group = TRUE;
4350 setString(&artwork_new->identifier, level_node->subdir);
4352 if (node_parent == NULL) // check for top tree node
4354 char *top_node_name = (empty_level_set_mode ?
4355 "artwork for certain level sets" :
4356 "artwork included in level sets");
4358 setString(&artwork_new->name, top_node_name);
4359 setString(&artwork_new->name_sorting, top_node_name);
4363 setString(&artwork_new->name, level_node->name);
4364 setString(&artwork_new->name_sorting, level_node->name_sorting);
4367 pushTreeInfo(artwork_node, artwork_new);
4369 // create node to link back to current custom artwork directory
4370 createParentTreeInfoNode(artwork_new);
4372 // recursively step into sub-directory and look for more custom artwork
4373 LoadArtworkInfoFromLevelInfoExt(&artwork_new->node_group, artwork_new,
4374 level_node->node_group,
4375 empty_level_set_mode);
4377 // if sub-tree has no custom artwork at all, remove it
4378 if (artwork_new->node_group->next == NULL)
4379 removeTreeInfo(artwork_node);
4382 level_node = level_node->next;
4386 static void LoadArtworkInfoFromLevelInfo(ArtworkDirTree **artwork_node)
4388 // move peviously loaded artwork tree into separate sub-tree
4389 MoveArtworkInfoIntoSubTree(artwork_node);
4391 // load artwork from level sets into separate sub-trees
4392 LoadArtworkInfoFromLevelInfoExt(artwork_node, NULL, leveldir_first_all, TRUE);
4393 LoadArtworkInfoFromLevelInfoExt(artwork_node, NULL, leveldir_first_all, FALSE);
4395 // add top tree node over all sub-trees and set parent links
4396 *artwork_node = addTopTreeInfoNode(*artwork_node);
4399 void LoadLevelArtworkInfo(void)
4401 print_timestamp_init("LoadLevelArtworkInfo");
4403 DrawInitTextHead("Looking for custom level artwork");
4405 print_timestamp_time("DrawTimeText");
4407 LoadArtworkInfoFromLevelInfo(&artwork.gfx_first);
4408 print_timestamp_time("LoadArtworkInfoFromLevelInfo (gfx)");
4409 LoadArtworkInfoFromLevelInfo(&artwork.snd_first);
4410 print_timestamp_time("LoadArtworkInfoFromLevelInfo (snd)");
4411 LoadArtworkInfoFromLevelInfo(&artwork.mus_first);
4412 print_timestamp_time("LoadArtworkInfoFromLevelInfo (mus)");
4414 SaveArtworkInfoCache();
4416 print_timestamp_time("SaveArtworkInfoCache");
4418 // needed for reloading level artwork not known at ealier stage
4419 ChangeCurrentArtworkIfNeeded(ARTWORK_TYPE_GRAPHICS);
4420 ChangeCurrentArtworkIfNeeded(ARTWORK_TYPE_SOUNDS);
4421 ChangeCurrentArtworkIfNeeded(ARTWORK_TYPE_MUSIC);
4423 print_timestamp_time("getTreeInfoFromIdentifier");
4425 sortTreeInfo(&artwork.gfx_first);
4426 sortTreeInfo(&artwork.snd_first);
4427 sortTreeInfo(&artwork.mus_first);
4429 print_timestamp_time("sortTreeInfo");
4431 #if ENABLE_UNUSED_CODE
4432 dumpTreeInfo(artwork.gfx_first, 0);
4433 dumpTreeInfo(artwork.snd_first, 0);
4434 dumpTreeInfo(artwork.mus_first, 0);
4437 print_timestamp_done("LoadLevelArtworkInfo");
4440 static boolean AddTreeSetToTreeInfoExt(TreeInfo *tree_node_old, char *tree_dir,
4441 char *tree_subdir_new, int type)
4443 if (tree_node_old == NULL)
4445 if (type == TREE_TYPE_LEVEL_DIR)
4447 // get level info tree node of personal user level set
4448 tree_node_old = getTreeInfoFromIdentifier(leveldir_first, getLoginName());
4450 // this may happen if "setup.internal.create_user_levelset" is FALSE
4451 // or if file "levelinfo.conf" is missing in personal user level set
4452 if (tree_node_old == NULL)
4453 tree_node_old = leveldir_first->node_group;
4457 // get artwork info tree node of first artwork set
4458 tree_node_old = ARTWORK_FIRST_NODE(artwork, type);
4462 if (tree_dir == NULL)
4463 tree_dir = TREE_USERDIR(type);
4465 if (tree_node_old == NULL ||
4467 tree_subdir_new == NULL) // should not happen
4470 int draw_deactivation_mask = GetDrawDeactivationMask();
4472 // override draw deactivation mask (temporarily disable drawing)
4473 SetDrawDeactivationMask(REDRAW_ALL);
4475 if (type == TREE_TYPE_LEVEL_DIR)
4477 // load new level set config and add it next to first user level set
4478 LoadLevelInfoFromLevelConf(&tree_node_old->next,
4479 tree_node_old->node_parent,
4480 tree_dir, tree_subdir_new);
4484 // load new artwork set config and add it next to first artwork set
4485 LoadArtworkInfoFromArtworkConf(&tree_node_old->next,
4486 tree_node_old->node_parent,
4487 tree_dir, tree_subdir_new, type);
4490 // set draw deactivation mask to previous value
4491 SetDrawDeactivationMask(draw_deactivation_mask);
4493 // get first node of level or artwork info tree
4494 TreeInfo **tree_node_first = TREE_FIRST_NODE_PTR(type);
4496 // get tree info node of newly added level or artwork set
4497 TreeInfo *tree_node_new = getTreeInfoFromIdentifier(*tree_node_first,
4500 if (tree_node_new == NULL) // should not happen
4503 // correct top link and parent node link of newly created tree node
4504 tree_node_new->node_top = tree_node_old->node_top;
4505 tree_node_new->node_parent = tree_node_old->node_parent;
4507 // sort tree info to adjust position of newly added tree set
4508 sortTreeInfo(tree_node_first);
4513 void AddTreeSetToTreeInfo(TreeInfo *tree_node, char *tree_dir,
4514 char *tree_subdir_new, int type)
4516 if (!AddTreeSetToTreeInfoExt(tree_node, tree_dir, tree_subdir_new, type))
4517 Fail("internal tree info structure corrupted -- aborting");
4520 void AddUserLevelSetToLevelInfo(char *level_subdir_new)
4522 AddTreeSetToTreeInfo(NULL, NULL, level_subdir_new, TREE_TYPE_LEVEL_DIR);
4525 char *getArtworkIdentifierForUserLevelSet(int type)
4527 char *classic_artwork_set = getClassicArtworkSet(type);
4529 // check for custom artwork configured in "levelinfo.conf"
4530 char *leveldir_artwork_set =
4531 *LEVELDIR_ARTWORK_SET_PTR(leveldir_current, type);
4532 boolean has_leveldir_artwork_set =
4533 (leveldir_artwork_set != NULL && !strEqual(leveldir_artwork_set,
4534 classic_artwork_set));
4536 // check for custom artwork in sub-directory "graphics" etc.
4537 TreeInfo *artwork_first_node = ARTWORK_FIRST_NODE(artwork, type);
4538 char *leveldir_identifier = leveldir_current->identifier;
4539 boolean has_artwork_subdir =
4540 (getTreeInfoFromIdentifier(artwork_first_node,
4541 leveldir_identifier) != NULL);
4543 return (has_leveldir_artwork_set ? leveldir_artwork_set :
4544 has_artwork_subdir ? leveldir_identifier :
4545 classic_artwork_set);
4548 TreeInfo *getArtworkTreeInfoForUserLevelSet(int type)
4550 char *artwork_set = getArtworkIdentifierForUserLevelSet(type);
4551 TreeInfo *artwork_first_node = ARTWORK_FIRST_NODE(artwork, type);
4552 TreeInfo *ti = getTreeInfoFromIdentifier(artwork_first_node, artwork_set);
4556 ti = getTreeInfoFromIdentifier(artwork_first_node,
4557 ARTWORK_DEFAULT_SUBDIR(type));
4559 Fail("cannot find default graphics -- should not happen");
4565 boolean checkIfCustomArtworkExistsForCurrentLevelSet(void)
4567 char *graphics_set =
4568 getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_GRAPHICS);
4570 getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_SOUNDS);
4572 getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_MUSIC);
4574 return (!strEqual(graphics_set, GFX_CLASSIC_SUBDIR) ||
4575 !strEqual(sounds_set, SND_CLASSIC_SUBDIR) ||
4576 !strEqual(music_set, MUS_CLASSIC_SUBDIR));
4579 boolean UpdateUserLevelSet(char *level_subdir, char *level_name,
4580 char *level_author, int num_levels)
4582 char *filename = getPath2(getUserLevelDir(level_subdir), LEVELINFO_FILENAME);
4583 char *filename_tmp = getStringCat2(filename, ".tmp");
4585 FILE *file_tmp = NULL;
4586 char line[MAX_LINE_LEN];
4587 boolean success = FALSE;
4588 LevelDirTree *leveldir = getTreeInfoFromIdentifier(leveldir_first,
4590 // update values in level directory tree
4592 if (level_name != NULL)
4593 setString(&leveldir->name, level_name);
4595 if (level_author != NULL)
4596 setString(&leveldir->author, level_author);
4598 if (num_levels != -1)
4599 leveldir->levels = num_levels;
4601 // update values that depend on other values
4603 setString(&leveldir->name_sorting, leveldir->name);
4605 leveldir->last_level = leveldir->first_level + leveldir->levels - 1;
4607 // sort order of level sets may have changed
4608 sortTreeInfo(&leveldir_first);
4610 if ((file = fopen(filename, MODE_READ)) &&
4611 (file_tmp = fopen(filename_tmp, MODE_WRITE)))
4613 while (fgets(line, MAX_LINE_LEN, file))
4615 if (strPrefix(line, "name:") && level_name != NULL)
4616 fprintf(file_tmp, "%-32s%s\n", "name:", level_name);
4617 else if (strPrefix(line, "author:") && level_author != NULL)
4618 fprintf(file_tmp, "%-32s%s\n", "author:", level_author);
4619 else if (strPrefix(line, "levels:") && num_levels != -1)
4620 fprintf(file_tmp, "%-32s%d\n", "levels:", num_levels);
4622 fputs(line, file_tmp);
4635 success = (rename(filename_tmp, filename) == 0);
4643 boolean CreateUserLevelSet(char *level_subdir, char *level_name,
4644 char *level_author, int num_levels,
4645 boolean use_artwork_set)
4647 LevelDirTree *level_info;
4652 // create user level sub-directory, if needed
4653 createDirectory(getUserLevelDir(level_subdir), "user level");
4655 filename = getPath2(getUserLevelDir(level_subdir), LEVELINFO_FILENAME);
4657 if (!(file = fopen(filename, MODE_WRITE)))
4659 Warn("cannot write level info file '%s'", filename);
4666 level_info = newTreeInfo();
4668 // always start with reliable default values
4669 setTreeInfoToDefaults(level_info, TREE_TYPE_LEVEL_DIR);
4671 setString(&level_info->name, level_name);
4672 setString(&level_info->author, level_author);
4673 level_info->levels = num_levels;
4674 level_info->first_level = 1;
4675 level_info->sort_priority = LEVELCLASS_PRIVATE_START;
4676 level_info->readonly = FALSE;
4678 if (use_artwork_set)
4680 level_info->graphics_set =
4681 getStringCopy(getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_GRAPHICS));
4682 level_info->sounds_set =
4683 getStringCopy(getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_SOUNDS));
4684 level_info->music_set =
4685 getStringCopy(getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_MUSIC));
4688 token_value_position = TOKEN_VALUE_POSITION_SHORT;
4690 fprintFileHeader(file, LEVELINFO_FILENAME);
4693 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
4695 if (i == LEVELINFO_TOKEN_NAME ||
4696 i == LEVELINFO_TOKEN_AUTHOR ||
4697 i == LEVELINFO_TOKEN_LEVELS ||
4698 i == LEVELINFO_TOKEN_FIRST_LEVEL ||
4699 i == LEVELINFO_TOKEN_SORT_PRIORITY ||
4700 i == LEVELINFO_TOKEN_READONLY ||
4701 (use_artwork_set && (i == LEVELINFO_TOKEN_GRAPHICS_SET ||
4702 i == LEVELINFO_TOKEN_SOUNDS_SET ||
4703 i == LEVELINFO_TOKEN_MUSIC_SET)))
4704 fprintf(file, "%s\n", getSetupLine(levelinfo_tokens, "", i));
4706 // just to make things nicer :)
4707 if (i == LEVELINFO_TOKEN_AUTHOR ||
4708 i == LEVELINFO_TOKEN_FIRST_LEVEL ||
4709 (use_artwork_set && i == LEVELINFO_TOKEN_READONLY))
4710 fprintf(file, "\n");
4713 token_value_position = TOKEN_VALUE_POSITION_DEFAULT;
4717 SetFilePermissions(filename, PERMS_PRIVATE);
4719 freeTreeInfo(level_info);
4725 static void SaveUserLevelInfo(void)
4727 CreateUserLevelSet(getLoginName(), getLoginName(), getRealName(), 100, FALSE);
4730 char *getSetupValue(int type, void *value)
4732 static char value_string[MAX_LINE_LEN];
4740 strcpy(value_string, (*(boolean *)value ? "true" : "false"));
4744 strcpy(value_string, (*(boolean *)value ? "on" : "off"));
4748 strcpy(value_string, (*(int *)value == AUTO ? "auto" :
4749 *(int *)value == FALSE ? "off" : "on"));
4753 strcpy(value_string, (*(boolean *)value ? "yes" : "no"));
4756 case TYPE_YES_NO_AUTO:
4757 strcpy(value_string, (*(int *)value == AUTO ? "auto" :
4758 *(int *)value == FALSE ? "no" : "yes"));
4762 strcpy(value_string, (*(boolean *)value ? "AGA" : "ECS"));
4766 strcpy(value_string, getKeyNameFromKey(*(Key *)value));
4770 strcpy(value_string, getX11KeyNameFromKey(*(Key *)value));
4774 sprintf(value_string, "%d", *(int *)value);
4778 if (*(char **)value == NULL)
4781 strcpy(value_string, *(char **)value);
4785 sprintf(value_string, "player_%d", *(int *)value + 1);
4789 value_string[0] = '\0';
4793 if (type & TYPE_GHOSTED)
4794 strcpy(value_string, "n/a");
4796 return value_string;
4799 char *getSetupLine(struct TokenInfo *token_info, char *prefix, int token_nr)
4803 static char token_string[MAX_LINE_LEN];
4804 int token_type = token_info[token_nr].type;
4805 void *setup_value = token_info[token_nr].value;
4806 char *token_text = token_info[token_nr].text;
4807 char *value_string = getSetupValue(token_type, setup_value);
4809 // build complete token string
4810 sprintf(token_string, "%s%s", prefix, token_text);
4812 // build setup entry line
4813 line = getFormattedSetupEntry(token_string, value_string);
4815 if (token_type == TYPE_KEY_X11)
4817 Key key = *(Key *)setup_value;
4818 char *keyname = getKeyNameFromKey(key);
4820 // add comment, if useful
4821 if (!strEqual(keyname, "(undefined)") &&
4822 !strEqual(keyname, "(unknown)"))
4824 // add at least one whitespace
4826 for (i = strlen(line); i < token_comment_position; i++)
4830 strcat(line, keyname);
4837 static void InitLastPlayedLevels_ParentNode(void)
4839 LevelDirTree **leveldir_top = &leveldir_first->node_group->next;
4840 LevelDirTree *leveldir_new = NULL;
4842 // check if parent node for last played levels already exists
4843 if (strEqual((*leveldir_top)->identifier, TOKEN_STR_LAST_LEVEL_SERIES))
4846 leveldir_new = newTreeInfo();
4848 setTreeInfoToDefaultsFromParent(leveldir_new, leveldir_first);
4850 leveldir_new->level_group = TRUE;
4851 leveldir_new->sort_priority = LEVELCLASS_LAST_PLAYED_LEVEL;
4853 setString(&leveldir_new->identifier, TOKEN_STR_LAST_LEVEL_SERIES);
4854 setString(&leveldir_new->name, "<< (last played level sets)");
4855 setString(&leveldir_new->name_sorting, leveldir_new->name);
4857 pushTreeInfo(leveldir_top, leveldir_new);
4859 // create node to link back to current level directory
4860 createParentTreeInfoNode(leveldir_new);
4863 void UpdateLastPlayedLevels_TreeInfo(void)
4865 char **last_level_series = setup.level_setup.last_level_series;
4866 LevelDirTree *leveldir_last;
4867 TreeInfo **node_new = NULL;
4870 if (last_level_series[0] == NULL)
4873 InitLastPlayedLevels_ParentNode();
4875 leveldir_last = getTreeInfoFromIdentifierExt(leveldir_first,
4876 TOKEN_STR_LAST_LEVEL_SERIES,
4877 TREE_NODE_TYPE_GROUP);
4878 if (leveldir_last == NULL)
4881 node_new = &leveldir_last->node_group->next;
4883 freeTreeInfo(*node_new);
4887 for (i = 0; last_level_series[i] != NULL; i++)
4889 LevelDirTree *node_last = getTreeInfoFromIdentifier(leveldir_first,
4890 last_level_series[i]);
4891 if (node_last == NULL)
4894 *node_new = getTreeInfoCopy(node_last); // copy complete node
4896 (*node_new)->node_top = &leveldir_first; // correct top node link
4897 (*node_new)->node_parent = leveldir_last; // correct parent node link
4899 (*node_new)->is_copy = TRUE; // mark entry as node copy
4901 (*node_new)->node_group = NULL;
4902 (*node_new)->next = NULL;
4904 (*node_new)->cl_first = -1; // force setting tree cursor
4906 node_new = &((*node_new)->next);
4910 static void UpdateLastPlayedLevels_List(void)
4912 char **last_level_series = setup.level_setup.last_level_series;
4913 int pos = MAX_LEVELDIR_HISTORY - 1;
4916 // search for potentially already existing entry in list of level sets
4917 for (i = 0; last_level_series[i] != NULL; i++)
4918 if (strEqual(last_level_series[i], leveldir_current->identifier))
4921 // move list of level sets one entry down (using potentially free entry)
4922 for (i = pos; i > 0; i--)
4923 setString(&last_level_series[i], last_level_series[i - 1]);
4925 // put last played level set at top position
4926 setString(&last_level_series[0], leveldir_current->identifier);
4929 static TreeInfo *StoreOrRestoreLastPlayedLevels(TreeInfo *node, boolean store)
4931 static char *identifier = NULL;
4935 setString(&identifier, (node && node->is_copy ? node->identifier : NULL));
4937 return NULL; // not used
4941 TreeInfo *node_new = getTreeInfoFromIdentifierExt(leveldir_first,
4943 TREE_NODE_TYPE_COPY);
4944 return (node_new != NULL ? node_new : node);
4948 void StoreLastPlayedLevels(TreeInfo *node)
4950 StoreOrRestoreLastPlayedLevels(node, TRUE);
4953 void RestoreLastPlayedLevels(TreeInfo **node)
4955 *node = StoreOrRestoreLastPlayedLevels(*node, FALSE);
4958 void LoadLevelSetup_LastSeries(void)
4960 // --------------------------------------------------------------------------
4961 // ~/.<program>/levelsetup.conf
4962 // --------------------------------------------------------------------------
4964 char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
4965 SetupFileHash *level_setup_hash = NULL;
4969 // always start with reliable default values
4970 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
4972 // start with empty history of last played level sets
4973 setString(&setup.level_setup.last_level_series[0], NULL);
4975 if (!strEqual(DEFAULT_LEVELSET, UNDEFINED_LEVELSET))
4977 leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
4979 if (leveldir_current == NULL)
4980 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
4983 if ((level_setup_hash = loadSetupFileHash(filename)))
4985 char *last_level_series =
4986 getHashEntry(level_setup_hash, TOKEN_STR_LAST_LEVEL_SERIES);
4988 leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
4990 if (leveldir_current == NULL)
4991 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
4993 for (i = 0; i < MAX_LEVELDIR_HISTORY; i++)
4995 char token[strlen(TOKEN_STR_LAST_LEVEL_SERIES) + 10];
4996 LevelDirTree *leveldir_last;
4998 sprintf(token, "%s.%03d", TOKEN_STR_LAST_LEVEL_SERIES, i);
5000 last_level_series = getHashEntry(level_setup_hash, token);
5002 leveldir_last = getTreeInfoFromIdentifier(leveldir_first,
5004 if (leveldir_last != NULL)
5005 setString(&setup.level_setup.last_level_series[pos++],
5009 setString(&setup.level_setup.last_level_series[pos], NULL);
5011 freeSetupFileHash(level_setup_hash);
5015 Debug("setup", "using default setup values");
5021 static void SaveLevelSetup_LastSeries_Ext(boolean deactivate_last_level_series)
5023 // --------------------------------------------------------------------------
5024 // ~/.<program>/levelsetup.conf
5025 // --------------------------------------------------------------------------
5027 // check if the current level directory structure is available at this point
5028 if (leveldir_current == NULL)
5031 char **last_level_series = setup.level_setup.last_level_series;
5032 char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
5036 InitUserDataDirectory();
5038 UpdateLastPlayedLevels_List();
5040 if (!(file = fopen(filename, MODE_WRITE)))
5042 Warn("cannot write setup file '%s'", filename);
5049 fprintFileHeader(file, LEVELSETUP_FILENAME);
5051 if (deactivate_last_level_series)
5052 fprintf(file, "# %s\n# ", "the following level set may have caused a problem and was deactivated");
5054 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_LAST_LEVEL_SERIES,
5055 leveldir_current->identifier));
5057 for (i = 0; last_level_series[i] != NULL; i++)
5059 char token[strlen(TOKEN_STR_LAST_LEVEL_SERIES) + 1 + 10 + 1];
5061 sprintf(token, "%s.%03d", TOKEN_STR_LAST_LEVEL_SERIES, i);
5063 fprintf(file, "%s\n", getFormattedSetupEntry(token, last_level_series[i]));
5068 SetFilePermissions(filename, PERMS_PRIVATE);
5073 void SaveLevelSetup_LastSeries(void)
5075 SaveLevelSetup_LastSeries_Ext(FALSE);
5078 void SaveLevelSetup_LastSeries_Deactivate(void)
5080 SaveLevelSetup_LastSeries_Ext(TRUE);
5083 static void checkSeriesInfo(void)
5085 static char *level_directory = NULL;
5088 DirectoryEntry *dir_entry;
5091 checked_free(level_directory);
5093 // check for more levels besides the 'levels' field of 'levelinfo.conf'
5095 level_directory = getPath2((leveldir_current->in_user_dir ?
5096 getUserLevelDir(NULL) :
5097 options.level_directory),
5098 leveldir_current->fullpath);
5100 if ((dir = openDirectory(level_directory)) == NULL)
5102 Warn("cannot read level directory '%s'", level_directory);
5108 while ((dir_entry = readDirectory(dir)) != NULL) // loop all entries
5110 if (strlen(dir_entry->basename) > 4 &&
5111 dir_entry->basename[3] == '.' &&
5112 strEqual(&dir_entry->basename[4], LEVELFILE_EXTENSION))
5114 char levelnum_str[4];
5117 strncpy(levelnum_str, dir_entry->basename, 3);
5118 levelnum_str[3] = '\0';
5120 levelnum_value = atoi(levelnum_str);
5122 if (levelnum_value < leveldir_current->first_level)
5124 Warn("additional level %d found", levelnum_value);
5126 leveldir_current->first_level = levelnum_value;
5128 else if (levelnum_value > leveldir_current->last_level)
5130 Warn("additional level %d found", levelnum_value);
5132 leveldir_current->last_level = levelnum_value;
5138 closeDirectory(dir);
5141 void LoadLevelSetup_SeriesInfo(void)
5144 SetupFileHash *level_setup_hash = NULL;
5145 char *level_subdir = leveldir_current->subdir;
5148 // always start with reliable default values
5149 level_nr = leveldir_current->first_level;
5151 for (i = 0; i < MAX_LEVELS; i++)
5153 LevelStats_setPlayed(i, 0);
5154 LevelStats_setSolved(i, 0);
5159 // --------------------------------------------------------------------------
5160 // ~/.<program>/levelsetup/<level series>/levelsetup.conf
5161 // --------------------------------------------------------------------------
5163 level_subdir = leveldir_current->subdir;
5165 filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
5167 if ((level_setup_hash = loadSetupFileHash(filename)))
5171 // get last played level in this level set
5173 token_value = getHashEntry(level_setup_hash, TOKEN_STR_LAST_PLAYED_LEVEL);
5177 level_nr = atoi(token_value);
5179 if (level_nr < leveldir_current->first_level)
5180 level_nr = leveldir_current->first_level;
5181 if (level_nr > leveldir_current->last_level)
5182 level_nr = leveldir_current->last_level;
5185 // get handicap level in this level set
5187 token_value = getHashEntry(level_setup_hash, TOKEN_STR_HANDICAP_LEVEL);
5191 int level_nr = atoi(token_value);
5193 if (level_nr < leveldir_current->first_level)
5194 level_nr = leveldir_current->first_level;
5195 if (level_nr > leveldir_current->last_level + 1)
5196 level_nr = leveldir_current->last_level;
5198 if (leveldir_current->user_defined || !leveldir_current->handicap)
5199 level_nr = leveldir_current->last_level;
5201 leveldir_current->handicap_level = level_nr;
5204 // get number of played and solved levels in this level set
5206 BEGIN_HASH_ITERATION(level_setup_hash, itr)
5208 char *token = HASH_ITERATION_TOKEN(itr);
5209 char *value = HASH_ITERATION_VALUE(itr);
5211 if (strlen(token) == 3 &&
5212 token[0] >= '0' && token[0] <= '9' &&
5213 token[1] >= '0' && token[1] <= '9' &&
5214 token[2] >= '0' && token[2] <= '9')
5216 int level_nr = atoi(token);
5219 LevelStats_setPlayed(level_nr, atoi(value)); // read 1st column
5221 value = strchr(value, ' ');
5224 LevelStats_setSolved(level_nr, atoi(value)); // read 2nd column
5227 END_HASH_ITERATION(hash, itr)
5229 freeSetupFileHash(level_setup_hash);
5233 Debug("setup", "using default setup values");
5239 void SaveLevelSetup_SeriesInfo(void)
5242 char *level_subdir = leveldir_current->subdir;
5243 char *level_nr_str = int2str(level_nr, 0);
5244 char *handicap_level_str = int2str(leveldir_current->handicap_level, 0);
5248 // --------------------------------------------------------------------------
5249 // ~/.<program>/levelsetup/<level series>/levelsetup.conf
5250 // --------------------------------------------------------------------------
5252 InitLevelSetupDirectory(level_subdir);
5254 filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
5256 if (!(file = fopen(filename, MODE_WRITE)))
5258 Warn("cannot write setup file '%s'", filename);
5265 fprintFileHeader(file, LEVELSETUP_FILENAME);
5267 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_PLAYED_LEVEL,
5269 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_HANDICAP_LEVEL,
5270 handicap_level_str));
5272 for (i = leveldir_current->first_level; i <= leveldir_current->last_level;
5275 if (LevelStats_getPlayed(i) > 0 ||
5276 LevelStats_getSolved(i) > 0)
5281 sprintf(token, "%03d", i);
5282 sprintf(value, "%d %d", LevelStats_getPlayed(i), LevelStats_getSolved(i));
5284 fprintf(file, "%s\n", getFormattedSetupEntry(token, value));
5290 SetFilePermissions(filename, PERMS_PRIVATE);
5295 int LevelStats_getPlayed(int nr)
5297 return (nr >= 0 && nr < MAX_LEVELS ? level_stats[nr].played : 0);
5300 int LevelStats_getSolved(int nr)
5302 return (nr >= 0 && nr < MAX_LEVELS ? level_stats[nr].solved : 0);
5305 void LevelStats_setPlayed(int nr, int value)
5307 if (nr >= 0 && nr < MAX_LEVELS)
5308 level_stats[nr].played = value;
5311 void LevelStats_setSolved(int nr, int value)
5313 if (nr >= 0 && nr < MAX_LEVELS)
5314 level_stats[nr].solved = value;
5317 void LevelStats_incPlayed(int nr)
5319 if (nr >= 0 && nr < MAX_LEVELS)
5320 level_stats[nr].played++;
5323 void LevelStats_incSolved(int nr)
5325 if (nr >= 0 && nr < MAX_LEVELS)
5326 level_stats[nr].solved++;
5329 void LoadUserSetup(void)
5331 // --------------------------------------------------------------------------
5332 // ~/.<program>/usersetup.conf
5333 // --------------------------------------------------------------------------
5335 char *filename = getPath2(getMainUserGameDataDir(), USERSETUP_FILENAME);
5336 SetupFileHash *user_setup_hash = NULL;
5338 // always start with reliable default values
5341 if ((user_setup_hash = loadSetupFileHash(filename)))
5345 // get last selected user number
5346 token_value = getHashEntry(user_setup_hash, TOKEN_STR_LAST_USER);
5349 user.nr = MIN(MAX(0, atoi(token_value)), MAX_PLAYER_NAMES - 1);
5351 freeSetupFileHash(user_setup_hash);
5355 Debug("setup", "using default setup values");
5361 void SaveUserSetup(void)
5363 // --------------------------------------------------------------------------
5364 // ~/.<program>/usersetup.conf
5365 // --------------------------------------------------------------------------
5367 char *filename = getPath2(getMainUserGameDataDir(), USERSETUP_FILENAME);
5370 InitMainUserDataDirectory();
5372 if (!(file = fopen(filename, MODE_WRITE)))
5374 Warn("cannot write setup file '%s'", filename);
5381 fprintFileHeader(file, USERSETUP_FILENAME);
5383 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_USER,
5387 SetFilePermissions(filename, PERMS_PRIVATE);