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");
1374 createDirectory(getUserLevelDir(level_subdir), "user level");
1376 if (setup.internal.create_user_levelset)
1377 SaveUserLevelInfo();
1381 void InitNetworkLevelDirectory(char *level_subdir)
1383 if (!directoryExists(getNetworkLevelDir(level_subdir)))
1385 createDirectory(getMainUserGameDataDir(), "main user data");
1386 createDirectory(getNetworkDir(), "network data");
1387 createDirectory(getNetworkLevelDir(NULL), "main network level");
1388 createDirectory(getNetworkLevelDir(level_subdir), "network level");
1392 void InitLevelSetupDirectory(char *level_subdir)
1394 createDirectory(getUserGameDataDir(), "user data");
1395 createDirectory(getLevelSetupDir(NULL), "main level setup");
1396 createDirectory(getLevelSetupDir(level_subdir), "level setup");
1399 static void InitCacheDirectory(void)
1401 createDirectory(getMainUserGameDataDir(), "main user data");
1402 createDirectory(getCacheDir(), "cache data");
1406 // ----------------------------------------------------------------------------
1407 // some functions to handle lists of level and artwork directories
1408 // ----------------------------------------------------------------------------
1410 TreeInfo *newTreeInfo(void)
1412 return checked_calloc(sizeof(TreeInfo));
1415 TreeInfo *newTreeInfo_setDefaults(int type)
1417 TreeInfo *ti = newTreeInfo();
1419 setTreeInfoToDefaults(ti, type);
1424 void pushTreeInfo(TreeInfo **node_first, TreeInfo *node_new)
1426 node_new->next = *node_first;
1427 *node_first = node_new;
1430 void removeTreeInfo(TreeInfo **node_first)
1432 TreeInfo *node_old = *node_first;
1434 *node_first = node_old->next;
1435 node_old->next = NULL;
1437 freeTreeInfo(node_old);
1440 int numTreeInfo(TreeInfo *node)
1453 boolean validLevelSeries(TreeInfo *node)
1455 // in a number of cases, tree node is no valid level set
1456 if (node == NULL || node->node_group || node->parent_link || node->is_copy)
1462 TreeInfo *getValidLevelSeries(TreeInfo *node, TreeInfo *default_node)
1464 if (validLevelSeries(node))
1466 else if (node->is_copy)
1467 return getTreeInfoFromIdentifier(leveldir_first, node->identifier);
1469 return getFirstValidTreeInfoEntry(default_node);
1472 static TreeInfo *getValidTreeInfoEntryExt(TreeInfo *node, boolean get_next_node)
1477 if (node->node_group) // enter node group (step down into tree)
1478 return getFirstValidTreeInfoEntry(node->node_group);
1480 if (node->parent_link) // skip first node (back link) of node group
1481 get_next_node = TRUE;
1483 if (!get_next_node) // get current regular tree node
1486 // get next regular tree node, or step up until one is found
1487 while (node->next == NULL && node->node_parent != NULL)
1488 node = node->node_parent;
1490 return getFirstValidTreeInfoEntry(node->next);
1493 TreeInfo *getFirstValidTreeInfoEntry(TreeInfo *node)
1495 return getValidTreeInfoEntryExt(node, FALSE);
1498 TreeInfo *getNextValidTreeInfoEntry(TreeInfo *node)
1500 return getValidTreeInfoEntryExt(node, TRUE);
1503 TreeInfo *getTreeInfoFirstGroupEntry(TreeInfo *node)
1508 if (node->node_parent == NULL) // top level group
1509 return *node->node_top;
1510 else // sub level group
1511 return node->node_parent->node_group;
1514 int numTreeInfoInGroup(TreeInfo *node)
1516 return numTreeInfo(getTreeInfoFirstGroupEntry(node));
1519 int getPosFromTreeInfo(TreeInfo *node)
1521 TreeInfo *node_cmp = getTreeInfoFirstGroupEntry(node);
1526 if (node_cmp == node)
1530 node_cmp = node_cmp->next;
1536 TreeInfo *getTreeInfoFromPos(TreeInfo *node, int pos)
1538 TreeInfo *node_default = node;
1550 return node_default;
1553 static TreeInfo *getTreeInfoFromIdentifierExt(TreeInfo *node, char *identifier,
1554 int node_type_wanted)
1556 if (identifier == NULL)
1561 if (TREE_NODE_TYPE(node) == node_type_wanted &&
1562 strEqual(identifier, node->identifier))
1565 if (node->node_group)
1567 TreeInfo *node_group = getTreeInfoFromIdentifierExt(node->node_group,
1580 TreeInfo *getTreeInfoFromIdentifier(TreeInfo *node, char *identifier)
1582 return getTreeInfoFromIdentifierExt(node, identifier, TREE_NODE_TYPE_DEFAULT);
1585 static TreeInfo *cloneTreeNode(TreeInfo **node_top, TreeInfo *node_parent,
1586 TreeInfo *node, boolean skip_sets_without_levels)
1593 if (!node->parent_link && !node->level_group &&
1594 skip_sets_without_levels && node->levels == 0)
1595 return cloneTreeNode(node_top, node_parent, node->next,
1596 skip_sets_without_levels);
1598 node_new = getTreeInfoCopy(node); // copy complete node
1600 node_new->node_top = node_top; // correct top node link
1601 node_new->node_parent = node_parent; // correct parent node link
1603 if (node->level_group)
1604 node_new->node_group = cloneTreeNode(node_top, node_new, node->node_group,
1605 skip_sets_without_levels);
1607 node_new->next = cloneTreeNode(node_top, node_parent, node->next,
1608 skip_sets_without_levels);
1613 static void cloneTree(TreeInfo **ti_new, TreeInfo *ti, boolean skip_empty_sets)
1615 TreeInfo *ti_cloned = cloneTreeNode(ti_new, NULL, ti, skip_empty_sets);
1617 *ti_new = ti_cloned;
1620 static boolean adjustTreeGraphicsForEMC(TreeInfo *node)
1622 boolean settings_changed = FALSE;
1626 boolean want_ecs = (setup.prefer_aga_graphics == FALSE);
1627 boolean want_aga = (setup.prefer_aga_graphics == TRUE);
1628 boolean has_only_ecs = (!node->graphics_set && !node->graphics_set_aga);
1629 boolean has_only_aga = (!node->graphics_set && !node->graphics_set_ecs);
1630 char *graphics_set = NULL;
1632 if (node->graphics_set_ecs && (want_ecs || has_only_ecs))
1633 graphics_set = node->graphics_set_ecs;
1635 if (node->graphics_set_aga && (want_aga || has_only_aga))
1636 graphics_set = node->graphics_set_aga;
1638 if (graphics_set && !strEqual(node->graphics_set, graphics_set))
1640 setString(&node->graphics_set, graphics_set);
1641 settings_changed = TRUE;
1644 if (node->node_group != NULL)
1645 settings_changed |= adjustTreeGraphicsForEMC(node->node_group);
1650 return settings_changed;
1653 static boolean adjustTreeSoundsForEMC(TreeInfo *node)
1655 boolean settings_changed = FALSE;
1659 boolean want_default = (setup.prefer_lowpass_sounds == FALSE);
1660 boolean want_lowpass = (setup.prefer_lowpass_sounds == TRUE);
1661 boolean has_only_default = (!node->sounds_set && !node->sounds_set_lowpass);
1662 boolean has_only_lowpass = (!node->sounds_set && !node->sounds_set_default);
1663 char *sounds_set = NULL;
1665 if (node->sounds_set_default && (want_default || has_only_default))
1666 sounds_set = node->sounds_set_default;
1668 if (node->sounds_set_lowpass && (want_lowpass || has_only_lowpass))
1669 sounds_set = node->sounds_set_lowpass;
1671 if (sounds_set && !strEqual(node->sounds_set, sounds_set))
1673 setString(&node->sounds_set, sounds_set);
1674 settings_changed = TRUE;
1677 if (node->node_group != NULL)
1678 settings_changed |= adjustTreeSoundsForEMC(node->node_group);
1683 return settings_changed;
1686 int dumpTreeInfo(TreeInfo *node, int depth)
1688 char bullet_list[] = { '-', '*', 'o' };
1689 int num_leaf_nodes = 0;
1693 Debug("tree", "Dumping TreeInfo:");
1697 char bullet = bullet_list[depth % ARRAY_SIZE(bullet_list)];
1699 for (i = 0; i < depth * 2; i++)
1700 DebugContinued("", " ");
1702 DebugContinued("tree", "%c '%s' ['%s] [PARENT: '%s'] %s\n",
1703 bullet, node->name, node->identifier,
1704 (node->node_parent ? node->node_parent->identifier : "-"),
1705 (node->node_group ? "[GROUP]" :
1706 node->is_copy ? "[COPY]" : ""));
1708 if (!node->node_group && !node->parent_link)
1712 // use for dumping artwork info tree
1713 Debug("tree", "subdir == '%s' ['%s', '%s'] [%d])",
1714 node->subdir, node->fullpath, node->basepath, node->in_user_dir);
1717 if (node->node_group != NULL)
1718 num_leaf_nodes += dumpTreeInfo(node->node_group, depth + 1);
1724 Debug("tree", "Summary: %d leaf nodes found", num_leaf_nodes);
1726 return num_leaf_nodes;
1729 void sortTreeInfoBySortFunction(TreeInfo **node_first,
1730 int (*compare_function)(const void *,
1733 int num_nodes = numTreeInfo(*node_first);
1734 TreeInfo **sort_array;
1735 TreeInfo *node = *node_first;
1741 // allocate array for sorting structure pointers
1742 sort_array = checked_calloc(num_nodes * sizeof(TreeInfo *));
1744 // writing structure pointers to sorting array
1745 while (i < num_nodes && node) // double boundary check...
1747 sort_array[i] = node;
1753 // sorting the structure pointers in the sorting array
1754 qsort(sort_array, num_nodes, sizeof(TreeInfo *),
1757 // update the linkage of list elements with the sorted node array
1758 for (i = 0; i < num_nodes - 1; i++)
1759 sort_array[i]->next = sort_array[i + 1];
1760 sort_array[num_nodes - 1]->next = NULL;
1762 // update the linkage of the main list anchor pointer
1763 *node_first = sort_array[0];
1767 // now recursively sort the level group structures
1771 if (node->node_group != NULL)
1772 sortTreeInfoBySortFunction(&node->node_group, compare_function);
1778 void sortTreeInfo(TreeInfo **node_first)
1780 sortTreeInfoBySortFunction(node_first, compareTreeInfoEntries);
1784 // ============================================================================
1785 // some stuff from "files.c"
1786 // ============================================================================
1788 #if defined(PLATFORM_WINDOWS)
1790 #define S_IRGRP S_IRUSR
1793 #define S_IROTH S_IRUSR
1796 #define S_IWGRP S_IWUSR
1799 #define S_IWOTH S_IWUSR
1802 #define S_IXGRP S_IXUSR
1805 #define S_IXOTH S_IXUSR
1808 #define S_IRWXG (S_IRGRP | S_IWGRP | S_IXGRP)
1813 #endif // PLATFORM_WINDOWS
1815 // file permissions for newly written files
1816 #define MODE_R_ALL (S_IRUSR | S_IRGRP | S_IROTH)
1817 #define MODE_W_ALL (S_IWUSR | S_IWGRP | S_IWOTH)
1818 #define MODE_X_ALL (S_IXUSR | S_IXGRP | S_IXOTH)
1820 #define MODE_W_PRIVATE (S_IWUSR)
1821 #define MODE_W_PUBLIC_FILE (S_IWUSR | S_IWGRP)
1822 #define MODE_W_PUBLIC_DIR (S_IWUSR | S_IWGRP | S_ISGID)
1824 #define DIR_PERMS_PRIVATE (MODE_R_ALL | MODE_X_ALL | MODE_W_PRIVATE)
1825 #define DIR_PERMS_PUBLIC (MODE_R_ALL | MODE_X_ALL | MODE_W_PUBLIC_DIR)
1826 #define DIR_PERMS_PUBLIC_ALL (MODE_R_ALL | MODE_X_ALL | MODE_W_ALL)
1828 #define FILE_PERMS_PRIVATE (MODE_R_ALL | MODE_W_PRIVATE)
1829 #define FILE_PERMS_PUBLIC (MODE_R_ALL | MODE_W_PUBLIC_FILE)
1830 #define FILE_PERMS_PUBLIC_ALL (MODE_R_ALL | MODE_W_ALL)
1833 char *getHomeDir(void)
1835 static char *dir = NULL;
1837 #if defined(PLATFORM_WINDOWS)
1840 dir = checked_malloc(MAX_PATH + 1);
1842 if (!SUCCEEDED(SHGetFolderPath(NULL, CSIDL_PERSONAL, NULL, 0, dir)))
1845 #elif defined(PLATFORM_EMSCRIPTEN)
1846 dir = PERSISTENT_DIRECTORY;
1847 #elif defined(PLATFORM_UNIX)
1850 if ((dir = getenv("HOME")) == NULL)
1852 dir = getUnixHomeDir();
1855 dir = getStringCopy(dir);
1867 char *getPersonalDataDir(void)
1869 static char *personal_data_dir = NULL;
1871 #if defined(PLATFORM_MAC)
1872 if (personal_data_dir == NULL)
1873 personal_data_dir = getPath2(getHomeDir(), "Documents");
1875 if (personal_data_dir == NULL)
1876 personal_data_dir = getHomeDir();
1879 return personal_data_dir;
1882 char *getMainUserGameDataDir(void)
1884 static char *main_user_data_dir = NULL;
1886 #if defined(PLATFORM_ANDROID)
1887 if (main_user_data_dir == NULL)
1888 main_user_data_dir = (char *)(SDL_AndroidGetExternalStorageState() &
1889 SDL_ANDROID_EXTERNAL_STORAGE_WRITE ?
1890 SDL_AndroidGetExternalStoragePath() :
1891 SDL_AndroidGetInternalStoragePath());
1893 if (main_user_data_dir == NULL)
1894 main_user_data_dir = getPath2(getPersonalDataDir(),
1895 program.userdata_subdir);
1898 return main_user_data_dir;
1901 char *getUserGameDataDir(void)
1904 return getMainUserGameDataDir();
1906 return getUserDir(user.nr);
1909 char *getSetupDir(void)
1911 return getUserGameDataDir();
1914 static mode_t posix_umask(mode_t mask)
1916 #if defined(PLATFORM_UNIX)
1923 static int posix_mkdir(const char *pathname, mode_t mode)
1925 #if defined(PLATFORM_WINDOWS)
1926 return mkdir(pathname);
1928 return mkdir(pathname, mode);
1932 static boolean posix_process_running_setgid(void)
1934 #if defined(PLATFORM_UNIX)
1935 return (getgid() != getegid());
1941 void createDirectory(char *dir, char *text)
1943 if (directoryExists(dir))
1946 // leave "other" permissions in umask untouched, but ensure group parts
1947 // of USERDATA_DIR_MODE are not masked
1948 int permission_class = PERMS_PRIVATE;
1949 mode_t dir_mode = (permission_class == PERMS_PRIVATE ?
1950 DIR_PERMS_PRIVATE : DIR_PERMS_PUBLIC);
1951 mode_t last_umask = posix_umask(0);
1952 mode_t group_umask = ~(dir_mode & S_IRWXG);
1953 int running_setgid = posix_process_running_setgid();
1955 if (permission_class == PERMS_PUBLIC)
1957 // if we're setgid, protect files against "other"
1958 // else keep umask(0) to make the dir world-writable
1961 posix_umask(last_umask & group_umask);
1963 dir_mode = DIR_PERMS_PUBLIC_ALL;
1966 if (posix_mkdir(dir, dir_mode) != 0)
1967 Warn("cannot create %s directory '%s': %s", text, dir, strerror(errno));
1969 if (permission_class == PERMS_PUBLIC && !running_setgid)
1970 chmod(dir, dir_mode);
1972 posix_umask(last_umask); // restore previous umask
1975 void InitMainUserDataDirectory(void)
1977 createDirectory(getMainUserGameDataDir(), "main user data");
1980 void InitUserDataDirectory(void)
1982 createDirectory(getMainUserGameDataDir(), "main user data");
1986 createDirectory(getUserDir(-1), "users");
1987 createDirectory(getUserDir(user.nr), "user data");
1991 void SetFilePermissions(char *filename, int permission_class)
1993 int running_setgid = posix_process_running_setgid();
1994 int perms = (permission_class == PERMS_PRIVATE ?
1995 FILE_PERMS_PRIVATE : FILE_PERMS_PUBLIC);
1997 if (permission_class == PERMS_PUBLIC && !running_setgid)
1998 perms = FILE_PERMS_PUBLIC_ALL;
2000 chmod(filename, perms);
2003 char *getCookie(char *file_type)
2005 static char cookie[MAX_COOKIE_LEN + 1];
2007 if (strlen(program.cookie_prefix) + 1 +
2008 strlen(file_type) + strlen("_FILE_VERSION_x.x") > MAX_COOKIE_LEN)
2009 return "[COOKIE ERROR]"; // should never happen
2011 sprintf(cookie, "%s_%s_FILE_VERSION_%d.%d",
2012 program.cookie_prefix, file_type,
2013 program.version_super, program.version_major);
2018 void fprintFileHeader(FILE *file, char *basename)
2020 char *prefix = "# ";
2023 fprintf_line_with_prefix(file, prefix, sep1, 77);
2024 fprintf(file, "%s%s\n", prefix, basename);
2025 fprintf_line_with_prefix(file, prefix, sep1, 77);
2026 fprintf(file, "\n");
2029 int getFileVersionFromCookieString(const char *cookie)
2031 const char *ptr_cookie1, *ptr_cookie2;
2032 const char *pattern1 = "_FILE_VERSION_";
2033 const char *pattern2 = "?.?";
2034 const int len_cookie = strlen(cookie);
2035 const int len_pattern1 = strlen(pattern1);
2036 const int len_pattern2 = strlen(pattern2);
2037 const int len_pattern = len_pattern1 + len_pattern2;
2038 int version_super, version_major;
2040 if (len_cookie <= len_pattern)
2043 ptr_cookie1 = &cookie[len_cookie - len_pattern];
2044 ptr_cookie2 = &cookie[len_cookie - len_pattern2];
2046 if (strncmp(ptr_cookie1, pattern1, len_pattern1) != 0)
2049 if (ptr_cookie2[0] < '0' || ptr_cookie2[0] > '9' ||
2050 ptr_cookie2[1] != '.' ||
2051 ptr_cookie2[2] < '0' || ptr_cookie2[2] > '9')
2054 version_super = ptr_cookie2[0] - '0';
2055 version_major = ptr_cookie2[2] - '0';
2057 return VERSION_IDENT(version_super, version_major, 0, 0);
2060 boolean checkCookieString(const char *cookie, const char *template)
2062 const char *pattern = "_FILE_VERSION_?.?";
2063 const int len_cookie = strlen(cookie);
2064 const int len_template = strlen(template);
2065 const int len_pattern = strlen(pattern);
2067 if (len_cookie != len_template)
2070 if (strncmp(cookie, template, len_cookie - len_pattern) != 0)
2077 // ----------------------------------------------------------------------------
2078 // setup file list and hash handling functions
2079 // ----------------------------------------------------------------------------
2081 char *getFormattedSetupEntry(char *token, char *value)
2084 static char entry[MAX_LINE_LEN];
2086 // if value is an empty string, just return token without value
2090 // start with the token and some spaces to format output line
2091 sprintf(entry, "%s:", token);
2092 for (i = strlen(entry); i < token_value_position; i++)
2095 // continue with the token's value
2096 strcat(entry, value);
2101 SetupFileList *newSetupFileList(char *token, char *value)
2103 SetupFileList *new = checked_malloc(sizeof(SetupFileList));
2105 new->token = getStringCopy(token);
2106 new->value = getStringCopy(value);
2113 void freeSetupFileList(SetupFileList *list)
2118 checked_free(list->token);
2119 checked_free(list->value);
2122 freeSetupFileList(list->next);
2127 char *getListEntry(SetupFileList *list, char *token)
2132 if (strEqual(list->token, token))
2135 return getListEntry(list->next, token);
2138 SetupFileList *setListEntry(SetupFileList *list, char *token, char *value)
2143 if (strEqual(list->token, token))
2145 checked_free(list->value);
2147 list->value = getStringCopy(value);
2151 else if (list->next == NULL)
2152 return (list->next = newSetupFileList(token, value));
2154 return setListEntry(list->next, token, value);
2157 SetupFileList *addListEntry(SetupFileList *list, char *token, char *value)
2162 if (list->next == NULL)
2163 return (list->next = newSetupFileList(token, value));
2165 return addListEntry(list->next, token, value);
2168 #if ENABLE_UNUSED_CODE
2170 static void printSetupFileList(SetupFileList *list)
2175 Debug("setup:printSetupFileList", "token: '%s'", list->token);
2176 Debug("setup:printSetupFileList", "value: '%s'", list->value);
2178 printSetupFileList(list->next);
2184 DEFINE_HASHTABLE_INSERT(insert_hash_entry, char, char);
2185 DEFINE_HASHTABLE_SEARCH(search_hash_entry, char, char);
2186 DEFINE_HASHTABLE_CHANGE(change_hash_entry, char, char);
2187 DEFINE_HASHTABLE_REMOVE(remove_hash_entry, char, char);
2189 #define insert_hash_entry hashtable_insert
2190 #define search_hash_entry hashtable_search
2191 #define change_hash_entry hashtable_change
2192 #define remove_hash_entry hashtable_remove
2195 unsigned int get_hash_from_key(void *key)
2200 This algorithm (k=33) was first reported by Dan Bernstein many years ago in
2201 'comp.lang.c'. Another version of this algorithm (now favored by Bernstein)
2202 uses XOR: hash(i) = hash(i - 1) * 33 ^ str[i]; the magic of number 33 (why
2203 it works better than many other constants, prime or not) has never been
2204 adequately explained.
2206 If you just want to have a good hash function, and cannot wait, djb2
2207 is one of the best string hash functions i know. It has excellent
2208 distribution and speed on many different sets of keys and table sizes.
2209 You are not likely to do better with one of the "well known" functions
2210 such as PJW, K&R, etc.
2212 Ozan (oz) Yigit [http://www.cs.yorku.ca/~oz/hash.html]
2215 char *str = (char *)key;
2216 unsigned int hash = 5381;
2219 while ((c = *str++))
2220 hash = ((hash << 5) + hash) + c; // hash * 33 + c
2225 int hash_keys_are_equal(void *key1, void *key2)
2227 return (strEqual((char *)key1, (char *)key2));
2230 SetupFileHash *newSetupFileHash(void)
2232 SetupFileHash *new_hash =
2233 create_hashtable(16, 0.75, get_hash_from_key, hash_keys_are_equal);
2235 if (new_hash == NULL)
2236 Fail("create_hashtable() failed -- out of memory");
2241 void freeSetupFileHash(SetupFileHash *hash)
2246 hashtable_destroy(hash, 1); // 1 == also free values stored in hash
2249 char *getHashEntry(SetupFileHash *hash, char *token)
2254 return search_hash_entry(hash, token);
2257 void setHashEntry(SetupFileHash *hash, char *token, char *value)
2264 value_copy = getStringCopy(value);
2266 // change value; if it does not exist, insert it as new
2267 if (!change_hash_entry(hash, token, value_copy))
2268 if (!insert_hash_entry(hash, getStringCopy(token), value_copy))
2269 Fail("cannot insert into hash -- aborting");
2272 char *removeHashEntry(SetupFileHash *hash, char *token)
2277 return remove_hash_entry(hash, token);
2280 #if ENABLE_UNUSED_CODE
2282 static void printSetupFileHash(SetupFileHash *hash)
2284 BEGIN_HASH_ITERATION(hash, itr)
2286 Debug("setup:printSetupFileHash", "token: '%s'", HASH_ITERATION_TOKEN(itr));
2287 Debug("setup:printSetupFileHash", "value: '%s'", HASH_ITERATION_VALUE(itr));
2289 END_HASH_ITERATION(hash, itr)
2294 #define ALLOW_TOKEN_VALUE_SEPARATOR_BEING_WHITESPACE 1
2295 #define CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING 0
2296 #define CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH 0
2298 static boolean token_value_separator_found = FALSE;
2299 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2300 static boolean token_value_separator_warning = FALSE;
2302 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2303 static boolean token_already_exists_warning = FALSE;
2306 static boolean getTokenValueFromSetupLineExt(char *line,
2307 char **token_ptr, char **value_ptr,
2308 char *filename, char *line_raw,
2310 boolean separator_required)
2312 static char line_copy[MAX_LINE_LEN + 1], line_raw_copy[MAX_LINE_LEN + 1];
2313 char *token, *value, *line_ptr;
2315 // when externally invoked via ReadTokenValueFromLine(), copy line buffers
2316 if (line_raw == NULL)
2318 strncpy(line_copy, line, MAX_LINE_LEN);
2319 line_copy[MAX_LINE_LEN] = '\0';
2322 strcpy(line_raw_copy, line_copy);
2323 line_raw = line_raw_copy;
2326 // cut trailing comment from input line
2327 for (line_ptr = line; *line_ptr; line_ptr++)
2329 if (*line_ptr == '#')
2336 // cut trailing whitespaces from input line
2337 for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
2338 if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
2341 // ignore empty lines
2345 // cut leading whitespaces from token
2346 for (token = line; *token; token++)
2347 if (*token != ' ' && *token != '\t')
2350 // start with empty value as reliable default
2353 token_value_separator_found = FALSE;
2355 // find end of token to determine start of value
2356 for (line_ptr = token; *line_ptr; line_ptr++)
2358 // first look for an explicit token/value separator, like ':' or '='
2359 if (*line_ptr == ':' || *line_ptr == '=')
2361 *line_ptr = '\0'; // terminate token string
2362 value = line_ptr + 1; // set beginning of value
2364 token_value_separator_found = TRUE;
2370 #if ALLOW_TOKEN_VALUE_SEPARATOR_BEING_WHITESPACE
2371 // fallback: if no token/value separator found, also allow whitespaces
2372 if (!token_value_separator_found && !separator_required)
2374 for (line_ptr = token; *line_ptr; line_ptr++)
2376 if (*line_ptr == ' ' || *line_ptr == '\t')
2378 *line_ptr = '\0'; // terminate token string
2379 value = line_ptr + 1; // set beginning of value
2381 token_value_separator_found = TRUE;
2387 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2388 if (token_value_separator_found)
2390 if (!token_value_separator_warning)
2392 Debug("setup", "---");
2394 if (filename != NULL)
2396 Debug("setup", "missing token/value separator(s) in config file:");
2397 Debug("setup", "- config file: '%s'", filename);
2401 Debug("setup", "missing token/value separator(s):");
2404 token_value_separator_warning = TRUE;
2407 if (filename != NULL)
2408 Debug("setup", "- line %d: '%s'", line_nr, line_raw);
2410 Debug("setup", "- line: '%s'", line_raw);
2416 // cut trailing whitespaces from token
2417 for (line_ptr = &token[strlen(token)]; line_ptr >= token; line_ptr--)
2418 if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
2421 // cut leading whitespaces from value
2422 for (; *value; value++)
2423 if (*value != ' ' && *value != '\t')
2432 boolean getTokenValueFromSetupLine(char *line, char **token, char **value)
2434 // while the internal (old) interface does not require a token/value
2435 // separator (for downwards compatibility with existing files which
2436 // don't use them), it is mandatory for the external (new) interface
2438 return getTokenValueFromSetupLineExt(line, token, value, NULL, NULL, 0, TRUE);
2441 static boolean loadSetupFileData(void *setup_file_data, char *filename,
2442 boolean top_recursion_level, boolean is_hash)
2444 static SetupFileHash *include_filename_hash = NULL;
2445 char line[MAX_LINE_LEN], line_raw[MAX_LINE_LEN], previous_line[MAX_LINE_LEN];
2446 char *token, *value, *line_ptr;
2447 void *insert_ptr = NULL;
2448 boolean read_continued_line = FALSE;
2450 int line_nr = 0, token_count = 0, include_count = 0;
2452 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2453 token_value_separator_warning = FALSE;
2456 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2457 token_already_exists_warning = FALSE;
2460 if (!(file = openFile(filename, MODE_READ)))
2462 #if DEBUG_NO_CONFIG_FILE
2463 Debug("setup", "cannot open configuration file '%s'", filename);
2469 // use "insert pointer" to store list end for constant insertion complexity
2471 insert_ptr = setup_file_data;
2473 // on top invocation, create hash to mark included files (to prevent loops)
2474 if (top_recursion_level)
2475 include_filename_hash = newSetupFileHash();
2477 // mark this file as already included (to prevent including it again)
2478 setHashEntry(include_filename_hash, getBaseNamePtr(filename), "true");
2480 while (!checkEndOfFile(file))
2482 // read next line of input file
2483 if (!getStringFromFile(file, line, MAX_LINE_LEN))
2486 // check if line was completely read and is terminated by line break
2487 if (strlen(line) > 0 && line[strlen(line) - 1] == '\n')
2490 // cut trailing line break (this can be newline and/or carriage return)
2491 for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
2492 if ((*line_ptr == '\n' || *line_ptr == '\r') && *(line_ptr + 1) == '\0')
2495 // copy raw input line for later use (mainly debugging output)
2496 strcpy(line_raw, line);
2498 if (read_continued_line)
2500 // append new line to existing line, if there is enough space
2501 if (strlen(previous_line) + strlen(line_ptr) < MAX_LINE_LEN)
2502 strcat(previous_line, line_ptr);
2504 strcpy(line, previous_line); // copy storage buffer to line
2506 read_continued_line = FALSE;
2509 // if the last character is '\', continue at next line
2510 if (strlen(line) > 0 && line[strlen(line) - 1] == '\\')
2512 line[strlen(line) - 1] = '\0'; // cut off trailing backslash
2513 strcpy(previous_line, line); // copy line to storage buffer
2515 read_continued_line = TRUE;
2520 if (!getTokenValueFromSetupLineExt(line, &token, &value, filename,
2521 line_raw, line_nr, FALSE))
2526 if (strEqual(token, "include"))
2528 if (getHashEntry(include_filename_hash, value) == NULL)
2530 char *basepath = getBasePath(filename);
2531 char *basename = getBaseName(value);
2532 char *filename_include = getPath2(basepath, basename);
2534 loadSetupFileData(setup_file_data, filename_include, FALSE, is_hash);
2538 free(filename_include);
2544 Warn("ignoring already processed file '%s'", value);
2551 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2553 getHashEntry((SetupFileHash *)setup_file_data, token);
2555 if (old_value != NULL)
2557 if (!token_already_exists_warning)
2559 Debug("setup", "---");
2560 Debug("setup", "duplicate token(s) found in config file:");
2561 Debug("setup", "- config file: '%s'", filename);
2563 token_already_exists_warning = TRUE;
2566 Debug("setup", "- token: '%s' (in line %d)", token, line_nr);
2567 Debug("setup", " old value: '%s'", old_value);
2568 Debug("setup", " new value: '%s'", value);
2572 setHashEntry((SetupFileHash *)setup_file_data, token, value);
2576 insert_ptr = addListEntry((SetupFileList *)insert_ptr, token, value);
2586 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2587 if (token_value_separator_warning)
2588 Debug("setup", "---");
2591 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2592 if (token_already_exists_warning)
2593 Debug("setup", "---");
2596 if (token_count == 0 && include_count == 0)
2597 Warn("configuration file '%s' is empty", filename);
2599 if (top_recursion_level)
2600 freeSetupFileHash(include_filename_hash);
2605 static int compareSetupFileData(const void *object1, const void *object2)
2607 const struct ConfigInfo *entry1 = (struct ConfigInfo *)object1;
2608 const struct ConfigInfo *entry2 = (struct ConfigInfo *)object2;
2610 return strcmp(entry1->token, entry2->token);
2613 static void saveSetupFileHash(SetupFileHash *hash, char *filename)
2615 int item_count = hashtable_count(hash);
2616 int item_size = sizeof(struct ConfigInfo);
2617 struct ConfigInfo *sort_array = checked_malloc(item_count * item_size);
2621 // copy string pointers from hash to array
2622 BEGIN_HASH_ITERATION(hash, itr)
2624 sort_array[i].token = HASH_ITERATION_TOKEN(itr);
2625 sort_array[i].value = HASH_ITERATION_VALUE(itr);
2629 if (i > item_count) // should never happen
2632 END_HASH_ITERATION(hash, itr)
2634 // sort string pointers from hash in array
2635 qsort(sort_array, item_count, item_size, compareSetupFileData);
2637 if (!(file = fopen(filename, MODE_WRITE)))
2639 Warn("cannot write configuration file '%s'", filename);
2644 fprintf(file, "%s\n\n", getFormattedSetupEntry("program.version",
2645 program.version_string));
2646 for (i = 0; i < item_count; i++)
2647 fprintf(file, "%s\n", getFormattedSetupEntry(sort_array[i].token,
2648 sort_array[i].value));
2651 checked_free(sort_array);
2654 SetupFileList *loadSetupFileList(char *filename)
2656 SetupFileList *setup_file_list = newSetupFileList("", "");
2657 SetupFileList *first_valid_list_entry;
2659 if (!loadSetupFileData(setup_file_list, filename, TRUE, FALSE))
2661 freeSetupFileList(setup_file_list);
2666 first_valid_list_entry = setup_file_list->next;
2668 // free empty list header
2669 setup_file_list->next = NULL;
2670 freeSetupFileList(setup_file_list);
2672 return first_valid_list_entry;
2675 SetupFileHash *loadSetupFileHash(char *filename)
2677 SetupFileHash *setup_file_hash = newSetupFileHash();
2679 if (!loadSetupFileData(setup_file_hash, filename, TRUE, TRUE))
2681 freeSetupFileHash(setup_file_hash);
2686 return setup_file_hash;
2690 // ============================================================================
2692 // ============================================================================
2694 #define TOKEN_STR_LAST_LEVEL_SERIES "last_level_series"
2695 #define TOKEN_STR_LAST_PLAYED_LEVEL "last_played_level"
2696 #define TOKEN_STR_HANDICAP_LEVEL "handicap_level"
2697 #define TOKEN_STR_LAST_USER "last_user"
2699 // level directory info
2700 #define LEVELINFO_TOKEN_IDENTIFIER 0
2701 #define LEVELINFO_TOKEN_NAME 1
2702 #define LEVELINFO_TOKEN_NAME_SORTING 2
2703 #define LEVELINFO_TOKEN_AUTHOR 3
2704 #define LEVELINFO_TOKEN_YEAR 4
2705 #define LEVELINFO_TOKEN_PROGRAM_TITLE 5
2706 #define LEVELINFO_TOKEN_PROGRAM_COPYRIGHT 6
2707 #define LEVELINFO_TOKEN_PROGRAM_COMPANY 7
2708 #define LEVELINFO_TOKEN_IMPORTED_FROM 8
2709 #define LEVELINFO_TOKEN_IMPORTED_BY 9
2710 #define LEVELINFO_TOKEN_TESTED_BY 10
2711 #define LEVELINFO_TOKEN_LEVELS 11
2712 #define LEVELINFO_TOKEN_FIRST_LEVEL 12
2713 #define LEVELINFO_TOKEN_SORT_PRIORITY 13
2714 #define LEVELINFO_TOKEN_LATEST_ENGINE 14
2715 #define LEVELINFO_TOKEN_LEVEL_GROUP 15
2716 #define LEVELINFO_TOKEN_READONLY 16
2717 #define LEVELINFO_TOKEN_GRAPHICS_SET_ECS 17
2718 #define LEVELINFO_TOKEN_GRAPHICS_SET_AGA 18
2719 #define LEVELINFO_TOKEN_GRAPHICS_SET 19
2720 #define LEVELINFO_TOKEN_SOUNDS_SET_DEFAULT 20
2721 #define LEVELINFO_TOKEN_SOUNDS_SET_LOWPASS 21
2722 #define LEVELINFO_TOKEN_SOUNDS_SET 22
2723 #define LEVELINFO_TOKEN_MUSIC_SET 23
2724 #define LEVELINFO_TOKEN_FILENAME 24
2725 #define LEVELINFO_TOKEN_FILETYPE 25
2726 #define LEVELINFO_TOKEN_SPECIAL_FLAGS 26
2727 #define LEVELINFO_TOKEN_EMPTY_LEVEL_NAME 27
2728 #define LEVELINFO_TOKEN_FORCE_LEVEL_NAME 28
2729 #define LEVELINFO_TOKEN_HANDICAP 29
2730 #define LEVELINFO_TOKEN_SKIP_LEVELS 30
2731 #define LEVELINFO_TOKEN_USE_EMC_TILES 31
2733 #define NUM_LEVELINFO_TOKENS 32
2735 static LevelDirTree ldi;
2737 static struct TokenInfo levelinfo_tokens[] =
2739 // level directory info
2740 { TYPE_STRING, &ldi.identifier, "identifier" },
2741 { TYPE_STRING, &ldi.name, "name" },
2742 { TYPE_STRING, &ldi.name_sorting, "name_sorting" },
2743 { TYPE_STRING, &ldi.author, "author" },
2744 { TYPE_STRING, &ldi.year, "year" },
2745 { TYPE_STRING, &ldi.program_title, "program_title" },
2746 { TYPE_STRING, &ldi.program_copyright, "program_copyright" },
2747 { TYPE_STRING, &ldi.program_company, "program_company" },
2748 { TYPE_STRING, &ldi.imported_from, "imported_from" },
2749 { TYPE_STRING, &ldi.imported_by, "imported_by" },
2750 { TYPE_STRING, &ldi.tested_by, "tested_by" },
2751 { TYPE_INTEGER, &ldi.levels, "levels" },
2752 { TYPE_INTEGER, &ldi.first_level, "first_level" },
2753 { TYPE_INTEGER, &ldi.sort_priority, "sort_priority" },
2754 { TYPE_BOOLEAN, &ldi.latest_engine, "latest_engine" },
2755 { TYPE_BOOLEAN, &ldi.level_group, "level_group" },
2756 { TYPE_BOOLEAN, &ldi.readonly, "readonly" },
2757 { TYPE_STRING, &ldi.graphics_set_ecs, "graphics_set.ecs" },
2758 { TYPE_STRING, &ldi.graphics_set_aga, "graphics_set.aga" },
2759 { TYPE_STRING, &ldi.graphics_set, "graphics_set" },
2760 { TYPE_STRING, &ldi.sounds_set_default,"sounds_set.default" },
2761 { TYPE_STRING, &ldi.sounds_set_lowpass,"sounds_set.lowpass" },
2762 { TYPE_STRING, &ldi.sounds_set, "sounds_set" },
2763 { TYPE_STRING, &ldi.music_set, "music_set" },
2764 { TYPE_STRING, &ldi.level_filename, "filename" },
2765 { TYPE_STRING, &ldi.level_filetype, "filetype" },
2766 { TYPE_STRING, &ldi.special_flags, "special_flags" },
2767 { TYPE_STRING, &ldi.empty_level_name, "empty_level_name" },
2768 { TYPE_BOOLEAN, &ldi.force_level_name, "force_level_name" },
2769 { TYPE_BOOLEAN, &ldi.handicap, "handicap" },
2770 { TYPE_BOOLEAN, &ldi.skip_levels, "skip_levels" },
2771 { TYPE_BOOLEAN, &ldi.use_emc_tiles, "use_emc_tiles" }
2774 static struct TokenInfo artworkinfo_tokens[] =
2776 // artwork directory info
2777 { TYPE_STRING, &ldi.identifier, "identifier" },
2778 { TYPE_STRING, &ldi.subdir, "subdir" },
2779 { TYPE_STRING, &ldi.name, "name" },
2780 { TYPE_STRING, &ldi.name_sorting, "name_sorting" },
2781 { TYPE_STRING, &ldi.author, "author" },
2782 { TYPE_STRING, &ldi.program_title, "program_title" },
2783 { TYPE_STRING, &ldi.program_copyright, "program_copyright" },
2784 { TYPE_STRING, &ldi.program_company, "program_company" },
2785 { TYPE_INTEGER, &ldi.sort_priority, "sort_priority" },
2786 { TYPE_STRING, &ldi.basepath, "basepath" },
2787 { TYPE_STRING, &ldi.fullpath, "fullpath" },
2788 { TYPE_BOOLEAN, &ldi.in_user_dir, "in_user_dir" },
2789 { TYPE_STRING, &ldi.class_desc, "class_desc" },
2794 static char *optional_tokens[] =
2797 "program_copyright",
2803 static void setTreeInfoToDefaults(TreeInfo *ti, int type)
2807 ti->node_top = (ti->type == TREE_TYPE_LEVEL_DIR ? &leveldir_first :
2808 ti->type == TREE_TYPE_GRAPHICS_DIR ? &artwork.gfx_first :
2809 ti->type == TREE_TYPE_SOUNDS_DIR ? &artwork.snd_first :
2810 ti->type == TREE_TYPE_MUSIC_DIR ? &artwork.mus_first :
2813 ti->node_parent = NULL;
2814 ti->node_group = NULL;
2821 ti->fullpath = NULL;
2822 ti->basepath = NULL;
2823 ti->identifier = NULL;
2824 ti->name = getStringCopy(ANONYMOUS_NAME);
2825 ti->name_sorting = NULL;
2826 ti->author = getStringCopy(ANONYMOUS_NAME);
2829 ti->program_title = NULL;
2830 ti->program_copyright = NULL;
2831 ti->program_company = NULL;
2833 ti->sort_priority = LEVELCLASS_UNDEFINED; // default: least priority
2834 ti->latest_engine = FALSE; // default: get from level
2835 ti->parent_link = FALSE;
2836 ti->is_copy = FALSE;
2837 ti->in_user_dir = FALSE;
2838 ti->user_defined = FALSE;
2840 ti->class_desc = NULL;
2842 ti->infotext = getStringCopy(TREE_INFOTEXT(ti->type));
2844 if (ti->type == TREE_TYPE_LEVEL_DIR)
2846 ti->imported_from = NULL;
2847 ti->imported_by = NULL;
2848 ti->tested_by = NULL;
2850 ti->graphics_set_ecs = NULL;
2851 ti->graphics_set_aga = NULL;
2852 ti->graphics_set = NULL;
2853 ti->sounds_set_default = NULL;
2854 ti->sounds_set_lowpass = NULL;
2855 ti->sounds_set = NULL;
2856 ti->music_set = NULL;
2857 ti->graphics_path = getStringCopy(UNDEFINED_FILENAME);
2858 ti->sounds_path = getStringCopy(UNDEFINED_FILENAME);
2859 ti->music_path = getStringCopy(UNDEFINED_FILENAME);
2861 ti->level_filename = NULL;
2862 ti->level_filetype = NULL;
2864 ti->special_flags = NULL;
2866 ti->empty_level_name = NULL;
2867 ti->force_level_name = FALSE;
2870 ti->first_level = 0;
2872 ti->level_group = FALSE;
2873 ti->handicap_level = 0;
2874 ti->readonly = TRUE;
2875 ti->handicap = TRUE;
2876 ti->skip_levels = FALSE;
2878 ti->use_emc_tiles = FALSE;
2882 static void setTreeInfoToDefaultsFromParent(TreeInfo *ti, TreeInfo *parent)
2886 Warn("setTreeInfoToDefaultsFromParent(): parent == NULL");
2888 setTreeInfoToDefaults(ti, TREE_TYPE_UNDEFINED);
2893 // copy all values from the parent structure
2895 ti->type = parent->type;
2897 ti->node_top = parent->node_top;
2898 ti->node_parent = parent;
2899 ti->node_group = NULL;
2906 ti->fullpath = NULL;
2907 ti->basepath = NULL;
2908 ti->identifier = NULL;
2909 ti->name = getStringCopy(ANONYMOUS_NAME);
2910 ti->name_sorting = NULL;
2911 ti->author = getStringCopy(parent->author);
2912 ti->year = getStringCopy(parent->year);
2914 ti->program_title = getStringCopy(parent->program_title);
2915 ti->program_copyright = getStringCopy(parent->program_copyright);
2916 ti->program_company = getStringCopy(parent->program_company);
2918 ti->sort_priority = parent->sort_priority;
2919 ti->latest_engine = parent->latest_engine;
2920 ti->parent_link = FALSE;
2921 ti->is_copy = FALSE;
2922 ti->in_user_dir = parent->in_user_dir;
2923 ti->user_defined = parent->user_defined;
2924 ti->color = parent->color;
2925 ti->class_desc = getStringCopy(parent->class_desc);
2927 ti->infotext = getStringCopy(parent->infotext);
2929 if (ti->type == TREE_TYPE_LEVEL_DIR)
2931 ti->imported_from = getStringCopy(parent->imported_from);
2932 ti->imported_by = getStringCopy(parent->imported_by);
2933 ti->tested_by = getStringCopy(parent->tested_by);
2935 ti->graphics_set_ecs = getStringCopy(parent->graphics_set_ecs);
2936 ti->graphics_set_aga = getStringCopy(parent->graphics_set_aga);
2937 ti->graphics_set = getStringCopy(parent->graphics_set);
2938 ti->sounds_set_default = getStringCopy(parent->sounds_set_default);
2939 ti->sounds_set_lowpass = getStringCopy(parent->sounds_set_lowpass);
2940 ti->sounds_set = getStringCopy(parent->sounds_set);
2941 ti->music_set = getStringCopy(parent->music_set);
2942 ti->graphics_path = getStringCopy(UNDEFINED_FILENAME);
2943 ti->sounds_path = getStringCopy(UNDEFINED_FILENAME);
2944 ti->music_path = getStringCopy(UNDEFINED_FILENAME);
2946 ti->level_filename = getStringCopy(parent->level_filename);
2947 ti->level_filetype = getStringCopy(parent->level_filetype);
2949 ti->special_flags = getStringCopy(parent->special_flags);
2951 ti->empty_level_name = getStringCopy(parent->empty_level_name);
2952 ti->force_level_name = parent->force_level_name;
2954 ti->levels = parent->levels;
2955 ti->first_level = parent->first_level;
2956 ti->last_level = parent->last_level;
2957 ti->level_group = FALSE;
2958 ti->handicap_level = parent->handicap_level;
2959 ti->readonly = parent->readonly;
2960 ti->handicap = parent->handicap;
2961 ti->skip_levels = parent->skip_levels;
2963 ti->use_emc_tiles = parent->use_emc_tiles;
2967 static TreeInfo *getTreeInfoCopy(TreeInfo *ti)
2969 TreeInfo *ti_copy = newTreeInfo();
2971 // copy all values from the original structure
2973 ti_copy->type = ti->type;
2975 ti_copy->node_top = ti->node_top;
2976 ti_copy->node_parent = ti->node_parent;
2977 ti_copy->node_group = ti->node_group;
2978 ti_copy->next = ti->next;
2980 ti_copy->cl_first = ti->cl_first;
2981 ti_copy->cl_cursor = ti->cl_cursor;
2983 ti_copy->subdir = getStringCopy(ti->subdir);
2984 ti_copy->fullpath = getStringCopy(ti->fullpath);
2985 ti_copy->basepath = getStringCopy(ti->basepath);
2986 ti_copy->identifier = getStringCopy(ti->identifier);
2987 ti_copy->name = getStringCopy(ti->name);
2988 ti_copy->name_sorting = getStringCopy(ti->name_sorting);
2989 ti_copy->author = getStringCopy(ti->author);
2990 ti_copy->year = getStringCopy(ti->year);
2992 ti_copy->program_title = getStringCopy(ti->program_title);
2993 ti_copy->program_copyright = getStringCopy(ti->program_copyright);
2994 ti_copy->program_company = getStringCopy(ti->program_company);
2996 ti_copy->imported_from = getStringCopy(ti->imported_from);
2997 ti_copy->imported_by = getStringCopy(ti->imported_by);
2998 ti_copy->tested_by = getStringCopy(ti->tested_by);
3000 ti_copy->graphics_set_ecs = getStringCopy(ti->graphics_set_ecs);
3001 ti_copy->graphics_set_aga = getStringCopy(ti->graphics_set_aga);
3002 ti_copy->graphics_set = getStringCopy(ti->graphics_set);
3003 ti_copy->sounds_set_default = getStringCopy(ti->sounds_set_default);
3004 ti_copy->sounds_set_lowpass = getStringCopy(ti->sounds_set_lowpass);
3005 ti_copy->sounds_set = getStringCopy(ti->sounds_set);
3006 ti_copy->music_set = getStringCopy(ti->music_set);
3007 ti_copy->graphics_path = getStringCopy(ti->graphics_path);
3008 ti_copy->sounds_path = getStringCopy(ti->sounds_path);
3009 ti_copy->music_path = getStringCopy(ti->music_path);
3011 ti_copy->level_filename = getStringCopy(ti->level_filename);
3012 ti_copy->level_filetype = getStringCopy(ti->level_filetype);
3014 ti_copy->special_flags = getStringCopy(ti->special_flags);
3016 ti_copy->empty_level_name = getStringCopy(ti->empty_level_name);
3017 ti_copy->force_level_name = ti->force_level_name;
3019 ti_copy->levels = ti->levels;
3020 ti_copy->first_level = ti->first_level;
3021 ti_copy->last_level = ti->last_level;
3022 ti_copy->sort_priority = ti->sort_priority;
3024 ti_copy->latest_engine = ti->latest_engine;
3026 ti_copy->level_group = ti->level_group;
3027 ti_copy->parent_link = ti->parent_link;
3028 ti_copy->is_copy = ti->is_copy;
3029 ti_copy->in_user_dir = ti->in_user_dir;
3030 ti_copy->user_defined = ti->user_defined;
3031 ti_copy->readonly = ti->readonly;
3032 ti_copy->handicap = ti->handicap;
3033 ti_copy->skip_levels = ti->skip_levels;
3035 ti_copy->use_emc_tiles = ti->use_emc_tiles;
3037 ti_copy->color = ti->color;
3038 ti_copy->class_desc = getStringCopy(ti->class_desc);
3039 ti_copy->handicap_level = ti->handicap_level;
3041 ti_copy->infotext = getStringCopy(ti->infotext);
3046 void freeTreeInfo(TreeInfo *ti)
3051 checked_free(ti->subdir);
3052 checked_free(ti->fullpath);
3053 checked_free(ti->basepath);
3054 checked_free(ti->identifier);
3056 checked_free(ti->name);
3057 checked_free(ti->name_sorting);
3058 checked_free(ti->author);
3059 checked_free(ti->year);
3061 checked_free(ti->program_title);
3062 checked_free(ti->program_copyright);
3063 checked_free(ti->program_company);
3065 checked_free(ti->class_desc);
3067 checked_free(ti->infotext);
3069 if (ti->type == TREE_TYPE_LEVEL_DIR)
3071 checked_free(ti->imported_from);
3072 checked_free(ti->imported_by);
3073 checked_free(ti->tested_by);
3075 checked_free(ti->graphics_set_ecs);
3076 checked_free(ti->graphics_set_aga);
3077 checked_free(ti->graphics_set);
3078 checked_free(ti->sounds_set_default);
3079 checked_free(ti->sounds_set_lowpass);
3080 checked_free(ti->sounds_set);
3081 checked_free(ti->music_set);
3083 checked_free(ti->graphics_path);
3084 checked_free(ti->sounds_path);
3085 checked_free(ti->music_path);
3087 checked_free(ti->level_filename);
3088 checked_free(ti->level_filetype);
3090 checked_free(ti->special_flags);
3093 // recursively free child node
3095 freeTreeInfo(ti->node_group);
3097 // recursively free next node
3099 freeTreeInfo(ti->next);
3104 void setSetupInfo(struct TokenInfo *token_info,
3105 int token_nr, char *token_value)
3107 int token_type = token_info[token_nr].type;
3108 void *setup_value = token_info[token_nr].value;
3110 if (token_value == NULL)
3113 // set setup field to corresponding token value
3118 *(boolean *)setup_value = get_boolean_from_string(token_value);
3122 *(int *)setup_value = get_switch3_from_string(token_value);
3126 *(Key *)setup_value = getKeyFromKeyName(token_value);
3130 *(Key *)setup_value = getKeyFromX11KeyName(token_value);
3134 *(int *)setup_value = get_integer_from_string(token_value);
3138 checked_free(*(char **)setup_value);
3139 *(char **)setup_value = getStringCopy(token_value);
3143 *(int *)setup_value = get_player_nr_from_string(token_value);
3151 static int compareTreeInfoEntries(const void *object1, const void *object2)
3153 const TreeInfo *entry1 = *((TreeInfo **)object1);
3154 const TreeInfo *entry2 = *((TreeInfo **)object2);
3155 int tree_sorting1 = TREE_SORTING(entry1);
3156 int tree_sorting2 = TREE_SORTING(entry2);
3158 if (tree_sorting1 != tree_sorting2)
3159 return (tree_sorting1 - tree_sorting2);
3161 return strcasecmp(entry1->name_sorting, entry2->name_sorting);
3164 static TreeInfo *createParentTreeInfoNode(TreeInfo *node_parent)
3168 if (node_parent == NULL)
3171 ti_new = newTreeInfo();
3172 setTreeInfoToDefaults(ti_new, node_parent->type);
3174 ti_new->node_parent = node_parent;
3175 ti_new->parent_link = TRUE;
3177 setString(&ti_new->identifier, node_parent->identifier);
3178 setString(&ti_new->name, BACKLINK_TEXT_PARENT);
3179 setString(&ti_new->name_sorting, ti_new->name);
3181 setString(&ti_new->subdir, STRING_PARENT_DIRECTORY);
3182 setString(&ti_new->fullpath, node_parent->fullpath);
3184 ti_new->sort_priority = LEVELCLASS_PARENT;
3185 ti_new->latest_engine = node_parent->latest_engine;
3187 setString(&ti_new->class_desc, getLevelClassDescription(ti_new));
3189 pushTreeInfo(&node_parent->node_group, ti_new);
3194 static TreeInfo *createTopTreeInfoNode(TreeInfo *node_first)
3196 if (node_first == NULL)
3199 TreeInfo *ti_new = newTreeInfo();
3200 int type = node_first->type;
3202 setTreeInfoToDefaults(ti_new, type);
3204 ti_new->node_parent = NULL;
3205 ti_new->parent_link = FALSE;
3207 setString(&ti_new->identifier, "top_tree_node");
3208 setString(&ti_new->name, TREE_INFOTEXT(type));
3209 setString(&ti_new->name_sorting, ti_new->name);
3211 setString(&ti_new->subdir, STRING_TOP_DIRECTORY);
3212 setString(&ti_new->fullpath, ".");
3214 ti_new->sort_priority = LEVELCLASS_TOP;
3215 ti_new->latest_engine = node_first->latest_engine;
3217 setString(&ti_new->class_desc, TREE_INFOTEXT(type));
3219 ti_new->node_group = node_first;
3220 ti_new->level_group = TRUE;
3222 TreeInfo *ti_new2 = createParentTreeInfoNode(ti_new);
3224 setString(&ti_new2->name, TREE_BACKLINK_TEXT(type));
3225 setString(&ti_new2->name_sorting, ti_new2->name);
3230 static void setTreeInfoParentNodes(TreeInfo *node, TreeInfo *node_parent)
3234 if (node->node_group)
3235 setTreeInfoParentNodes(node->node_group, node);
3237 node->node_parent = node_parent;
3243 TreeInfo *addTopTreeInfoNode(TreeInfo *node_first)
3245 // add top tree node with back link node in previous tree
3246 node_first = createTopTreeInfoNode(node_first);
3248 // set all parent links (back links) in complete tree
3249 setTreeInfoParentNodes(node_first, NULL);
3255 // ----------------------------------------------------------------------------
3256 // functions for handling level and custom artwork info cache
3257 // ----------------------------------------------------------------------------
3259 static void LoadArtworkInfoCache(void)
3261 InitCacheDirectory();
3263 if (artworkinfo_cache_old == NULL)
3265 char *filename = getPath2(getCacheDir(), ARTWORKINFO_CACHE_FILE);
3267 // try to load artwork info hash from already existing cache file
3268 artworkinfo_cache_old = loadSetupFileHash(filename);
3270 // try to get program version that artwork info cache was written with
3271 char *version = getHashEntry(artworkinfo_cache_old, "program.version");
3273 // check program version of artwork info cache against current version
3274 if (!strEqual(version, program.version_string))
3276 freeSetupFileHash(artworkinfo_cache_old);
3278 artworkinfo_cache_old = NULL;
3281 // if no artwork info cache file was found, start with empty hash
3282 if (artworkinfo_cache_old == NULL)
3283 artworkinfo_cache_old = newSetupFileHash();
3288 if (artworkinfo_cache_new == NULL)
3289 artworkinfo_cache_new = newSetupFileHash();
3291 update_artworkinfo_cache = FALSE;
3294 static void SaveArtworkInfoCache(void)
3296 if (!update_artworkinfo_cache)
3299 char *filename = getPath2(getCacheDir(), ARTWORKINFO_CACHE_FILE);
3301 InitCacheDirectory();
3303 saveSetupFileHash(artworkinfo_cache_new, filename);
3308 static char *getCacheTokenPrefix(char *prefix1, char *prefix2)
3310 static char *prefix = NULL;
3312 checked_free(prefix);
3314 prefix = getStringCat2WithSeparator(prefix1, prefix2, ".");
3319 // (identical to above function, but separate string buffer needed -- nasty)
3320 static char *getCacheToken(char *prefix, char *suffix)
3322 static char *token = NULL;
3324 checked_free(token);
3326 token = getStringCat2WithSeparator(prefix, suffix, ".");
3331 static char *getFileTimestampString(char *filename)
3333 return getStringCopy(i_to_a(getFileTimestampEpochSeconds(filename)));
3336 static boolean modifiedFileTimestamp(char *filename, char *timestamp_string)
3338 struct stat file_status;
3340 if (timestamp_string == NULL)
3343 if (!fileExists(filename)) // file does not exist
3344 return (atoi(timestamp_string) != 0);
3346 if (stat(filename, &file_status) != 0) // cannot stat file
3349 return (file_status.st_mtime != atoi(timestamp_string));
3352 static TreeInfo *getArtworkInfoCacheEntry(LevelDirTree *level_node, int type)
3354 char *identifier = level_node->subdir;
3355 char *type_string = ARTWORK_DIRECTORY(type);
3356 char *token_prefix = getCacheTokenPrefix(type_string, identifier);
3357 char *token_main = getCacheToken(token_prefix, "CACHED");
3358 char *cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
3359 boolean cached = (cache_entry != NULL && strEqual(cache_entry, "true"));
3360 TreeInfo *artwork_info = NULL;
3362 if (!use_artworkinfo_cache)
3365 if (optional_tokens_hash == NULL)
3369 // create hash from list of optional tokens (for quick access)
3370 optional_tokens_hash = newSetupFileHash();
3371 for (i = 0; optional_tokens[i] != NULL; i++)
3372 setHashEntry(optional_tokens_hash, optional_tokens[i], "");
3379 artwork_info = newTreeInfo();
3380 setTreeInfoToDefaults(artwork_info, type);
3382 // set all structure fields according to the token/value pairs
3383 ldi = *artwork_info;
3384 for (i = 0; artworkinfo_tokens[i].type != -1; i++)
3386 char *token_suffix = artworkinfo_tokens[i].text;
3387 char *token = getCacheToken(token_prefix, token_suffix);
3388 char *value = getHashEntry(artworkinfo_cache_old, token);
3390 (getHashEntry(optional_tokens_hash, token_suffix) != NULL);
3392 setSetupInfo(artworkinfo_tokens, i, value);
3394 // check if cache entry for this item is mandatory, but missing
3395 if (value == NULL && !optional)
3397 Warn("missing cache entry '%s'", token);
3403 *artwork_info = ldi;
3408 char *filename_levelinfo = getPath2(getLevelDirFromTreeInfo(level_node),
3409 LEVELINFO_FILENAME);
3410 char *filename_artworkinfo = getPath2(getSetupArtworkDir(artwork_info),
3411 ARTWORKINFO_FILENAME(type));
3413 // check if corresponding "levelinfo.conf" file has changed
3414 token_main = getCacheToken(token_prefix, "TIMESTAMP_LEVELINFO");
3415 cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
3417 if (modifiedFileTimestamp(filename_levelinfo, cache_entry))
3420 // check if corresponding "<artworkinfo>.conf" file has changed
3421 token_main = getCacheToken(token_prefix, "TIMESTAMP_ARTWORKINFO");
3422 cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
3424 if (modifiedFileTimestamp(filename_artworkinfo, cache_entry))
3427 checked_free(filename_levelinfo);
3428 checked_free(filename_artworkinfo);
3431 if (!cached && artwork_info != NULL)
3433 freeTreeInfo(artwork_info);
3438 return artwork_info;
3441 static void setArtworkInfoCacheEntry(TreeInfo *artwork_info,
3442 LevelDirTree *level_node, int type)
3444 char *identifier = level_node->subdir;
3445 char *type_string = ARTWORK_DIRECTORY(type);
3446 char *token_prefix = getCacheTokenPrefix(type_string, identifier);
3447 char *token_main = getCacheToken(token_prefix, "CACHED");
3448 boolean set_cache_timestamps = TRUE;
3451 setHashEntry(artworkinfo_cache_new, token_main, "true");
3453 if (set_cache_timestamps)
3455 char *filename_levelinfo = getPath2(getLevelDirFromTreeInfo(level_node),
3456 LEVELINFO_FILENAME);
3457 char *filename_artworkinfo = getPath2(getSetupArtworkDir(artwork_info),
3458 ARTWORKINFO_FILENAME(type));
3459 char *timestamp_levelinfo = getFileTimestampString(filename_levelinfo);
3460 char *timestamp_artworkinfo = getFileTimestampString(filename_artworkinfo);
3462 token_main = getCacheToken(token_prefix, "TIMESTAMP_LEVELINFO");
3463 setHashEntry(artworkinfo_cache_new, token_main, timestamp_levelinfo);
3465 token_main = getCacheToken(token_prefix, "TIMESTAMP_ARTWORKINFO");
3466 setHashEntry(artworkinfo_cache_new, token_main, timestamp_artworkinfo);
3468 checked_free(filename_levelinfo);
3469 checked_free(filename_artworkinfo);
3470 checked_free(timestamp_levelinfo);
3471 checked_free(timestamp_artworkinfo);
3474 ldi = *artwork_info;
3475 for (i = 0; artworkinfo_tokens[i].type != -1; i++)
3477 char *token = getCacheToken(token_prefix, artworkinfo_tokens[i].text);
3478 char *value = getSetupValue(artworkinfo_tokens[i].type,
3479 artworkinfo_tokens[i].value);
3481 setHashEntry(artworkinfo_cache_new, token, value);
3486 // ----------------------------------------------------------------------------
3487 // functions for loading level info and custom artwork info
3488 // ----------------------------------------------------------------------------
3490 int GetZipFileTreeType(char *zip_filename)
3492 static char *top_dir_path = NULL;
3493 static char *top_dir_conf_filename[NUM_BASE_TREE_TYPES] = { NULL };
3494 static char *conf_basename[NUM_BASE_TREE_TYPES] =
3496 GRAPHICSINFO_FILENAME,
3497 SOUNDSINFO_FILENAME,
3503 checked_free(top_dir_path);
3504 top_dir_path = NULL;
3506 for (j = 0; j < NUM_BASE_TREE_TYPES; j++)
3508 checked_free(top_dir_conf_filename[j]);
3509 top_dir_conf_filename[j] = NULL;
3512 char **zip_entries = zip_list(zip_filename);
3514 // check if zip file successfully opened
3515 if (zip_entries == NULL || zip_entries[0] == NULL)
3516 return TREE_TYPE_UNDEFINED;
3518 // first zip file entry is expected to be top level directory
3519 char *top_dir = zip_entries[0];
3521 // check if valid top level directory found in zip file
3522 if (!strSuffix(top_dir, "/"))
3523 return TREE_TYPE_UNDEFINED;
3525 // get filenames of valid configuration files in top level directory
3526 for (j = 0; j < NUM_BASE_TREE_TYPES; j++)
3527 top_dir_conf_filename[j] = getStringCat2(top_dir, conf_basename[j]);
3529 int tree_type = TREE_TYPE_UNDEFINED;
3532 while (zip_entries[e] != NULL)
3534 // check if every zip file entry is below top level directory
3535 if (!strPrefix(zip_entries[e], top_dir))
3536 return TREE_TYPE_UNDEFINED;
3538 // check if this zip file entry is a valid configuration filename
3539 for (j = 0; j < NUM_BASE_TREE_TYPES; j++)
3541 if (strEqual(zip_entries[e], top_dir_conf_filename[j]))
3543 // only exactly one valid configuration file allowed
3544 if (tree_type != TREE_TYPE_UNDEFINED)
3545 return TREE_TYPE_UNDEFINED;
3557 static boolean CheckZipFileForDirectory(char *zip_filename, char *directory,
3560 static char *top_dir_path = NULL;
3561 static char *top_dir_conf_filename = NULL;
3563 checked_free(top_dir_path);
3564 checked_free(top_dir_conf_filename);
3566 top_dir_path = NULL;
3567 top_dir_conf_filename = NULL;
3569 char *conf_basename = (tree_type == TREE_TYPE_LEVEL_DIR ? LEVELINFO_FILENAME :
3570 ARTWORKINFO_FILENAME(tree_type));
3572 // check if valid configuration filename determined
3573 if (conf_basename == NULL || strEqual(conf_basename, ""))
3576 char **zip_entries = zip_list(zip_filename);
3578 // check if zip file successfully opened
3579 if (zip_entries == NULL || zip_entries[0] == NULL)
3582 // first zip file entry is expected to be top level directory
3583 char *top_dir = zip_entries[0];
3585 // check if valid top level directory found in zip file
3586 if (!strSuffix(top_dir, "/"))
3589 // get path of extracted top level directory
3590 top_dir_path = getPath2(directory, top_dir);
3592 // remove trailing directory separator from top level directory path
3593 // (required to be able to check for file and directory in next step)
3594 top_dir_path[strlen(top_dir_path) - 1] = '\0';
3596 // check if zip file's top level directory already exists in target directory
3597 if (fileExists(top_dir_path)) // (checks for file and directory)
3600 // get filename of configuration file in top level directory
3601 top_dir_conf_filename = getStringCat2(top_dir, conf_basename);
3603 boolean found_top_dir_conf_filename = FALSE;
3606 while (zip_entries[i] != NULL)
3608 // check if every zip file entry is below top level directory
3609 if (!strPrefix(zip_entries[i], top_dir))
3612 // check if this zip file entry is the configuration filename
3613 if (strEqual(zip_entries[i], top_dir_conf_filename))
3614 found_top_dir_conf_filename = TRUE;
3619 // check if valid configuration filename was found in zip file
3620 if (!found_top_dir_conf_filename)
3626 char *ExtractZipFileIntoDirectory(char *zip_filename, char *directory,
3629 boolean zip_file_valid = CheckZipFileForDirectory(zip_filename, directory,
3632 if (!zip_file_valid)
3634 Warn("zip file '%s' rejected!", zip_filename);
3639 char **zip_entries = zip_extract(zip_filename, directory);
3641 if (zip_entries == NULL)
3643 Warn("zip file '%s' could not be extracted!", zip_filename);
3648 Info("zip file '%s' successfully extracted!", zip_filename);
3650 // first zip file entry contains top level directory
3651 char *top_dir = zip_entries[0];
3653 // remove trailing directory separator from top level directory
3654 top_dir[strlen(top_dir) - 1] = '\0';
3659 static void ProcessZipFilesInDirectory(char *directory, int tree_type)
3662 DirectoryEntry *dir_entry;
3664 if ((dir = openDirectory(directory)) == NULL)
3666 // display error if directory is main "options.graphics_directory" etc.
3667 if (tree_type == TREE_TYPE_LEVEL_DIR ||
3668 directory == OPTIONS_ARTWORK_DIRECTORY(tree_type))
3669 Warn("cannot read directory '%s'", directory);
3674 while ((dir_entry = readDirectory(dir)) != NULL) // loop all entries
3676 // skip non-zip files (and also directories with zip extension)
3677 if (!strSuffixLower(dir_entry->basename, ".zip") || dir_entry->is_directory)
3680 char *zip_filename = getPath2(directory, dir_entry->basename);
3681 char *zip_filename_extracted = getStringCat2(zip_filename, ".extracted");
3682 char *zip_filename_rejected = getStringCat2(zip_filename, ".rejected");
3684 // check if zip file hasn't already been extracted or rejected
3685 if (!fileExists(zip_filename_extracted) &&
3686 !fileExists(zip_filename_rejected))
3688 char *top_dir = ExtractZipFileIntoDirectory(zip_filename, directory,
3690 char *marker_filename = (top_dir != NULL ? zip_filename_extracted :
3691 zip_filename_rejected);
3694 // create empty file to mark zip file as extracted or rejected
3695 if ((marker_file = fopen(marker_filename, MODE_WRITE)))
3696 fclose(marker_file);
3699 free(zip_filename_extracted);
3700 free(zip_filename_rejected);
3704 closeDirectory(dir);
3707 // forward declaration for recursive call by "LoadLevelInfoFromLevelDir()"
3708 static void LoadLevelInfoFromLevelDir(TreeInfo **, TreeInfo *, char *);
3710 static boolean LoadLevelInfoFromLevelConf(TreeInfo **node_first,
3711 TreeInfo *node_parent,
3712 char *level_directory,
3713 char *directory_name)
3715 char *directory_path = getPath2(level_directory, directory_name);
3716 char *filename = getPath2(directory_path, LEVELINFO_FILENAME);
3717 SetupFileHash *setup_file_hash;
3718 LevelDirTree *leveldir_new = NULL;
3721 // unless debugging, silently ignore directories without "levelinfo.conf"
3722 if (!options.debug && !fileExists(filename))
3724 free(directory_path);
3730 setup_file_hash = loadSetupFileHash(filename);
3732 if (setup_file_hash == NULL)
3734 #if DEBUG_NO_CONFIG_FILE
3735 Debug("setup", "ignoring level directory '%s'", directory_path);
3738 free(directory_path);
3744 leveldir_new = newTreeInfo();
3747 setTreeInfoToDefaultsFromParent(leveldir_new, node_parent);
3749 setTreeInfoToDefaults(leveldir_new, TREE_TYPE_LEVEL_DIR);
3751 leveldir_new->subdir = getStringCopy(directory_name);
3753 // set all structure fields according to the token/value pairs
3754 ldi = *leveldir_new;
3755 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
3756 setSetupInfo(levelinfo_tokens, i,
3757 getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
3758 *leveldir_new = ldi;
3760 if (strEqual(leveldir_new->name, ANONYMOUS_NAME))
3761 setString(&leveldir_new->name, leveldir_new->subdir);
3763 if (leveldir_new->identifier == NULL)
3764 leveldir_new->identifier = getStringCopy(leveldir_new->subdir);
3766 if (leveldir_new->name_sorting == NULL)
3767 leveldir_new->name_sorting = getStringCopy(leveldir_new->name);
3769 if (node_parent == NULL) // top level group
3771 leveldir_new->basepath = getStringCopy(level_directory);
3772 leveldir_new->fullpath = getStringCopy(leveldir_new->subdir);
3774 else // sub level group
3776 leveldir_new->basepath = getStringCopy(node_parent->basepath);
3777 leveldir_new->fullpath = getPath2(node_parent->fullpath, directory_name);
3780 leveldir_new->last_level =
3781 leveldir_new->first_level + leveldir_new->levels - 1;
3783 leveldir_new->in_user_dir =
3784 (!strEqual(leveldir_new->basepath, options.level_directory));
3786 // adjust some settings if user's private level directory was detected
3787 if (leveldir_new->sort_priority == LEVELCLASS_UNDEFINED &&
3788 leveldir_new->in_user_dir &&
3789 (strEqual(leveldir_new->subdir, getLoginName()) ||
3790 strEqual(leveldir_new->name, getLoginName()) ||
3791 strEqual(leveldir_new->author, getRealName())))
3793 leveldir_new->sort_priority = LEVELCLASS_PRIVATE_START;
3794 leveldir_new->readonly = FALSE;
3797 leveldir_new->user_defined =
3798 (leveldir_new->in_user_dir && IS_LEVELCLASS_PRIVATE(leveldir_new));
3800 setString(&leveldir_new->class_desc, getLevelClassDescription(leveldir_new));
3802 leveldir_new->handicap_level = // set handicap to default value
3803 (leveldir_new->user_defined || !leveldir_new->handicap ?
3804 leveldir_new->last_level : leveldir_new->first_level);
3806 DrawInitTextItem(leveldir_new->name);
3808 pushTreeInfo(node_first, leveldir_new);
3810 freeSetupFileHash(setup_file_hash);
3812 if (leveldir_new->level_group)
3814 // create node to link back to current level directory
3815 createParentTreeInfoNode(leveldir_new);
3817 // recursively step into sub-directory and look for more level series
3818 LoadLevelInfoFromLevelDir(&leveldir_new->node_group,
3819 leveldir_new, directory_path);
3822 free(directory_path);
3828 static void LoadLevelInfoFromLevelDir(TreeInfo **node_first,
3829 TreeInfo *node_parent,
3830 char *level_directory)
3832 // ---------- 1st stage: process any level set zip files ----------
3834 ProcessZipFilesInDirectory(level_directory, TREE_TYPE_LEVEL_DIR);
3836 // ---------- 2nd stage: check for level set directories ----------
3839 DirectoryEntry *dir_entry;
3840 boolean valid_entry_found = FALSE;
3842 if ((dir = openDirectory(level_directory)) == NULL)
3844 Warn("cannot read level directory '%s'", level_directory);
3849 while ((dir_entry = readDirectory(dir)) != NULL) // loop all entries
3851 char *directory_name = dir_entry->basename;
3852 char *directory_path = getPath2(level_directory, directory_name);
3854 // skip entries for current and parent directory
3855 if (strEqual(directory_name, ".") ||
3856 strEqual(directory_name, ".."))
3858 free(directory_path);
3863 // find out if directory entry is itself a directory
3864 if (!dir_entry->is_directory) // not a directory
3866 free(directory_path);
3871 free(directory_path);
3873 if (strEqual(directory_name, GRAPHICS_DIRECTORY) ||
3874 strEqual(directory_name, SOUNDS_DIRECTORY) ||
3875 strEqual(directory_name, MUSIC_DIRECTORY))
3878 valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
3883 closeDirectory(dir);
3885 // special case: top level directory may directly contain "levelinfo.conf"
3886 if (node_parent == NULL && !valid_entry_found)
3888 // check if this directory directly contains a file "levelinfo.conf"
3889 valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
3890 level_directory, ".");
3893 if (!valid_entry_found)
3894 Warn("cannot find any valid level series in directory '%s'",
3898 boolean AdjustGraphicsForEMC(void)
3900 boolean settings_changed = FALSE;
3902 settings_changed |= adjustTreeGraphicsForEMC(leveldir_first_all);
3903 settings_changed |= adjustTreeGraphicsForEMC(leveldir_first);
3905 return settings_changed;
3908 boolean AdjustSoundsForEMC(void)
3910 boolean settings_changed = FALSE;
3912 settings_changed |= adjustTreeSoundsForEMC(leveldir_first_all);
3913 settings_changed |= adjustTreeSoundsForEMC(leveldir_first);
3915 return settings_changed;
3918 void LoadLevelInfo(void)
3920 InitUserLevelDirectory(getLoginName());
3922 DrawInitTextHead("Loading level series");
3924 LoadLevelInfoFromLevelDir(&leveldir_first, NULL, options.level_directory);
3925 LoadLevelInfoFromLevelDir(&leveldir_first, NULL, getUserLevelDir(NULL));
3927 leveldir_first = createTopTreeInfoNode(leveldir_first);
3929 /* after loading all level set information, clone the level directory tree
3930 and remove all level sets without levels (these may still contain artwork
3931 to be offered in the setup menu as "custom artwork", and are therefore
3932 checked for existing artwork in the function "LoadLevelArtworkInfo()") */
3933 leveldir_first_all = leveldir_first;
3934 cloneTree(&leveldir_first, leveldir_first_all, TRUE);
3936 AdjustGraphicsForEMC();
3937 AdjustSoundsForEMC();
3939 // before sorting, the first entries will be from the user directory
3940 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
3942 if (leveldir_first == NULL)
3943 Fail("cannot find any valid level series in any directory");
3945 sortTreeInfo(&leveldir_first);
3947 #if ENABLE_UNUSED_CODE
3948 dumpTreeInfo(leveldir_first, 0);
3952 static boolean LoadArtworkInfoFromArtworkConf(TreeInfo **node_first,
3953 TreeInfo *node_parent,
3954 char *base_directory,
3955 char *directory_name, int type)
3957 char *directory_path = getPath2(base_directory, directory_name);
3958 char *filename = getPath2(directory_path, ARTWORKINFO_FILENAME(type));
3959 SetupFileHash *setup_file_hash = NULL;
3960 TreeInfo *artwork_new = NULL;
3963 if (fileExists(filename))
3964 setup_file_hash = loadSetupFileHash(filename);
3966 if (setup_file_hash == NULL) // no config file -- look for artwork files
3969 DirectoryEntry *dir_entry;
3970 boolean valid_file_found = FALSE;
3972 if ((dir = openDirectory(directory_path)) != NULL)
3974 while ((dir_entry = readDirectory(dir)) != NULL)
3976 if (FileIsArtworkType(dir_entry->filename, type))
3978 valid_file_found = TRUE;
3984 closeDirectory(dir);
3987 if (!valid_file_found)
3989 #if DEBUG_NO_CONFIG_FILE
3990 if (!strEqual(directory_name, "."))
3991 Debug("setup", "ignoring artwork directory '%s'", directory_path);
3994 free(directory_path);
4001 artwork_new = newTreeInfo();
4004 setTreeInfoToDefaultsFromParent(artwork_new, node_parent);
4006 setTreeInfoToDefaults(artwork_new, type);
4008 artwork_new->subdir = getStringCopy(directory_name);
4010 if (setup_file_hash) // (before defining ".color" and ".class_desc")
4012 // set all structure fields according to the token/value pairs
4014 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
4015 setSetupInfo(levelinfo_tokens, i,
4016 getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
4019 if (strEqual(artwork_new->name, ANONYMOUS_NAME))
4020 setString(&artwork_new->name, artwork_new->subdir);
4022 if (artwork_new->identifier == NULL)
4023 artwork_new->identifier = getStringCopy(artwork_new->subdir);
4025 if (artwork_new->name_sorting == NULL)
4026 artwork_new->name_sorting = getStringCopy(artwork_new->name);
4029 if (node_parent == NULL) // top level group
4031 artwork_new->basepath = getStringCopy(base_directory);
4032 artwork_new->fullpath = getStringCopy(artwork_new->subdir);
4034 else // sub level group
4036 artwork_new->basepath = getStringCopy(node_parent->basepath);
4037 artwork_new->fullpath = getPath2(node_parent->fullpath, directory_name);
4040 artwork_new->in_user_dir =
4041 (!strEqual(artwork_new->basepath, OPTIONS_ARTWORK_DIRECTORY(type)));
4043 setString(&artwork_new->class_desc, getLevelClassDescription(artwork_new));
4045 if (setup_file_hash == NULL) // (after determining ".user_defined")
4047 if (strEqual(artwork_new->subdir, "."))
4049 if (artwork_new->user_defined)
4051 setString(&artwork_new->identifier, "private");
4052 artwork_new->sort_priority = ARTWORKCLASS_PRIVATE;
4056 setString(&artwork_new->identifier, "classic");
4057 artwork_new->sort_priority = ARTWORKCLASS_CLASSICS;
4060 setString(&artwork_new->class_desc,
4061 getLevelClassDescription(artwork_new));
4065 setString(&artwork_new->identifier, artwork_new->subdir);
4068 setString(&artwork_new->name, artwork_new->identifier);
4069 setString(&artwork_new->name_sorting, artwork_new->name);
4072 pushTreeInfo(node_first, artwork_new);
4074 freeSetupFileHash(setup_file_hash);
4076 free(directory_path);
4082 static void LoadArtworkInfoFromArtworkDir(TreeInfo **node_first,
4083 TreeInfo *node_parent,
4084 char *base_directory, int type)
4086 // ---------- 1st stage: process any artwork set zip files ----------
4088 ProcessZipFilesInDirectory(base_directory, type);
4090 // ---------- 2nd stage: check for artwork set directories ----------
4093 DirectoryEntry *dir_entry;
4094 boolean valid_entry_found = FALSE;
4096 if ((dir = openDirectory(base_directory)) == NULL)
4098 // display error if directory is main "options.graphics_directory" etc.
4099 if (base_directory == OPTIONS_ARTWORK_DIRECTORY(type))
4100 Warn("cannot read directory '%s'", base_directory);
4105 while ((dir_entry = readDirectory(dir)) != NULL) // loop all entries
4107 char *directory_name = dir_entry->basename;
4108 char *directory_path = getPath2(base_directory, directory_name);
4110 // skip directory entries for current and parent directory
4111 if (strEqual(directory_name, ".") ||
4112 strEqual(directory_name, ".."))
4114 free(directory_path);
4119 // skip directory entries which are not a directory
4120 if (!dir_entry->is_directory) // not a directory
4122 free(directory_path);
4127 free(directory_path);
4129 // check if this directory contains artwork with or without config file
4130 valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
4132 directory_name, type);
4135 closeDirectory(dir);
4137 // check if this directory directly contains artwork itself
4138 valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
4139 base_directory, ".",
4141 if (!valid_entry_found)
4142 Warn("cannot find any valid artwork in directory '%s'", base_directory);
4145 static TreeInfo *getDummyArtworkInfo(int type)
4147 // this is only needed when there is completely no artwork available
4148 TreeInfo *artwork_new = newTreeInfo();
4150 setTreeInfoToDefaults(artwork_new, type);
4152 setString(&artwork_new->subdir, UNDEFINED_FILENAME);
4153 setString(&artwork_new->fullpath, UNDEFINED_FILENAME);
4154 setString(&artwork_new->basepath, UNDEFINED_FILENAME);
4156 setString(&artwork_new->identifier, UNDEFINED_FILENAME);
4157 setString(&artwork_new->name, UNDEFINED_FILENAME);
4158 setString(&artwork_new->name_sorting, UNDEFINED_FILENAME);
4163 void SetCurrentArtwork(int type)
4165 ArtworkDirTree **current_ptr = ARTWORK_CURRENT_PTR(artwork, type);
4166 ArtworkDirTree *first_node = ARTWORK_FIRST_NODE(artwork, type);
4167 char *setup_set = SETUP_ARTWORK_SET(setup, type);
4168 char *default_subdir = ARTWORK_DEFAULT_SUBDIR(type);
4170 // set current artwork to artwork configured in setup menu
4171 *current_ptr = getTreeInfoFromIdentifier(first_node, setup_set);
4173 // if not found, set current artwork to default artwork
4174 if (*current_ptr == NULL)
4175 *current_ptr = getTreeInfoFromIdentifier(first_node, default_subdir);
4177 // if not found, set current artwork to first artwork in tree
4178 if (*current_ptr == NULL)
4179 *current_ptr = getFirstValidTreeInfoEntry(first_node);
4182 void ChangeCurrentArtworkIfNeeded(int type)
4184 char *current_identifier = ARTWORK_CURRENT_IDENTIFIER(artwork, type);
4185 char *setup_set = SETUP_ARTWORK_SET(setup, type);
4187 if (!strEqual(current_identifier, setup_set))
4188 SetCurrentArtwork(type);
4191 void LoadArtworkInfo(void)
4193 LoadArtworkInfoCache();
4195 DrawInitTextHead("Looking for custom artwork");
4197 LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
4198 options.graphics_directory,
4199 TREE_TYPE_GRAPHICS_DIR);
4200 LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
4201 getUserGraphicsDir(),
4202 TREE_TYPE_GRAPHICS_DIR);
4204 LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
4205 options.sounds_directory,
4206 TREE_TYPE_SOUNDS_DIR);
4207 LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
4209 TREE_TYPE_SOUNDS_DIR);
4211 LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
4212 options.music_directory,
4213 TREE_TYPE_MUSIC_DIR);
4214 LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
4216 TREE_TYPE_MUSIC_DIR);
4218 if (artwork.gfx_first == NULL)
4219 artwork.gfx_first = getDummyArtworkInfo(TREE_TYPE_GRAPHICS_DIR);
4220 if (artwork.snd_first == NULL)
4221 artwork.snd_first = getDummyArtworkInfo(TREE_TYPE_SOUNDS_DIR);
4222 if (artwork.mus_first == NULL)
4223 artwork.mus_first = getDummyArtworkInfo(TREE_TYPE_MUSIC_DIR);
4225 // before sorting, the first entries will be from the user directory
4226 SetCurrentArtwork(ARTWORK_TYPE_GRAPHICS);
4227 SetCurrentArtwork(ARTWORK_TYPE_SOUNDS);
4228 SetCurrentArtwork(ARTWORK_TYPE_MUSIC);
4230 artwork.gfx_current_identifier = artwork.gfx_current->identifier;
4231 artwork.snd_current_identifier = artwork.snd_current->identifier;
4232 artwork.mus_current_identifier = artwork.mus_current->identifier;
4234 #if ENABLE_UNUSED_CODE
4235 Debug("setup:LoadArtworkInfo", "graphics set == %s",
4236 artwork.gfx_current_identifier);
4237 Debug("setup:LoadArtworkInfo", "sounds set == %s",
4238 artwork.snd_current_identifier);
4239 Debug("setup:LoadArtworkInfo", "music set == %s",
4240 artwork.mus_current_identifier);
4243 sortTreeInfo(&artwork.gfx_first);
4244 sortTreeInfo(&artwork.snd_first);
4245 sortTreeInfo(&artwork.mus_first);
4247 #if ENABLE_UNUSED_CODE
4248 dumpTreeInfo(artwork.gfx_first, 0);
4249 dumpTreeInfo(artwork.snd_first, 0);
4250 dumpTreeInfo(artwork.mus_first, 0);
4254 static void MoveArtworkInfoIntoSubTree(ArtworkDirTree **artwork_node)
4256 ArtworkDirTree *artwork_new = newTreeInfo();
4257 char *top_node_name = "standalone artwork";
4259 setTreeInfoToDefaults(artwork_new, (*artwork_node)->type);
4261 artwork_new->level_group = TRUE;
4263 setString(&artwork_new->identifier, top_node_name);
4264 setString(&artwork_new->name, top_node_name);
4265 setString(&artwork_new->name_sorting, top_node_name);
4267 // create node to link back to current custom artwork directory
4268 createParentTreeInfoNode(artwork_new);
4270 // move existing custom artwork tree into newly created sub-tree
4271 artwork_new->node_group->next = *artwork_node;
4273 // change custom artwork tree to contain only newly created node
4274 *artwork_node = artwork_new;
4277 static void LoadArtworkInfoFromLevelInfoExt(ArtworkDirTree **artwork_node,
4278 ArtworkDirTree *node_parent,
4279 LevelDirTree *level_node,
4280 boolean empty_level_set_mode)
4282 int type = (*artwork_node)->type;
4284 // recursively check all level directories for artwork sub-directories
4288 boolean empty_level_set = (level_node->levels == 0);
4290 // check all tree entries for artwork, but skip parent link entries
4291 if (!level_node->parent_link && empty_level_set == empty_level_set_mode)
4293 TreeInfo *artwork_new = getArtworkInfoCacheEntry(level_node, type);
4294 boolean cached = (artwork_new != NULL);
4298 pushTreeInfo(artwork_node, artwork_new);
4302 TreeInfo *topnode_last = *artwork_node;
4303 char *path = getPath2(getLevelDirFromTreeInfo(level_node),
4304 ARTWORK_DIRECTORY(type));
4306 LoadArtworkInfoFromArtworkDir(artwork_node, NULL, path, type);
4308 if (topnode_last != *artwork_node) // check for newly added node
4310 artwork_new = *artwork_node;
4312 setString(&artwork_new->identifier, level_node->subdir);
4313 setString(&artwork_new->name, level_node->name);
4314 setString(&artwork_new->name_sorting, level_node->name_sorting);
4316 artwork_new->sort_priority = level_node->sort_priority;
4317 artwork_new->in_user_dir = level_node->in_user_dir;
4319 update_artworkinfo_cache = TRUE;
4325 // insert artwork info (from old cache or filesystem) into new cache
4326 if (artwork_new != NULL)
4327 setArtworkInfoCacheEntry(artwork_new, level_node, type);
4330 DrawInitTextItem(level_node->name);
4332 if (level_node->node_group != NULL)
4334 TreeInfo *artwork_new = newTreeInfo();
4337 setTreeInfoToDefaultsFromParent(artwork_new, node_parent);
4339 setTreeInfoToDefaults(artwork_new, type);
4341 artwork_new->level_group = TRUE;
4343 setString(&artwork_new->identifier, level_node->subdir);
4345 if (node_parent == NULL) // check for top tree node
4347 char *top_node_name = (empty_level_set_mode ?
4348 "artwork for certain level sets" :
4349 "artwork included in level sets");
4351 setString(&artwork_new->name, top_node_name);
4352 setString(&artwork_new->name_sorting, top_node_name);
4356 setString(&artwork_new->name, level_node->name);
4357 setString(&artwork_new->name_sorting, level_node->name_sorting);
4360 pushTreeInfo(artwork_node, artwork_new);
4362 // create node to link back to current custom artwork directory
4363 createParentTreeInfoNode(artwork_new);
4365 // recursively step into sub-directory and look for more custom artwork
4366 LoadArtworkInfoFromLevelInfoExt(&artwork_new->node_group, artwork_new,
4367 level_node->node_group,
4368 empty_level_set_mode);
4370 // if sub-tree has no custom artwork at all, remove it
4371 if (artwork_new->node_group->next == NULL)
4372 removeTreeInfo(artwork_node);
4375 level_node = level_node->next;
4379 static void LoadArtworkInfoFromLevelInfo(ArtworkDirTree **artwork_node)
4381 // move peviously loaded artwork tree into separate sub-tree
4382 MoveArtworkInfoIntoSubTree(artwork_node);
4384 // load artwork from level sets into separate sub-trees
4385 LoadArtworkInfoFromLevelInfoExt(artwork_node, NULL, leveldir_first_all, TRUE);
4386 LoadArtworkInfoFromLevelInfoExt(artwork_node, NULL, leveldir_first_all, FALSE);
4388 // add top tree node over all sub-trees and set parent links
4389 *artwork_node = addTopTreeInfoNode(*artwork_node);
4392 void LoadLevelArtworkInfo(void)
4394 print_timestamp_init("LoadLevelArtworkInfo");
4396 DrawInitTextHead("Looking for custom level artwork");
4398 print_timestamp_time("DrawTimeText");
4400 LoadArtworkInfoFromLevelInfo(&artwork.gfx_first);
4401 print_timestamp_time("LoadArtworkInfoFromLevelInfo (gfx)");
4402 LoadArtworkInfoFromLevelInfo(&artwork.snd_first);
4403 print_timestamp_time("LoadArtworkInfoFromLevelInfo (snd)");
4404 LoadArtworkInfoFromLevelInfo(&artwork.mus_first);
4405 print_timestamp_time("LoadArtworkInfoFromLevelInfo (mus)");
4407 SaveArtworkInfoCache();
4409 print_timestamp_time("SaveArtworkInfoCache");
4411 // needed for reloading level artwork not known at ealier stage
4412 ChangeCurrentArtworkIfNeeded(ARTWORK_TYPE_GRAPHICS);
4413 ChangeCurrentArtworkIfNeeded(ARTWORK_TYPE_SOUNDS);
4414 ChangeCurrentArtworkIfNeeded(ARTWORK_TYPE_MUSIC);
4416 print_timestamp_time("getTreeInfoFromIdentifier");
4418 sortTreeInfo(&artwork.gfx_first);
4419 sortTreeInfo(&artwork.snd_first);
4420 sortTreeInfo(&artwork.mus_first);
4422 print_timestamp_time("sortTreeInfo");
4424 #if ENABLE_UNUSED_CODE
4425 dumpTreeInfo(artwork.gfx_first, 0);
4426 dumpTreeInfo(artwork.snd_first, 0);
4427 dumpTreeInfo(artwork.mus_first, 0);
4430 print_timestamp_done("LoadLevelArtworkInfo");
4433 static boolean AddTreeSetToTreeInfoExt(TreeInfo *tree_node_old, char *tree_dir,
4434 char *tree_subdir_new, int type)
4436 if (tree_node_old == NULL)
4438 if (type == TREE_TYPE_LEVEL_DIR)
4440 // get level info tree node of personal user level set
4441 tree_node_old = getTreeInfoFromIdentifier(leveldir_first, getLoginName());
4443 // this may happen if "setup.internal.create_user_levelset" is FALSE
4444 // or if file "levelinfo.conf" is missing in personal user level set
4445 if (tree_node_old == NULL)
4446 tree_node_old = leveldir_first->node_group;
4450 // get artwork info tree node of first artwork set
4451 tree_node_old = ARTWORK_FIRST_NODE(artwork, type);
4455 if (tree_dir == NULL)
4456 tree_dir = TREE_USERDIR(type);
4458 if (tree_node_old == NULL ||
4460 tree_subdir_new == NULL) // should not happen
4463 int draw_deactivation_mask = GetDrawDeactivationMask();
4465 // override draw deactivation mask (temporarily disable drawing)
4466 SetDrawDeactivationMask(REDRAW_ALL);
4468 if (type == TREE_TYPE_LEVEL_DIR)
4470 // load new level set config and add it next to first user level set
4471 LoadLevelInfoFromLevelConf(&tree_node_old->next,
4472 tree_node_old->node_parent,
4473 tree_dir, tree_subdir_new);
4477 // load new artwork set config and add it next to first artwork set
4478 LoadArtworkInfoFromArtworkConf(&tree_node_old->next,
4479 tree_node_old->node_parent,
4480 tree_dir, tree_subdir_new, type);
4483 // set draw deactivation mask to previous value
4484 SetDrawDeactivationMask(draw_deactivation_mask);
4486 // get first node of level or artwork info tree
4487 TreeInfo **tree_node_first = TREE_FIRST_NODE_PTR(type);
4489 // get tree info node of newly added level or artwork set
4490 TreeInfo *tree_node_new = getTreeInfoFromIdentifier(*tree_node_first,
4493 if (tree_node_new == NULL) // should not happen
4496 // correct top link and parent node link of newly created tree node
4497 tree_node_new->node_top = tree_node_old->node_top;
4498 tree_node_new->node_parent = tree_node_old->node_parent;
4500 // sort tree info to adjust position of newly added tree set
4501 sortTreeInfo(tree_node_first);
4506 void AddTreeSetToTreeInfo(TreeInfo *tree_node, char *tree_dir,
4507 char *tree_subdir_new, int type)
4509 if (!AddTreeSetToTreeInfoExt(tree_node, tree_dir, tree_subdir_new, type))
4510 Fail("internal tree info structure corrupted -- aborting");
4513 void AddUserLevelSetToLevelInfo(char *level_subdir_new)
4515 AddTreeSetToTreeInfo(NULL, NULL, level_subdir_new, TREE_TYPE_LEVEL_DIR);
4518 char *getArtworkIdentifierForUserLevelSet(int type)
4520 char *classic_artwork_set = getClassicArtworkSet(type);
4522 // check for custom artwork configured in "levelinfo.conf"
4523 char *leveldir_artwork_set =
4524 *LEVELDIR_ARTWORK_SET_PTR(leveldir_current, type);
4525 boolean has_leveldir_artwork_set =
4526 (leveldir_artwork_set != NULL && !strEqual(leveldir_artwork_set,
4527 classic_artwork_set));
4529 // check for custom artwork in sub-directory "graphics" etc.
4530 TreeInfo *artwork_first_node = ARTWORK_FIRST_NODE(artwork, type);
4531 char *leveldir_identifier = leveldir_current->identifier;
4532 boolean has_artwork_subdir =
4533 (getTreeInfoFromIdentifier(artwork_first_node,
4534 leveldir_identifier) != NULL);
4536 return (has_leveldir_artwork_set ? leveldir_artwork_set :
4537 has_artwork_subdir ? leveldir_identifier :
4538 classic_artwork_set);
4541 TreeInfo *getArtworkTreeInfoForUserLevelSet(int type)
4543 char *artwork_set = getArtworkIdentifierForUserLevelSet(type);
4544 TreeInfo *artwork_first_node = ARTWORK_FIRST_NODE(artwork, type);
4545 TreeInfo *ti = getTreeInfoFromIdentifier(artwork_first_node, artwork_set);
4549 ti = getTreeInfoFromIdentifier(artwork_first_node,
4550 ARTWORK_DEFAULT_SUBDIR(type));
4552 Fail("cannot find default graphics -- should not happen");
4558 boolean checkIfCustomArtworkExistsForCurrentLevelSet(void)
4560 char *graphics_set =
4561 getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_GRAPHICS);
4563 getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_SOUNDS);
4565 getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_MUSIC);
4567 return (!strEqual(graphics_set, GFX_CLASSIC_SUBDIR) ||
4568 !strEqual(sounds_set, SND_CLASSIC_SUBDIR) ||
4569 !strEqual(music_set, MUS_CLASSIC_SUBDIR));
4572 boolean UpdateUserLevelSet(char *level_subdir, char *level_name,
4573 char *level_author, int num_levels)
4575 char *filename = getPath2(getUserLevelDir(level_subdir), LEVELINFO_FILENAME);
4576 char *filename_tmp = getStringCat2(filename, ".tmp");
4578 FILE *file_tmp = NULL;
4579 char line[MAX_LINE_LEN];
4580 boolean success = FALSE;
4581 LevelDirTree *leveldir = getTreeInfoFromIdentifier(leveldir_first,
4583 // update values in level directory tree
4585 if (level_name != NULL)
4586 setString(&leveldir->name, level_name);
4588 if (level_author != NULL)
4589 setString(&leveldir->author, level_author);
4591 if (num_levels != -1)
4592 leveldir->levels = num_levels;
4594 // update values that depend on other values
4596 setString(&leveldir->name_sorting, leveldir->name);
4598 leveldir->last_level = leveldir->first_level + leveldir->levels - 1;
4600 // sort order of level sets may have changed
4601 sortTreeInfo(&leveldir_first);
4603 if ((file = fopen(filename, MODE_READ)) &&
4604 (file_tmp = fopen(filename_tmp, MODE_WRITE)))
4606 while (fgets(line, MAX_LINE_LEN, file))
4608 if (strPrefix(line, "name:") && level_name != NULL)
4609 fprintf(file_tmp, "%-32s%s\n", "name:", level_name);
4610 else if (strPrefix(line, "author:") && level_author != NULL)
4611 fprintf(file_tmp, "%-32s%s\n", "author:", level_author);
4612 else if (strPrefix(line, "levels:") && num_levels != -1)
4613 fprintf(file_tmp, "%-32s%d\n", "levels:", num_levels);
4615 fputs(line, file_tmp);
4628 success = (rename(filename_tmp, filename) == 0);
4636 boolean CreateUserLevelSet(char *level_subdir, char *level_name,
4637 char *level_author, int num_levels,
4638 boolean use_artwork_set)
4640 LevelDirTree *level_info;
4645 // create user level sub-directory, if needed
4646 createDirectory(getUserLevelDir(level_subdir), "user level");
4648 filename = getPath2(getUserLevelDir(level_subdir), LEVELINFO_FILENAME);
4650 if (!(file = fopen(filename, MODE_WRITE)))
4652 Warn("cannot write level info file '%s'", filename);
4659 level_info = newTreeInfo();
4661 // always start with reliable default values
4662 setTreeInfoToDefaults(level_info, TREE_TYPE_LEVEL_DIR);
4664 setString(&level_info->name, level_name);
4665 setString(&level_info->author, level_author);
4666 level_info->levels = num_levels;
4667 level_info->first_level = 1;
4668 level_info->sort_priority = LEVELCLASS_PRIVATE_START;
4669 level_info->readonly = FALSE;
4671 if (use_artwork_set)
4673 level_info->graphics_set =
4674 getStringCopy(getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_GRAPHICS));
4675 level_info->sounds_set =
4676 getStringCopy(getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_SOUNDS));
4677 level_info->music_set =
4678 getStringCopy(getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_MUSIC));
4681 token_value_position = TOKEN_VALUE_POSITION_SHORT;
4683 fprintFileHeader(file, LEVELINFO_FILENAME);
4686 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
4688 if (i == LEVELINFO_TOKEN_NAME ||
4689 i == LEVELINFO_TOKEN_AUTHOR ||
4690 i == LEVELINFO_TOKEN_LEVELS ||
4691 i == LEVELINFO_TOKEN_FIRST_LEVEL ||
4692 i == LEVELINFO_TOKEN_SORT_PRIORITY ||
4693 i == LEVELINFO_TOKEN_READONLY ||
4694 (use_artwork_set && (i == LEVELINFO_TOKEN_GRAPHICS_SET ||
4695 i == LEVELINFO_TOKEN_SOUNDS_SET ||
4696 i == LEVELINFO_TOKEN_MUSIC_SET)))
4697 fprintf(file, "%s\n", getSetupLine(levelinfo_tokens, "", i));
4699 // just to make things nicer :)
4700 if (i == LEVELINFO_TOKEN_AUTHOR ||
4701 i == LEVELINFO_TOKEN_FIRST_LEVEL ||
4702 (use_artwork_set && i == LEVELINFO_TOKEN_READONLY))
4703 fprintf(file, "\n");
4706 token_value_position = TOKEN_VALUE_POSITION_DEFAULT;
4710 SetFilePermissions(filename, PERMS_PRIVATE);
4712 freeTreeInfo(level_info);
4718 static void SaveUserLevelInfo(void)
4720 CreateUserLevelSet(getLoginName(), getLoginName(), getRealName(), 100, FALSE);
4723 char *getSetupValue(int type, void *value)
4725 static char value_string[MAX_LINE_LEN];
4733 strcpy(value_string, (*(boolean *)value ? "true" : "false"));
4737 strcpy(value_string, (*(boolean *)value ? "on" : "off"));
4741 strcpy(value_string, (*(int *)value == AUTO ? "auto" :
4742 *(int *)value == FALSE ? "off" : "on"));
4746 strcpy(value_string, (*(boolean *)value ? "yes" : "no"));
4749 case TYPE_YES_NO_AUTO:
4750 strcpy(value_string, (*(int *)value == AUTO ? "auto" :
4751 *(int *)value == FALSE ? "no" : "yes"));
4755 strcpy(value_string, (*(boolean *)value ? "AGA" : "ECS"));
4759 strcpy(value_string, getKeyNameFromKey(*(Key *)value));
4763 strcpy(value_string, getX11KeyNameFromKey(*(Key *)value));
4767 sprintf(value_string, "%d", *(int *)value);
4771 if (*(char **)value == NULL)
4774 strcpy(value_string, *(char **)value);
4778 sprintf(value_string, "player_%d", *(int *)value + 1);
4782 value_string[0] = '\0';
4786 if (type & TYPE_GHOSTED)
4787 strcpy(value_string, "n/a");
4789 return value_string;
4792 char *getSetupLine(struct TokenInfo *token_info, char *prefix, int token_nr)
4796 static char token_string[MAX_LINE_LEN];
4797 int token_type = token_info[token_nr].type;
4798 void *setup_value = token_info[token_nr].value;
4799 char *token_text = token_info[token_nr].text;
4800 char *value_string = getSetupValue(token_type, setup_value);
4802 // build complete token string
4803 sprintf(token_string, "%s%s", prefix, token_text);
4805 // build setup entry line
4806 line = getFormattedSetupEntry(token_string, value_string);
4808 if (token_type == TYPE_KEY_X11)
4810 Key key = *(Key *)setup_value;
4811 char *keyname = getKeyNameFromKey(key);
4813 // add comment, if useful
4814 if (!strEqual(keyname, "(undefined)") &&
4815 !strEqual(keyname, "(unknown)"))
4817 // add at least one whitespace
4819 for (i = strlen(line); i < token_comment_position; i++)
4823 strcat(line, keyname);
4830 static void InitLastPlayedLevels_ParentNode(void)
4832 LevelDirTree **leveldir_top = &leveldir_first->node_group->next;
4833 LevelDirTree *leveldir_new = NULL;
4835 // check if parent node for last played levels already exists
4836 if (strEqual((*leveldir_top)->identifier, TOKEN_STR_LAST_LEVEL_SERIES))
4839 leveldir_new = newTreeInfo();
4841 setTreeInfoToDefaultsFromParent(leveldir_new, leveldir_first);
4843 leveldir_new->level_group = TRUE;
4844 leveldir_new->sort_priority = LEVELCLASS_LAST_PLAYED_LEVEL;
4846 setString(&leveldir_new->identifier, TOKEN_STR_LAST_LEVEL_SERIES);
4847 setString(&leveldir_new->name, "<< (last played level sets)");
4848 setString(&leveldir_new->name_sorting, leveldir_new->name);
4850 pushTreeInfo(leveldir_top, leveldir_new);
4852 // create node to link back to current level directory
4853 createParentTreeInfoNode(leveldir_new);
4856 void UpdateLastPlayedLevels_TreeInfo(void)
4858 char **last_level_series = setup.level_setup.last_level_series;
4859 LevelDirTree *leveldir_last;
4860 TreeInfo **node_new = NULL;
4863 if (last_level_series[0] == NULL)
4866 InitLastPlayedLevels_ParentNode();
4868 leveldir_last = getTreeInfoFromIdentifierExt(leveldir_first,
4869 TOKEN_STR_LAST_LEVEL_SERIES,
4870 TREE_NODE_TYPE_GROUP);
4871 if (leveldir_last == NULL)
4874 node_new = &leveldir_last->node_group->next;
4876 freeTreeInfo(*node_new);
4880 for (i = 0; last_level_series[i] != NULL; i++)
4882 LevelDirTree *node_last = getTreeInfoFromIdentifier(leveldir_first,
4883 last_level_series[i]);
4884 if (node_last == NULL)
4887 *node_new = getTreeInfoCopy(node_last); // copy complete node
4889 (*node_new)->node_top = &leveldir_first; // correct top node link
4890 (*node_new)->node_parent = leveldir_last; // correct parent node link
4892 (*node_new)->is_copy = TRUE; // mark entry as node copy
4894 (*node_new)->node_group = NULL;
4895 (*node_new)->next = NULL;
4897 (*node_new)->cl_first = -1; // force setting tree cursor
4899 node_new = &((*node_new)->next);
4903 static void UpdateLastPlayedLevels_List(void)
4905 char **last_level_series = setup.level_setup.last_level_series;
4906 int pos = MAX_LEVELDIR_HISTORY - 1;
4909 // search for potentially already existing entry in list of level sets
4910 for (i = 0; last_level_series[i] != NULL; i++)
4911 if (strEqual(last_level_series[i], leveldir_current->identifier))
4914 // move list of level sets one entry down (using potentially free entry)
4915 for (i = pos; i > 0; i--)
4916 setString(&last_level_series[i], last_level_series[i - 1]);
4918 // put last played level set at top position
4919 setString(&last_level_series[0], leveldir_current->identifier);
4922 static TreeInfo *StoreOrRestoreLastPlayedLevels(TreeInfo *node, boolean store)
4924 static char *identifier = NULL;
4928 setString(&identifier, (node && node->is_copy ? node->identifier : NULL));
4930 return NULL; // not used
4934 TreeInfo *node_new = getTreeInfoFromIdentifierExt(leveldir_first,
4936 TREE_NODE_TYPE_COPY);
4937 return (node_new != NULL ? node_new : node);
4941 void StoreLastPlayedLevels(TreeInfo *node)
4943 StoreOrRestoreLastPlayedLevels(node, TRUE);
4946 void RestoreLastPlayedLevels(TreeInfo **node)
4948 *node = StoreOrRestoreLastPlayedLevels(*node, FALSE);
4951 void LoadLevelSetup_LastSeries(void)
4953 // --------------------------------------------------------------------------
4954 // ~/.<program>/levelsetup.conf
4955 // --------------------------------------------------------------------------
4957 char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
4958 SetupFileHash *level_setup_hash = NULL;
4962 // always start with reliable default values
4963 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
4965 // start with empty history of last played level sets
4966 setString(&setup.level_setup.last_level_series[0], NULL);
4968 if (!strEqual(DEFAULT_LEVELSET, UNDEFINED_LEVELSET))
4970 leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
4972 if (leveldir_current == NULL)
4973 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
4976 if ((level_setup_hash = loadSetupFileHash(filename)))
4978 char *last_level_series =
4979 getHashEntry(level_setup_hash, TOKEN_STR_LAST_LEVEL_SERIES);
4981 leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
4983 if (leveldir_current == NULL)
4984 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
4986 for (i = 0; i < MAX_LEVELDIR_HISTORY; i++)
4988 char token[strlen(TOKEN_STR_LAST_LEVEL_SERIES) + 10];
4989 LevelDirTree *leveldir_last;
4991 sprintf(token, "%s.%03d", TOKEN_STR_LAST_LEVEL_SERIES, i);
4993 last_level_series = getHashEntry(level_setup_hash, token);
4995 leveldir_last = getTreeInfoFromIdentifier(leveldir_first,
4997 if (leveldir_last != NULL)
4998 setString(&setup.level_setup.last_level_series[pos++],
5002 setString(&setup.level_setup.last_level_series[pos], NULL);
5004 freeSetupFileHash(level_setup_hash);
5008 Debug("setup", "using default setup values");
5014 static void SaveLevelSetup_LastSeries_Ext(boolean deactivate_last_level_series)
5016 // --------------------------------------------------------------------------
5017 // ~/.<program>/levelsetup.conf
5018 // --------------------------------------------------------------------------
5020 // check if the current level directory structure is available at this point
5021 if (leveldir_current == NULL)
5024 char **last_level_series = setup.level_setup.last_level_series;
5025 char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
5029 InitUserDataDirectory();
5031 UpdateLastPlayedLevels_List();
5033 if (!(file = fopen(filename, MODE_WRITE)))
5035 Warn("cannot write setup file '%s'", filename);
5042 fprintFileHeader(file, LEVELSETUP_FILENAME);
5044 if (deactivate_last_level_series)
5045 fprintf(file, "# %s\n# ", "the following level set may have caused a problem and was deactivated");
5047 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_LAST_LEVEL_SERIES,
5048 leveldir_current->identifier));
5050 for (i = 0; last_level_series[i] != NULL; i++)
5052 char token[strlen(TOKEN_STR_LAST_LEVEL_SERIES) + 1 + 10 + 1];
5054 sprintf(token, "%s.%03d", TOKEN_STR_LAST_LEVEL_SERIES, i);
5056 fprintf(file, "%s\n", getFormattedSetupEntry(token, last_level_series[i]));
5061 SetFilePermissions(filename, PERMS_PRIVATE);
5066 void SaveLevelSetup_LastSeries(void)
5068 SaveLevelSetup_LastSeries_Ext(FALSE);
5071 void SaveLevelSetup_LastSeries_Deactivate(void)
5073 SaveLevelSetup_LastSeries_Ext(TRUE);
5076 static void checkSeriesInfo(void)
5078 static char *level_directory = NULL;
5081 DirectoryEntry *dir_entry;
5084 checked_free(level_directory);
5086 // check for more levels besides the 'levels' field of 'levelinfo.conf'
5088 level_directory = getPath2((leveldir_current->in_user_dir ?
5089 getUserLevelDir(NULL) :
5090 options.level_directory),
5091 leveldir_current->fullpath);
5093 if ((dir = openDirectory(level_directory)) == NULL)
5095 Warn("cannot read level directory '%s'", level_directory);
5101 while ((dir_entry = readDirectory(dir)) != NULL) // loop all entries
5103 if (strlen(dir_entry->basename) > 4 &&
5104 dir_entry->basename[3] == '.' &&
5105 strEqual(&dir_entry->basename[4], LEVELFILE_EXTENSION))
5107 char levelnum_str[4];
5110 strncpy(levelnum_str, dir_entry->basename, 3);
5111 levelnum_str[3] = '\0';
5113 levelnum_value = atoi(levelnum_str);
5115 if (levelnum_value < leveldir_current->first_level)
5117 Warn("additional level %d found", levelnum_value);
5119 leveldir_current->first_level = levelnum_value;
5121 else if (levelnum_value > leveldir_current->last_level)
5123 Warn("additional level %d found", levelnum_value);
5125 leveldir_current->last_level = levelnum_value;
5131 closeDirectory(dir);
5134 void LoadLevelSetup_SeriesInfo(void)
5137 SetupFileHash *level_setup_hash = NULL;
5138 char *level_subdir = leveldir_current->subdir;
5141 // always start with reliable default values
5142 level_nr = leveldir_current->first_level;
5144 for (i = 0; i < MAX_LEVELS; i++)
5146 LevelStats_setPlayed(i, 0);
5147 LevelStats_setSolved(i, 0);
5152 // --------------------------------------------------------------------------
5153 // ~/.<program>/levelsetup/<level series>/levelsetup.conf
5154 // --------------------------------------------------------------------------
5156 level_subdir = leveldir_current->subdir;
5158 filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
5160 if ((level_setup_hash = loadSetupFileHash(filename)))
5164 // get last played level in this level set
5166 token_value = getHashEntry(level_setup_hash, TOKEN_STR_LAST_PLAYED_LEVEL);
5170 level_nr = atoi(token_value);
5172 if (level_nr < leveldir_current->first_level)
5173 level_nr = leveldir_current->first_level;
5174 if (level_nr > leveldir_current->last_level)
5175 level_nr = leveldir_current->last_level;
5178 // get handicap level in this level set
5180 token_value = getHashEntry(level_setup_hash, TOKEN_STR_HANDICAP_LEVEL);
5184 int level_nr = atoi(token_value);
5186 if (level_nr < leveldir_current->first_level)
5187 level_nr = leveldir_current->first_level;
5188 if (level_nr > leveldir_current->last_level + 1)
5189 level_nr = leveldir_current->last_level;
5191 if (leveldir_current->user_defined || !leveldir_current->handicap)
5192 level_nr = leveldir_current->last_level;
5194 leveldir_current->handicap_level = level_nr;
5197 // get number of played and solved levels in this level set
5199 BEGIN_HASH_ITERATION(level_setup_hash, itr)
5201 char *token = HASH_ITERATION_TOKEN(itr);
5202 char *value = HASH_ITERATION_VALUE(itr);
5204 if (strlen(token) == 3 &&
5205 token[0] >= '0' && token[0] <= '9' &&
5206 token[1] >= '0' && token[1] <= '9' &&
5207 token[2] >= '0' && token[2] <= '9')
5209 int level_nr = atoi(token);
5212 LevelStats_setPlayed(level_nr, atoi(value)); // read 1st column
5214 value = strchr(value, ' ');
5217 LevelStats_setSolved(level_nr, atoi(value)); // read 2nd column
5220 END_HASH_ITERATION(hash, itr)
5222 freeSetupFileHash(level_setup_hash);
5226 Debug("setup", "using default setup values");
5232 void SaveLevelSetup_SeriesInfo(void)
5235 char *level_subdir = leveldir_current->subdir;
5236 char *level_nr_str = int2str(level_nr, 0);
5237 char *handicap_level_str = int2str(leveldir_current->handicap_level, 0);
5241 // --------------------------------------------------------------------------
5242 // ~/.<program>/levelsetup/<level series>/levelsetup.conf
5243 // --------------------------------------------------------------------------
5245 InitLevelSetupDirectory(level_subdir);
5247 filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
5249 if (!(file = fopen(filename, MODE_WRITE)))
5251 Warn("cannot write setup file '%s'", filename);
5258 fprintFileHeader(file, LEVELSETUP_FILENAME);
5260 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_PLAYED_LEVEL,
5262 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_HANDICAP_LEVEL,
5263 handicap_level_str));
5265 for (i = leveldir_current->first_level; i <= leveldir_current->last_level;
5268 if (LevelStats_getPlayed(i) > 0 ||
5269 LevelStats_getSolved(i) > 0)
5274 sprintf(token, "%03d", i);
5275 sprintf(value, "%d %d", LevelStats_getPlayed(i), LevelStats_getSolved(i));
5277 fprintf(file, "%s\n", getFormattedSetupEntry(token, value));
5283 SetFilePermissions(filename, PERMS_PRIVATE);
5288 int LevelStats_getPlayed(int nr)
5290 return (nr >= 0 && nr < MAX_LEVELS ? level_stats[nr].played : 0);
5293 int LevelStats_getSolved(int nr)
5295 return (nr >= 0 && nr < MAX_LEVELS ? level_stats[nr].solved : 0);
5298 void LevelStats_setPlayed(int nr, int value)
5300 if (nr >= 0 && nr < MAX_LEVELS)
5301 level_stats[nr].played = value;
5304 void LevelStats_setSolved(int nr, int value)
5306 if (nr >= 0 && nr < MAX_LEVELS)
5307 level_stats[nr].solved = value;
5310 void LevelStats_incPlayed(int nr)
5312 if (nr >= 0 && nr < MAX_LEVELS)
5313 level_stats[nr].played++;
5316 void LevelStats_incSolved(int nr)
5318 if (nr >= 0 && nr < MAX_LEVELS)
5319 level_stats[nr].solved++;
5322 void LoadUserSetup(void)
5324 // --------------------------------------------------------------------------
5325 // ~/.<program>/usersetup.conf
5326 // --------------------------------------------------------------------------
5328 char *filename = getPath2(getMainUserGameDataDir(), USERSETUP_FILENAME);
5329 SetupFileHash *user_setup_hash = NULL;
5331 // always start with reliable default values
5334 if ((user_setup_hash = loadSetupFileHash(filename)))
5338 // get last selected user number
5339 token_value = getHashEntry(user_setup_hash, TOKEN_STR_LAST_USER);
5342 user.nr = MIN(MAX(0, atoi(token_value)), MAX_PLAYER_NAMES - 1);
5344 freeSetupFileHash(user_setup_hash);
5348 Debug("setup", "using default setup values");
5354 void SaveUserSetup(void)
5356 // --------------------------------------------------------------------------
5357 // ~/.<program>/usersetup.conf
5358 // --------------------------------------------------------------------------
5360 char *filename = getPath2(getMainUserGameDataDir(), USERSETUP_FILENAME);
5363 InitMainUserDataDirectory();
5365 if (!(file = fopen(filename, MODE_WRITE)))
5367 Warn("cannot write setup file '%s'", filename);
5374 fprintFileHeader(file, USERSETUP_FILENAME);
5376 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_USER,
5380 SetFilePermissions(filename, PERMS_PRIVATE);