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_TIME_LIMIT 30
2734 #define LEVELINFO_TOKEN_SKIP_LEVELS 31
2735 #define LEVELINFO_TOKEN_USE_EMC_TILES 32
2737 #define NUM_LEVELINFO_TOKENS 33
2739 static LevelDirTree ldi;
2741 static struct TokenInfo levelinfo_tokens[] =
2743 // level directory info
2744 { TYPE_STRING, &ldi.identifier, "identifier" },
2745 { TYPE_STRING, &ldi.name, "name" },
2746 { TYPE_STRING, &ldi.name_sorting, "name_sorting" },
2747 { TYPE_STRING, &ldi.author, "author" },
2748 { TYPE_STRING, &ldi.year, "year" },
2749 { TYPE_STRING, &ldi.program_title, "program_title" },
2750 { TYPE_STRING, &ldi.program_copyright, "program_copyright" },
2751 { TYPE_STRING, &ldi.program_company, "program_company" },
2752 { TYPE_STRING, &ldi.imported_from, "imported_from" },
2753 { TYPE_STRING, &ldi.imported_by, "imported_by" },
2754 { TYPE_STRING, &ldi.tested_by, "tested_by" },
2755 { TYPE_INTEGER, &ldi.levels, "levels" },
2756 { TYPE_INTEGER, &ldi.first_level, "first_level" },
2757 { TYPE_INTEGER, &ldi.sort_priority, "sort_priority" },
2758 { TYPE_BOOLEAN, &ldi.latest_engine, "latest_engine" },
2759 { TYPE_BOOLEAN, &ldi.level_group, "level_group" },
2760 { TYPE_BOOLEAN, &ldi.readonly, "readonly" },
2761 { TYPE_STRING, &ldi.graphics_set_ecs, "graphics_set.ecs" },
2762 { TYPE_STRING, &ldi.graphics_set_aga, "graphics_set.aga" },
2763 { TYPE_STRING, &ldi.graphics_set, "graphics_set" },
2764 { TYPE_STRING, &ldi.sounds_set_default,"sounds_set.default" },
2765 { TYPE_STRING, &ldi.sounds_set_lowpass,"sounds_set.lowpass" },
2766 { TYPE_STRING, &ldi.sounds_set, "sounds_set" },
2767 { TYPE_STRING, &ldi.music_set, "music_set" },
2768 { TYPE_STRING, &ldi.level_filename, "filename" },
2769 { TYPE_STRING, &ldi.level_filetype, "filetype" },
2770 { TYPE_STRING, &ldi.special_flags, "special_flags" },
2771 { TYPE_STRING, &ldi.empty_level_name, "empty_level_name" },
2772 { TYPE_BOOLEAN, &ldi.force_level_name, "force_level_name" },
2773 { TYPE_BOOLEAN, &ldi.handicap, "handicap" },
2774 { TYPE_BOOLEAN, &ldi.time_limit, "time_limit" },
2775 { TYPE_BOOLEAN, &ldi.skip_levels, "skip_levels" },
2776 { TYPE_BOOLEAN, &ldi.use_emc_tiles, "use_emc_tiles" }
2779 static struct TokenInfo artworkinfo_tokens[] =
2781 // artwork directory info
2782 { TYPE_STRING, &ldi.identifier, "identifier" },
2783 { TYPE_STRING, &ldi.subdir, "subdir" },
2784 { TYPE_STRING, &ldi.name, "name" },
2785 { TYPE_STRING, &ldi.name_sorting, "name_sorting" },
2786 { TYPE_STRING, &ldi.author, "author" },
2787 { TYPE_STRING, &ldi.program_title, "program_title" },
2788 { TYPE_STRING, &ldi.program_copyright, "program_copyright" },
2789 { TYPE_STRING, &ldi.program_company, "program_company" },
2790 { TYPE_INTEGER, &ldi.sort_priority, "sort_priority" },
2791 { TYPE_STRING, &ldi.basepath, "basepath" },
2792 { TYPE_STRING, &ldi.fullpath, "fullpath" },
2793 { TYPE_BOOLEAN, &ldi.in_user_dir, "in_user_dir" },
2794 { TYPE_STRING, &ldi.class_desc, "class_desc" },
2799 static char *optional_tokens[] =
2802 "program_copyright",
2808 static void setTreeInfoToDefaults(TreeInfo *ti, int type)
2812 ti->node_top = (ti->type == TREE_TYPE_LEVEL_DIR ? &leveldir_first :
2813 ti->type == TREE_TYPE_GRAPHICS_DIR ? &artwork.gfx_first :
2814 ti->type == TREE_TYPE_SOUNDS_DIR ? &artwork.snd_first :
2815 ti->type == TREE_TYPE_MUSIC_DIR ? &artwork.mus_first :
2818 ti->node_parent = NULL;
2819 ti->node_group = NULL;
2826 ti->fullpath = NULL;
2827 ti->basepath = NULL;
2828 ti->identifier = NULL;
2829 ti->name = getStringCopy(ANONYMOUS_NAME);
2830 ti->name_sorting = NULL;
2831 ti->author = getStringCopy(ANONYMOUS_NAME);
2834 ti->program_title = NULL;
2835 ti->program_copyright = NULL;
2836 ti->program_company = NULL;
2838 ti->sort_priority = LEVELCLASS_UNDEFINED; // default: least priority
2839 ti->latest_engine = FALSE; // default: get from level
2840 ti->parent_link = FALSE;
2841 ti->is_copy = FALSE;
2842 ti->in_user_dir = FALSE;
2843 ti->user_defined = FALSE;
2845 ti->class_desc = NULL;
2847 ti->infotext = getStringCopy(TREE_INFOTEXT(ti->type));
2849 if (ti->type == TREE_TYPE_LEVEL_DIR)
2851 ti->imported_from = NULL;
2852 ti->imported_by = NULL;
2853 ti->tested_by = NULL;
2855 ti->graphics_set_ecs = NULL;
2856 ti->graphics_set_aga = NULL;
2857 ti->graphics_set = NULL;
2858 ti->sounds_set_default = NULL;
2859 ti->sounds_set_lowpass = NULL;
2860 ti->sounds_set = NULL;
2861 ti->music_set = NULL;
2862 ti->graphics_path = getStringCopy(UNDEFINED_FILENAME);
2863 ti->sounds_path = getStringCopy(UNDEFINED_FILENAME);
2864 ti->music_path = getStringCopy(UNDEFINED_FILENAME);
2866 ti->level_filename = NULL;
2867 ti->level_filetype = NULL;
2869 ti->special_flags = NULL;
2871 ti->empty_level_name = NULL;
2872 ti->force_level_name = FALSE;
2875 ti->first_level = 0;
2877 ti->level_group = FALSE;
2878 ti->handicap_level = 0;
2879 ti->readonly = TRUE;
2880 ti->handicap = TRUE;
2881 ti->time_limit = TRUE;
2882 ti->skip_levels = FALSE;
2884 ti->use_emc_tiles = FALSE;
2888 static void setTreeInfoToDefaultsFromParent(TreeInfo *ti, TreeInfo *parent)
2892 Warn("setTreeInfoToDefaultsFromParent(): parent == NULL");
2894 setTreeInfoToDefaults(ti, TREE_TYPE_UNDEFINED);
2899 // copy all values from the parent structure
2901 ti->type = parent->type;
2903 ti->node_top = parent->node_top;
2904 ti->node_parent = parent;
2905 ti->node_group = NULL;
2912 ti->fullpath = NULL;
2913 ti->basepath = NULL;
2914 ti->identifier = NULL;
2915 ti->name = getStringCopy(ANONYMOUS_NAME);
2916 ti->name_sorting = NULL;
2917 ti->author = getStringCopy(parent->author);
2918 ti->year = getStringCopy(parent->year);
2920 ti->program_title = getStringCopy(parent->program_title);
2921 ti->program_copyright = getStringCopy(parent->program_copyright);
2922 ti->program_company = getStringCopy(parent->program_company);
2924 ti->sort_priority = parent->sort_priority;
2925 ti->latest_engine = parent->latest_engine;
2926 ti->parent_link = FALSE;
2927 ti->is_copy = FALSE;
2928 ti->in_user_dir = parent->in_user_dir;
2929 ti->user_defined = parent->user_defined;
2930 ti->color = parent->color;
2931 ti->class_desc = getStringCopy(parent->class_desc);
2933 ti->infotext = getStringCopy(parent->infotext);
2935 if (ti->type == TREE_TYPE_LEVEL_DIR)
2937 ti->imported_from = getStringCopy(parent->imported_from);
2938 ti->imported_by = getStringCopy(parent->imported_by);
2939 ti->tested_by = getStringCopy(parent->tested_by);
2941 ti->graphics_set_ecs = getStringCopy(parent->graphics_set_ecs);
2942 ti->graphics_set_aga = getStringCopy(parent->graphics_set_aga);
2943 ti->graphics_set = getStringCopy(parent->graphics_set);
2944 ti->sounds_set_default = getStringCopy(parent->sounds_set_default);
2945 ti->sounds_set_lowpass = getStringCopy(parent->sounds_set_lowpass);
2946 ti->sounds_set = getStringCopy(parent->sounds_set);
2947 ti->music_set = getStringCopy(parent->music_set);
2948 ti->graphics_path = getStringCopy(UNDEFINED_FILENAME);
2949 ti->sounds_path = getStringCopy(UNDEFINED_FILENAME);
2950 ti->music_path = getStringCopy(UNDEFINED_FILENAME);
2952 ti->level_filename = getStringCopy(parent->level_filename);
2953 ti->level_filetype = getStringCopy(parent->level_filetype);
2955 ti->special_flags = getStringCopy(parent->special_flags);
2957 ti->empty_level_name = getStringCopy(parent->empty_level_name);
2958 ti->force_level_name = parent->force_level_name;
2960 ti->levels = parent->levels;
2961 ti->first_level = parent->first_level;
2962 ti->last_level = parent->last_level;
2963 ti->level_group = FALSE;
2964 ti->handicap_level = parent->handicap_level;
2965 ti->readonly = parent->readonly;
2966 ti->handicap = parent->handicap;
2967 ti->time_limit = parent->time_limit;
2968 ti->skip_levels = parent->skip_levels;
2970 ti->use_emc_tiles = parent->use_emc_tiles;
2974 static TreeInfo *getTreeInfoCopy(TreeInfo *ti)
2976 TreeInfo *ti_copy = newTreeInfo();
2978 // copy all values from the original structure
2980 ti_copy->type = ti->type;
2982 ti_copy->node_top = ti->node_top;
2983 ti_copy->node_parent = ti->node_parent;
2984 ti_copy->node_group = ti->node_group;
2985 ti_copy->next = ti->next;
2987 ti_copy->cl_first = ti->cl_first;
2988 ti_copy->cl_cursor = ti->cl_cursor;
2990 ti_copy->subdir = getStringCopy(ti->subdir);
2991 ti_copy->fullpath = getStringCopy(ti->fullpath);
2992 ti_copy->basepath = getStringCopy(ti->basepath);
2993 ti_copy->identifier = getStringCopy(ti->identifier);
2994 ti_copy->name = getStringCopy(ti->name);
2995 ti_copy->name_sorting = getStringCopy(ti->name_sorting);
2996 ti_copy->author = getStringCopy(ti->author);
2997 ti_copy->year = getStringCopy(ti->year);
2999 ti_copy->program_title = getStringCopy(ti->program_title);
3000 ti_copy->program_copyright = getStringCopy(ti->program_copyright);
3001 ti_copy->program_company = getStringCopy(ti->program_company);
3003 ti_copy->imported_from = getStringCopy(ti->imported_from);
3004 ti_copy->imported_by = getStringCopy(ti->imported_by);
3005 ti_copy->tested_by = getStringCopy(ti->tested_by);
3007 ti_copy->graphics_set_ecs = getStringCopy(ti->graphics_set_ecs);
3008 ti_copy->graphics_set_aga = getStringCopy(ti->graphics_set_aga);
3009 ti_copy->graphics_set = getStringCopy(ti->graphics_set);
3010 ti_copy->sounds_set_default = getStringCopy(ti->sounds_set_default);
3011 ti_copy->sounds_set_lowpass = getStringCopy(ti->sounds_set_lowpass);
3012 ti_copy->sounds_set = getStringCopy(ti->sounds_set);
3013 ti_copy->music_set = getStringCopy(ti->music_set);
3014 ti_copy->graphics_path = getStringCopy(ti->graphics_path);
3015 ti_copy->sounds_path = getStringCopy(ti->sounds_path);
3016 ti_copy->music_path = getStringCopy(ti->music_path);
3018 ti_copy->level_filename = getStringCopy(ti->level_filename);
3019 ti_copy->level_filetype = getStringCopy(ti->level_filetype);
3021 ti_copy->special_flags = getStringCopy(ti->special_flags);
3023 ti_copy->empty_level_name = getStringCopy(ti->empty_level_name);
3024 ti_copy->force_level_name = ti->force_level_name;
3026 ti_copy->levels = ti->levels;
3027 ti_copy->first_level = ti->first_level;
3028 ti_copy->last_level = ti->last_level;
3029 ti_copy->sort_priority = ti->sort_priority;
3031 ti_copy->latest_engine = ti->latest_engine;
3033 ti_copy->level_group = ti->level_group;
3034 ti_copy->parent_link = ti->parent_link;
3035 ti_copy->is_copy = ti->is_copy;
3036 ti_copy->in_user_dir = ti->in_user_dir;
3037 ti_copy->user_defined = ti->user_defined;
3038 ti_copy->readonly = ti->readonly;
3039 ti_copy->handicap = ti->handicap;
3040 ti_copy->time_limit = ti->time_limit;
3041 ti_copy->skip_levels = ti->skip_levels;
3043 ti_copy->use_emc_tiles = ti->use_emc_tiles;
3045 ti_copy->color = ti->color;
3046 ti_copy->class_desc = getStringCopy(ti->class_desc);
3047 ti_copy->handicap_level = ti->handicap_level;
3049 ti_copy->infotext = getStringCopy(ti->infotext);
3054 void freeTreeInfo(TreeInfo *ti)
3059 checked_free(ti->subdir);
3060 checked_free(ti->fullpath);
3061 checked_free(ti->basepath);
3062 checked_free(ti->identifier);
3064 checked_free(ti->name);
3065 checked_free(ti->name_sorting);
3066 checked_free(ti->author);
3067 checked_free(ti->year);
3069 checked_free(ti->program_title);
3070 checked_free(ti->program_copyright);
3071 checked_free(ti->program_company);
3073 checked_free(ti->class_desc);
3075 checked_free(ti->infotext);
3077 if (ti->type == TREE_TYPE_LEVEL_DIR)
3079 checked_free(ti->imported_from);
3080 checked_free(ti->imported_by);
3081 checked_free(ti->tested_by);
3083 checked_free(ti->graphics_set_ecs);
3084 checked_free(ti->graphics_set_aga);
3085 checked_free(ti->graphics_set);
3086 checked_free(ti->sounds_set_default);
3087 checked_free(ti->sounds_set_lowpass);
3088 checked_free(ti->sounds_set);
3089 checked_free(ti->music_set);
3091 checked_free(ti->graphics_path);
3092 checked_free(ti->sounds_path);
3093 checked_free(ti->music_path);
3095 checked_free(ti->level_filename);
3096 checked_free(ti->level_filetype);
3098 checked_free(ti->special_flags);
3101 // recursively free child node
3103 freeTreeInfo(ti->node_group);
3105 // recursively free next node
3107 freeTreeInfo(ti->next);
3112 void setSetupInfo(struct TokenInfo *token_info,
3113 int token_nr, char *token_value)
3115 int token_type = token_info[token_nr].type;
3116 void *setup_value = token_info[token_nr].value;
3118 if (token_value == NULL)
3121 // set setup field to corresponding token value
3126 *(boolean *)setup_value = get_boolean_from_string(token_value);
3130 *(int *)setup_value = get_switch3_from_string(token_value);
3134 *(Key *)setup_value = getKeyFromKeyName(token_value);
3138 *(Key *)setup_value = getKeyFromX11KeyName(token_value);
3142 *(int *)setup_value = get_integer_from_string(token_value);
3146 checked_free(*(char **)setup_value);
3147 *(char **)setup_value = getStringCopy(token_value);
3151 *(int *)setup_value = get_player_nr_from_string(token_value);
3159 static int compareTreeInfoEntries(const void *object1, const void *object2)
3161 const TreeInfo *entry1 = *((TreeInfo **)object1);
3162 const TreeInfo *entry2 = *((TreeInfo **)object2);
3163 int tree_sorting1 = TREE_SORTING(entry1);
3164 int tree_sorting2 = TREE_SORTING(entry2);
3166 if (tree_sorting1 != tree_sorting2)
3167 return (tree_sorting1 - tree_sorting2);
3169 return strcasecmp(entry1->name_sorting, entry2->name_sorting);
3172 static TreeInfo *createParentTreeInfoNode(TreeInfo *node_parent)
3176 if (node_parent == NULL)
3179 ti_new = newTreeInfo();
3180 setTreeInfoToDefaults(ti_new, node_parent->type);
3182 ti_new->node_parent = node_parent;
3183 ti_new->parent_link = TRUE;
3185 setString(&ti_new->identifier, node_parent->identifier);
3186 setString(&ti_new->name, BACKLINK_TEXT_PARENT);
3187 setString(&ti_new->name_sorting, ti_new->name);
3189 setString(&ti_new->subdir, STRING_PARENT_DIRECTORY);
3190 setString(&ti_new->fullpath, node_parent->fullpath);
3192 ti_new->sort_priority = LEVELCLASS_PARENT;
3193 ti_new->latest_engine = node_parent->latest_engine;
3195 setString(&ti_new->class_desc, getLevelClassDescription(ti_new));
3197 pushTreeInfo(&node_parent->node_group, ti_new);
3202 static TreeInfo *createTopTreeInfoNode(TreeInfo *node_first)
3204 if (node_first == NULL)
3207 TreeInfo *ti_new = newTreeInfo();
3208 int type = node_first->type;
3210 setTreeInfoToDefaults(ti_new, type);
3212 ti_new->node_parent = NULL;
3213 ti_new->parent_link = FALSE;
3215 setString(&ti_new->identifier, "top_tree_node");
3216 setString(&ti_new->name, TREE_INFOTEXT(type));
3217 setString(&ti_new->name_sorting, ti_new->name);
3219 setString(&ti_new->subdir, STRING_TOP_DIRECTORY);
3220 setString(&ti_new->fullpath, ".");
3222 ti_new->sort_priority = LEVELCLASS_TOP;
3223 ti_new->latest_engine = node_first->latest_engine;
3225 setString(&ti_new->class_desc, TREE_INFOTEXT(type));
3227 ti_new->node_group = node_first;
3228 ti_new->level_group = TRUE;
3230 TreeInfo *ti_new2 = createParentTreeInfoNode(ti_new);
3232 setString(&ti_new2->name, TREE_BACKLINK_TEXT(type));
3233 setString(&ti_new2->name_sorting, ti_new2->name);
3238 static void setTreeInfoParentNodes(TreeInfo *node, TreeInfo *node_parent)
3242 if (node->node_group)
3243 setTreeInfoParentNodes(node->node_group, node);
3245 node->node_parent = node_parent;
3251 TreeInfo *addTopTreeInfoNode(TreeInfo *node_first)
3253 // add top tree node with back link node in previous tree
3254 node_first = createTopTreeInfoNode(node_first);
3256 // set all parent links (back links) in complete tree
3257 setTreeInfoParentNodes(node_first, NULL);
3263 // ----------------------------------------------------------------------------
3264 // functions for handling level and custom artwork info cache
3265 // ----------------------------------------------------------------------------
3267 static void LoadArtworkInfoCache(void)
3269 InitCacheDirectory();
3271 if (artworkinfo_cache_old == NULL)
3273 char *filename = getPath2(getCacheDir(), ARTWORKINFO_CACHE_FILE);
3275 // try to load artwork info hash from already existing cache file
3276 artworkinfo_cache_old = loadSetupFileHash(filename);
3278 // try to get program version that artwork info cache was written with
3279 char *version = getHashEntry(artworkinfo_cache_old, "program.version");
3281 // check program version of artwork info cache against current version
3282 if (!strEqual(version, program.version_string))
3284 freeSetupFileHash(artworkinfo_cache_old);
3286 artworkinfo_cache_old = NULL;
3289 // if no artwork info cache file was found, start with empty hash
3290 if (artworkinfo_cache_old == NULL)
3291 artworkinfo_cache_old = newSetupFileHash();
3296 if (artworkinfo_cache_new == NULL)
3297 artworkinfo_cache_new = newSetupFileHash();
3299 update_artworkinfo_cache = FALSE;
3302 static void SaveArtworkInfoCache(void)
3304 if (!update_artworkinfo_cache)
3307 char *filename = getPath2(getCacheDir(), ARTWORKINFO_CACHE_FILE);
3309 InitCacheDirectory();
3311 saveSetupFileHash(artworkinfo_cache_new, filename);
3316 static char *getCacheTokenPrefix(char *prefix1, char *prefix2)
3318 static char *prefix = NULL;
3320 checked_free(prefix);
3322 prefix = getStringCat2WithSeparator(prefix1, prefix2, ".");
3327 // (identical to above function, but separate string buffer needed -- nasty)
3328 static char *getCacheToken(char *prefix, char *suffix)
3330 static char *token = NULL;
3332 checked_free(token);
3334 token = getStringCat2WithSeparator(prefix, suffix, ".");
3339 static char *getFileTimestampString(char *filename)
3341 return getStringCopy(i_to_a(getFileTimestampEpochSeconds(filename)));
3344 static boolean modifiedFileTimestamp(char *filename, char *timestamp_string)
3346 struct stat file_status;
3348 if (timestamp_string == NULL)
3351 if (!fileExists(filename)) // file does not exist
3352 return (atoi(timestamp_string) != 0);
3354 if (stat(filename, &file_status) != 0) // cannot stat file
3357 return (file_status.st_mtime != atoi(timestamp_string));
3360 static TreeInfo *getArtworkInfoCacheEntry(LevelDirTree *level_node, int type)
3362 char *identifier = level_node->subdir;
3363 char *type_string = ARTWORK_DIRECTORY(type);
3364 char *token_prefix = getCacheTokenPrefix(type_string, identifier);
3365 char *token_main = getCacheToken(token_prefix, "CACHED");
3366 char *cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
3367 boolean cached = (cache_entry != NULL && strEqual(cache_entry, "true"));
3368 TreeInfo *artwork_info = NULL;
3370 if (!use_artworkinfo_cache)
3373 if (optional_tokens_hash == NULL)
3377 // create hash from list of optional tokens (for quick access)
3378 optional_tokens_hash = newSetupFileHash();
3379 for (i = 0; optional_tokens[i] != NULL; i++)
3380 setHashEntry(optional_tokens_hash, optional_tokens[i], "");
3387 artwork_info = newTreeInfo();
3388 setTreeInfoToDefaults(artwork_info, type);
3390 // set all structure fields according to the token/value pairs
3391 ldi = *artwork_info;
3392 for (i = 0; artworkinfo_tokens[i].type != -1; i++)
3394 char *token_suffix = artworkinfo_tokens[i].text;
3395 char *token = getCacheToken(token_prefix, token_suffix);
3396 char *value = getHashEntry(artworkinfo_cache_old, token);
3398 (getHashEntry(optional_tokens_hash, token_suffix) != NULL);
3400 setSetupInfo(artworkinfo_tokens, i, value);
3402 // check if cache entry for this item is mandatory, but missing
3403 if (value == NULL && !optional)
3405 Warn("missing cache entry '%s'", token);
3411 *artwork_info = ldi;
3416 char *filename_levelinfo = getPath2(getLevelDirFromTreeInfo(level_node),
3417 LEVELINFO_FILENAME);
3418 char *filename_artworkinfo = getPath2(getSetupArtworkDir(artwork_info),
3419 ARTWORKINFO_FILENAME(type));
3421 // check if corresponding "levelinfo.conf" file has changed
3422 token_main = getCacheToken(token_prefix, "TIMESTAMP_LEVELINFO");
3423 cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
3425 if (modifiedFileTimestamp(filename_levelinfo, cache_entry))
3428 // check if corresponding "<artworkinfo>.conf" file has changed
3429 token_main = getCacheToken(token_prefix, "TIMESTAMP_ARTWORKINFO");
3430 cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
3432 if (modifiedFileTimestamp(filename_artworkinfo, cache_entry))
3435 checked_free(filename_levelinfo);
3436 checked_free(filename_artworkinfo);
3439 if (!cached && artwork_info != NULL)
3441 freeTreeInfo(artwork_info);
3446 return artwork_info;
3449 static void setArtworkInfoCacheEntry(TreeInfo *artwork_info,
3450 LevelDirTree *level_node, int type)
3452 char *identifier = level_node->subdir;
3453 char *type_string = ARTWORK_DIRECTORY(type);
3454 char *token_prefix = getCacheTokenPrefix(type_string, identifier);
3455 char *token_main = getCacheToken(token_prefix, "CACHED");
3456 boolean set_cache_timestamps = TRUE;
3459 setHashEntry(artworkinfo_cache_new, token_main, "true");
3461 if (set_cache_timestamps)
3463 char *filename_levelinfo = getPath2(getLevelDirFromTreeInfo(level_node),
3464 LEVELINFO_FILENAME);
3465 char *filename_artworkinfo = getPath2(getSetupArtworkDir(artwork_info),
3466 ARTWORKINFO_FILENAME(type));
3467 char *timestamp_levelinfo = getFileTimestampString(filename_levelinfo);
3468 char *timestamp_artworkinfo = getFileTimestampString(filename_artworkinfo);
3470 token_main = getCacheToken(token_prefix, "TIMESTAMP_LEVELINFO");
3471 setHashEntry(artworkinfo_cache_new, token_main, timestamp_levelinfo);
3473 token_main = getCacheToken(token_prefix, "TIMESTAMP_ARTWORKINFO");
3474 setHashEntry(artworkinfo_cache_new, token_main, timestamp_artworkinfo);
3476 checked_free(filename_levelinfo);
3477 checked_free(filename_artworkinfo);
3478 checked_free(timestamp_levelinfo);
3479 checked_free(timestamp_artworkinfo);
3482 ldi = *artwork_info;
3483 for (i = 0; artworkinfo_tokens[i].type != -1; i++)
3485 char *token = getCacheToken(token_prefix, artworkinfo_tokens[i].text);
3486 char *value = getSetupValue(artworkinfo_tokens[i].type,
3487 artworkinfo_tokens[i].value);
3489 setHashEntry(artworkinfo_cache_new, token, value);
3494 // ----------------------------------------------------------------------------
3495 // functions for loading level info and custom artwork info
3496 // ----------------------------------------------------------------------------
3498 int GetZipFileTreeType(char *zip_filename)
3500 static char *top_dir_path = NULL;
3501 static char *top_dir_conf_filename[NUM_BASE_TREE_TYPES] = { NULL };
3502 static char *conf_basename[NUM_BASE_TREE_TYPES] =
3504 GRAPHICSINFO_FILENAME,
3505 SOUNDSINFO_FILENAME,
3511 checked_free(top_dir_path);
3512 top_dir_path = NULL;
3514 for (j = 0; j < NUM_BASE_TREE_TYPES; j++)
3516 checked_free(top_dir_conf_filename[j]);
3517 top_dir_conf_filename[j] = NULL;
3520 char **zip_entries = zip_list(zip_filename);
3522 // check if zip file successfully opened
3523 if (zip_entries == NULL || zip_entries[0] == NULL)
3524 return TREE_TYPE_UNDEFINED;
3526 // first zip file entry is expected to be top level directory
3527 char *top_dir = zip_entries[0];
3529 // check if valid top level directory found in zip file
3530 if (!strSuffix(top_dir, "/"))
3531 return TREE_TYPE_UNDEFINED;
3533 // get filenames of valid configuration files in top level directory
3534 for (j = 0; j < NUM_BASE_TREE_TYPES; j++)
3535 top_dir_conf_filename[j] = getStringCat2(top_dir, conf_basename[j]);
3537 int tree_type = TREE_TYPE_UNDEFINED;
3540 while (zip_entries[e] != NULL)
3542 // check if every zip file entry is below top level directory
3543 if (!strPrefix(zip_entries[e], top_dir))
3544 return TREE_TYPE_UNDEFINED;
3546 // check if this zip file entry is a valid configuration filename
3547 for (j = 0; j < NUM_BASE_TREE_TYPES; j++)
3549 if (strEqual(zip_entries[e], top_dir_conf_filename[j]))
3551 // only exactly one valid configuration file allowed
3552 if (tree_type != TREE_TYPE_UNDEFINED)
3553 return TREE_TYPE_UNDEFINED;
3565 static boolean CheckZipFileForDirectory(char *zip_filename, char *directory,
3568 static char *top_dir_path = NULL;
3569 static char *top_dir_conf_filename = NULL;
3571 checked_free(top_dir_path);
3572 checked_free(top_dir_conf_filename);
3574 top_dir_path = NULL;
3575 top_dir_conf_filename = NULL;
3577 char *conf_basename = (tree_type == TREE_TYPE_LEVEL_DIR ? LEVELINFO_FILENAME :
3578 ARTWORKINFO_FILENAME(tree_type));
3580 // check if valid configuration filename determined
3581 if (conf_basename == NULL || strEqual(conf_basename, ""))
3584 char **zip_entries = zip_list(zip_filename);
3586 // check if zip file successfully opened
3587 if (zip_entries == NULL || zip_entries[0] == NULL)
3590 // first zip file entry is expected to be top level directory
3591 char *top_dir = zip_entries[0];
3593 // check if valid top level directory found in zip file
3594 if (!strSuffix(top_dir, "/"))
3597 // get path of extracted top level directory
3598 top_dir_path = getPath2(directory, top_dir);
3600 // remove trailing directory separator from top level directory path
3601 // (required to be able to check for file and directory in next step)
3602 top_dir_path[strlen(top_dir_path) - 1] = '\0';
3604 // check if zip file's top level directory already exists in target directory
3605 if (fileExists(top_dir_path)) // (checks for file and directory)
3608 // get filename of configuration file in top level directory
3609 top_dir_conf_filename = getStringCat2(top_dir, conf_basename);
3611 boolean found_top_dir_conf_filename = FALSE;
3614 while (zip_entries[i] != NULL)
3616 // check if every zip file entry is below top level directory
3617 if (!strPrefix(zip_entries[i], top_dir))
3620 // check if this zip file entry is the configuration filename
3621 if (strEqual(zip_entries[i], top_dir_conf_filename))
3622 found_top_dir_conf_filename = TRUE;
3627 // check if valid configuration filename was found in zip file
3628 if (!found_top_dir_conf_filename)
3634 char *ExtractZipFileIntoDirectory(char *zip_filename, char *directory,
3637 boolean zip_file_valid = CheckZipFileForDirectory(zip_filename, directory,
3640 if (!zip_file_valid)
3642 Warn("zip file '%s' rejected!", zip_filename);
3647 char **zip_entries = zip_extract(zip_filename, directory);
3649 if (zip_entries == NULL)
3651 Warn("zip file '%s' could not be extracted!", zip_filename);
3656 Info("zip file '%s' successfully extracted!", zip_filename);
3658 // first zip file entry contains top level directory
3659 char *top_dir = zip_entries[0];
3661 // remove trailing directory separator from top level directory
3662 top_dir[strlen(top_dir) - 1] = '\0';
3667 static void ProcessZipFilesInDirectory(char *directory, int tree_type)
3670 DirectoryEntry *dir_entry;
3672 if ((dir = openDirectory(directory)) == NULL)
3674 // display error if directory is main "options.graphics_directory" etc.
3675 if (tree_type == TREE_TYPE_LEVEL_DIR ||
3676 directory == OPTIONS_ARTWORK_DIRECTORY(tree_type))
3677 Warn("cannot read directory '%s'", directory);
3682 while ((dir_entry = readDirectory(dir)) != NULL) // loop all entries
3684 // skip non-zip files (and also directories with zip extension)
3685 if (!strSuffixLower(dir_entry->basename, ".zip") || dir_entry->is_directory)
3688 char *zip_filename = getPath2(directory, dir_entry->basename);
3689 char *zip_filename_extracted = getStringCat2(zip_filename, ".extracted");
3690 char *zip_filename_rejected = getStringCat2(zip_filename, ".rejected");
3692 // check if zip file hasn't already been extracted or rejected
3693 if (!fileExists(zip_filename_extracted) &&
3694 !fileExists(zip_filename_rejected))
3696 char *top_dir = ExtractZipFileIntoDirectory(zip_filename, directory,
3698 char *marker_filename = (top_dir != NULL ? zip_filename_extracted :
3699 zip_filename_rejected);
3702 // create empty file to mark zip file as extracted or rejected
3703 if ((marker_file = fopen(marker_filename, MODE_WRITE)))
3704 fclose(marker_file);
3707 free(zip_filename_extracted);
3708 free(zip_filename_rejected);
3712 closeDirectory(dir);
3715 // forward declaration for recursive call by "LoadLevelInfoFromLevelDir()"
3716 static void LoadLevelInfoFromLevelDir(TreeInfo **, TreeInfo *, char *);
3718 static boolean LoadLevelInfoFromLevelConf(TreeInfo **node_first,
3719 TreeInfo *node_parent,
3720 char *level_directory,
3721 char *directory_name)
3723 char *directory_path = getPath2(level_directory, directory_name);
3724 char *filename = getPath2(directory_path, LEVELINFO_FILENAME);
3725 SetupFileHash *setup_file_hash;
3726 LevelDirTree *leveldir_new = NULL;
3729 // unless debugging, silently ignore directories without "levelinfo.conf"
3730 if (!options.debug && !fileExists(filename))
3732 free(directory_path);
3738 setup_file_hash = loadSetupFileHash(filename);
3740 if (setup_file_hash == NULL)
3742 #if DEBUG_NO_CONFIG_FILE
3743 Debug("setup", "ignoring level directory '%s'", directory_path);
3746 free(directory_path);
3752 leveldir_new = newTreeInfo();
3755 setTreeInfoToDefaultsFromParent(leveldir_new, node_parent);
3757 setTreeInfoToDefaults(leveldir_new, TREE_TYPE_LEVEL_DIR);
3759 leveldir_new->subdir = getStringCopy(directory_name);
3761 // set all structure fields according to the token/value pairs
3762 ldi = *leveldir_new;
3763 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
3764 setSetupInfo(levelinfo_tokens, i,
3765 getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
3766 *leveldir_new = ldi;
3768 if (strEqual(leveldir_new->name, ANONYMOUS_NAME))
3769 setString(&leveldir_new->name, leveldir_new->subdir);
3771 if (leveldir_new->identifier == NULL)
3772 leveldir_new->identifier = getStringCopy(leveldir_new->subdir);
3774 if (leveldir_new->name_sorting == NULL)
3775 leveldir_new->name_sorting = getStringCopy(leveldir_new->name);
3777 if (node_parent == NULL) // top level group
3779 leveldir_new->basepath = getStringCopy(level_directory);
3780 leveldir_new->fullpath = getStringCopy(leveldir_new->subdir);
3782 else // sub level group
3784 leveldir_new->basepath = getStringCopy(node_parent->basepath);
3785 leveldir_new->fullpath = getPath2(node_parent->fullpath, directory_name);
3788 leveldir_new->last_level =
3789 leveldir_new->first_level + leveldir_new->levels - 1;
3791 leveldir_new->in_user_dir =
3792 (!strEqual(leveldir_new->basepath, options.level_directory));
3794 // adjust some settings if user's private level directory was detected
3795 if (leveldir_new->sort_priority == LEVELCLASS_UNDEFINED &&
3796 leveldir_new->in_user_dir &&
3797 (strEqual(leveldir_new->subdir, getLoginName()) ||
3798 strEqual(leveldir_new->name, getLoginName()) ||
3799 strEqual(leveldir_new->author, getRealName())))
3801 leveldir_new->sort_priority = LEVELCLASS_PRIVATE_START;
3802 leveldir_new->readonly = FALSE;
3805 leveldir_new->user_defined =
3806 (leveldir_new->in_user_dir && IS_LEVELCLASS_PRIVATE(leveldir_new));
3808 setString(&leveldir_new->class_desc, getLevelClassDescription(leveldir_new));
3810 leveldir_new->handicap_level = // set handicap to default value
3811 (leveldir_new->user_defined || !leveldir_new->handicap ?
3812 leveldir_new->last_level : leveldir_new->first_level);
3814 DrawInitTextItem(leveldir_new->name);
3816 pushTreeInfo(node_first, leveldir_new);
3818 freeSetupFileHash(setup_file_hash);
3820 if (leveldir_new->level_group)
3822 // create node to link back to current level directory
3823 createParentTreeInfoNode(leveldir_new);
3825 // recursively step into sub-directory and look for more level series
3826 LoadLevelInfoFromLevelDir(&leveldir_new->node_group,
3827 leveldir_new, directory_path);
3830 free(directory_path);
3836 static void LoadLevelInfoFromLevelDir(TreeInfo **node_first,
3837 TreeInfo *node_parent,
3838 char *level_directory)
3840 // ---------- 1st stage: process any level set zip files ----------
3842 ProcessZipFilesInDirectory(level_directory, TREE_TYPE_LEVEL_DIR);
3844 // ---------- 2nd stage: check for level set directories ----------
3847 DirectoryEntry *dir_entry;
3848 boolean valid_entry_found = FALSE;
3850 if ((dir = openDirectory(level_directory)) == NULL)
3852 Warn("cannot read level directory '%s'", level_directory);
3857 while ((dir_entry = readDirectory(dir)) != NULL) // loop all entries
3859 char *directory_name = dir_entry->basename;
3860 char *directory_path = getPath2(level_directory, directory_name);
3862 // skip entries for current and parent directory
3863 if (strEqual(directory_name, ".") ||
3864 strEqual(directory_name, ".."))
3866 free(directory_path);
3871 // find out if directory entry is itself a directory
3872 if (!dir_entry->is_directory) // not a directory
3874 free(directory_path);
3879 free(directory_path);
3881 if (strEqual(directory_name, GRAPHICS_DIRECTORY) ||
3882 strEqual(directory_name, SOUNDS_DIRECTORY) ||
3883 strEqual(directory_name, MUSIC_DIRECTORY))
3886 valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
3891 closeDirectory(dir);
3893 // special case: top level directory may directly contain "levelinfo.conf"
3894 if (node_parent == NULL && !valid_entry_found)
3896 // check if this directory directly contains a file "levelinfo.conf"
3897 valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
3898 level_directory, ".");
3901 boolean valid_entry_expected =
3902 (strEqual(level_directory, options.level_directory) ||
3903 setup.internal.create_user_levelset);
3905 if (valid_entry_expected && !valid_entry_found)
3906 Warn("cannot find any valid level series in directory '%s'",
3910 boolean AdjustGraphicsForEMC(void)
3912 boolean settings_changed = FALSE;
3914 settings_changed |= adjustTreeGraphicsForEMC(leveldir_first_all);
3915 settings_changed |= adjustTreeGraphicsForEMC(leveldir_first);
3917 return settings_changed;
3920 boolean AdjustSoundsForEMC(void)
3922 boolean settings_changed = FALSE;
3924 settings_changed |= adjustTreeSoundsForEMC(leveldir_first_all);
3925 settings_changed |= adjustTreeSoundsForEMC(leveldir_first);
3927 return settings_changed;
3930 void LoadLevelInfo(void)
3932 InitUserLevelDirectory(getLoginName());
3934 DrawInitTextHead("Loading level series");
3936 LoadLevelInfoFromLevelDir(&leveldir_first, NULL, options.level_directory);
3937 LoadLevelInfoFromLevelDir(&leveldir_first, NULL, getUserLevelDir(NULL));
3939 leveldir_first = createTopTreeInfoNode(leveldir_first);
3941 /* after loading all level set information, clone the level directory tree
3942 and remove all level sets without levels (these may still contain artwork
3943 to be offered in the setup menu as "custom artwork", and are therefore
3944 checked for existing artwork in the function "LoadLevelArtworkInfo()") */
3945 leveldir_first_all = leveldir_first;
3946 cloneTree(&leveldir_first, leveldir_first_all, TRUE);
3948 AdjustGraphicsForEMC();
3949 AdjustSoundsForEMC();
3951 // before sorting, the first entries will be from the user directory
3952 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
3954 if (leveldir_first == NULL)
3955 Fail("cannot find any valid level series in any directory");
3957 sortTreeInfo(&leveldir_first);
3959 #if ENABLE_UNUSED_CODE
3960 dumpTreeInfo(leveldir_first, 0);
3964 static boolean LoadArtworkInfoFromArtworkConf(TreeInfo **node_first,
3965 TreeInfo *node_parent,
3966 char *base_directory,
3967 char *directory_name, int type)
3969 char *directory_path = getPath2(base_directory, directory_name);
3970 char *filename = getPath2(directory_path, ARTWORKINFO_FILENAME(type));
3971 SetupFileHash *setup_file_hash = NULL;
3972 TreeInfo *artwork_new = NULL;
3975 if (fileExists(filename))
3976 setup_file_hash = loadSetupFileHash(filename);
3978 if (setup_file_hash == NULL) // no config file -- look for artwork files
3981 DirectoryEntry *dir_entry;
3982 boolean valid_file_found = FALSE;
3984 if ((dir = openDirectory(directory_path)) != NULL)
3986 while ((dir_entry = readDirectory(dir)) != NULL)
3988 if (FileIsArtworkType(dir_entry->filename, type))
3990 valid_file_found = TRUE;
3996 closeDirectory(dir);
3999 if (!valid_file_found)
4001 #if DEBUG_NO_CONFIG_FILE
4002 if (!strEqual(directory_name, "."))
4003 Debug("setup", "ignoring artwork directory '%s'", directory_path);
4006 free(directory_path);
4013 artwork_new = newTreeInfo();
4016 setTreeInfoToDefaultsFromParent(artwork_new, node_parent);
4018 setTreeInfoToDefaults(artwork_new, type);
4020 artwork_new->subdir = getStringCopy(directory_name);
4022 if (setup_file_hash) // (before defining ".color" and ".class_desc")
4024 // set all structure fields according to the token/value pairs
4026 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
4027 setSetupInfo(levelinfo_tokens, i,
4028 getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
4031 if (strEqual(artwork_new->name, ANONYMOUS_NAME))
4032 setString(&artwork_new->name, artwork_new->subdir);
4034 if (artwork_new->identifier == NULL)
4035 artwork_new->identifier = getStringCopy(artwork_new->subdir);
4037 if (artwork_new->name_sorting == NULL)
4038 artwork_new->name_sorting = getStringCopy(artwork_new->name);
4041 if (node_parent == NULL) // top level group
4043 artwork_new->basepath = getStringCopy(base_directory);
4044 artwork_new->fullpath = getStringCopy(artwork_new->subdir);
4046 else // sub level group
4048 artwork_new->basepath = getStringCopy(node_parent->basepath);
4049 artwork_new->fullpath = getPath2(node_parent->fullpath, directory_name);
4052 artwork_new->in_user_dir =
4053 (!strEqual(artwork_new->basepath, OPTIONS_ARTWORK_DIRECTORY(type)));
4055 setString(&artwork_new->class_desc, getLevelClassDescription(artwork_new));
4057 if (setup_file_hash == NULL) // (after determining ".user_defined")
4059 if (strEqual(artwork_new->subdir, "."))
4061 if (artwork_new->user_defined)
4063 setString(&artwork_new->identifier, "private");
4064 artwork_new->sort_priority = ARTWORKCLASS_PRIVATE;
4068 setString(&artwork_new->identifier, "classic");
4069 artwork_new->sort_priority = ARTWORKCLASS_CLASSICS;
4072 setString(&artwork_new->class_desc,
4073 getLevelClassDescription(artwork_new));
4077 setString(&artwork_new->identifier, artwork_new->subdir);
4080 setString(&artwork_new->name, artwork_new->identifier);
4081 setString(&artwork_new->name_sorting, artwork_new->name);
4084 pushTreeInfo(node_first, artwork_new);
4086 freeSetupFileHash(setup_file_hash);
4088 free(directory_path);
4094 static void LoadArtworkInfoFromArtworkDir(TreeInfo **node_first,
4095 TreeInfo *node_parent,
4096 char *base_directory, int type)
4098 // ---------- 1st stage: process any artwork set zip files ----------
4100 ProcessZipFilesInDirectory(base_directory, type);
4102 // ---------- 2nd stage: check for artwork set directories ----------
4105 DirectoryEntry *dir_entry;
4106 boolean valid_entry_found = FALSE;
4108 if ((dir = openDirectory(base_directory)) == NULL)
4110 // display error if directory is main "options.graphics_directory" etc.
4111 if (base_directory == OPTIONS_ARTWORK_DIRECTORY(type))
4112 Warn("cannot read directory '%s'", base_directory);
4117 while ((dir_entry = readDirectory(dir)) != NULL) // loop all entries
4119 char *directory_name = dir_entry->basename;
4120 char *directory_path = getPath2(base_directory, directory_name);
4122 // skip directory entries for current and parent directory
4123 if (strEqual(directory_name, ".") ||
4124 strEqual(directory_name, ".."))
4126 free(directory_path);
4131 // skip directory entries which are not a directory
4132 if (!dir_entry->is_directory) // not a directory
4134 free(directory_path);
4139 free(directory_path);
4141 // check if this directory contains artwork with or without config file
4142 valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
4144 directory_name, type);
4147 closeDirectory(dir);
4149 // check if this directory directly contains artwork itself
4150 valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
4151 base_directory, ".",
4153 if (!valid_entry_found)
4154 Warn("cannot find any valid artwork in directory '%s'", base_directory);
4157 static TreeInfo *getDummyArtworkInfo(int type)
4159 // this is only needed when there is completely no artwork available
4160 TreeInfo *artwork_new = newTreeInfo();
4162 setTreeInfoToDefaults(artwork_new, type);
4164 setString(&artwork_new->subdir, UNDEFINED_FILENAME);
4165 setString(&artwork_new->fullpath, UNDEFINED_FILENAME);
4166 setString(&artwork_new->basepath, UNDEFINED_FILENAME);
4168 setString(&artwork_new->identifier, UNDEFINED_FILENAME);
4169 setString(&artwork_new->name, UNDEFINED_FILENAME);
4170 setString(&artwork_new->name_sorting, UNDEFINED_FILENAME);
4175 void SetCurrentArtwork(int type)
4177 ArtworkDirTree **current_ptr = ARTWORK_CURRENT_PTR(artwork, type);
4178 ArtworkDirTree *first_node = ARTWORK_FIRST_NODE(artwork, type);
4179 char *setup_set = SETUP_ARTWORK_SET(setup, type);
4180 char *default_subdir = ARTWORK_DEFAULT_SUBDIR(type);
4182 // set current artwork to artwork configured in setup menu
4183 *current_ptr = getTreeInfoFromIdentifier(first_node, setup_set);
4185 // if not found, set current artwork to default artwork
4186 if (*current_ptr == NULL)
4187 *current_ptr = getTreeInfoFromIdentifier(first_node, default_subdir);
4189 // if not found, set current artwork to first artwork in tree
4190 if (*current_ptr == NULL)
4191 *current_ptr = getFirstValidTreeInfoEntry(first_node);
4194 void ChangeCurrentArtworkIfNeeded(int type)
4196 char *current_identifier = ARTWORK_CURRENT_IDENTIFIER(artwork, type);
4197 char *setup_set = SETUP_ARTWORK_SET(setup, type);
4199 if (!strEqual(current_identifier, setup_set))
4200 SetCurrentArtwork(type);
4203 void LoadArtworkInfo(void)
4205 LoadArtworkInfoCache();
4207 DrawInitTextHead("Looking for custom artwork");
4209 LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
4210 options.graphics_directory,
4211 TREE_TYPE_GRAPHICS_DIR);
4212 LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
4213 getUserGraphicsDir(),
4214 TREE_TYPE_GRAPHICS_DIR);
4216 LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
4217 options.sounds_directory,
4218 TREE_TYPE_SOUNDS_DIR);
4219 LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
4221 TREE_TYPE_SOUNDS_DIR);
4223 LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
4224 options.music_directory,
4225 TREE_TYPE_MUSIC_DIR);
4226 LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
4228 TREE_TYPE_MUSIC_DIR);
4230 if (artwork.gfx_first == NULL)
4231 artwork.gfx_first = getDummyArtworkInfo(TREE_TYPE_GRAPHICS_DIR);
4232 if (artwork.snd_first == NULL)
4233 artwork.snd_first = getDummyArtworkInfo(TREE_TYPE_SOUNDS_DIR);
4234 if (artwork.mus_first == NULL)
4235 artwork.mus_first = getDummyArtworkInfo(TREE_TYPE_MUSIC_DIR);
4237 // before sorting, the first entries will be from the user directory
4238 SetCurrentArtwork(ARTWORK_TYPE_GRAPHICS);
4239 SetCurrentArtwork(ARTWORK_TYPE_SOUNDS);
4240 SetCurrentArtwork(ARTWORK_TYPE_MUSIC);
4242 artwork.gfx_current_identifier = artwork.gfx_current->identifier;
4243 artwork.snd_current_identifier = artwork.snd_current->identifier;
4244 artwork.mus_current_identifier = artwork.mus_current->identifier;
4246 #if ENABLE_UNUSED_CODE
4247 Debug("setup:LoadArtworkInfo", "graphics set == %s",
4248 artwork.gfx_current_identifier);
4249 Debug("setup:LoadArtworkInfo", "sounds set == %s",
4250 artwork.snd_current_identifier);
4251 Debug("setup:LoadArtworkInfo", "music set == %s",
4252 artwork.mus_current_identifier);
4255 sortTreeInfo(&artwork.gfx_first);
4256 sortTreeInfo(&artwork.snd_first);
4257 sortTreeInfo(&artwork.mus_first);
4259 #if ENABLE_UNUSED_CODE
4260 dumpTreeInfo(artwork.gfx_first, 0);
4261 dumpTreeInfo(artwork.snd_first, 0);
4262 dumpTreeInfo(artwork.mus_first, 0);
4266 static void MoveArtworkInfoIntoSubTree(ArtworkDirTree **artwork_node)
4268 ArtworkDirTree *artwork_new = newTreeInfo();
4269 char *top_node_name = "standalone artwork";
4271 setTreeInfoToDefaults(artwork_new, (*artwork_node)->type);
4273 artwork_new->level_group = TRUE;
4275 setString(&artwork_new->identifier, top_node_name);
4276 setString(&artwork_new->name, top_node_name);
4277 setString(&artwork_new->name_sorting, top_node_name);
4279 // create node to link back to current custom artwork directory
4280 createParentTreeInfoNode(artwork_new);
4282 // move existing custom artwork tree into newly created sub-tree
4283 artwork_new->node_group->next = *artwork_node;
4285 // change custom artwork tree to contain only newly created node
4286 *artwork_node = artwork_new;
4289 static void LoadArtworkInfoFromLevelInfoExt(ArtworkDirTree **artwork_node,
4290 ArtworkDirTree *node_parent,
4291 LevelDirTree *level_node,
4292 boolean empty_level_set_mode)
4294 int type = (*artwork_node)->type;
4296 // recursively check all level directories for artwork sub-directories
4300 boolean empty_level_set = (level_node->levels == 0);
4302 // check all tree entries for artwork, but skip parent link entries
4303 if (!level_node->parent_link && empty_level_set == empty_level_set_mode)
4305 TreeInfo *artwork_new = getArtworkInfoCacheEntry(level_node, type);
4306 boolean cached = (artwork_new != NULL);
4310 pushTreeInfo(artwork_node, artwork_new);
4314 TreeInfo *topnode_last = *artwork_node;
4315 char *path = getPath2(getLevelDirFromTreeInfo(level_node),
4316 ARTWORK_DIRECTORY(type));
4318 LoadArtworkInfoFromArtworkDir(artwork_node, NULL, path, type);
4320 if (topnode_last != *artwork_node) // check for newly added node
4322 artwork_new = *artwork_node;
4324 setString(&artwork_new->identifier, level_node->subdir);
4325 setString(&artwork_new->name, level_node->name);
4326 setString(&artwork_new->name_sorting, level_node->name_sorting);
4328 artwork_new->sort_priority = level_node->sort_priority;
4329 artwork_new->in_user_dir = level_node->in_user_dir;
4331 update_artworkinfo_cache = TRUE;
4337 // insert artwork info (from old cache or filesystem) into new cache
4338 if (artwork_new != NULL)
4339 setArtworkInfoCacheEntry(artwork_new, level_node, type);
4342 DrawInitTextItem(level_node->name);
4344 if (level_node->node_group != NULL)
4346 TreeInfo *artwork_new = newTreeInfo();
4349 setTreeInfoToDefaultsFromParent(artwork_new, node_parent);
4351 setTreeInfoToDefaults(artwork_new, type);
4353 artwork_new->level_group = TRUE;
4355 setString(&artwork_new->identifier, level_node->subdir);
4357 if (node_parent == NULL) // check for top tree node
4359 char *top_node_name = (empty_level_set_mode ?
4360 "artwork for certain level sets" :
4361 "artwork included in level sets");
4363 setString(&artwork_new->name, top_node_name);
4364 setString(&artwork_new->name_sorting, top_node_name);
4368 setString(&artwork_new->name, level_node->name);
4369 setString(&artwork_new->name_sorting, level_node->name_sorting);
4372 pushTreeInfo(artwork_node, artwork_new);
4374 // create node to link back to current custom artwork directory
4375 createParentTreeInfoNode(artwork_new);
4377 // recursively step into sub-directory and look for more custom artwork
4378 LoadArtworkInfoFromLevelInfoExt(&artwork_new->node_group, artwork_new,
4379 level_node->node_group,
4380 empty_level_set_mode);
4382 // if sub-tree has no custom artwork at all, remove it
4383 if (artwork_new->node_group->next == NULL)
4384 removeTreeInfo(artwork_node);
4387 level_node = level_node->next;
4391 static void LoadArtworkInfoFromLevelInfo(ArtworkDirTree **artwork_node)
4393 // move peviously loaded artwork tree into separate sub-tree
4394 MoveArtworkInfoIntoSubTree(artwork_node);
4396 // load artwork from level sets into separate sub-trees
4397 LoadArtworkInfoFromLevelInfoExt(artwork_node, NULL, leveldir_first_all, TRUE);
4398 LoadArtworkInfoFromLevelInfoExt(artwork_node, NULL, leveldir_first_all, FALSE);
4400 // add top tree node over all sub-trees and set parent links
4401 *artwork_node = addTopTreeInfoNode(*artwork_node);
4404 void LoadLevelArtworkInfo(void)
4406 print_timestamp_init("LoadLevelArtworkInfo");
4408 DrawInitTextHead("Looking for custom level artwork");
4410 print_timestamp_time("DrawTimeText");
4412 LoadArtworkInfoFromLevelInfo(&artwork.gfx_first);
4413 print_timestamp_time("LoadArtworkInfoFromLevelInfo (gfx)");
4414 LoadArtworkInfoFromLevelInfo(&artwork.snd_first);
4415 print_timestamp_time("LoadArtworkInfoFromLevelInfo (snd)");
4416 LoadArtworkInfoFromLevelInfo(&artwork.mus_first);
4417 print_timestamp_time("LoadArtworkInfoFromLevelInfo (mus)");
4419 SaveArtworkInfoCache();
4421 print_timestamp_time("SaveArtworkInfoCache");
4423 // needed for reloading level artwork not known at ealier stage
4424 ChangeCurrentArtworkIfNeeded(ARTWORK_TYPE_GRAPHICS);
4425 ChangeCurrentArtworkIfNeeded(ARTWORK_TYPE_SOUNDS);
4426 ChangeCurrentArtworkIfNeeded(ARTWORK_TYPE_MUSIC);
4428 print_timestamp_time("getTreeInfoFromIdentifier");
4430 sortTreeInfo(&artwork.gfx_first);
4431 sortTreeInfo(&artwork.snd_first);
4432 sortTreeInfo(&artwork.mus_first);
4434 print_timestamp_time("sortTreeInfo");
4436 #if ENABLE_UNUSED_CODE
4437 dumpTreeInfo(artwork.gfx_first, 0);
4438 dumpTreeInfo(artwork.snd_first, 0);
4439 dumpTreeInfo(artwork.mus_first, 0);
4442 print_timestamp_done("LoadLevelArtworkInfo");
4445 static boolean AddTreeSetToTreeInfoExt(TreeInfo *tree_node_old, char *tree_dir,
4446 char *tree_subdir_new, int type)
4448 if (tree_node_old == NULL)
4450 if (type == TREE_TYPE_LEVEL_DIR)
4452 // get level info tree node of personal user level set
4453 tree_node_old = getTreeInfoFromIdentifier(leveldir_first, getLoginName());
4455 // this may happen if "setup.internal.create_user_levelset" is FALSE
4456 // or if file "levelinfo.conf" is missing in personal user level set
4457 if (tree_node_old == NULL)
4458 tree_node_old = leveldir_first->node_group;
4462 // get artwork info tree node of first artwork set
4463 tree_node_old = ARTWORK_FIRST_NODE(artwork, type);
4467 if (tree_dir == NULL)
4468 tree_dir = TREE_USERDIR(type);
4470 if (tree_node_old == NULL ||
4472 tree_subdir_new == NULL) // should not happen
4475 int draw_deactivation_mask = GetDrawDeactivationMask();
4477 // override draw deactivation mask (temporarily disable drawing)
4478 SetDrawDeactivationMask(REDRAW_ALL);
4480 if (type == TREE_TYPE_LEVEL_DIR)
4482 // load new level set config and add it next to first user level set
4483 LoadLevelInfoFromLevelConf(&tree_node_old->next,
4484 tree_node_old->node_parent,
4485 tree_dir, tree_subdir_new);
4489 // load new artwork set config and add it next to first artwork set
4490 LoadArtworkInfoFromArtworkConf(&tree_node_old->next,
4491 tree_node_old->node_parent,
4492 tree_dir, tree_subdir_new, type);
4495 // set draw deactivation mask to previous value
4496 SetDrawDeactivationMask(draw_deactivation_mask);
4498 // get first node of level or artwork info tree
4499 TreeInfo **tree_node_first = TREE_FIRST_NODE_PTR(type);
4501 // get tree info node of newly added level or artwork set
4502 TreeInfo *tree_node_new = getTreeInfoFromIdentifier(*tree_node_first,
4505 if (tree_node_new == NULL) // should not happen
4508 // correct top link and parent node link of newly created tree node
4509 tree_node_new->node_top = tree_node_old->node_top;
4510 tree_node_new->node_parent = tree_node_old->node_parent;
4512 // sort tree info to adjust position of newly added tree set
4513 sortTreeInfo(tree_node_first);
4518 void AddTreeSetToTreeInfo(TreeInfo *tree_node, char *tree_dir,
4519 char *tree_subdir_new, int type)
4521 if (!AddTreeSetToTreeInfoExt(tree_node, tree_dir, tree_subdir_new, type))
4522 Fail("internal tree info structure corrupted -- aborting");
4525 void AddUserLevelSetToLevelInfo(char *level_subdir_new)
4527 AddTreeSetToTreeInfo(NULL, NULL, level_subdir_new, TREE_TYPE_LEVEL_DIR);
4530 char *getArtworkIdentifierForUserLevelSet(int type)
4532 char *classic_artwork_set = getClassicArtworkSet(type);
4534 // check for custom artwork configured in "levelinfo.conf"
4535 char *leveldir_artwork_set =
4536 *LEVELDIR_ARTWORK_SET_PTR(leveldir_current, type);
4537 boolean has_leveldir_artwork_set =
4538 (leveldir_artwork_set != NULL && !strEqual(leveldir_artwork_set,
4539 classic_artwork_set));
4541 // check for custom artwork in sub-directory "graphics" etc.
4542 TreeInfo *artwork_first_node = ARTWORK_FIRST_NODE(artwork, type);
4543 char *leveldir_identifier = leveldir_current->identifier;
4544 boolean has_artwork_subdir =
4545 (getTreeInfoFromIdentifier(artwork_first_node,
4546 leveldir_identifier) != NULL);
4548 return (has_leveldir_artwork_set ? leveldir_artwork_set :
4549 has_artwork_subdir ? leveldir_identifier :
4550 classic_artwork_set);
4553 TreeInfo *getArtworkTreeInfoForUserLevelSet(int type)
4555 char *artwork_set = getArtworkIdentifierForUserLevelSet(type);
4556 TreeInfo *artwork_first_node = ARTWORK_FIRST_NODE(artwork, type);
4557 TreeInfo *ti = getTreeInfoFromIdentifier(artwork_first_node, artwork_set);
4561 ti = getTreeInfoFromIdentifier(artwork_first_node,
4562 ARTWORK_DEFAULT_SUBDIR(type));
4564 Fail("cannot find default graphics -- should not happen");
4570 boolean checkIfCustomArtworkExistsForCurrentLevelSet(void)
4572 char *graphics_set =
4573 getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_GRAPHICS);
4575 getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_SOUNDS);
4577 getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_MUSIC);
4579 return (!strEqual(graphics_set, GFX_CLASSIC_SUBDIR) ||
4580 !strEqual(sounds_set, SND_CLASSIC_SUBDIR) ||
4581 !strEqual(music_set, MUS_CLASSIC_SUBDIR));
4584 boolean UpdateUserLevelSet(char *level_subdir, char *level_name,
4585 char *level_author, int num_levels)
4587 char *filename = getPath2(getUserLevelDir(level_subdir), LEVELINFO_FILENAME);
4588 char *filename_tmp = getStringCat2(filename, ".tmp");
4590 FILE *file_tmp = NULL;
4591 char line[MAX_LINE_LEN];
4592 boolean success = FALSE;
4593 LevelDirTree *leveldir = getTreeInfoFromIdentifier(leveldir_first,
4595 // update values in level directory tree
4597 if (level_name != NULL)
4598 setString(&leveldir->name, level_name);
4600 if (level_author != NULL)
4601 setString(&leveldir->author, level_author);
4603 if (num_levels != -1)
4604 leveldir->levels = num_levels;
4606 // update values that depend on other values
4608 setString(&leveldir->name_sorting, leveldir->name);
4610 leveldir->last_level = leveldir->first_level + leveldir->levels - 1;
4612 // sort order of level sets may have changed
4613 sortTreeInfo(&leveldir_first);
4615 if ((file = fopen(filename, MODE_READ)) &&
4616 (file_tmp = fopen(filename_tmp, MODE_WRITE)))
4618 while (fgets(line, MAX_LINE_LEN, file))
4620 if (strPrefix(line, "name:") && level_name != NULL)
4621 fprintf(file_tmp, "%-32s%s\n", "name:", level_name);
4622 else if (strPrefix(line, "author:") && level_author != NULL)
4623 fprintf(file_tmp, "%-32s%s\n", "author:", level_author);
4624 else if (strPrefix(line, "levels:") && num_levels != -1)
4625 fprintf(file_tmp, "%-32s%d\n", "levels:", num_levels);
4627 fputs(line, file_tmp);
4640 success = (rename(filename_tmp, filename) == 0);
4648 boolean CreateUserLevelSet(char *level_subdir, char *level_name,
4649 char *level_author, int num_levels,
4650 boolean use_artwork_set)
4652 LevelDirTree *level_info;
4657 // create user level sub-directory, if needed
4658 createDirectory(getUserLevelDir(level_subdir), "user level");
4660 filename = getPath2(getUserLevelDir(level_subdir), LEVELINFO_FILENAME);
4662 if (!(file = fopen(filename, MODE_WRITE)))
4664 Warn("cannot write level info file '%s'", filename);
4671 level_info = newTreeInfo();
4673 // always start with reliable default values
4674 setTreeInfoToDefaults(level_info, TREE_TYPE_LEVEL_DIR);
4676 setString(&level_info->name, level_name);
4677 setString(&level_info->author, level_author);
4678 level_info->levels = num_levels;
4679 level_info->first_level = 1;
4680 level_info->sort_priority = LEVELCLASS_PRIVATE_START;
4681 level_info->readonly = FALSE;
4683 if (use_artwork_set)
4685 level_info->graphics_set =
4686 getStringCopy(getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_GRAPHICS));
4687 level_info->sounds_set =
4688 getStringCopy(getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_SOUNDS));
4689 level_info->music_set =
4690 getStringCopy(getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_MUSIC));
4693 token_value_position = TOKEN_VALUE_POSITION_SHORT;
4695 fprintFileHeader(file, LEVELINFO_FILENAME);
4698 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
4700 if (i == LEVELINFO_TOKEN_NAME ||
4701 i == LEVELINFO_TOKEN_AUTHOR ||
4702 i == LEVELINFO_TOKEN_LEVELS ||
4703 i == LEVELINFO_TOKEN_FIRST_LEVEL ||
4704 i == LEVELINFO_TOKEN_SORT_PRIORITY ||
4705 i == LEVELINFO_TOKEN_READONLY ||
4706 (use_artwork_set && (i == LEVELINFO_TOKEN_GRAPHICS_SET ||
4707 i == LEVELINFO_TOKEN_SOUNDS_SET ||
4708 i == LEVELINFO_TOKEN_MUSIC_SET)))
4709 fprintf(file, "%s\n", getSetupLine(levelinfo_tokens, "", i));
4711 // just to make things nicer :)
4712 if (i == LEVELINFO_TOKEN_AUTHOR ||
4713 i == LEVELINFO_TOKEN_FIRST_LEVEL ||
4714 (use_artwork_set && i == LEVELINFO_TOKEN_READONLY))
4715 fprintf(file, "\n");
4718 token_value_position = TOKEN_VALUE_POSITION_DEFAULT;
4722 SetFilePermissions(filename, PERMS_PRIVATE);
4724 freeTreeInfo(level_info);
4730 static void SaveUserLevelInfo(void)
4732 CreateUserLevelSet(getLoginName(), getLoginName(), getRealName(), 100, FALSE);
4735 char *getSetupValue(int type, void *value)
4737 static char value_string[MAX_LINE_LEN];
4745 strcpy(value_string, (*(boolean *)value ? "true" : "false"));
4749 strcpy(value_string, (*(boolean *)value ? "on" : "off"));
4753 strcpy(value_string, (*(int *)value == AUTO ? "auto" :
4754 *(int *)value == FALSE ? "off" : "on"));
4758 strcpy(value_string, (*(boolean *)value ? "yes" : "no"));
4761 case TYPE_YES_NO_AUTO:
4762 strcpy(value_string, (*(int *)value == AUTO ? "auto" :
4763 *(int *)value == FALSE ? "no" : "yes"));
4767 strcpy(value_string, (*(boolean *)value ? "AGA" : "ECS"));
4771 strcpy(value_string, getKeyNameFromKey(*(Key *)value));
4775 strcpy(value_string, getX11KeyNameFromKey(*(Key *)value));
4779 sprintf(value_string, "%d", *(int *)value);
4783 if (*(char **)value == NULL)
4786 strcpy(value_string, *(char **)value);
4790 sprintf(value_string, "player_%d", *(int *)value + 1);
4794 value_string[0] = '\0';
4798 if (type & TYPE_GHOSTED)
4799 strcpy(value_string, "n/a");
4801 return value_string;
4804 char *getSetupLine(struct TokenInfo *token_info, char *prefix, int token_nr)
4808 static char token_string[MAX_LINE_LEN];
4809 int token_type = token_info[token_nr].type;
4810 void *setup_value = token_info[token_nr].value;
4811 char *token_text = token_info[token_nr].text;
4812 char *value_string = getSetupValue(token_type, setup_value);
4814 // build complete token string
4815 sprintf(token_string, "%s%s", prefix, token_text);
4817 // build setup entry line
4818 line = getFormattedSetupEntry(token_string, value_string);
4820 if (token_type == TYPE_KEY_X11)
4822 Key key = *(Key *)setup_value;
4823 char *keyname = getKeyNameFromKey(key);
4825 // add comment, if useful
4826 if (!strEqual(keyname, "(undefined)") &&
4827 !strEqual(keyname, "(unknown)"))
4829 // add at least one whitespace
4831 for (i = strlen(line); i < token_comment_position; i++)
4835 strcat(line, keyname);
4842 static void InitLastPlayedLevels_ParentNode(void)
4844 LevelDirTree **leveldir_top = &leveldir_first->node_group->next;
4845 LevelDirTree *leveldir_new = NULL;
4847 // check if parent node for last played levels already exists
4848 if (strEqual((*leveldir_top)->identifier, TOKEN_STR_LAST_LEVEL_SERIES))
4851 leveldir_new = newTreeInfo();
4853 setTreeInfoToDefaultsFromParent(leveldir_new, leveldir_first);
4855 leveldir_new->level_group = TRUE;
4856 leveldir_new->sort_priority = LEVELCLASS_LAST_PLAYED_LEVEL;
4858 setString(&leveldir_new->identifier, TOKEN_STR_LAST_LEVEL_SERIES);
4859 setString(&leveldir_new->name, "<< (last played level sets)");
4860 setString(&leveldir_new->name_sorting, leveldir_new->name);
4862 pushTreeInfo(leveldir_top, leveldir_new);
4864 // create node to link back to current level directory
4865 createParentTreeInfoNode(leveldir_new);
4868 void UpdateLastPlayedLevels_TreeInfo(void)
4870 char **last_level_series = setup.level_setup.last_level_series;
4871 LevelDirTree *leveldir_last;
4872 TreeInfo **node_new = NULL;
4875 if (last_level_series[0] == NULL)
4878 InitLastPlayedLevels_ParentNode();
4880 leveldir_last = getTreeInfoFromIdentifierExt(leveldir_first,
4881 TOKEN_STR_LAST_LEVEL_SERIES,
4882 TREE_NODE_TYPE_GROUP);
4883 if (leveldir_last == NULL)
4886 node_new = &leveldir_last->node_group->next;
4888 freeTreeInfo(*node_new);
4892 for (i = 0; last_level_series[i] != NULL; i++)
4894 LevelDirTree *node_last = getTreeInfoFromIdentifier(leveldir_first,
4895 last_level_series[i]);
4896 if (node_last == NULL)
4899 *node_new = getTreeInfoCopy(node_last); // copy complete node
4901 (*node_new)->node_top = &leveldir_first; // correct top node link
4902 (*node_new)->node_parent = leveldir_last; // correct parent node link
4904 (*node_new)->is_copy = TRUE; // mark entry as node copy
4906 (*node_new)->node_group = NULL;
4907 (*node_new)->next = NULL;
4909 (*node_new)->cl_first = -1; // force setting tree cursor
4911 node_new = &((*node_new)->next);
4915 static void UpdateLastPlayedLevels_List(void)
4917 char **last_level_series = setup.level_setup.last_level_series;
4918 int pos = MAX_LEVELDIR_HISTORY - 1;
4921 // search for potentially already existing entry in list of level sets
4922 for (i = 0; last_level_series[i] != NULL; i++)
4923 if (strEqual(last_level_series[i], leveldir_current->identifier))
4926 // move list of level sets one entry down (using potentially free entry)
4927 for (i = pos; i > 0; i--)
4928 setString(&last_level_series[i], last_level_series[i - 1]);
4930 // put last played level set at top position
4931 setString(&last_level_series[0], leveldir_current->identifier);
4934 static TreeInfo *StoreOrRestoreLastPlayedLevels(TreeInfo *node, boolean store)
4936 static char *identifier = NULL;
4940 setString(&identifier, (node && node->is_copy ? node->identifier : NULL));
4942 return NULL; // not used
4946 TreeInfo *node_new = getTreeInfoFromIdentifierExt(leveldir_first,
4948 TREE_NODE_TYPE_COPY);
4949 return (node_new != NULL ? node_new : node);
4953 void StoreLastPlayedLevels(TreeInfo *node)
4955 StoreOrRestoreLastPlayedLevels(node, TRUE);
4958 void RestoreLastPlayedLevels(TreeInfo **node)
4960 *node = StoreOrRestoreLastPlayedLevels(*node, FALSE);
4963 void LoadLevelSetup_LastSeries(void)
4965 // --------------------------------------------------------------------------
4966 // ~/.<program>/levelsetup.conf
4967 // --------------------------------------------------------------------------
4969 char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
4970 SetupFileHash *level_setup_hash = NULL;
4974 // always start with reliable default values
4975 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
4977 // start with empty history of last played level sets
4978 setString(&setup.level_setup.last_level_series[0], NULL);
4980 if (!strEqual(DEFAULT_LEVELSET, UNDEFINED_LEVELSET))
4982 leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
4984 if (leveldir_current == NULL)
4985 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
4988 if ((level_setup_hash = loadSetupFileHash(filename)))
4990 char *last_level_series =
4991 getHashEntry(level_setup_hash, TOKEN_STR_LAST_LEVEL_SERIES);
4993 leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
4995 if (leveldir_current == NULL)
4996 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
4998 for (i = 0; i < MAX_LEVELDIR_HISTORY; i++)
5000 char token[strlen(TOKEN_STR_LAST_LEVEL_SERIES) + 10];
5001 LevelDirTree *leveldir_last;
5003 sprintf(token, "%s.%03d", TOKEN_STR_LAST_LEVEL_SERIES, i);
5005 last_level_series = getHashEntry(level_setup_hash, token);
5007 leveldir_last = getTreeInfoFromIdentifier(leveldir_first,
5009 if (leveldir_last != NULL)
5010 setString(&setup.level_setup.last_level_series[pos++],
5014 setString(&setup.level_setup.last_level_series[pos], NULL);
5016 freeSetupFileHash(level_setup_hash);
5020 Debug("setup", "using default setup values");
5026 static void SaveLevelSetup_LastSeries_Ext(boolean deactivate_last_level_series)
5028 // --------------------------------------------------------------------------
5029 // ~/.<program>/levelsetup.conf
5030 // --------------------------------------------------------------------------
5032 // check if the current level directory structure is available at this point
5033 if (leveldir_current == NULL)
5036 char **last_level_series = setup.level_setup.last_level_series;
5037 char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
5041 InitUserDataDirectory();
5043 UpdateLastPlayedLevels_List();
5045 if (!(file = fopen(filename, MODE_WRITE)))
5047 Warn("cannot write setup file '%s'", filename);
5054 fprintFileHeader(file, LEVELSETUP_FILENAME);
5056 if (deactivate_last_level_series)
5057 fprintf(file, "# %s\n# ", "the following level set may have caused a problem and was deactivated");
5059 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_LAST_LEVEL_SERIES,
5060 leveldir_current->identifier));
5062 for (i = 0; last_level_series[i] != NULL; i++)
5064 char token[strlen(TOKEN_STR_LAST_LEVEL_SERIES) + 1 + 10 + 1];
5066 sprintf(token, "%s.%03d", TOKEN_STR_LAST_LEVEL_SERIES, i);
5068 fprintf(file, "%s\n", getFormattedSetupEntry(token, last_level_series[i]));
5073 SetFilePermissions(filename, PERMS_PRIVATE);
5078 void SaveLevelSetup_LastSeries(void)
5080 SaveLevelSetup_LastSeries_Ext(FALSE);
5083 void SaveLevelSetup_LastSeries_Deactivate(void)
5085 SaveLevelSetup_LastSeries_Ext(TRUE);
5088 static void checkSeriesInfo(void)
5090 static char *level_directory = NULL;
5093 DirectoryEntry *dir_entry;
5096 checked_free(level_directory);
5098 // check for more levels besides the 'levels' field of 'levelinfo.conf'
5100 level_directory = getPath2((leveldir_current->in_user_dir ?
5101 getUserLevelDir(NULL) :
5102 options.level_directory),
5103 leveldir_current->fullpath);
5105 if ((dir = openDirectory(level_directory)) == NULL)
5107 Warn("cannot read level directory '%s'", level_directory);
5113 while ((dir_entry = readDirectory(dir)) != NULL) // loop all entries
5115 if (strlen(dir_entry->basename) > 4 &&
5116 dir_entry->basename[3] == '.' &&
5117 strEqual(&dir_entry->basename[4], LEVELFILE_EXTENSION))
5119 char levelnum_str[4];
5122 strncpy(levelnum_str, dir_entry->basename, 3);
5123 levelnum_str[3] = '\0';
5125 levelnum_value = atoi(levelnum_str);
5127 if (levelnum_value < leveldir_current->first_level)
5129 Warn("additional level %d found", levelnum_value);
5131 leveldir_current->first_level = levelnum_value;
5133 else if (levelnum_value > leveldir_current->last_level)
5135 Warn("additional level %d found", levelnum_value);
5137 leveldir_current->last_level = levelnum_value;
5143 closeDirectory(dir);
5146 void LoadLevelSetup_SeriesInfo(void)
5149 SetupFileHash *level_setup_hash = NULL;
5150 char *level_subdir = leveldir_current->subdir;
5153 // always start with reliable default values
5154 level_nr = leveldir_current->first_level;
5156 for (i = 0; i < MAX_LEVELS; i++)
5158 LevelStats_setPlayed(i, 0);
5159 LevelStats_setSolved(i, 0);
5164 // --------------------------------------------------------------------------
5165 // ~/.<program>/levelsetup/<level series>/levelsetup.conf
5166 // --------------------------------------------------------------------------
5168 level_subdir = leveldir_current->subdir;
5170 filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
5172 if ((level_setup_hash = loadSetupFileHash(filename)))
5176 // get last played level in this level set
5178 token_value = getHashEntry(level_setup_hash, TOKEN_STR_LAST_PLAYED_LEVEL);
5182 level_nr = atoi(token_value);
5184 if (level_nr < leveldir_current->first_level)
5185 level_nr = leveldir_current->first_level;
5186 if (level_nr > leveldir_current->last_level)
5187 level_nr = leveldir_current->last_level;
5190 // get handicap level in this level set
5192 token_value = getHashEntry(level_setup_hash, TOKEN_STR_HANDICAP_LEVEL);
5196 int level_nr = atoi(token_value);
5198 if (level_nr < leveldir_current->first_level)
5199 level_nr = leveldir_current->first_level;
5200 if (level_nr > leveldir_current->last_level + 1)
5201 level_nr = leveldir_current->last_level;
5203 if (leveldir_current->user_defined || !leveldir_current->handicap)
5204 level_nr = leveldir_current->last_level;
5206 leveldir_current->handicap_level = level_nr;
5209 // get number of played and solved levels in this level set
5211 BEGIN_HASH_ITERATION(level_setup_hash, itr)
5213 char *token = HASH_ITERATION_TOKEN(itr);
5214 char *value = HASH_ITERATION_VALUE(itr);
5216 if (strlen(token) == 3 &&
5217 token[0] >= '0' && token[0] <= '9' &&
5218 token[1] >= '0' && token[1] <= '9' &&
5219 token[2] >= '0' && token[2] <= '9')
5221 int level_nr = atoi(token);
5224 LevelStats_setPlayed(level_nr, atoi(value)); // read 1st column
5226 value = strchr(value, ' ');
5229 LevelStats_setSolved(level_nr, atoi(value)); // read 2nd column
5232 END_HASH_ITERATION(hash, itr)
5234 freeSetupFileHash(level_setup_hash);
5238 Debug("setup", "using default setup values");
5244 void SaveLevelSetup_SeriesInfo(void)
5247 char *level_subdir = leveldir_current->subdir;
5248 char *level_nr_str = int2str(level_nr, 0);
5249 char *handicap_level_str = int2str(leveldir_current->handicap_level, 0);
5253 // --------------------------------------------------------------------------
5254 // ~/.<program>/levelsetup/<level series>/levelsetup.conf
5255 // --------------------------------------------------------------------------
5257 InitLevelSetupDirectory(level_subdir);
5259 filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
5261 if (!(file = fopen(filename, MODE_WRITE)))
5263 Warn("cannot write setup file '%s'", filename);
5270 fprintFileHeader(file, LEVELSETUP_FILENAME);
5272 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_PLAYED_LEVEL,
5274 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_HANDICAP_LEVEL,
5275 handicap_level_str));
5277 for (i = leveldir_current->first_level; i <= leveldir_current->last_level;
5280 if (LevelStats_getPlayed(i) > 0 ||
5281 LevelStats_getSolved(i) > 0)
5286 sprintf(token, "%03d", i);
5287 sprintf(value, "%d %d", LevelStats_getPlayed(i), LevelStats_getSolved(i));
5289 fprintf(file, "%s\n", getFormattedSetupEntry(token, value));
5295 SetFilePermissions(filename, PERMS_PRIVATE);
5300 int LevelStats_getPlayed(int nr)
5302 return (nr >= 0 && nr < MAX_LEVELS ? level_stats[nr].played : 0);
5305 int LevelStats_getSolved(int nr)
5307 return (nr >= 0 && nr < MAX_LEVELS ? level_stats[nr].solved : 0);
5310 void LevelStats_setPlayed(int nr, int value)
5312 if (nr >= 0 && nr < MAX_LEVELS)
5313 level_stats[nr].played = value;
5316 void LevelStats_setSolved(int nr, int value)
5318 if (nr >= 0 && nr < MAX_LEVELS)
5319 level_stats[nr].solved = value;
5322 void LevelStats_incPlayed(int nr)
5324 if (nr >= 0 && nr < MAX_LEVELS)
5325 level_stats[nr].played++;
5328 void LevelStats_incSolved(int nr)
5330 if (nr >= 0 && nr < MAX_LEVELS)
5331 level_stats[nr].solved++;
5334 void LoadUserSetup(void)
5336 // --------------------------------------------------------------------------
5337 // ~/.<program>/usersetup.conf
5338 // --------------------------------------------------------------------------
5340 char *filename = getPath2(getMainUserGameDataDir(), USERSETUP_FILENAME);
5341 SetupFileHash *user_setup_hash = NULL;
5343 // always start with reliable default values
5346 if ((user_setup_hash = loadSetupFileHash(filename)))
5350 // get last selected user number
5351 token_value = getHashEntry(user_setup_hash, TOKEN_STR_LAST_USER);
5354 user.nr = MIN(MAX(0, atoi(token_value)), MAX_PLAYER_NAMES - 1);
5356 freeSetupFileHash(user_setup_hash);
5360 Debug("setup", "using default setup values");
5366 void SaveUserSetup(void)
5368 // --------------------------------------------------------------------------
5369 // ~/.<program>/usersetup.conf
5370 // --------------------------------------------------------------------------
5372 char *filename = getPath2(getMainUserGameDataDir(), USERSETUP_FILENAME);
5375 InitMainUserDataDirectory();
5377 if (!(file = fopen(filename, MODE_WRITE)))
5379 Warn("cannot write setup file '%s'", filename);
5386 fprintFileHeader(file, USERSETUP_FILENAME);
5388 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_USER,
5392 SetFilePermissions(filename, PERMS_PRIVATE);