1 // ============================================================================
2 // Artsoft Retro-Game Library
3 // ----------------------------------------------------------------------------
4 // (c) 1995-2014 by Artsoft Entertainment
7 // https://www.artsoft.org/
8 // ----------------------------------------------------------------------------
10 // ============================================================================
12 #include <sys/types.h>
26 #include "zip/miniunz.h"
29 #define ENABLE_UNUSED_CODE FALSE // for currently unused functions
30 #define DEBUG_NO_CONFIG_FILE FALSE // for extra-verbose debug output
32 #define NUM_LEVELCLASS_DESC 8
34 static char *levelclass_desc[NUM_LEVELCLASS_DESC] =
46 #define TOKEN_VALUE_POSITION_SHORT 32
47 #define TOKEN_VALUE_POSITION_DEFAULT 40
48 #define TOKEN_COMMENT_POSITION_DEFAULT 60
50 #define MAX_COOKIE_LEN 256
52 #define TREE_NODE_TYPE_DEFAULT 0
53 #define TREE_NODE_TYPE_PARENT 1
54 #define TREE_NODE_TYPE_GROUP 2
55 #define TREE_NODE_TYPE_COPY 3
57 #define TREE_NODE_TYPE(ti) (ti->node_group ? TREE_NODE_TYPE_GROUP : \
58 ti->parent_link ? TREE_NODE_TYPE_PARENT : \
59 ti->is_copy ? TREE_NODE_TYPE_COPY : \
60 TREE_NODE_TYPE_DEFAULT)
63 static void setTreeInfoToDefaults(TreeInfo *, int);
64 static TreeInfo *getTreeInfoCopy(TreeInfo *ti);
65 static int compareTreeInfoEntries(const void *, const void *);
67 static int token_value_position = TOKEN_VALUE_POSITION_DEFAULT;
68 static int token_comment_position = TOKEN_COMMENT_POSITION_DEFAULT;
70 static SetupFileHash *artworkinfo_cache_old = NULL;
71 static SetupFileHash *artworkinfo_cache_new = NULL;
72 static SetupFileHash *optional_tokens_hash = NULL;
73 static SetupFileHash *missing_file_hash = NULL;
74 static boolean use_artworkinfo_cache = TRUE;
75 static boolean update_artworkinfo_cache = FALSE;
78 // ----------------------------------------------------------------------------
80 // ----------------------------------------------------------------------------
82 static void WarnUsingFallback(char *filename)
84 if (getHashEntry(missing_file_hash, filename) == NULL)
86 setHashEntry(missing_file_hash, filename, "");
88 Warn("cannot find artwork file '%s' (using fallback)", filename);
92 static char *getLevelClassDescription(TreeInfo *ti)
94 int position = ti->sort_priority / 100;
96 if (position >= 0 && position < NUM_LEVELCLASS_DESC)
97 return levelclass_desc[position];
99 return "Unknown Level Class";
102 static char *getCacheDir(void)
104 static char *cache_dir = NULL;
106 if (cache_dir == NULL)
107 cache_dir = getPath2(getMainUserGameDataDir(), CACHE_DIRECTORY);
112 static char *getScoreDir(char *level_subdir)
114 static char *score_dir = NULL;
115 static char *score_level_dir = NULL;
116 char *score_subdir = SCORES_DIRECTORY;
118 if (score_dir == NULL)
119 score_dir = getPath2(getMainUserGameDataDir(), score_subdir);
121 if (level_subdir != NULL)
123 checked_free(score_level_dir);
125 score_level_dir = getPath2(score_dir, level_subdir);
127 return score_level_dir;
133 static char *getScoreCacheDir(char *level_subdir)
135 static char *score_dir = NULL;
136 static char *score_level_dir = NULL;
137 char *score_subdir = SCORES_DIRECTORY;
139 if (score_dir == NULL)
140 score_dir = getPath2(getCacheDir(), score_subdir);
142 if (level_subdir != NULL)
144 checked_free(score_level_dir);
146 score_level_dir = getPath2(score_dir, level_subdir);
148 return score_level_dir;
154 static char *getScoreTapeDir(char *level_subdir, int nr)
156 static char *score_tape_dir = NULL;
157 char tape_subdir[MAX_FILENAME_LEN];
159 checked_free(score_tape_dir);
161 sprintf(tape_subdir, "%03d", nr);
162 score_tape_dir = getPath2(getScoreDir(level_subdir), tape_subdir);
164 return score_tape_dir;
167 static char *getScoreCacheTapeDir(char *level_subdir, int nr)
169 static char *score_cache_tape_dir = NULL;
170 char tape_subdir[MAX_FILENAME_LEN];
172 checked_free(score_cache_tape_dir);
174 sprintf(tape_subdir, "%03d", nr);
175 score_cache_tape_dir = getPath2(getScoreCacheDir(level_subdir), tape_subdir);
177 return score_cache_tape_dir;
180 static char *getUserSubdir(int nr)
182 static char user_subdir[16] = { 0 };
184 sprintf(user_subdir, "%03d", nr);
189 static char *getUserDir(int nr)
191 static char *user_dir = NULL;
192 char *main_data_dir = getMainUserGameDataDir();
193 char *users_subdir = USERS_DIRECTORY;
194 char *user_subdir = getUserSubdir(nr);
196 checked_free(user_dir);
199 user_dir = getPath3(main_data_dir, users_subdir, user_subdir);
201 user_dir = getPath2(main_data_dir, users_subdir);
206 static char *getLevelSetupDir(char *level_subdir)
208 static char *levelsetup_dir = NULL;
209 char *data_dir = getUserGameDataDir();
210 char *levelsetup_subdir = LEVELSETUP_DIRECTORY;
212 checked_free(levelsetup_dir);
214 if (level_subdir != NULL)
215 levelsetup_dir = getPath3(data_dir, levelsetup_subdir, level_subdir);
217 levelsetup_dir = getPath2(data_dir, levelsetup_subdir);
219 return levelsetup_dir;
222 static char *getNetworkDir(void)
224 static char *network_dir = NULL;
226 if (network_dir == NULL)
227 network_dir = getPath2(getMainUserGameDataDir(), NETWORK_DIRECTORY);
232 char *getLevelDirFromTreeInfo(TreeInfo *node)
234 static char *level_dir = NULL;
237 return options.level_directory;
239 checked_free(level_dir);
241 level_dir = getPath2((node->in_user_dir ? getUserLevelDir(NULL) :
242 options.level_directory), node->fullpath);
247 char *getUserLevelDir(char *level_subdir)
249 static char *userlevel_dir = NULL;
250 char *data_dir = getMainUserGameDataDir();
251 char *userlevel_subdir = LEVELS_DIRECTORY;
253 checked_free(userlevel_dir);
255 if (level_subdir != NULL)
256 userlevel_dir = getPath3(data_dir, userlevel_subdir, level_subdir);
258 userlevel_dir = getPath2(data_dir, userlevel_subdir);
260 return userlevel_dir;
263 char *getNetworkLevelDir(char *level_subdir)
265 static char *network_level_dir = NULL;
266 char *data_dir = getNetworkDir();
267 char *networklevel_subdir = LEVELS_DIRECTORY;
269 checked_free(network_level_dir);
271 if (level_subdir != NULL)
272 network_level_dir = getPath3(data_dir, networklevel_subdir, level_subdir);
274 network_level_dir = getPath2(data_dir, networklevel_subdir);
276 return network_level_dir;
279 char *getCurrentLevelDir(void)
281 return getLevelDirFromTreeInfo(leveldir_current);
284 char *getNewUserLevelSubdir(void)
286 static char *new_level_subdir = NULL;
287 char *subdir_prefix = getLoginName();
288 char subdir_suffix[10];
289 int max_suffix_number = 1000;
292 while (++i < max_suffix_number)
294 sprintf(subdir_suffix, "_%d", i);
296 checked_free(new_level_subdir);
297 new_level_subdir = getStringCat2(subdir_prefix, subdir_suffix);
299 if (!directoryExists(getUserLevelDir(new_level_subdir)))
303 return new_level_subdir;
306 char *getTapeDir(char *level_subdir)
308 static char *tape_dir = NULL;
309 char *data_dir = getUserGameDataDir();
310 char *tape_subdir = TAPES_DIRECTORY;
312 checked_free(tape_dir);
314 if (level_subdir != NULL)
315 tape_dir = getPath3(data_dir, tape_subdir, level_subdir);
317 tape_dir = getPath2(data_dir, tape_subdir);
322 static char *getSolutionTapeDir(void)
324 static char *tape_dir = NULL;
325 char *data_dir = getCurrentLevelDir();
326 char *tape_subdir = TAPES_DIRECTORY;
328 checked_free(tape_dir);
330 tape_dir = getPath2(data_dir, tape_subdir);
335 static char *getDefaultGraphicsDir(char *graphics_subdir)
337 static char *graphics_dir = NULL;
339 if (graphics_subdir == NULL)
340 return options.graphics_directory;
342 checked_free(graphics_dir);
344 graphics_dir = getPath2(options.graphics_directory, graphics_subdir);
349 static char *getDefaultSoundsDir(char *sounds_subdir)
351 static char *sounds_dir = NULL;
353 if (sounds_subdir == NULL)
354 return options.sounds_directory;
356 checked_free(sounds_dir);
358 sounds_dir = getPath2(options.sounds_directory, sounds_subdir);
363 static char *getDefaultMusicDir(char *music_subdir)
365 static char *music_dir = NULL;
367 if (music_subdir == NULL)
368 return options.music_directory;
370 checked_free(music_dir);
372 music_dir = getPath2(options.music_directory, music_subdir);
377 static char *getClassicArtworkSet(int type)
379 return (type == TREE_TYPE_GRAPHICS_DIR ? GFX_CLASSIC_SUBDIR :
380 type == TREE_TYPE_SOUNDS_DIR ? SND_CLASSIC_SUBDIR :
381 type == TREE_TYPE_MUSIC_DIR ? MUS_CLASSIC_SUBDIR : "");
384 static char *getClassicArtworkDir(int type)
386 return (type == TREE_TYPE_GRAPHICS_DIR ?
387 getDefaultGraphicsDir(GFX_CLASSIC_SUBDIR) :
388 type == TREE_TYPE_SOUNDS_DIR ?
389 getDefaultSoundsDir(SND_CLASSIC_SUBDIR) :
390 type == TREE_TYPE_MUSIC_DIR ?
391 getDefaultMusicDir(MUS_CLASSIC_SUBDIR) : "");
394 char *getUserGraphicsDir(void)
396 static char *usergraphics_dir = NULL;
398 if (usergraphics_dir == NULL)
399 usergraphics_dir = getPath2(getMainUserGameDataDir(), GRAPHICS_DIRECTORY);
401 return usergraphics_dir;
404 char *getUserSoundsDir(void)
406 static char *usersounds_dir = NULL;
408 if (usersounds_dir == NULL)
409 usersounds_dir = getPath2(getMainUserGameDataDir(), SOUNDS_DIRECTORY);
411 return usersounds_dir;
414 char *getUserMusicDir(void)
416 static char *usermusic_dir = NULL;
418 if (usermusic_dir == NULL)
419 usermusic_dir = getPath2(getMainUserGameDataDir(), MUSIC_DIRECTORY);
421 return usermusic_dir;
424 static char *getSetupArtworkDir(TreeInfo *ti)
426 static char *artwork_dir = NULL;
431 checked_free(artwork_dir);
433 artwork_dir = getPath2(ti->basepath, ti->fullpath);
438 char *setLevelArtworkDir(TreeInfo *ti)
440 char **artwork_path_ptr, **artwork_set_ptr;
441 TreeInfo *level_artwork;
443 if (ti == NULL || leveldir_current == NULL)
446 artwork_path_ptr = LEVELDIR_ARTWORK_PATH_PTR(leveldir_current, ti->type);
447 artwork_set_ptr = LEVELDIR_ARTWORK_SET_PTR( leveldir_current, ti->type);
449 checked_free(*artwork_path_ptr);
451 if ((level_artwork = getTreeInfoFromIdentifier(ti, *artwork_set_ptr)))
453 *artwork_path_ptr = getStringCopy(getSetupArtworkDir(level_artwork));
458 No (or non-existing) artwork configured in "levelinfo.conf". This would
459 normally result in using the artwork configured in the setup menu. But
460 if an artwork subdirectory exists (which might contain custom artwork
461 or an artwork configuration file), this level artwork must be treated
462 as relative to the default "classic" artwork, not to the artwork that
463 is currently configured in the setup menu.
465 Update: For "special" versions of R'n'D (like "R'n'D jue"), do not use
466 the "default" artwork (which would be "jue0" for "R'n'D jue"), but use
467 the real "classic" artwork from the original R'n'D (like "gfx_classic").
470 char *dir = getPath2(getCurrentLevelDir(), ARTWORK_DIRECTORY(ti->type));
472 checked_free(*artwork_set_ptr);
474 if (directoryExists(dir))
476 *artwork_path_ptr = getStringCopy(getClassicArtworkDir(ti->type));
477 *artwork_set_ptr = getStringCopy(getClassicArtworkSet(ti->type));
481 *artwork_path_ptr = getStringCopy(UNDEFINED_FILENAME);
482 *artwork_set_ptr = NULL;
488 return *artwork_set_ptr;
491 static char *getLevelArtworkSet(int type)
493 if (leveldir_current == NULL)
496 return LEVELDIR_ARTWORK_SET(leveldir_current, type);
499 static char *getLevelArtworkDir(int type)
501 if (leveldir_current == NULL)
502 return UNDEFINED_FILENAME;
504 return LEVELDIR_ARTWORK_PATH(leveldir_current, type);
507 char *getProgramMainDataPath(char *command_filename, char *base_path)
509 // check if the program's main data base directory is configured
510 if (!strEqual(base_path, "."))
511 return getStringCopy(base_path);
513 /* if the program is configured to start from current directory (default),
514 determine program package directory from program binary (some versions
515 of KDE/Konqueror and Mac OS X (especially "Mavericks") apparently do not
516 set the current working directory to the program package directory) */
517 char *main_data_path = getBasePath(command_filename);
519 #if defined(PLATFORM_MACOSX)
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 char *getTapeFilename(int nr)
598 static char *filename = NULL;
599 char basename[MAX_FILENAME_LEN];
601 checked_free(filename);
603 sprintf(basename, "%03d.%s", nr, TAPEFILE_EXTENSION);
604 filename = getPath2(getTapeDir(leveldir_current->subdir), basename);
609 char *getTemporaryTapeFilename(void)
611 static char *filename = NULL;
612 char basename[MAX_FILENAME_LEN];
614 checked_free(filename);
616 sprintf(basename, "tmp.%s", TAPEFILE_EXTENSION);
617 filename = getPath2(getTapeDir(NULL), basename);
622 char *getDefaultSolutionTapeFilename(int nr)
624 static char *filename = NULL;
625 char basename[MAX_FILENAME_LEN];
627 checked_free(filename);
629 sprintf(basename, "%03d.%s", nr, TAPEFILE_EXTENSION);
630 filename = getPath2(getSolutionTapeDir(), basename);
635 char *getSokobanSolutionTapeFilename(int nr)
637 static char *filename = NULL;
638 char basename[MAX_FILENAME_LEN];
640 checked_free(filename);
642 sprintf(basename, "%03d.sln", nr);
643 filename = getPath2(getSolutionTapeDir(), basename);
648 char *getSolutionTapeFilename(int nr)
650 char *filename = getDefaultSolutionTapeFilename(nr);
652 if (!fileExists(filename))
654 char *filename2 = getSokobanSolutionTapeFilename(nr);
656 if (fileExists(filename2))
663 char *getScoreFilename(int nr)
665 static char *filename = NULL;
666 char basename[MAX_FILENAME_LEN];
668 checked_free(filename);
670 sprintf(basename, "%03d.%s", nr, SCOREFILE_EXTENSION);
672 // used instead of "leveldir_current->subdir" (for network games)
673 filename = getPath2(getScoreDir(levelset.identifier), basename);
678 char *getScoreCacheFilename(int nr)
680 static char *filename = NULL;
681 char basename[MAX_FILENAME_LEN];
683 checked_free(filename);
685 sprintf(basename, "%03d.%s", nr, SCOREFILE_EXTENSION);
687 // used instead of "leveldir_current->subdir" (for network games)
688 filename = getPath2(getScoreCacheDir(levelset.identifier), basename);
693 char *getScoreTapeBasename(char *name)
695 static char basename[MAX_FILENAME_LEN];
696 char basename_raw[MAX_FILENAME_LEN];
699 sprintf(timestamp, "%s", getCurrentTimestamp());
700 sprintf(basename_raw, "%s-%s", timestamp, name);
701 sprintf(basename, "%s-%08x", timestamp, get_hash_from_key(basename_raw));
706 char *getScoreTapeFilename(char *basename_no_ext, int nr)
708 static char *filename = NULL;
709 char basename[MAX_FILENAME_LEN];
711 checked_free(filename);
713 sprintf(basename, "%s.%s", basename_no_ext, TAPEFILE_EXTENSION);
715 // used instead of "leveldir_current->subdir" (for network games)
716 filename = getPath2(getScoreTapeDir(levelset.identifier, nr), basename);
721 char *getScoreCacheTapeFilename(char *basename_no_ext, int nr)
723 static char *filename = NULL;
724 char basename[MAX_FILENAME_LEN];
726 checked_free(filename);
728 sprintf(basename, "%s.%s", basename_no_ext, TAPEFILE_EXTENSION);
730 // used instead of "leveldir_current->subdir" (for network games)
731 filename = getPath2(getScoreCacheTapeDir(levelset.identifier, nr), basename);
736 char *getSetupFilename(void)
738 static char *filename = NULL;
740 checked_free(filename);
742 filename = getPath2(getSetupDir(), SETUP_FILENAME);
747 char *getDefaultSetupFilename(void)
749 return program.config_filename;
752 char *getEditorSetupFilename(void)
754 static char *filename = NULL;
756 checked_free(filename);
757 filename = getPath2(getCurrentLevelDir(), EDITORSETUP_FILENAME);
759 if (fileExists(filename))
762 checked_free(filename);
763 filename = getPath2(getSetupDir(), EDITORSETUP_FILENAME);
768 char *getHelpAnimFilename(void)
770 static char *filename = NULL;
772 checked_free(filename);
774 filename = getPath2(getCurrentLevelDir(), HELPANIM_FILENAME);
779 char *getHelpTextFilename(void)
781 static char *filename = NULL;
783 checked_free(filename);
785 filename = getPath2(getCurrentLevelDir(), HELPTEXT_FILENAME);
790 char *getLevelSetInfoFilename(void)
792 static char *filename = NULL;
807 for (i = 0; basenames[i] != NULL; i++)
809 checked_free(filename);
810 filename = getPath2(getCurrentLevelDir(), basenames[i]);
812 if (fileExists(filename))
819 static char *getLevelSetTitleMessageBasename(int nr, boolean initial)
821 static char basename[32];
823 sprintf(basename, "%s_%d.txt",
824 (initial ? "titlemessage_initial" : "titlemessage"), nr + 1);
829 char *getLevelSetTitleMessageFilename(int nr, boolean initial)
831 static char *filename = NULL;
833 boolean skip_setup_artwork = FALSE;
835 checked_free(filename);
837 basename = getLevelSetTitleMessageBasename(nr, initial);
839 if (!gfx.override_level_graphics)
841 // 1st try: look for special artwork in current level series directory
842 filename = getPath3(getCurrentLevelDir(), GRAPHICS_DIRECTORY, basename);
843 if (fileExists(filename))
848 // 2nd try: look for message file in current level set directory
849 filename = getPath2(getCurrentLevelDir(), basename);
850 if (fileExists(filename))
855 // check if there is special artwork configured in level series config
856 if (getLevelArtworkSet(ARTWORK_TYPE_GRAPHICS) != NULL)
858 // 3rd try: look for special artwork configured in level series config
859 filename = getPath2(getLevelArtworkDir(ARTWORK_TYPE_GRAPHICS), basename);
860 if (fileExists(filename))
865 // take missing artwork configured in level set config from default
866 skip_setup_artwork = TRUE;
870 if (!skip_setup_artwork)
872 // 4th try: look for special artwork in configured artwork directory
873 filename = getPath2(getSetupArtworkDir(artwork.gfx_current), basename);
874 if (fileExists(filename))
880 // 5th try: look for default artwork in new default artwork directory
881 filename = getPath2(getDefaultGraphicsDir(GFX_DEFAULT_SUBDIR), basename);
882 if (fileExists(filename))
887 // 6th try: look for default artwork in old default artwork directory
888 filename = getPath2(options.graphics_directory, basename);
889 if (fileExists(filename))
892 return NULL; // cannot find specified artwork file anywhere
895 static char *getCreditsBasename(int nr)
897 static char basename[32];
899 sprintf(basename, "credits_%d.txt", nr + 1);
904 char *getCreditsFilename(int nr, boolean global)
906 char *basename = getCreditsBasename(nr);
907 char *basepath = NULL;
908 static char *credits_subdir = NULL;
909 static char *filename = NULL;
911 if (credits_subdir == NULL)
912 credits_subdir = getPath2(DOCS_DIRECTORY, CREDITS_DIRECTORY);
914 checked_free(filename);
916 // look for credits file in the game's base or current level set directory
917 basepath = (global ? options.base_directory : getCurrentLevelDir());
919 filename = getPath3(basepath, credits_subdir, basename);
920 if (fileExists(filename))
923 return NULL; // cannot find credits file
926 static char *getProgramInfoBasename(int nr)
928 static char basename[32];
930 sprintf(basename, "program_%d.txt", nr + 1);
935 char *getProgramInfoFilename(int nr)
937 char *basename = getProgramInfoBasename(nr);
938 static char *info_subdir = NULL;
939 static char *filename = NULL;
941 if (info_subdir == NULL)
942 info_subdir = getPath2(DOCS_DIRECTORY, INFO_DIRECTORY);
944 checked_free(filename);
946 // look for program info file in the game's base directory
947 filename = getPath3(options.base_directory, info_subdir, basename);
948 if (fileExists(filename))
951 return NULL; // cannot find program info file
954 static char *getCorrectedArtworkBasename(char *basename)
959 char *getCustomImageFilename(char *basename)
961 static char *filename = NULL;
962 boolean skip_setup_artwork = FALSE;
964 checked_free(filename);
966 basename = getCorrectedArtworkBasename(basename);
968 if (!gfx.override_level_graphics)
970 // 1st try: look for special artwork in current level series directory
971 filename = getImg3(getCurrentLevelDir(), GRAPHICS_DIRECTORY, basename);
972 if (fileExists(filename))
977 // check if there is special artwork configured in level series config
978 if (getLevelArtworkSet(ARTWORK_TYPE_GRAPHICS) != NULL)
980 // 2nd try: look for special artwork configured in level series config
981 filename = getImg2(getLevelArtworkDir(ARTWORK_TYPE_GRAPHICS), basename);
982 if (fileExists(filename))
987 // take missing artwork configured in level set config from default
988 skip_setup_artwork = TRUE;
992 if (!skip_setup_artwork)
994 // 3rd try: look for special artwork in configured artwork directory
995 filename = getImg2(getSetupArtworkDir(artwork.gfx_current), basename);
996 if (fileExists(filename))
1002 // 4th try: look for default artwork in new default artwork directory
1003 filename = getImg2(getDefaultGraphicsDir(GFX_DEFAULT_SUBDIR), basename);
1004 if (fileExists(filename))
1009 // 5th try: look for default artwork in old default artwork directory
1010 filename = getImg2(options.graphics_directory, basename);
1011 if (fileExists(filename))
1014 if (!strEqual(GFX_FALLBACK_FILENAME, UNDEFINED_FILENAME))
1018 WarnUsingFallback(basename);
1020 // 6th try: look for fallback artwork in old default artwork directory
1021 // (needed to prevent errors when trying to access unused artwork files)
1022 filename = getImg2(options.graphics_directory, GFX_FALLBACK_FILENAME);
1023 if (fileExists(filename))
1027 return NULL; // cannot find specified artwork file anywhere
1030 char *getCustomSoundFilename(char *basename)
1032 static char *filename = NULL;
1033 boolean skip_setup_artwork = FALSE;
1035 checked_free(filename);
1037 basename = getCorrectedArtworkBasename(basename);
1039 if (!gfx.override_level_sounds)
1041 // 1st try: look for special artwork in current level series directory
1042 filename = getPath3(getCurrentLevelDir(), SOUNDS_DIRECTORY, basename);
1043 if (fileExists(filename))
1048 // check if there is special artwork configured in level series config
1049 if (getLevelArtworkSet(ARTWORK_TYPE_SOUNDS) != NULL)
1051 // 2nd try: look for special artwork configured in level series config
1052 filename = getPath2(getLevelArtworkDir(TREE_TYPE_SOUNDS_DIR), basename);
1053 if (fileExists(filename))
1058 // take missing artwork configured in level set config from default
1059 skip_setup_artwork = TRUE;
1063 if (!skip_setup_artwork)
1065 // 3rd try: look for special artwork in configured artwork directory
1066 filename = getPath2(getSetupArtworkDir(artwork.snd_current), basename);
1067 if (fileExists(filename))
1073 // 4th try: look for default artwork in new default artwork directory
1074 filename = getPath2(getDefaultSoundsDir(SND_DEFAULT_SUBDIR), basename);
1075 if (fileExists(filename))
1080 // 5th try: look for default artwork in old default artwork directory
1081 filename = getPath2(options.sounds_directory, basename);
1082 if (fileExists(filename))
1085 if (!strEqual(SND_FALLBACK_FILENAME, UNDEFINED_FILENAME))
1089 WarnUsingFallback(basename);
1091 // 6th try: look for fallback artwork in old default artwork directory
1092 // (needed to prevent errors when trying to access unused artwork files)
1093 filename = getPath2(options.sounds_directory, SND_FALLBACK_FILENAME);
1094 if (fileExists(filename))
1098 return NULL; // cannot find specified artwork file anywhere
1101 char *getCustomMusicFilename(char *basename)
1103 static char *filename = NULL;
1104 boolean skip_setup_artwork = FALSE;
1106 checked_free(filename);
1108 basename = getCorrectedArtworkBasename(basename);
1110 if (!gfx.override_level_music)
1112 // 1st try: look for special artwork in current level series directory
1113 filename = getPath3(getCurrentLevelDir(), MUSIC_DIRECTORY, basename);
1114 if (fileExists(filename))
1119 // check if there is special artwork configured in level series config
1120 if (getLevelArtworkSet(ARTWORK_TYPE_MUSIC) != NULL)
1122 // 2nd try: look for special artwork configured in level series config
1123 filename = getPath2(getLevelArtworkDir(TREE_TYPE_MUSIC_DIR), basename);
1124 if (fileExists(filename))
1129 // take missing artwork configured in level set config from default
1130 skip_setup_artwork = TRUE;
1134 if (!skip_setup_artwork)
1136 // 3rd try: look for special artwork in configured artwork directory
1137 filename = getPath2(getSetupArtworkDir(artwork.mus_current), basename);
1138 if (fileExists(filename))
1144 // 4th try: look for default artwork in new default artwork directory
1145 filename = getPath2(getDefaultMusicDir(MUS_DEFAULT_SUBDIR), basename);
1146 if (fileExists(filename))
1151 // 5th try: look for default artwork in old default artwork directory
1152 filename = getPath2(options.music_directory, basename);
1153 if (fileExists(filename))
1156 if (!strEqual(MUS_FALLBACK_FILENAME, UNDEFINED_FILENAME))
1160 WarnUsingFallback(basename);
1162 // 6th try: look for fallback artwork in old default artwork directory
1163 // (needed to prevent errors when trying to access unused artwork files)
1164 filename = getPath2(options.music_directory, MUS_FALLBACK_FILENAME);
1165 if (fileExists(filename))
1169 return NULL; // cannot find specified artwork file anywhere
1172 char *getCustomArtworkFilename(char *basename, int type)
1174 if (type == ARTWORK_TYPE_GRAPHICS)
1175 return getCustomImageFilename(basename);
1176 else if (type == ARTWORK_TYPE_SOUNDS)
1177 return getCustomSoundFilename(basename);
1178 else if (type == ARTWORK_TYPE_MUSIC)
1179 return getCustomMusicFilename(basename);
1181 return UNDEFINED_FILENAME;
1184 char *getCustomArtworkConfigFilename(int type)
1186 return getCustomArtworkFilename(ARTWORKINFO_FILENAME(type), type);
1189 char *getCustomArtworkLevelConfigFilename(int type)
1191 static char *filename = NULL;
1193 checked_free(filename);
1195 filename = getPath2(getLevelArtworkDir(type), ARTWORKINFO_FILENAME(type));
1200 char *getCustomMusicDirectory(void)
1202 static char *directory = NULL;
1203 boolean skip_setup_artwork = FALSE;
1205 checked_free(directory);
1207 if (!gfx.override_level_music)
1209 // 1st try: look for special artwork in current level series directory
1210 directory = getPath2(getCurrentLevelDir(), MUSIC_DIRECTORY);
1211 if (directoryExists(directory))
1216 // check if there is special artwork configured in level series config
1217 if (getLevelArtworkSet(ARTWORK_TYPE_MUSIC) != NULL)
1219 // 2nd try: look for special artwork configured in level series config
1220 directory = getStringCopy(getLevelArtworkDir(TREE_TYPE_MUSIC_DIR));
1221 if (directoryExists(directory))
1226 // take missing artwork configured in level set config from default
1227 skip_setup_artwork = TRUE;
1231 if (!skip_setup_artwork)
1233 // 3rd try: look for special artwork in configured artwork directory
1234 directory = getStringCopy(getSetupArtworkDir(artwork.mus_current));
1235 if (directoryExists(directory))
1241 // 4th try: look for default artwork in new default artwork directory
1242 directory = getStringCopy(getDefaultMusicDir(MUS_DEFAULT_SUBDIR));
1243 if (directoryExists(directory))
1248 // 5th try: look for default artwork in old default artwork directory
1249 directory = getStringCopy(options.music_directory);
1250 if (directoryExists(directory))
1253 return NULL; // cannot find specified artwork file anywhere
1256 void MarkTapeDirectoryUploadsAsComplete(char *level_subdir)
1258 char *filename = getPath2(getTapeDir(level_subdir), UPLOADED_FILENAME);
1260 touchFile(filename);
1262 checked_free(filename);
1265 void MarkTapeDirectoryUploadsAsIncomplete(char *level_subdir)
1267 char *filename = getPath2(getTapeDir(level_subdir), UPLOADED_FILENAME);
1271 checked_free(filename);
1274 boolean CheckTapeDirectoryUploadsComplete(char *level_subdir)
1276 char *filename = getPath2(getTapeDir(level_subdir), UPLOADED_FILENAME);
1277 boolean success = fileExists(filename);
1279 checked_free(filename);
1284 void InitMissingFileHash(void)
1286 if (missing_file_hash == NULL)
1287 freeSetupFileHash(missing_file_hash);
1289 missing_file_hash = newSetupFileHash();
1292 void InitTapeDirectory(char *level_subdir)
1294 boolean new_tape_dir = !directoryExists(getTapeDir(level_subdir));
1296 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
1297 createDirectory(getTapeDir(NULL), "main tape", PERMS_PRIVATE);
1298 createDirectory(getTapeDir(level_subdir), "level tape", PERMS_PRIVATE);
1301 MarkTapeDirectoryUploadsAsComplete(level_subdir);
1304 void InitScoreDirectory(char *level_subdir)
1306 createDirectory(getMainUserGameDataDir(), "main user data", PERMS_PRIVATE);
1307 createDirectory(getScoreDir(NULL), "main score", PERMS_PRIVATE);
1308 createDirectory(getScoreDir(level_subdir), "level score", PERMS_PRIVATE);
1311 void InitScoreCacheDirectory(char *level_subdir)
1313 createDirectory(getMainUserGameDataDir(), "main user data", PERMS_PRIVATE);
1314 createDirectory(getCacheDir(), "cache data", PERMS_PRIVATE);
1315 createDirectory(getScoreCacheDir(NULL), "main score", PERMS_PRIVATE);
1316 createDirectory(getScoreCacheDir(level_subdir), "level score", PERMS_PRIVATE);
1319 void InitScoreTapeDirectory(char *level_subdir, int nr)
1321 InitScoreDirectory(level_subdir);
1323 createDirectory(getScoreTapeDir(level_subdir, nr), "score tape", PERMS_PRIVATE);
1326 void InitScoreCacheTapeDirectory(char *level_subdir, int nr)
1328 InitScoreCacheDirectory(level_subdir);
1330 createDirectory(getScoreCacheTapeDir(level_subdir, nr), "score tape", PERMS_PRIVATE);
1333 static void SaveUserLevelInfo(void);
1335 void InitUserLevelDirectory(char *level_subdir)
1337 if (!directoryExists(getUserLevelDir(level_subdir)))
1339 createDirectory(getMainUserGameDataDir(), "main user data", PERMS_PRIVATE);
1340 createDirectory(getUserLevelDir(NULL), "main user level", PERMS_PRIVATE);
1341 createDirectory(getUserLevelDir(level_subdir), "user level", PERMS_PRIVATE);
1343 if (setup.internal.create_user_levelset)
1344 SaveUserLevelInfo();
1348 void InitNetworkLevelDirectory(char *level_subdir)
1350 if (!directoryExists(getNetworkLevelDir(level_subdir)))
1352 createDirectory(getMainUserGameDataDir(), "main user data", PERMS_PRIVATE);
1353 createDirectory(getNetworkDir(), "network data", PERMS_PRIVATE);
1354 createDirectory(getNetworkLevelDir(NULL), "main network level", PERMS_PRIVATE);
1355 createDirectory(getNetworkLevelDir(level_subdir), "network level", PERMS_PRIVATE);
1359 void InitLevelSetupDirectory(char *level_subdir)
1361 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
1362 createDirectory(getLevelSetupDir(NULL), "main level setup", PERMS_PRIVATE);
1363 createDirectory(getLevelSetupDir(level_subdir), "level setup", PERMS_PRIVATE);
1366 static void InitCacheDirectory(void)
1368 createDirectory(getMainUserGameDataDir(), "main user data", PERMS_PRIVATE);
1369 createDirectory(getCacheDir(), "cache data", PERMS_PRIVATE);
1373 // ----------------------------------------------------------------------------
1374 // some functions to handle lists of level and artwork directories
1375 // ----------------------------------------------------------------------------
1377 TreeInfo *newTreeInfo(void)
1379 return checked_calloc(sizeof(TreeInfo));
1382 TreeInfo *newTreeInfo_setDefaults(int type)
1384 TreeInfo *ti = newTreeInfo();
1386 setTreeInfoToDefaults(ti, type);
1391 void pushTreeInfo(TreeInfo **node_first, TreeInfo *node_new)
1393 node_new->next = *node_first;
1394 *node_first = node_new;
1397 void removeTreeInfo(TreeInfo **node_first)
1399 TreeInfo *node_old = *node_first;
1401 *node_first = node_old->next;
1402 node_old->next = NULL;
1404 freeTreeInfo(node_old);
1407 int numTreeInfo(TreeInfo *node)
1420 boolean validLevelSeries(TreeInfo *node)
1422 // in a number of cases, tree node is no valid level set
1423 if (node == NULL || node->node_group || node->parent_link || node->is_copy)
1429 TreeInfo *getValidLevelSeries(TreeInfo *node, TreeInfo *default_node)
1431 if (validLevelSeries(node))
1433 else if (node->is_copy)
1434 return getTreeInfoFromIdentifier(leveldir_first, node->identifier);
1436 return getFirstValidTreeInfoEntry(default_node);
1439 static TreeInfo *getValidTreeInfoEntryExt(TreeInfo *node, boolean get_next_node)
1444 if (node->node_group) // enter node group (step down into tree)
1445 return getFirstValidTreeInfoEntry(node->node_group);
1447 if (node->parent_link) // skip first node (back link) of node group
1448 get_next_node = TRUE;
1450 if (!get_next_node) // get current regular tree node
1453 // get next regular tree node, or step up until one is found
1454 while (node->next == NULL && node->node_parent != NULL)
1455 node = node->node_parent;
1457 return getFirstValidTreeInfoEntry(node->next);
1460 TreeInfo *getFirstValidTreeInfoEntry(TreeInfo *node)
1462 return getValidTreeInfoEntryExt(node, FALSE);
1465 TreeInfo *getNextValidTreeInfoEntry(TreeInfo *node)
1467 return getValidTreeInfoEntryExt(node, TRUE);
1470 TreeInfo *getTreeInfoFirstGroupEntry(TreeInfo *node)
1475 if (node->node_parent == NULL) // top level group
1476 return *node->node_top;
1477 else // sub level group
1478 return node->node_parent->node_group;
1481 int numTreeInfoInGroup(TreeInfo *node)
1483 return numTreeInfo(getTreeInfoFirstGroupEntry(node));
1486 int getPosFromTreeInfo(TreeInfo *node)
1488 TreeInfo *node_cmp = getTreeInfoFirstGroupEntry(node);
1493 if (node_cmp == node)
1497 node_cmp = node_cmp->next;
1503 TreeInfo *getTreeInfoFromPos(TreeInfo *node, int pos)
1505 TreeInfo *node_default = node;
1517 return node_default;
1520 static TreeInfo *getTreeInfoFromIdentifierExt(TreeInfo *node, char *identifier,
1521 int node_type_wanted)
1523 if (identifier == NULL)
1528 if (TREE_NODE_TYPE(node) == node_type_wanted &&
1529 strEqual(identifier, node->identifier))
1532 if (node->node_group)
1534 TreeInfo *node_group = getTreeInfoFromIdentifierExt(node->node_group,
1547 TreeInfo *getTreeInfoFromIdentifier(TreeInfo *node, char *identifier)
1549 return getTreeInfoFromIdentifierExt(node, identifier, TREE_NODE_TYPE_DEFAULT);
1552 static TreeInfo *cloneTreeNode(TreeInfo **node_top, TreeInfo *node_parent,
1553 TreeInfo *node, boolean skip_sets_without_levels)
1560 if (!node->parent_link && !node->level_group &&
1561 skip_sets_without_levels && node->levels == 0)
1562 return cloneTreeNode(node_top, node_parent, node->next,
1563 skip_sets_without_levels);
1565 node_new = getTreeInfoCopy(node); // copy complete node
1567 node_new->node_top = node_top; // correct top node link
1568 node_new->node_parent = node_parent; // correct parent node link
1570 if (node->level_group)
1571 node_new->node_group = cloneTreeNode(node_top, node_new, node->node_group,
1572 skip_sets_without_levels);
1574 node_new->next = cloneTreeNode(node_top, node_parent, node->next,
1575 skip_sets_without_levels);
1580 static void cloneTree(TreeInfo **ti_new, TreeInfo *ti, boolean skip_empty_sets)
1582 TreeInfo *ti_cloned = cloneTreeNode(ti_new, NULL, ti, skip_empty_sets);
1584 *ti_new = ti_cloned;
1587 static boolean adjustTreeGraphicsForEMC(TreeInfo *node)
1589 boolean settings_changed = FALSE;
1593 boolean want_ecs = (setup.prefer_aga_graphics == FALSE);
1594 boolean want_aga = (setup.prefer_aga_graphics == TRUE);
1595 boolean has_only_ecs = (!node->graphics_set && !node->graphics_set_aga);
1596 boolean has_only_aga = (!node->graphics_set && !node->graphics_set_ecs);
1597 char *graphics_set = NULL;
1599 if (node->graphics_set_ecs && (want_ecs || has_only_ecs))
1600 graphics_set = node->graphics_set_ecs;
1602 if (node->graphics_set_aga && (want_aga || has_only_aga))
1603 graphics_set = node->graphics_set_aga;
1605 if (graphics_set && !strEqual(node->graphics_set, graphics_set))
1607 setString(&node->graphics_set, graphics_set);
1608 settings_changed = TRUE;
1611 if (node->node_group != NULL)
1612 settings_changed |= adjustTreeGraphicsForEMC(node->node_group);
1617 return settings_changed;
1620 static boolean adjustTreeSoundsForEMC(TreeInfo *node)
1622 boolean settings_changed = FALSE;
1626 boolean want_default = (setup.prefer_lowpass_sounds == FALSE);
1627 boolean want_lowpass = (setup.prefer_lowpass_sounds == TRUE);
1628 boolean has_only_default = (!node->sounds_set && !node->sounds_set_lowpass);
1629 boolean has_only_lowpass = (!node->sounds_set && !node->sounds_set_default);
1630 char *sounds_set = NULL;
1632 if (node->sounds_set_default && (want_default || has_only_default))
1633 sounds_set = node->sounds_set_default;
1635 if (node->sounds_set_lowpass && (want_lowpass || has_only_lowpass))
1636 sounds_set = node->sounds_set_lowpass;
1638 if (sounds_set && !strEqual(node->sounds_set, sounds_set))
1640 setString(&node->sounds_set, sounds_set);
1641 settings_changed = TRUE;
1644 if (node->node_group != NULL)
1645 settings_changed |= adjustTreeSoundsForEMC(node->node_group);
1650 return settings_changed;
1653 int dumpTreeInfo(TreeInfo *node, int depth)
1655 char bullet_list[] = { '-', '*', 'o' };
1656 int num_leaf_nodes = 0;
1660 Debug("tree", "Dumping TreeInfo:");
1664 char bullet = bullet_list[depth % ARRAY_SIZE(bullet_list)];
1666 for (i = 0; i < depth * 2; i++)
1667 DebugContinued("", " ");
1669 DebugContinued("tree", "%c '%s' ['%s] [PARENT: '%s'] %s\n",
1670 bullet, node->name, node->identifier,
1671 (node->node_parent ? node->node_parent->identifier : "-"),
1672 (node->node_group ? "[GROUP]" :
1673 node->is_copy ? "[COPY]" : ""));
1675 if (!node->node_group && !node->parent_link)
1679 // use for dumping artwork info tree
1680 Debug("tree", "subdir == '%s' ['%s', '%s'] [%d])",
1681 node->subdir, node->fullpath, node->basepath, node->in_user_dir);
1684 if (node->node_group != NULL)
1685 num_leaf_nodes += dumpTreeInfo(node->node_group, depth + 1);
1691 Debug("tree", "Summary: %d leaf nodes found", num_leaf_nodes);
1693 return num_leaf_nodes;
1696 void sortTreeInfoBySortFunction(TreeInfo **node_first,
1697 int (*compare_function)(const void *,
1700 int num_nodes = numTreeInfo(*node_first);
1701 TreeInfo **sort_array;
1702 TreeInfo *node = *node_first;
1708 // allocate array for sorting structure pointers
1709 sort_array = checked_calloc(num_nodes * sizeof(TreeInfo *));
1711 // writing structure pointers to sorting array
1712 while (i < num_nodes && node) // double boundary check...
1714 sort_array[i] = node;
1720 // sorting the structure pointers in the sorting array
1721 qsort(sort_array, num_nodes, sizeof(TreeInfo *),
1724 // update the linkage of list elements with the sorted node array
1725 for (i = 0; i < num_nodes - 1; i++)
1726 sort_array[i]->next = sort_array[i + 1];
1727 sort_array[num_nodes - 1]->next = NULL;
1729 // update the linkage of the main list anchor pointer
1730 *node_first = sort_array[0];
1734 // now recursively sort the level group structures
1738 if (node->node_group != NULL)
1739 sortTreeInfoBySortFunction(&node->node_group, compare_function);
1745 void sortTreeInfo(TreeInfo **node_first)
1747 sortTreeInfoBySortFunction(node_first, compareTreeInfoEntries);
1751 // ============================================================================
1752 // some stuff from "files.c"
1753 // ============================================================================
1755 #if defined(PLATFORM_WIN32)
1757 #define S_IRGRP S_IRUSR
1760 #define S_IROTH S_IRUSR
1763 #define S_IWGRP S_IWUSR
1766 #define S_IWOTH S_IWUSR
1769 #define S_IXGRP S_IXUSR
1772 #define S_IXOTH S_IXUSR
1775 #define S_IRWXG (S_IRGRP | S_IWGRP | S_IXGRP)
1780 #endif // PLATFORM_WIN32
1782 // file permissions for newly written files
1783 #define MODE_R_ALL (S_IRUSR | S_IRGRP | S_IROTH)
1784 #define MODE_W_ALL (S_IWUSR | S_IWGRP | S_IWOTH)
1785 #define MODE_X_ALL (S_IXUSR | S_IXGRP | S_IXOTH)
1787 #define MODE_W_PRIVATE (S_IWUSR)
1788 #define MODE_W_PUBLIC_FILE (S_IWUSR | S_IWGRP)
1789 #define MODE_W_PUBLIC_DIR (S_IWUSR | S_IWGRP | S_ISGID)
1791 #define DIR_PERMS_PRIVATE (MODE_R_ALL | MODE_X_ALL | MODE_W_PRIVATE)
1792 #define DIR_PERMS_PUBLIC (MODE_R_ALL | MODE_X_ALL | MODE_W_PUBLIC_DIR)
1793 #define DIR_PERMS_PUBLIC_ALL (MODE_R_ALL | MODE_X_ALL | MODE_W_ALL)
1795 #define FILE_PERMS_PRIVATE (MODE_R_ALL | MODE_W_PRIVATE)
1796 #define FILE_PERMS_PUBLIC (MODE_R_ALL | MODE_W_PUBLIC_FILE)
1797 #define FILE_PERMS_PUBLIC_ALL (MODE_R_ALL | MODE_W_ALL)
1800 char *getHomeDir(void)
1802 static char *dir = NULL;
1804 #if defined(PLATFORM_WIN32)
1807 dir = checked_malloc(MAX_PATH + 1);
1809 if (!SUCCEEDED(SHGetFolderPath(NULL, CSIDL_PERSONAL, NULL, 0, dir)))
1812 #elif defined(PLATFORM_EMSCRIPTEN)
1813 dir = PERSISTENT_DIRECTORY;
1814 #elif defined(PLATFORM_UNIX)
1817 if ((dir = getenv("HOME")) == NULL)
1819 dir = getUnixHomeDir();
1822 dir = getStringCopy(dir);
1834 char *getPersonalDataDir(void)
1836 static char *personal_data_dir = NULL;
1838 #if defined(PLATFORM_MACOSX)
1839 if (personal_data_dir == NULL)
1840 personal_data_dir = getPath2(getHomeDir(), "Documents");
1842 if (personal_data_dir == NULL)
1843 personal_data_dir = getHomeDir();
1846 return personal_data_dir;
1849 char *getMainUserGameDataDir(void)
1851 static char *main_user_data_dir = NULL;
1853 #if defined(PLATFORM_ANDROID)
1854 if (main_user_data_dir == NULL)
1855 main_user_data_dir = (char *)(SDL_AndroidGetExternalStorageState() &
1856 SDL_ANDROID_EXTERNAL_STORAGE_WRITE ?
1857 SDL_AndroidGetExternalStoragePath() :
1858 SDL_AndroidGetInternalStoragePath());
1860 if (main_user_data_dir == NULL)
1861 main_user_data_dir = getPath2(getPersonalDataDir(),
1862 program.userdata_subdir);
1865 return main_user_data_dir;
1868 char *getUserGameDataDir(void)
1871 return getMainUserGameDataDir();
1873 return getUserDir(user.nr);
1876 char *getSetupDir(void)
1878 return getUserGameDataDir();
1881 static mode_t posix_umask(mode_t mask)
1883 #if defined(PLATFORM_UNIX)
1890 static int posix_mkdir(const char *pathname, mode_t mode)
1892 #if defined(PLATFORM_WIN32)
1893 return mkdir(pathname);
1895 return mkdir(pathname, mode);
1899 static boolean posix_process_running_setgid(void)
1901 #if defined(PLATFORM_UNIX)
1902 return (getgid() != getegid());
1908 void createDirectory(char *dir, char *text, int permission_class)
1910 if (directoryExists(dir))
1913 // leave "other" permissions in umask untouched, but ensure group parts
1914 // of USERDATA_DIR_MODE are not masked
1915 mode_t dir_mode = (permission_class == PERMS_PRIVATE ?
1916 DIR_PERMS_PRIVATE : DIR_PERMS_PUBLIC);
1917 mode_t last_umask = posix_umask(0);
1918 mode_t group_umask = ~(dir_mode & S_IRWXG);
1919 int running_setgid = posix_process_running_setgid();
1921 if (permission_class == PERMS_PUBLIC)
1923 // if we're setgid, protect files against "other"
1924 // else keep umask(0) to make the dir world-writable
1927 posix_umask(last_umask & group_umask);
1929 dir_mode = DIR_PERMS_PUBLIC_ALL;
1932 if (posix_mkdir(dir, dir_mode) != 0)
1933 Warn("cannot create %s directory '%s': %s", text, dir, strerror(errno));
1935 if (permission_class == PERMS_PUBLIC && !running_setgid)
1936 chmod(dir, dir_mode);
1938 posix_umask(last_umask); // restore previous umask
1941 void InitMainUserDataDirectory(void)
1943 createDirectory(getMainUserGameDataDir(), "main user data", PERMS_PRIVATE);
1946 void InitUserDataDirectory(void)
1948 createDirectory(getMainUserGameDataDir(), "main user data", PERMS_PRIVATE);
1952 createDirectory(getUserDir(-1), "users", PERMS_PRIVATE);
1953 createDirectory(getUserDir(user.nr), "user data", PERMS_PRIVATE);
1957 void SetFilePermissions(char *filename, int permission_class)
1959 int running_setgid = posix_process_running_setgid();
1960 int perms = (permission_class == PERMS_PRIVATE ?
1961 FILE_PERMS_PRIVATE : FILE_PERMS_PUBLIC);
1963 if (permission_class == PERMS_PUBLIC && !running_setgid)
1964 perms = FILE_PERMS_PUBLIC_ALL;
1966 chmod(filename, perms);
1969 char *getCookie(char *file_type)
1971 static char cookie[MAX_COOKIE_LEN + 1];
1973 if (strlen(program.cookie_prefix) + 1 +
1974 strlen(file_type) + strlen("_FILE_VERSION_x.x") > MAX_COOKIE_LEN)
1975 return "[COOKIE ERROR]"; // should never happen
1977 sprintf(cookie, "%s_%s_FILE_VERSION_%d.%d",
1978 program.cookie_prefix, file_type,
1979 program.version_super, program.version_major);
1984 void fprintFileHeader(FILE *file, char *basename)
1986 char *prefix = "# ";
1989 fprintf_line_with_prefix(file, prefix, sep1, 77);
1990 fprintf(file, "%s%s\n", prefix, basename);
1991 fprintf_line_with_prefix(file, prefix, sep1, 77);
1992 fprintf(file, "\n");
1995 int getFileVersionFromCookieString(const char *cookie)
1997 const char *ptr_cookie1, *ptr_cookie2;
1998 const char *pattern1 = "_FILE_VERSION_";
1999 const char *pattern2 = "?.?";
2000 const int len_cookie = strlen(cookie);
2001 const int len_pattern1 = strlen(pattern1);
2002 const int len_pattern2 = strlen(pattern2);
2003 const int len_pattern = len_pattern1 + len_pattern2;
2004 int version_super, version_major;
2006 if (len_cookie <= len_pattern)
2009 ptr_cookie1 = &cookie[len_cookie - len_pattern];
2010 ptr_cookie2 = &cookie[len_cookie - len_pattern2];
2012 if (strncmp(ptr_cookie1, pattern1, len_pattern1) != 0)
2015 if (ptr_cookie2[0] < '0' || ptr_cookie2[0] > '9' ||
2016 ptr_cookie2[1] != '.' ||
2017 ptr_cookie2[2] < '0' || ptr_cookie2[2] > '9')
2020 version_super = ptr_cookie2[0] - '0';
2021 version_major = ptr_cookie2[2] - '0';
2023 return VERSION_IDENT(version_super, version_major, 0, 0);
2026 boolean checkCookieString(const char *cookie, const char *template)
2028 const char *pattern = "_FILE_VERSION_?.?";
2029 const int len_cookie = strlen(cookie);
2030 const int len_template = strlen(template);
2031 const int len_pattern = strlen(pattern);
2033 if (len_cookie != len_template)
2036 if (strncmp(cookie, template, len_cookie - len_pattern) != 0)
2043 // ----------------------------------------------------------------------------
2044 // setup file list and hash handling functions
2045 // ----------------------------------------------------------------------------
2047 char *getFormattedSetupEntry(char *token, char *value)
2050 static char entry[MAX_LINE_LEN];
2052 // if value is an empty string, just return token without value
2056 // start with the token and some spaces to format output line
2057 sprintf(entry, "%s:", token);
2058 for (i = strlen(entry); i < token_value_position; i++)
2061 // continue with the token's value
2062 strcat(entry, value);
2067 SetupFileList *newSetupFileList(char *token, char *value)
2069 SetupFileList *new = checked_malloc(sizeof(SetupFileList));
2071 new->token = getStringCopy(token);
2072 new->value = getStringCopy(value);
2079 void freeSetupFileList(SetupFileList *list)
2084 checked_free(list->token);
2085 checked_free(list->value);
2088 freeSetupFileList(list->next);
2093 char *getListEntry(SetupFileList *list, char *token)
2098 if (strEqual(list->token, token))
2101 return getListEntry(list->next, token);
2104 SetupFileList *setListEntry(SetupFileList *list, char *token, char *value)
2109 if (strEqual(list->token, token))
2111 checked_free(list->value);
2113 list->value = getStringCopy(value);
2117 else if (list->next == NULL)
2118 return (list->next = newSetupFileList(token, value));
2120 return setListEntry(list->next, token, value);
2123 SetupFileList *addListEntry(SetupFileList *list, char *token, char *value)
2128 if (list->next == NULL)
2129 return (list->next = newSetupFileList(token, value));
2131 return addListEntry(list->next, token, value);
2134 #if ENABLE_UNUSED_CODE
2136 static void printSetupFileList(SetupFileList *list)
2141 Debug("setup:printSetupFileList", "token: '%s'", list->token);
2142 Debug("setup:printSetupFileList", "value: '%s'", list->value);
2144 printSetupFileList(list->next);
2150 DEFINE_HASHTABLE_INSERT(insert_hash_entry, char, char);
2151 DEFINE_HASHTABLE_SEARCH(search_hash_entry, char, char);
2152 DEFINE_HASHTABLE_CHANGE(change_hash_entry, char, char);
2153 DEFINE_HASHTABLE_REMOVE(remove_hash_entry, char, char);
2155 #define insert_hash_entry hashtable_insert
2156 #define search_hash_entry hashtable_search
2157 #define change_hash_entry hashtable_change
2158 #define remove_hash_entry hashtable_remove
2161 unsigned int get_hash_from_key(void *key)
2166 This algorithm (k=33) was first reported by Dan Bernstein many years ago in
2167 'comp.lang.c'. Another version of this algorithm (now favored by Bernstein)
2168 uses XOR: hash(i) = hash(i - 1) * 33 ^ str[i]; the magic of number 33 (why
2169 it works better than many other constants, prime or not) has never been
2170 adequately explained.
2172 If you just want to have a good hash function, and cannot wait, djb2
2173 is one of the best string hash functions i know. It has excellent
2174 distribution and speed on many different sets of keys and table sizes.
2175 You are not likely to do better with one of the "well known" functions
2176 such as PJW, K&R, etc.
2178 Ozan (oz) Yigit [http://www.cs.yorku.ca/~oz/hash.html]
2181 char *str = (char *)key;
2182 unsigned int hash = 5381;
2185 while ((c = *str++))
2186 hash = ((hash << 5) + hash) + c; // hash * 33 + c
2191 int hash_keys_are_equal(void *key1, void *key2)
2193 return (strEqual((char *)key1, (char *)key2));
2196 SetupFileHash *newSetupFileHash(void)
2198 SetupFileHash *new_hash =
2199 create_hashtable(16, 0.75, get_hash_from_key, hash_keys_are_equal);
2201 if (new_hash == NULL)
2202 Fail("create_hashtable() failed -- out of memory");
2207 void freeSetupFileHash(SetupFileHash *hash)
2212 hashtable_destroy(hash, 1); // 1 == also free values stored in hash
2215 char *getHashEntry(SetupFileHash *hash, char *token)
2220 return search_hash_entry(hash, token);
2223 void setHashEntry(SetupFileHash *hash, char *token, char *value)
2230 value_copy = getStringCopy(value);
2232 // change value; if it does not exist, insert it as new
2233 if (!change_hash_entry(hash, token, value_copy))
2234 if (!insert_hash_entry(hash, getStringCopy(token), value_copy))
2235 Fail("cannot insert into hash -- aborting");
2238 char *removeHashEntry(SetupFileHash *hash, char *token)
2243 return remove_hash_entry(hash, token);
2246 #if ENABLE_UNUSED_CODE
2248 static void printSetupFileHash(SetupFileHash *hash)
2250 BEGIN_HASH_ITERATION(hash, itr)
2252 Debug("setup:printSetupFileHash", "token: '%s'", HASH_ITERATION_TOKEN(itr));
2253 Debug("setup:printSetupFileHash", "value: '%s'", HASH_ITERATION_VALUE(itr));
2255 END_HASH_ITERATION(hash, itr)
2260 #define ALLOW_TOKEN_VALUE_SEPARATOR_BEING_WHITESPACE 1
2261 #define CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING 0
2262 #define CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH 0
2264 static boolean token_value_separator_found = FALSE;
2265 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2266 static boolean token_value_separator_warning = FALSE;
2268 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2269 static boolean token_already_exists_warning = FALSE;
2272 static boolean getTokenValueFromSetupLineExt(char *line,
2273 char **token_ptr, char **value_ptr,
2274 char *filename, char *line_raw,
2276 boolean separator_required)
2278 static char line_copy[MAX_LINE_LEN + 1], line_raw_copy[MAX_LINE_LEN + 1];
2279 char *token, *value, *line_ptr;
2281 // when externally invoked via ReadTokenValueFromLine(), copy line buffers
2282 if (line_raw == NULL)
2284 strncpy(line_copy, line, MAX_LINE_LEN);
2285 line_copy[MAX_LINE_LEN] = '\0';
2288 strcpy(line_raw_copy, line_copy);
2289 line_raw = line_raw_copy;
2292 // cut trailing comment from input line
2293 for (line_ptr = line; *line_ptr; line_ptr++)
2295 if (*line_ptr == '#')
2302 // cut trailing whitespaces from input line
2303 for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
2304 if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
2307 // ignore empty lines
2311 // cut leading whitespaces from token
2312 for (token = line; *token; token++)
2313 if (*token != ' ' && *token != '\t')
2316 // start with empty value as reliable default
2319 token_value_separator_found = FALSE;
2321 // find end of token to determine start of value
2322 for (line_ptr = token; *line_ptr; line_ptr++)
2324 // first look for an explicit token/value separator, like ':' or '='
2325 if (*line_ptr == ':' || *line_ptr == '=')
2327 *line_ptr = '\0'; // terminate token string
2328 value = line_ptr + 1; // set beginning of value
2330 token_value_separator_found = TRUE;
2336 #if ALLOW_TOKEN_VALUE_SEPARATOR_BEING_WHITESPACE
2337 // fallback: if no token/value separator found, also allow whitespaces
2338 if (!token_value_separator_found && !separator_required)
2340 for (line_ptr = token; *line_ptr; line_ptr++)
2342 if (*line_ptr == ' ' || *line_ptr == '\t')
2344 *line_ptr = '\0'; // terminate token string
2345 value = line_ptr + 1; // set beginning of value
2347 token_value_separator_found = TRUE;
2353 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2354 if (token_value_separator_found)
2356 if (!token_value_separator_warning)
2358 Debug("setup", "---");
2360 if (filename != NULL)
2362 Debug("setup", "missing token/value separator(s) in config file:");
2363 Debug("setup", "- config file: '%s'", filename);
2367 Debug("setup", "missing token/value separator(s):");
2370 token_value_separator_warning = TRUE;
2373 if (filename != NULL)
2374 Debug("setup", "- line %d: '%s'", line_nr, line_raw);
2376 Debug("setup", "- line: '%s'", line_raw);
2382 // cut trailing whitespaces from token
2383 for (line_ptr = &token[strlen(token)]; line_ptr >= token; line_ptr--)
2384 if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
2387 // cut leading whitespaces from value
2388 for (; *value; value++)
2389 if (*value != ' ' && *value != '\t')
2398 boolean getTokenValueFromSetupLine(char *line, char **token, char **value)
2400 // while the internal (old) interface does not require a token/value
2401 // separator (for downwards compatibility with existing files which
2402 // don't use them), it is mandatory for the external (new) interface
2404 return getTokenValueFromSetupLineExt(line, token, value, NULL, NULL, 0, TRUE);
2407 static boolean loadSetupFileData(void *setup_file_data, char *filename,
2408 boolean top_recursion_level, boolean is_hash)
2410 static SetupFileHash *include_filename_hash = NULL;
2411 char line[MAX_LINE_LEN], line_raw[MAX_LINE_LEN], previous_line[MAX_LINE_LEN];
2412 char *token, *value, *line_ptr;
2413 void *insert_ptr = NULL;
2414 boolean read_continued_line = FALSE;
2416 int line_nr = 0, token_count = 0, include_count = 0;
2418 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2419 token_value_separator_warning = FALSE;
2422 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2423 token_already_exists_warning = FALSE;
2426 if (!(file = openFile(filename, MODE_READ)))
2428 #if DEBUG_NO_CONFIG_FILE
2429 Debug("setup", "cannot open configuration file '%s'", filename);
2435 // use "insert pointer" to store list end for constant insertion complexity
2437 insert_ptr = setup_file_data;
2439 // on top invocation, create hash to mark included files (to prevent loops)
2440 if (top_recursion_level)
2441 include_filename_hash = newSetupFileHash();
2443 // mark this file as already included (to prevent including it again)
2444 setHashEntry(include_filename_hash, getBaseNamePtr(filename), "true");
2446 while (!checkEndOfFile(file))
2448 // read next line of input file
2449 if (!getStringFromFile(file, line, MAX_LINE_LEN))
2452 // check if line was completely read and is terminated by line break
2453 if (strlen(line) > 0 && line[strlen(line) - 1] == '\n')
2456 // cut trailing line break (this can be newline and/or carriage return)
2457 for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
2458 if ((*line_ptr == '\n' || *line_ptr == '\r') && *(line_ptr + 1) == '\0')
2461 // copy raw input line for later use (mainly debugging output)
2462 strcpy(line_raw, line);
2464 if (read_continued_line)
2466 // append new line to existing line, if there is enough space
2467 if (strlen(previous_line) + strlen(line_ptr) < MAX_LINE_LEN)
2468 strcat(previous_line, line_ptr);
2470 strcpy(line, previous_line); // copy storage buffer to line
2472 read_continued_line = FALSE;
2475 // if the last character is '\', continue at next line
2476 if (strlen(line) > 0 && line[strlen(line) - 1] == '\\')
2478 line[strlen(line) - 1] = '\0'; // cut off trailing backslash
2479 strcpy(previous_line, line); // copy line to storage buffer
2481 read_continued_line = TRUE;
2486 if (!getTokenValueFromSetupLineExt(line, &token, &value, filename,
2487 line_raw, line_nr, FALSE))
2492 if (strEqual(token, "include"))
2494 if (getHashEntry(include_filename_hash, value) == NULL)
2496 char *basepath = getBasePath(filename);
2497 char *basename = getBaseName(value);
2498 char *filename_include = getPath2(basepath, basename);
2500 loadSetupFileData(setup_file_data, filename_include, FALSE, is_hash);
2504 free(filename_include);
2510 Warn("ignoring already processed file '%s'", value);
2517 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2519 getHashEntry((SetupFileHash *)setup_file_data, token);
2521 if (old_value != NULL)
2523 if (!token_already_exists_warning)
2525 Debug("setup", "---");
2526 Debug("setup", "duplicate token(s) found in config file:");
2527 Debug("setup", "- config file: '%s'", filename);
2529 token_already_exists_warning = TRUE;
2532 Debug("setup", "- token: '%s' (in line %d)", token, line_nr);
2533 Debug("setup", " old value: '%s'", old_value);
2534 Debug("setup", " new value: '%s'", value);
2538 setHashEntry((SetupFileHash *)setup_file_data, token, value);
2542 insert_ptr = addListEntry((SetupFileList *)insert_ptr, token, value);
2552 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2553 if (token_value_separator_warning)
2554 Debug("setup", "---");
2557 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2558 if (token_already_exists_warning)
2559 Debug("setup", "---");
2562 if (token_count == 0 && include_count == 0)
2563 Warn("configuration file '%s' is empty", filename);
2565 if (top_recursion_level)
2566 freeSetupFileHash(include_filename_hash);
2571 static int compareSetupFileData(const void *object1, const void *object2)
2573 const struct ConfigInfo *entry1 = (struct ConfigInfo *)object1;
2574 const struct ConfigInfo *entry2 = (struct ConfigInfo *)object2;
2576 return strcmp(entry1->token, entry2->token);
2579 static void saveSetupFileHash(SetupFileHash *hash, char *filename)
2581 int item_count = hashtable_count(hash);
2582 int item_size = sizeof(struct ConfigInfo);
2583 struct ConfigInfo *sort_array = checked_malloc(item_count * item_size);
2587 // copy string pointers from hash to array
2588 BEGIN_HASH_ITERATION(hash, itr)
2590 sort_array[i].token = HASH_ITERATION_TOKEN(itr);
2591 sort_array[i].value = HASH_ITERATION_VALUE(itr);
2595 if (i > item_count) // should never happen
2598 END_HASH_ITERATION(hash, itr)
2600 // sort string pointers from hash in array
2601 qsort(sort_array, item_count, item_size, compareSetupFileData);
2603 if (!(file = fopen(filename, MODE_WRITE)))
2605 Warn("cannot write configuration file '%s'", filename);
2610 fprintf(file, "%s\n\n", getFormattedSetupEntry("program.version",
2611 program.version_string));
2612 for (i = 0; i < item_count; i++)
2613 fprintf(file, "%s\n", getFormattedSetupEntry(sort_array[i].token,
2614 sort_array[i].value));
2617 checked_free(sort_array);
2620 SetupFileList *loadSetupFileList(char *filename)
2622 SetupFileList *setup_file_list = newSetupFileList("", "");
2623 SetupFileList *first_valid_list_entry;
2625 if (!loadSetupFileData(setup_file_list, filename, TRUE, FALSE))
2627 freeSetupFileList(setup_file_list);
2632 first_valid_list_entry = setup_file_list->next;
2634 // free empty list header
2635 setup_file_list->next = NULL;
2636 freeSetupFileList(setup_file_list);
2638 return first_valid_list_entry;
2641 SetupFileHash *loadSetupFileHash(char *filename)
2643 SetupFileHash *setup_file_hash = newSetupFileHash();
2645 if (!loadSetupFileData(setup_file_hash, filename, TRUE, TRUE))
2647 freeSetupFileHash(setup_file_hash);
2652 return setup_file_hash;
2656 // ============================================================================
2658 // ============================================================================
2660 #define TOKEN_STR_LAST_LEVEL_SERIES "last_level_series"
2661 #define TOKEN_STR_LAST_PLAYED_LEVEL "last_played_level"
2662 #define TOKEN_STR_HANDICAP_LEVEL "handicap_level"
2663 #define TOKEN_STR_LAST_USER "last_user"
2665 // level directory info
2666 #define LEVELINFO_TOKEN_IDENTIFIER 0
2667 #define LEVELINFO_TOKEN_NAME 1
2668 #define LEVELINFO_TOKEN_NAME_SORTING 2
2669 #define LEVELINFO_TOKEN_AUTHOR 3
2670 #define LEVELINFO_TOKEN_YEAR 4
2671 #define LEVELINFO_TOKEN_PROGRAM_TITLE 5
2672 #define LEVELINFO_TOKEN_PROGRAM_COPYRIGHT 6
2673 #define LEVELINFO_TOKEN_PROGRAM_COMPANY 7
2674 #define LEVELINFO_TOKEN_IMPORTED_FROM 8
2675 #define LEVELINFO_TOKEN_IMPORTED_BY 9
2676 #define LEVELINFO_TOKEN_TESTED_BY 10
2677 #define LEVELINFO_TOKEN_LEVELS 11
2678 #define LEVELINFO_TOKEN_FIRST_LEVEL 12
2679 #define LEVELINFO_TOKEN_SORT_PRIORITY 13
2680 #define LEVELINFO_TOKEN_LATEST_ENGINE 14
2681 #define LEVELINFO_TOKEN_LEVEL_GROUP 15
2682 #define LEVELINFO_TOKEN_READONLY 16
2683 #define LEVELINFO_TOKEN_GRAPHICS_SET_ECS 17
2684 #define LEVELINFO_TOKEN_GRAPHICS_SET_AGA 18
2685 #define LEVELINFO_TOKEN_GRAPHICS_SET 19
2686 #define LEVELINFO_TOKEN_SOUNDS_SET_DEFAULT 20
2687 #define LEVELINFO_TOKEN_SOUNDS_SET_LOWPASS 21
2688 #define LEVELINFO_TOKEN_SOUNDS_SET 22
2689 #define LEVELINFO_TOKEN_MUSIC_SET 23
2690 #define LEVELINFO_TOKEN_FILENAME 24
2691 #define LEVELINFO_TOKEN_FILETYPE 25
2692 #define LEVELINFO_TOKEN_SPECIAL_FLAGS 26
2693 #define LEVELINFO_TOKEN_EMPTY_LEVEL_NAME 27
2694 #define LEVELINFO_TOKEN_FORCE_LEVEL_NAME 28
2695 #define LEVELINFO_TOKEN_HANDICAP 29
2696 #define LEVELINFO_TOKEN_SKIP_LEVELS 30
2697 #define LEVELINFO_TOKEN_USE_EMC_TILES 31
2699 #define NUM_LEVELINFO_TOKENS 32
2701 static LevelDirTree ldi;
2703 static struct TokenInfo levelinfo_tokens[] =
2705 // level directory info
2706 { TYPE_STRING, &ldi.identifier, "identifier" },
2707 { TYPE_STRING, &ldi.name, "name" },
2708 { TYPE_STRING, &ldi.name_sorting, "name_sorting" },
2709 { TYPE_STRING, &ldi.author, "author" },
2710 { TYPE_STRING, &ldi.year, "year" },
2711 { TYPE_STRING, &ldi.program_title, "program_title" },
2712 { TYPE_STRING, &ldi.program_copyright, "program_copyright" },
2713 { TYPE_STRING, &ldi.program_company, "program_company" },
2714 { TYPE_STRING, &ldi.imported_from, "imported_from" },
2715 { TYPE_STRING, &ldi.imported_by, "imported_by" },
2716 { TYPE_STRING, &ldi.tested_by, "tested_by" },
2717 { TYPE_INTEGER, &ldi.levels, "levels" },
2718 { TYPE_INTEGER, &ldi.first_level, "first_level" },
2719 { TYPE_INTEGER, &ldi.sort_priority, "sort_priority" },
2720 { TYPE_BOOLEAN, &ldi.latest_engine, "latest_engine" },
2721 { TYPE_BOOLEAN, &ldi.level_group, "level_group" },
2722 { TYPE_BOOLEAN, &ldi.readonly, "readonly" },
2723 { TYPE_STRING, &ldi.graphics_set_ecs, "graphics_set.ecs" },
2724 { TYPE_STRING, &ldi.graphics_set_aga, "graphics_set.aga" },
2725 { TYPE_STRING, &ldi.graphics_set, "graphics_set" },
2726 { TYPE_STRING, &ldi.sounds_set_default,"sounds_set.default" },
2727 { TYPE_STRING, &ldi.sounds_set_lowpass,"sounds_set.lowpass" },
2728 { TYPE_STRING, &ldi.sounds_set, "sounds_set" },
2729 { TYPE_STRING, &ldi.music_set, "music_set" },
2730 { TYPE_STRING, &ldi.level_filename, "filename" },
2731 { TYPE_STRING, &ldi.level_filetype, "filetype" },
2732 { TYPE_STRING, &ldi.special_flags, "special_flags" },
2733 { TYPE_STRING, &ldi.empty_level_name, "empty_level_name" },
2734 { TYPE_BOOLEAN, &ldi.force_level_name, "force_level_name" },
2735 { TYPE_BOOLEAN, &ldi.handicap, "handicap" },
2736 { TYPE_BOOLEAN, &ldi.skip_levels, "skip_levels" },
2737 { TYPE_BOOLEAN, &ldi.use_emc_tiles, "use_emc_tiles" }
2740 static struct TokenInfo artworkinfo_tokens[] =
2742 // artwork directory info
2743 { TYPE_STRING, &ldi.identifier, "identifier" },
2744 { TYPE_STRING, &ldi.subdir, "subdir" },
2745 { TYPE_STRING, &ldi.name, "name" },
2746 { TYPE_STRING, &ldi.name_sorting, "name_sorting" },
2747 { TYPE_STRING, &ldi.author, "author" },
2748 { TYPE_STRING, &ldi.program_title, "program_title" },
2749 { TYPE_STRING, &ldi.program_copyright, "program_copyright" },
2750 { TYPE_STRING, &ldi.program_company, "program_company" },
2751 { TYPE_INTEGER, &ldi.sort_priority, "sort_priority" },
2752 { TYPE_STRING, &ldi.basepath, "basepath" },
2753 { TYPE_STRING, &ldi.fullpath, "fullpath" },
2754 { TYPE_BOOLEAN, &ldi.in_user_dir, "in_user_dir" },
2755 { TYPE_STRING, &ldi.class_desc, "class_desc" },
2760 static char *optional_tokens[] =
2763 "program_copyright",
2769 static void setTreeInfoToDefaults(TreeInfo *ti, int type)
2773 ti->node_top = (ti->type == TREE_TYPE_LEVEL_DIR ? &leveldir_first :
2774 ti->type == TREE_TYPE_GRAPHICS_DIR ? &artwork.gfx_first :
2775 ti->type == TREE_TYPE_SOUNDS_DIR ? &artwork.snd_first :
2776 ti->type == TREE_TYPE_MUSIC_DIR ? &artwork.mus_first :
2779 ti->node_parent = NULL;
2780 ti->node_group = NULL;
2787 ti->fullpath = NULL;
2788 ti->basepath = NULL;
2789 ti->identifier = NULL;
2790 ti->name = getStringCopy(ANONYMOUS_NAME);
2791 ti->name_sorting = NULL;
2792 ti->author = getStringCopy(ANONYMOUS_NAME);
2795 ti->program_title = NULL;
2796 ti->program_copyright = NULL;
2797 ti->program_company = NULL;
2799 ti->sort_priority = LEVELCLASS_UNDEFINED; // default: least priority
2800 ti->latest_engine = FALSE; // default: get from level
2801 ti->parent_link = FALSE;
2802 ti->is_copy = FALSE;
2803 ti->in_user_dir = FALSE;
2804 ti->user_defined = FALSE;
2806 ti->class_desc = NULL;
2808 ti->infotext = getStringCopy(TREE_INFOTEXT(ti->type));
2810 if (ti->type == TREE_TYPE_LEVEL_DIR)
2812 ti->imported_from = NULL;
2813 ti->imported_by = NULL;
2814 ti->tested_by = NULL;
2816 ti->graphics_set_ecs = NULL;
2817 ti->graphics_set_aga = NULL;
2818 ti->graphics_set = NULL;
2819 ti->sounds_set_default = NULL;
2820 ti->sounds_set_lowpass = NULL;
2821 ti->sounds_set = NULL;
2822 ti->music_set = NULL;
2823 ti->graphics_path = getStringCopy(UNDEFINED_FILENAME);
2824 ti->sounds_path = getStringCopy(UNDEFINED_FILENAME);
2825 ti->music_path = getStringCopy(UNDEFINED_FILENAME);
2827 ti->level_filename = NULL;
2828 ti->level_filetype = NULL;
2830 ti->special_flags = NULL;
2832 ti->empty_level_name = NULL;
2833 ti->force_level_name = FALSE;
2836 ti->first_level = 0;
2838 ti->level_group = FALSE;
2839 ti->handicap_level = 0;
2840 ti->readonly = TRUE;
2841 ti->handicap = TRUE;
2842 ti->skip_levels = FALSE;
2844 ti->use_emc_tiles = FALSE;
2848 static void setTreeInfoToDefaultsFromParent(TreeInfo *ti, TreeInfo *parent)
2852 Warn("setTreeInfoToDefaultsFromParent(): parent == NULL");
2854 setTreeInfoToDefaults(ti, TREE_TYPE_UNDEFINED);
2859 // copy all values from the parent structure
2861 ti->type = parent->type;
2863 ti->node_top = parent->node_top;
2864 ti->node_parent = parent;
2865 ti->node_group = NULL;
2872 ti->fullpath = NULL;
2873 ti->basepath = NULL;
2874 ti->identifier = NULL;
2875 ti->name = getStringCopy(ANONYMOUS_NAME);
2876 ti->name_sorting = NULL;
2877 ti->author = getStringCopy(parent->author);
2878 ti->year = getStringCopy(parent->year);
2880 ti->program_title = getStringCopy(parent->program_title);
2881 ti->program_copyright = getStringCopy(parent->program_copyright);
2882 ti->program_company = getStringCopy(parent->program_company);
2884 ti->sort_priority = parent->sort_priority;
2885 ti->latest_engine = parent->latest_engine;
2886 ti->parent_link = FALSE;
2887 ti->is_copy = FALSE;
2888 ti->in_user_dir = parent->in_user_dir;
2889 ti->user_defined = parent->user_defined;
2890 ti->color = parent->color;
2891 ti->class_desc = getStringCopy(parent->class_desc);
2893 ti->infotext = getStringCopy(parent->infotext);
2895 if (ti->type == TREE_TYPE_LEVEL_DIR)
2897 ti->imported_from = getStringCopy(parent->imported_from);
2898 ti->imported_by = getStringCopy(parent->imported_by);
2899 ti->tested_by = getStringCopy(parent->tested_by);
2901 ti->graphics_set_ecs = getStringCopy(parent->graphics_set_ecs);
2902 ti->graphics_set_aga = getStringCopy(parent->graphics_set_aga);
2903 ti->graphics_set = getStringCopy(parent->graphics_set);
2904 ti->sounds_set_default = getStringCopy(parent->sounds_set_default);
2905 ti->sounds_set_lowpass = getStringCopy(parent->sounds_set_lowpass);
2906 ti->sounds_set = getStringCopy(parent->sounds_set);
2907 ti->music_set = getStringCopy(parent->music_set);
2908 ti->graphics_path = getStringCopy(UNDEFINED_FILENAME);
2909 ti->sounds_path = getStringCopy(UNDEFINED_FILENAME);
2910 ti->music_path = getStringCopy(UNDEFINED_FILENAME);
2912 ti->level_filename = getStringCopy(parent->level_filename);
2913 ti->level_filetype = getStringCopy(parent->level_filetype);
2915 ti->special_flags = getStringCopy(parent->special_flags);
2917 ti->empty_level_name = getStringCopy(parent->empty_level_name);
2918 ti->force_level_name = parent->force_level_name;
2920 ti->levels = parent->levels;
2921 ti->first_level = parent->first_level;
2922 ti->last_level = parent->last_level;
2923 ti->level_group = FALSE;
2924 ti->handicap_level = parent->handicap_level;
2925 ti->readonly = parent->readonly;
2926 ti->handicap = parent->handicap;
2927 ti->skip_levels = parent->skip_levels;
2929 ti->use_emc_tiles = parent->use_emc_tiles;
2933 static TreeInfo *getTreeInfoCopy(TreeInfo *ti)
2935 TreeInfo *ti_copy = newTreeInfo();
2937 // copy all values from the original structure
2939 ti_copy->type = ti->type;
2941 ti_copy->node_top = ti->node_top;
2942 ti_copy->node_parent = ti->node_parent;
2943 ti_copy->node_group = ti->node_group;
2944 ti_copy->next = ti->next;
2946 ti_copy->cl_first = ti->cl_first;
2947 ti_copy->cl_cursor = ti->cl_cursor;
2949 ti_copy->subdir = getStringCopy(ti->subdir);
2950 ti_copy->fullpath = getStringCopy(ti->fullpath);
2951 ti_copy->basepath = getStringCopy(ti->basepath);
2952 ti_copy->identifier = getStringCopy(ti->identifier);
2953 ti_copy->name = getStringCopy(ti->name);
2954 ti_copy->name_sorting = getStringCopy(ti->name_sorting);
2955 ti_copy->author = getStringCopy(ti->author);
2956 ti_copy->year = getStringCopy(ti->year);
2958 ti_copy->program_title = getStringCopy(ti->program_title);
2959 ti_copy->program_copyright = getStringCopy(ti->program_copyright);
2960 ti_copy->program_company = getStringCopy(ti->program_company);
2962 ti_copy->imported_from = getStringCopy(ti->imported_from);
2963 ti_copy->imported_by = getStringCopy(ti->imported_by);
2964 ti_copy->tested_by = getStringCopy(ti->tested_by);
2966 ti_copy->graphics_set_ecs = getStringCopy(ti->graphics_set_ecs);
2967 ti_copy->graphics_set_aga = getStringCopy(ti->graphics_set_aga);
2968 ti_copy->graphics_set = getStringCopy(ti->graphics_set);
2969 ti_copy->sounds_set_default = getStringCopy(ti->sounds_set_default);
2970 ti_copy->sounds_set_lowpass = getStringCopy(ti->sounds_set_lowpass);
2971 ti_copy->sounds_set = getStringCopy(ti->sounds_set);
2972 ti_copy->music_set = getStringCopy(ti->music_set);
2973 ti_copy->graphics_path = getStringCopy(ti->graphics_path);
2974 ti_copy->sounds_path = getStringCopy(ti->sounds_path);
2975 ti_copy->music_path = getStringCopy(ti->music_path);
2977 ti_copy->level_filename = getStringCopy(ti->level_filename);
2978 ti_copy->level_filetype = getStringCopy(ti->level_filetype);
2980 ti_copy->special_flags = getStringCopy(ti->special_flags);
2982 ti_copy->empty_level_name = getStringCopy(ti->empty_level_name);
2983 ti_copy->force_level_name = ti->force_level_name;
2985 ti_copy->levels = ti->levels;
2986 ti_copy->first_level = ti->first_level;
2987 ti_copy->last_level = ti->last_level;
2988 ti_copy->sort_priority = ti->sort_priority;
2990 ti_copy->latest_engine = ti->latest_engine;
2992 ti_copy->level_group = ti->level_group;
2993 ti_copy->parent_link = ti->parent_link;
2994 ti_copy->is_copy = ti->is_copy;
2995 ti_copy->in_user_dir = ti->in_user_dir;
2996 ti_copy->user_defined = ti->user_defined;
2997 ti_copy->readonly = ti->readonly;
2998 ti_copy->handicap = ti->handicap;
2999 ti_copy->skip_levels = ti->skip_levels;
3001 ti_copy->use_emc_tiles = ti->use_emc_tiles;
3003 ti_copy->color = ti->color;
3004 ti_copy->class_desc = getStringCopy(ti->class_desc);
3005 ti_copy->handicap_level = ti->handicap_level;
3007 ti_copy->infotext = getStringCopy(ti->infotext);
3012 void freeTreeInfo(TreeInfo *ti)
3017 checked_free(ti->subdir);
3018 checked_free(ti->fullpath);
3019 checked_free(ti->basepath);
3020 checked_free(ti->identifier);
3022 checked_free(ti->name);
3023 checked_free(ti->name_sorting);
3024 checked_free(ti->author);
3025 checked_free(ti->year);
3027 checked_free(ti->program_title);
3028 checked_free(ti->program_copyright);
3029 checked_free(ti->program_company);
3031 checked_free(ti->class_desc);
3033 checked_free(ti->infotext);
3035 if (ti->type == TREE_TYPE_LEVEL_DIR)
3037 checked_free(ti->imported_from);
3038 checked_free(ti->imported_by);
3039 checked_free(ti->tested_by);
3041 checked_free(ti->graphics_set_ecs);
3042 checked_free(ti->graphics_set_aga);
3043 checked_free(ti->graphics_set);
3044 checked_free(ti->sounds_set_default);
3045 checked_free(ti->sounds_set_lowpass);
3046 checked_free(ti->sounds_set);
3047 checked_free(ti->music_set);
3049 checked_free(ti->graphics_path);
3050 checked_free(ti->sounds_path);
3051 checked_free(ti->music_path);
3053 checked_free(ti->level_filename);
3054 checked_free(ti->level_filetype);
3056 checked_free(ti->special_flags);
3059 // recursively free child node
3061 freeTreeInfo(ti->node_group);
3063 // recursively free next node
3065 freeTreeInfo(ti->next);
3070 void setSetupInfo(struct TokenInfo *token_info,
3071 int token_nr, char *token_value)
3073 int token_type = token_info[token_nr].type;
3074 void *setup_value = token_info[token_nr].value;
3076 if (token_value == NULL)
3079 // set setup field to corresponding token value
3084 *(boolean *)setup_value = get_boolean_from_string(token_value);
3088 *(int *)setup_value = get_switch3_from_string(token_value);
3092 *(Key *)setup_value = getKeyFromKeyName(token_value);
3096 *(Key *)setup_value = getKeyFromX11KeyName(token_value);
3100 *(int *)setup_value = get_integer_from_string(token_value);
3104 checked_free(*(char **)setup_value);
3105 *(char **)setup_value = getStringCopy(token_value);
3109 *(int *)setup_value = get_player_nr_from_string(token_value);
3117 static int compareTreeInfoEntries(const void *object1, const void *object2)
3119 const TreeInfo *entry1 = *((TreeInfo **)object1);
3120 const TreeInfo *entry2 = *((TreeInfo **)object2);
3121 int tree_sorting1 = TREE_SORTING(entry1);
3122 int tree_sorting2 = TREE_SORTING(entry2);
3124 if (tree_sorting1 != tree_sorting2)
3125 return (tree_sorting1 - tree_sorting2);
3127 return strcasecmp(entry1->name_sorting, entry2->name_sorting);
3130 static TreeInfo *createParentTreeInfoNode(TreeInfo *node_parent)
3134 if (node_parent == NULL)
3137 ti_new = newTreeInfo();
3138 setTreeInfoToDefaults(ti_new, node_parent->type);
3140 ti_new->node_parent = node_parent;
3141 ti_new->parent_link = TRUE;
3143 setString(&ti_new->identifier, node_parent->identifier);
3144 setString(&ti_new->name, BACKLINK_TEXT_PARENT);
3145 setString(&ti_new->name_sorting, ti_new->name);
3147 setString(&ti_new->subdir, STRING_PARENT_DIRECTORY);
3148 setString(&ti_new->fullpath, node_parent->fullpath);
3150 ti_new->sort_priority = LEVELCLASS_PARENT;
3151 ti_new->latest_engine = node_parent->latest_engine;
3153 setString(&ti_new->class_desc, getLevelClassDescription(ti_new));
3155 pushTreeInfo(&node_parent->node_group, ti_new);
3160 static TreeInfo *createTopTreeInfoNode(TreeInfo *node_first)
3162 if (node_first == NULL)
3165 TreeInfo *ti_new = newTreeInfo();
3166 int type = node_first->type;
3168 setTreeInfoToDefaults(ti_new, type);
3170 ti_new->node_parent = NULL;
3171 ti_new->parent_link = FALSE;
3173 setString(&ti_new->identifier, "top_tree_node");
3174 setString(&ti_new->name, TREE_INFOTEXT(type));
3175 setString(&ti_new->name_sorting, ti_new->name);
3177 setString(&ti_new->subdir, STRING_TOP_DIRECTORY);
3178 setString(&ti_new->fullpath, ".");
3180 ti_new->sort_priority = LEVELCLASS_TOP;
3181 ti_new->latest_engine = node_first->latest_engine;
3183 setString(&ti_new->class_desc, TREE_INFOTEXT(type));
3185 ti_new->node_group = node_first;
3186 ti_new->level_group = TRUE;
3188 TreeInfo *ti_new2 = createParentTreeInfoNode(ti_new);
3190 setString(&ti_new2->name, TREE_BACKLINK_TEXT(type));
3191 setString(&ti_new2->name_sorting, ti_new2->name);
3196 static void setTreeInfoParentNodes(TreeInfo *node, TreeInfo *node_parent)
3200 if (node->node_group)
3201 setTreeInfoParentNodes(node->node_group, node);
3203 node->node_parent = node_parent;
3209 TreeInfo *addTopTreeInfoNode(TreeInfo *node_first)
3211 // add top tree node with back link node in previous tree
3212 node_first = createTopTreeInfoNode(node_first);
3214 // set all parent links (back links) in complete tree
3215 setTreeInfoParentNodes(node_first, NULL);
3221 // ----------------------------------------------------------------------------
3222 // functions for handling level and custom artwork info cache
3223 // ----------------------------------------------------------------------------
3225 static void LoadArtworkInfoCache(void)
3227 InitCacheDirectory();
3229 if (artworkinfo_cache_old == NULL)
3231 char *filename = getPath2(getCacheDir(), ARTWORKINFO_CACHE_FILE);
3233 // try to load artwork info hash from already existing cache file
3234 artworkinfo_cache_old = loadSetupFileHash(filename);
3236 // try to get program version that artwork info cache was written with
3237 char *version = getHashEntry(artworkinfo_cache_old, "program.version");
3239 // check program version of artwork info cache against current version
3240 if (!strEqual(version, program.version_string))
3242 freeSetupFileHash(artworkinfo_cache_old);
3244 artworkinfo_cache_old = NULL;
3247 // if no artwork info cache file was found, start with empty hash
3248 if (artworkinfo_cache_old == NULL)
3249 artworkinfo_cache_old = newSetupFileHash();
3254 if (artworkinfo_cache_new == NULL)
3255 artworkinfo_cache_new = newSetupFileHash();
3257 update_artworkinfo_cache = FALSE;
3260 static void SaveArtworkInfoCache(void)
3262 if (!update_artworkinfo_cache)
3265 char *filename = getPath2(getCacheDir(), ARTWORKINFO_CACHE_FILE);
3267 InitCacheDirectory();
3269 saveSetupFileHash(artworkinfo_cache_new, filename);
3274 static char *getCacheTokenPrefix(char *prefix1, char *prefix2)
3276 static char *prefix = NULL;
3278 checked_free(prefix);
3280 prefix = getStringCat2WithSeparator(prefix1, prefix2, ".");
3285 // (identical to above function, but separate string buffer needed -- nasty)
3286 static char *getCacheToken(char *prefix, char *suffix)
3288 static char *token = NULL;
3290 checked_free(token);
3292 token = getStringCat2WithSeparator(prefix, suffix, ".");
3297 static char *getFileTimestampString(char *filename)
3299 return getStringCopy(i_to_a(getFileTimestampEpochSeconds(filename)));
3302 static boolean modifiedFileTimestamp(char *filename, char *timestamp_string)
3304 struct stat file_status;
3306 if (timestamp_string == NULL)
3309 if (!fileExists(filename)) // file does not exist
3310 return (atoi(timestamp_string) != 0);
3312 if (stat(filename, &file_status) != 0) // cannot stat file
3315 return (file_status.st_mtime != atoi(timestamp_string));
3318 static TreeInfo *getArtworkInfoCacheEntry(LevelDirTree *level_node, int type)
3320 char *identifier = level_node->subdir;
3321 char *type_string = ARTWORK_DIRECTORY(type);
3322 char *token_prefix = getCacheTokenPrefix(type_string, identifier);
3323 char *token_main = getCacheToken(token_prefix, "CACHED");
3324 char *cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
3325 boolean cached = (cache_entry != NULL && strEqual(cache_entry, "true"));
3326 TreeInfo *artwork_info = NULL;
3328 if (!use_artworkinfo_cache)
3331 if (optional_tokens_hash == NULL)
3335 // create hash from list of optional tokens (for quick access)
3336 optional_tokens_hash = newSetupFileHash();
3337 for (i = 0; optional_tokens[i] != NULL; i++)
3338 setHashEntry(optional_tokens_hash, optional_tokens[i], "");
3345 artwork_info = newTreeInfo();
3346 setTreeInfoToDefaults(artwork_info, type);
3348 // set all structure fields according to the token/value pairs
3349 ldi = *artwork_info;
3350 for (i = 0; artworkinfo_tokens[i].type != -1; i++)
3352 char *token_suffix = artworkinfo_tokens[i].text;
3353 char *token = getCacheToken(token_prefix, token_suffix);
3354 char *value = getHashEntry(artworkinfo_cache_old, token);
3356 (getHashEntry(optional_tokens_hash, token_suffix) != NULL);
3358 setSetupInfo(artworkinfo_tokens, i, value);
3360 // check if cache entry for this item is mandatory, but missing
3361 if (value == NULL && !optional)
3363 Warn("missing cache entry '%s'", token);
3369 *artwork_info = ldi;
3374 char *filename_levelinfo = getPath2(getLevelDirFromTreeInfo(level_node),
3375 LEVELINFO_FILENAME);
3376 char *filename_artworkinfo = getPath2(getSetupArtworkDir(artwork_info),
3377 ARTWORKINFO_FILENAME(type));
3379 // check if corresponding "levelinfo.conf" file has changed
3380 token_main = getCacheToken(token_prefix, "TIMESTAMP_LEVELINFO");
3381 cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
3383 if (modifiedFileTimestamp(filename_levelinfo, cache_entry))
3386 // check if corresponding "<artworkinfo>.conf" file has changed
3387 token_main = getCacheToken(token_prefix, "TIMESTAMP_ARTWORKINFO");
3388 cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
3390 if (modifiedFileTimestamp(filename_artworkinfo, cache_entry))
3393 checked_free(filename_levelinfo);
3394 checked_free(filename_artworkinfo);
3397 if (!cached && artwork_info != NULL)
3399 freeTreeInfo(artwork_info);
3404 return artwork_info;
3407 static void setArtworkInfoCacheEntry(TreeInfo *artwork_info,
3408 LevelDirTree *level_node, int type)
3410 char *identifier = level_node->subdir;
3411 char *type_string = ARTWORK_DIRECTORY(type);
3412 char *token_prefix = getCacheTokenPrefix(type_string, identifier);
3413 char *token_main = getCacheToken(token_prefix, "CACHED");
3414 boolean set_cache_timestamps = TRUE;
3417 setHashEntry(artworkinfo_cache_new, token_main, "true");
3419 if (set_cache_timestamps)
3421 char *filename_levelinfo = getPath2(getLevelDirFromTreeInfo(level_node),
3422 LEVELINFO_FILENAME);
3423 char *filename_artworkinfo = getPath2(getSetupArtworkDir(artwork_info),
3424 ARTWORKINFO_FILENAME(type));
3425 char *timestamp_levelinfo = getFileTimestampString(filename_levelinfo);
3426 char *timestamp_artworkinfo = getFileTimestampString(filename_artworkinfo);
3428 token_main = getCacheToken(token_prefix, "TIMESTAMP_LEVELINFO");
3429 setHashEntry(artworkinfo_cache_new, token_main, timestamp_levelinfo);
3431 token_main = getCacheToken(token_prefix, "TIMESTAMP_ARTWORKINFO");
3432 setHashEntry(artworkinfo_cache_new, token_main, timestamp_artworkinfo);
3434 checked_free(filename_levelinfo);
3435 checked_free(filename_artworkinfo);
3436 checked_free(timestamp_levelinfo);
3437 checked_free(timestamp_artworkinfo);
3440 ldi = *artwork_info;
3441 for (i = 0; artworkinfo_tokens[i].type != -1; i++)
3443 char *token = getCacheToken(token_prefix, artworkinfo_tokens[i].text);
3444 char *value = getSetupValue(artworkinfo_tokens[i].type,
3445 artworkinfo_tokens[i].value);
3447 setHashEntry(artworkinfo_cache_new, token, value);
3452 // ----------------------------------------------------------------------------
3453 // functions for loading level info and custom artwork info
3454 // ----------------------------------------------------------------------------
3456 int GetZipFileTreeType(char *zip_filename)
3458 static char *top_dir_path = NULL;
3459 static char *top_dir_conf_filename[NUM_BASE_TREE_TYPES] = { NULL };
3460 static char *conf_basename[NUM_BASE_TREE_TYPES] =
3462 GRAPHICSINFO_FILENAME,
3463 SOUNDSINFO_FILENAME,
3469 checked_free(top_dir_path);
3470 top_dir_path = NULL;
3472 for (j = 0; j < NUM_BASE_TREE_TYPES; j++)
3474 checked_free(top_dir_conf_filename[j]);
3475 top_dir_conf_filename[j] = NULL;
3478 char **zip_entries = zip_list(zip_filename);
3480 // check if zip file successfully opened
3481 if (zip_entries == NULL || zip_entries[0] == NULL)
3482 return TREE_TYPE_UNDEFINED;
3484 // first zip file entry is expected to be top level directory
3485 char *top_dir = zip_entries[0];
3487 // check if valid top level directory found in zip file
3488 if (!strSuffix(top_dir, "/"))
3489 return TREE_TYPE_UNDEFINED;
3491 // get filenames of valid configuration files in top level directory
3492 for (j = 0; j < NUM_BASE_TREE_TYPES; j++)
3493 top_dir_conf_filename[j] = getStringCat2(top_dir, conf_basename[j]);
3495 int tree_type = TREE_TYPE_UNDEFINED;
3498 while (zip_entries[e] != NULL)
3500 // check if every zip file entry is below top level directory
3501 if (!strPrefix(zip_entries[e], top_dir))
3502 return TREE_TYPE_UNDEFINED;
3504 // check if this zip file entry is a valid configuration filename
3505 for (j = 0; j < NUM_BASE_TREE_TYPES; j++)
3507 if (strEqual(zip_entries[e], top_dir_conf_filename[j]))
3509 // only exactly one valid configuration file allowed
3510 if (tree_type != TREE_TYPE_UNDEFINED)
3511 return TREE_TYPE_UNDEFINED;
3523 static boolean CheckZipFileForDirectory(char *zip_filename, char *directory,
3526 static char *top_dir_path = NULL;
3527 static char *top_dir_conf_filename = NULL;
3529 checked_free(top_dir_path);
3530 checked_free(top_dir_conf_filename);
3532 top_dir_path = NULL;
3533 top_dir_conf_filename = NULL;
3535 char *conf_basename = (tree_type == TREE_TYPE_LEVEL_DIR ? LEVELINFO_FILENAME :
3536 ARTWORKINFO_FILENAME(tree_type));
3538 // check if valid configuration filename determined
3539 if (conf_basename == NULL || strEqual(conf_basename, ""))
3542 char **zip_entries = zip_list(zip_filename);
3544 // check if zip file successfully opened
3545 if (zip_entries == NULL || zip_entries[0] == NULL)
3548 // first zip file entry is expected to be top level directory
3549 char *top_dir = zip_entries[0];
3551 // check if valid top level directory found in zip file
3552 if (!strSuffix(top_dir, "/"))
3555 // get path of extracted top level directory
3556 top_dir_path = getPath2(directory, top_dir);
3558 // remove trailing directory separator from top level directory path
3559 // (required to be able to check for file and directory in next step)
3560 top_dir_path[strlen(top_dir_path) - 1] = '\0';
3562 // check if zip file's top level directory already exists in target directory
3563 if (fileExists(top_dir_path)) // (checks for file and directory)
3566 // get filename of configuration file in top level directory
3567 top_dir_conf_filename = getStringCat2(top_dir, conf_basename);
3569 boolean found_top_dir_conf_filename = FALSE;
3572 while (zip_entries[i] != NULL)
3574 // check if every zip file entry is below top level directory
3575 if (!strPrefix(zip_entries[i], top_dir))
3578 // check if this zip file entry is the configuration filename
3579 if (strEqual(zip_entries[i], top_dir_conf_filename))
3580 found_top_dir_conf_filename = TRUE;
3585 // check if valid configuration filename was found in zip file
3586 if (!found_top_dir_conf_filename)
3592 char *ExtractZipFileIntoDirectory(char *zip_filename, char *directory,
3595 boolean zip_file_valid = CheckZipFileForDirectory(zip_filename, directory,
3598 if (!zip_file_valid)
3600 Warn("zip file '%s' rejected!", zip_filename);
3605 char **zip_entries = zip_extract(zip_filename, directory);
3607 if (zip_entries == NULL)
3609 Warn("zip file '%s' could not be extracted!", zip_filename);
3614 Info("zip file '%s' successfully extracted!", zip_filename);
3616 // first zip file entry contains top level directory
3617 char *top_dir = zip_entries[0];
3619 // remove trailing directory separator from top level directory
3620 top_dir[strlen(top_dir) - 1] = '\0';
3625 static void ProcessZipFilesInDirectory(char *directory, int tree_type)
3628 DirectoryEntry *dir_entry;
3630 if ((dir = openDirectory(directory)) == NULL)
3632 // display error if directory is main "options.graphics_directory" etc.
3633 if (tree_type == TREE_TYPE_LEVEL_DIR ||
3634 directory == OPTIONS_ARTWORK_DIRECTORY(tree_type))
3635 Warn("cannot read directory '%s'", directory);
3640 while ((dir_entry = readDirectory(dir)) != NULL) // loop all entries
3642 // skip non-zip files (and also directories with zip extension)
3643 if (!strSuffixLower(dir_entry->basename, ".zip") || dir_entry->is_directory)
3646 char *zip_filename = getPath2(directory, dir_entry->basename);
3647 char *zip_filename_extracted = getStringCat2(zip_filename, ".extracted");
3648 char *zip_filename_rejected = getStringCat2(zip_filename, ".rejected");
3650 // check if zip file hasn't already been extracted or rejected
3651 if (!fileExists(zip_filename_extracted) &&
3652 !fileExists(zip_filename_rejected))
3654 char *top_dir = ExtractZipFileIntoDirectory(zip_filename, directory,
3656 char *marker_filename = (top_dir != NULL ? zip_filename_extracted :
3657 zip_filename_rejected);
3660 // create empty file to mark zip file as extracted or rejected
3661 if ((marker_file = fopen(marker_filename, MODE_WRITE)))
3662 fclose(marker_file);
3665 free(zip_filename_extracted);
3666 free(zip_filename_rejected);
3670 closeDirectory(dir);
3673 // forward declaration for recursive call by "LoadLevelInfoFromLevelDir()"
3674 static void LoadLevelInfoFromLevelDir(TreeInfo **, TreeInfo *, char *);
3676 static boolean LoadLevelInfoFromLevelConf(TreeInfo **node_first,
3677 TreeInfo *node_parent,
3678 char *level_directory,
3679 char *directory_name)
3681 char *directory_path = getPath2(level_directory, directory_name);
3682 char *filename = getPath2(directory_path, LEVELINFO_FILENAME);
3683 SetupFileHash *setup_file_hash;
3684 LevelDirTree *leveldir_new = NULL;
3687 // unless debugging, silently ignore directories without "levelinfo.conf"
3688 if (!options.debug && !fileExists(filename))
3690 free(directory_path);
3696 setup_file_hash = loadSetupFileHash(filename);
3698 if (setup_file_hash == NULL)
3700 #if DEBUG_NO_CONFIG_FILE
3701 Debug("setup", "ignoring level directory '%s'", directory_path);
3704 free(directory_path);
3710 leveldir_new = newTreeInfo();
3713 setTreeInfoToDefaultsFromParent(leveldir_new, node_parent);
3715 setTreeInfoToDefaults(leveldir_new, TREE_TYPE_LEVEL_DIR);
3717 leveldir_new->subdir = getStringCopy(directory_name);
3719 // set all structure fields according to the token/value pairs
3720 ldi = *leveldir_new;
3721 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
3722 setSetupInfo(levelinfo_tokens, i,
3723 getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
3724 *leveldir_new = ldi;
3726 if (strEqual(leveldir_new->name, ANONYMOUS_NAME))
3727 setString(&leveldir_new->name, leveldir_new->subdir);
3729 if (leveldir_new->identifier == NULL)
3730 leveldir_new->identifier = getStringCopy(leveldir_new->subdir);
3732 if (leveldir_new->name_sorting == NULL)
3733 leveldir_new->name_sorting = getStringCopy(leveldir_new->name);
3735 if (node_parent == NULL) // top level group
3737 leveldir_new->basepath = getStringCopy(level_directory);
3738 leveldir_new->fullpath = getStringCopy(leveldir_new->subdir);
3740 else // sub level group
3742 leveldir_new->basepath = getStringCopy(node_parent->basepath);
3743 leveldir_new->fullpath = getPath2(node_parent->fullpath, directory_name);
3746 leveldir_new->last_level =
3747 leveldir_new->first_level + leveldir_new->levels - 1;
3749 leveldir_new->in_user_dir =
3750 (!strEqual(leveldir_new->basepath, options.level_directory));
3752 // adjust some settings if user's private level directory was detected
3753 if (leveldir_new->sort_priority == LEVELCLASS_UNDEFINED &&
3754 leveldir_new->in_user_dir &&
3755 (strEqual(leveldir_new->subdir, getLoginName()) ||
3756 strEqual(leveldir_new->name, getLoginName()) ||
3757 strEqual(leveldir_new->author, getRealName())))
3759 leveldir_new->sort_priority = LEVELCLASS_PRIVATE_START;
3760 leveldir_new->readonly = FALSE;
3763 leveldir_new->user_defined =
3764 (leveldir_new->in_user_dir && IS_LEVELCLASS_PRIVATE(leveldir_new));
3766 setString(&leveldir_new->class_desc, getLevelClassDescription(leveldir_new));
3768 leveldir_new->handicap_level = // set handicap to default value
3769 (leveldir_new->user_defined || !leveldir_new->handicap ?
3770 leveldir_new->last_level : leveldir_new->first_level);
3772 DrawInitTextItem(leveldir_new->name);
3774 pushTreeInfo(node_first, leveldir_new);
3776 freeSetupFileHash(setup_file_hash);
3778 if (leveldir_new->level_group)
3780 // create node to link back to current level directory
3781 createParentTreeInfoNode(leveldir_new);
3783 // recursively step into sub-directory and look for more level series
3784 LoadLevelInfoFromLevelDir(&leveldir_new->node_group,
3785 leveldir_new, directory_path);
3788 free(directory_path);
3794 static void LoadLevelInfoFromLevelDir(TreeInfo **node_first,
3795 TreeInfo *node_parent,
3796 char *level_directory)
3798 // ---------- 1st stage: process any level set zip files ----------
3800 ProcessZipFilesInDirectory(level_directory, TREE_TYPE_LEVEL_DIR);
3802 // ---------- 2nd stage: check for level set directories ----------
3805 DirectoryEntry *dir_entry;
3806 boolean valid_entry_found = FALSE;
3808 if ((dir = openDirectory(level_directory)) == NULL)
3810 Warn("cannot read level directory '%s'", level_directory);
3815 while ((dir_entry = readDirectory(dir)) != NULL) // loop all entries
3817 char *directory_name = dir_entry->basename;
3818 char *directory_path = getPath2(level_directory, directory_name);
3820 // skip entries for current and parent directory
3821 if (strEqual(directory_name, ".") ||
3822 strEqual(directory_name, ".."))
3824 free(directory_path);
3829 // find out if directory entry is itself a directory
3830 if (!dir_entry->is_directory) // not a directory
3832 free(directory_path);
3837 free(directory_path);
3839 if (strEqual(directory_name, GRAPHICS_DIRECTORY) ||
3840 strEqual(directory_name, SOUNDS_DIRECTORY) ||
3841 strEqual(directory_name, MUSIC_DIRECTORY))
3844 valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
3849 closeDirectory(dir);
3851 // special case: top level directory may directly contain "levelinfo.conf"
3852 if (node_parent == NULL && !valid_entry_found)
3854 // check if this directory directly contains a file "levelinfo.conf"
3855 valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
3856 level_directory, ".");
3859 if (!valid_entry_found)
3860 Warn("cannot find any valid level series in directory '%s'",
3864 boolean AdjustGraphicsForEMC(void)
3866 boolean settings_changed = FALSE;
3868 settings_changed |= adjustTreeGraphicsForEMC(leveldir_first_all);
3869 settings_changed |= adjustTreeGraphicsForEMC(leveldir_first);
3871 return settings_changed;
3874 boolean AdjustSoundsForEMC(void)
3876 boolean settings_changed = FALSE;
3878 settings_changed |= adjustTreeSoundsForEMC(leveldir_first_all);
3879 settings_changed |= adjustTreeSoundsForEMC(leveldir_first);
3881 return settings_changed;
3884 void LoadLevelInfo(void)
3886 InitUserLevelDirectory(getLoginName());
3888 DrawInitTextHead("Loading level series");
3890 LoadLevelInfoFromLevelDir(&leveldir_first, NULL, options.level_directory);
3891 LoadLevelInfoFromLevelDir(&leveldir_first, NULL, getUserLevelDir(NULL));
3893 leveldir_first = createTopTreeInfoNode(leveldir_first);
3895 /* after loading all level set information, clone the level directory tree
3896 and remove all level sets without levels (these may still contain artwork
3897 to be offered in the setup menu as "custom artwork", and are therefore
3898 checked for existing artwork in the function "LoadLevelArtworkInfo()") */
3899 leveldir_first_all = leveldir_first;
3900 cloneTree(&leveldir_first, leveldir_first_all, TRUE);
3902 AdjustGraphicsForEMC();
3903 AdjustSoundsForEMC();
3905 // before sorting, the first entries will be from the user directory
3906 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
3908 if (leveldir_first == NULL)
3909 Fail("cannot find any valid level series in any directory");
3911 sortTreeInfo(&leveldir_first);
3913 #if ENABLE_UNUSED_CODE
3914 dumpTreeInfo(leveldir_first, 0);
3918 static boolean LoadArtworkInfoFromArtworkConf(TreeInfo **node_first,
3919 TreeInfo *node_parent,
3920 char *base_directory,
3921 char *directory_name, int type)
3923 char *directory_path = getPath2(base_directory, directory_name);
3924 char *filename = getPath2(directory_path, ARTWORKINFO_FILENAME(type));
3925 SetupFileHash *setup_file_hash = NULL;
3926 TreeInfo *artwork_new = NULL;
3929 if (fileExists(filename))
3930 setup_file_hash = loadSetupFileHash(filename);
3932 if (setup_file_hash == NULL) // no config file -- look for artwork files
3935 DirectoryEntry *dir_entry;
3936 boolean valid_file_found = FALSE;
3938 if ((dir = openDirectory(directory_path)) != NULL)
3940 while ((dir_entry = readDirectory(dir)) != NULL)
3942 if (FileIsArtworkType(dir_entry->filename, type))
3944 valid_file_found = TRUE;
3950 closeDirectory(dir);
3953 if (!valid_file_found)
3955 #if DEBUG_NO_CONFIG_FILE
3956 if (!strEqual(directory_name, "."))
3957 Debug("setup", "ignoring artwork directory '%s'", directory_path);
3960 free(directory_path);
3967 artwork_new = newTreeInfo();
3970 setTreeInfoToDefaultsFromParent(artwork_new, node_parent);
3972 setTreeInfoToDefaults(artwork_new, type);
3974 artwork_new->subdir = getStringCopy(directory_name);
3976 if (setup_file_hash) // (before defining ".color" and ".class_desc")
3978 // set all structure fields according to the token/value pairs
3980 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
3981 setSetupInfo(levelinfo_tokens, i,
3982 getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
3985 if (strEqual(artwork_new->name, ANONYMOUS_NAME))
3986 setString(&artwork_new->name, artwork_new->subdir);
3988 if (artwork_new->identifier == NULL)
3989 artwork_new->identifier = getStringCopy(artwork_new->subdir);
3991 if (artwork_new->name_sorting == NULL)
3992 artwork_new->name_sorting = getStringCopy(artwork_new->name);
3995 if (node_parent == NULL) // top level group
3997 artwork_new->basepath = getStringCopy(base_directory);
3998 artwork_new->fullpath = getStringCopy(artwork_new->subdir);
4000 else // sub level group
4002 artwork_new->basepath = getStringCopy(node_parent->basepath);
4003 artwork_new->fullpath = getPath2(node_parent->fullpath, directory_name);
4006 artwork_new->in_user_dir =
4007 (!strEqual(artwork_new->basepath, OPTIONS_ARTWORK_DIRECTORY(type)));
4009 setString(&artwork_new->class_desc, getLevelClassDescription(artwork_new));
4011 if (setup_file_hash == NULL) // (after determining ".user_defined")
4013 if (strEqual(artwork_new->subdir, "."))
4015 if (artwork_new->user_defined)
4017 setString(&artwork_new->identifier, "private");
4018 artwork_new->sort_priority = ARTWORKCLASS_PRIVATE;
4022 setString(&artwork_new->identifier, "classic");
4023 artwork_new->sort_priority = ARTWORKCLASS_CLASSICS;
4026 setString(&artwork_new->class_desc,
4027 getLevelClassDescription(artwork_new));
4031 setString(&artwork_new->identifier, artwork_new->subdir);
4034 setString(&artwork_new->name, artwork_new->identifier);
4035 setString(&artwork_new->name_sorting, artwork_new->name);
4038 pushTreeInfo(node_first, artwork_new);
4040 freeSetupFileHash(setup_file_hash);
4042 free(directory_path);
4048 static void LoadArtworkInfoFromArtworkDir(TreeInfo **node_first,
4049 TreeInfo *node_parent,
4050 char *base_directory, int type)
4052 // ---------- 1st stage: process any artwork set zip files ----------
4054 ProcessZipFilesInDirectory(base_directory, type);
4056 // ---------- 2nd stage: check for artwork set directories ----------
4059 DirectoryEntry *dir_entry;
4060 boolean valid_entry_found = FALSE;
4062 if ((dir = openDirectory(base_directory)) == NULL)
4064 // display error if directory is main "options.graphics_directory" etc.
4065 if (base_directory == OPTIONS_ARTWORK_DIRECTORY(type))
4066 Warn("cannot read directory '%s'", base_directory);
4071 while ((dir_entry = readDirectory(dir)) != NULL) // loop all entries
4073 char *directory_name = dir_entry->basename;
4074 char *directory_path = getPath2(base_directory, directory_name);
4076 // skip directory entries for current and parent directory
4077 if (strEqual(directory_name, ".") ||
4078 strEqual(directory_name, ".."))
4080 free(directory_path);
4085 // skip directory entries which are not a directory
4086 if (!dir_entry->is_directory) // not a directory
4088 free(directory_path);
4093 free(directory_path);
4095 // check if this directory contains artwork with or without config file
4096 valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
4098 directory_name, type);
4101 closeDirectory(dir);
4103 // check if this directory directly contains artwork itself
4104 valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
4105 base_directory, ".",
4107 if (!valid_entry_found)
4108 Warn("cannot find any valid artwork in directory '%s'", base_directory);
4111 static TreeInfo *getDummyArtworkInfo(int type)
4113 // this is only needed when there is completely no artwork available
4114 TreeInfo *artwork_new = newTreeInfo();
4116 setTreeInfoToDefaults(artwork_new, type);
4118 setString(&artwork_new->subdir, UNDEFINED_FILENAME);
4119 setString(&artwork_new->fullpath, UNDEFINED_FILENAME);
4120 setString(&artwork_new->basepath, UNDEFINED_FILENAME);
4122 setString(&artwork_new->identifier, UNDEFINED_FILENAME);
4123 setString(&artwork_new->name, UNDEFINED_FILENAME);
4124 setString(&artwork_new->name_sorting, UNDEFINED_FILENAME);
4129 void SetCurrentArtwork(int type)
4131 ArtworkDirTree **current_ptr = ARTWORK_CURRENT_PTR(artwork, type);
4132 ArtworkDirTree *first_node = ARTWORK_FIRST_NODE(artwork, type);
4133 char *setup_set = SETUP_ARTWORK_SET(setup, type);
4134 char *default_subdir = ARTWORK_DEFAULT_SUBDIR(type);
4136 // set current artwork to artwork configured in setup menu
4137 *current_ptr = getTreeInfoFromIdentifier(first_node, setup_set);
4139 // if not found, set current artwork to default artwork
4140 if (*current_ptr == NULL)
4141 *current_ptr = getTreeInfoFromIdentifier(first_node, default_subdir);
4143 // if not found, set current artwork to first artwork in tree
4144 if (*current_ptr == NULL)
4145 *current_ptr = getFirstValidTreeInfoEntry(first_node);
4148 void ChangeCurrentArtworkIfNeeded(int type)
4150 char *current_identifier = ARTWORK_CURRENT_IDENTIFIER(artwork, type);
4151 char *setup_set = SETUP_ARTWORK_SET(setup, type);
4153 if (!strEqual(current_identifier, setup_set))
4154 SetCurrentArtwork(type);
4157 void LoadArtworkInfo(void)
4159 LoadArtworkInfoCache();
4161 DrawInitTextHead("Looking for custom artwork");
4163 LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
4164 options.graphics_directory,
4165 TREE_TYPE_GRAPHICS_DIR);
4166 LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
4167 getUserGraphicsDir(),
4168 TREE_TYPE_GRAPHICS_DIR);
4170 LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
4171 options.sounds_directory,
4172 TREE_TYPE_SOUNDS_DIR);
4173 LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
4175 TREE_TYPE_SOUNDS_DIR);
4177 LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
4178 options.music_directory,
4179 TREE_TYPE_MUSIC_DIR);
4180 LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
4182 TREE_TYPE_MUSIC_DIR);
4184 if (artwork.gfx_first == NULL)
4185 artwork.gfx_first = getDummyArtworkInfo(TREE_TYPE_GRAPHICS_DIR);
4186 if (artwork.snd_first == NULL)
4187 artwork.snd_first = getDummyArtworkInfo(TREE_TYPE_SOUNDS_DIR);
4188 if (artwork.mus_first == NULL)
4189 artwork.mus_first = getDummyArtworkInfo(TREE_TYPE_MUSIC_DIR);
4191 // before sorting, the first entries will be from the user directory
4192 SetCurrentArtwork(ARTWORK_TYPE_GRAPHICS);
4193 SetCurrentArtwork(ARTWORK_TYPE_SOUNDS);
4194 SetCurrentArtwork(ARTWORK_TYPE_MUSIC);
4196 artwork.gfx_current_identifier = artwork.gfx_current->identifier;
4197 artwork.snd_current_identifier = artwork.snd_current->identifier;
4198 artwork.mus_current_identifier = artwork.mus_current->identifier;
4200 #if ENABLE_UNUSED_CODE
4201 Debug("setup:LoadArtworkInfo", "graphics set == %s",
4202 artwork.gfx_current_identifier);
4203 Debug("setup:LoadArtworkInfo", "sounds set == %s",
4204 artwork.snd_current_identifier);
4205 Debug("setup:LoadArtworkInfo", "music set == %s",
4206 artwork.mus_current_identifier);
4209 sortTreeInfo(&artwork.gfx_first);
4210 sortTreeInfo(&artwork.snd_first);
4211 sortTreeInfo(&artwork.mus_first);
4213 #if ENABLE_UNUSED_CODE
4214 dumpTreeInfo(artwork.gfx_first, 0);
4215 dumpTreeInfo(artwork.snd_first, 0);
4216 dumpTreeInfo(artwork.mus_first, 0);
4220 static void MoveArtworkInfoIntoSubTree(ArtworkDirTree **artwork_node)
4222 ArtworkDirTree *artwork_new = newTreeInfo();
4223 char *top_node_name = "standalone artwork";
4225 setTreeInfoToDefaults(artwork_new, (*artwork_node)->type);
4227 artwork_new->level_group = TRUE;
4229 setString(&artwork_new->identifier, top_node_name);
4230 setString(&artwork_new->name, top_node_name);
4231 setString(&artwork_new->name_sorting, top_node_name);
4233 // create node to link back to current custom artwork directory
4234 createParentTreeInfoNode(artwork_new);
4236 // move existing custom artwork tree into newly created sub-tree
4237 artwork_new->node_group->next = *artwork_node;
4239 // change custom artwork tree to contain only newly created node
4240 *artwork_node = artwork_new;
4243 static void LoadArtworkInfoFromLevelInfoExt(ArtworkDirTree **artwork_node,
4244 ArtworkDirTree *node_parent,
4245 LevelDirTree *level_node,
4246 boolean empty_level_set_mode)
4248 int type = (*artwork_node)->type;
4250 // recursively check all level directories for artwork sub-directories
4254 boolean empty_level_set = (level_node->levels == 0);
4256 // check all tree entries for artwork, but skip parent link entries
4257 if (!level_node->parent_link && empty_level_set == empty_level_set_mode)
4259 TreeInfo *artwork_new = getArtworkInfoCacheEntry(level_node, type);
4260 boolean cached = (artwork_new != NULL);
4264 pushTreeInfo(artwork_node, artwork_new);
4268 TreeInfo *topnode_last = *artwork_node;
4269 char *path = getPath2(getLevelDirFromTreeInfo(level_node),
4270 ARTWORK_DIRECTORY(type));
4272 LoadArtworkInfoFromArtworkDir(artwork_node, NULL, path, type);
4274 if (topnode_last != *artwork_node) // check for newly added node
4276 artwork_new = *artwork_node;
4278 setString(&artwork_new->identifier, level_node->subdir);
4279 setString(&artwork_new->name, level_node->name);
4280 setString(&artwork_new->name_sorting, level_node->name_sorting);
4282 artwork_new->sort_priority = level_node->sort_priority;
4283 artwork_new->in_user_dir = level_node->in_user_dir;
4285 update_artworkinfo_cache = TRUE;
4291 // insert artwork info (from old cache or filesystem) into new cache
4292 if (artwork_new != NULL)
4293 setArtworkInfoCacheEntry(artwork_new, level_node, type);
4296 DrawInitTextItem(level_node->name);
4298 if (level_node->node_group != NULL)
4300 TreeInfo *artwork_new = newTreeInfo();
4303 setTreeInfoToDefaultsFromParent(artwork_new, node_parent);
4305 setTreeInfoToDefaults(artwork_new, type);
4307 artwork_new->level_group = TRUE;
4309 setString(&artwork_new->identifier, level_node->subdir);
4311 if (node_parent == NULL) // check for top tree node
4313 char *top_node_name = (empty_level_set_mode ?
4314 "artwork for certain level sets" :
4315 "artwork included in level sets");
4317 setString(&artwork_new->name, top_node_name);
4318 setString(&artwork_new->name_sorting, top_node_name);
4322 setString(&artwork_new->name, level_node->name);
4323 setString(&artwork_new->name_sorting, level_node->name_sorting);
4326 pushTreeInfo(artwork_node, artwork_new);
4328 // create node to link back to current custom artwork directory
4329 createParentTreeInfoNode(artwork_new);
4331 // recursively step into sub-directory and look for more custom artwork
4332 LoadArtworkInfoFromLevelInfoExt(&artwork_new->node_group, artwork_new,
4333 level_node->node_group,
4334 empty_level_set_mode);
4336 // if sub-tree has no custom artwork at all, remove it
4337 if (artwork_new->node_group->next == NULL)
4338 removeTreeInfo(artwork_node);
4341 level_node = level_node->next;
4345 static void LoadArtworkInfoFromLevelInfo(ArtworkDirTree **artwork_node)
4347 // move peviously loaded artwork tree into separate sub-tree
4348 MoveArtworkInfoIntoSubTree(artwork_node);
4350 // load artwork from level sets into separate sub-trees
4351 LoadArtworkInfoFromLevelInfoExt(artwork_node, NULL, leveldir_first_all, TRUE);
4352 LoadArtworkInfoFromLevelInfoExt(artwork_node, NULL, leveldir_first_all, FALSE);
4354 // add top tree node over all sub-trees and set parent links
4355 *artwork_node = addTopTreeInfoNode(*artwork_node);
4358 void LoadLevelArtworkInfo(void)
4360 print_timestamp_init("LoadLevelArtworkInfo");
4362 DrawInitTextHead("Looking for custom level artwork");
4364 print_timestamp_time("DrawTimeText");
4366 LoadArtworkInfoFromLevelInfo(&artwork.gfx_first);
4367 print_timestamp_time("LoadArtworkInfoFromLevelInfo (gfx)");
4368 LoadArtworkInfoFromLevelInfo(&artwork.snd_first);
4369 print_timestamp_time("LoadArtworkInfoFromLevelInfo (snd)");
4370 LoadArtworkInfoFromLevelInfo(&artwork.mus_first);
4371 print_timestamp_time("LoadArtworkInfoFromLevelInfo (mus)");
4373 SaveArtworkInfoCache();
4375 print_timestamp_time("SaveArtworkInfoCache");
4377 // needed for reloading level artwork not known at ealier stage
4378 ChangeCurrentArtworkIfNeeded(ARTWORK_TYPE_GRAPHICS);
4379 ChangeCurrentArtworkIfNeeded(ARTWORK_TYPE_SOUNDS);
4380 ChangeCurrentArtworkIfNeeded(ARTWORK_TYPE_MUSIC);
4382 print_timestamp_time("getTreeInfoFromIdentifier");
4384 sortTreeInfo(&artwork.gfx_first);
4385 sortTreeInfo(&artwork.snd_first);
4386 sortTreeInfo(&artwork.mus_first);
4388 print_timestamp_time("sortTreeInfo");
4390 #if ENABLE_UNUSED_CODE
4391 dumpTreeInfo(artwork.gfx_first, 0);
4392 dumpTreeInfo(artwork.snd_first, 0);
4393 dumpTreeInfo(artwork.mus_first, 0);
4396 print_timestamp_done("LoadLevelArtworkInfo");
4399 static boolean AddTreeSetToTreeInfoExt(TreeInfo *tree_node_old, char *tree_dir,
4400 char *tree_subdir_new, int type)
4402 if (tree_node_old == NULL)
4404 if (type == TREE_TYPE_LEVEL_DIR)
4406 // get level info tree node of personal user level set
4407 tree_node_old = getTreeInfoFromIdentifier(leveldir_first, getLoginName());
4409 // this may happen if "setup.internal.create_user_levelset" is FALSE
4410 // or if file "levelinfo.conf" is missing in personal user level set
4411 if (tree_node_old == NULL)
4412 tree_node_old = leveldir_first->node_group;
4416 // get artwork info tree node of first artwork set
4417 tree_node_old = ARTWORK_FIRST_NODE(artwork, type);
4421 if (tree_dir == NULL)
4422 tree_dir = TREE_USERDIR(type);
4424 if (tree_node_old == NULL ||
4426 tree_subdir_new == NULL) // should not happen
4429 int draw_deactivation_mask = GetDrawDeactivationMask();
4431 // override draw deactivation mask (temporarily disable drawing)
4432 SetDrawDeactivationMask(REDRAW_ALL);
4434 if (type == TREE_TYPE_LEVEL_DIR)
4436 // load new level set config and add it next to first user level set
4437 LoadLevelInfoFromLevelConf(&tree_node_old->next,
4438 tree_node_old->node_parent,
4439 tree_dir, tree_subdir_new);
4443 // load new artwork set config and add it next to first artwork set
4444 LoadArtworkInfoFromArtworkConf(&tree_node_old->next,
4445 tree_node_old->node_parent,
4446 tree_dir, tree_subdir_new, type);
4449 // set draw deactivation mask to previous value
4450 SetDrawDeactivationMask(draw_deactivation_mask);
4452 // get first node of level or artwork info tree
4453 TreeInfo **tree_node_first = TREE_FIRST_NODE_PTR(type);
4455 // get tree info node of newly added level or artwork set
4456 TreeInfo *tree_node_new = getTreeInfoFromIdentifier(*tree_node_first,
4459 if (tree_node_new == NULL) // should not happen
4462 // correct top link and parent node link of newly created tree node
4463 tree_node_new->node_top = tree_node_old->node_top;
4464 tree_node_new->node_parent = tree_node_old->node_parent;
4466 // sort tree info to adjust position of newly added tree set
4467 sortTreeInfo(tree_node_first);
4472 void AddTreeSetToTreeInfo(TreeInfo *tree_node, char *tree_dir,
4473 char *tree_subdir_new, int type)
4475 if (!AddTreeSetToTreeInfoExt(tree_node, tree_dir, tree_subdir_new, type))
4476 Fail("internal tree info structure corrupted -- aborting");
4479 void AddUserLevelSetToLevelInfo(char *level_subdir_new)
4481 AddTreeSetToTreeInfo(NULL, NULL, level_subdir_new, TREE_TYPE_LEVEL_DIR);
4484 char *getArtworkIdentifierForUserLevelSet(int type)
4486 char *classic_artwork_set = getClassicArtworkSet(type);
4488 // check for custom artwork configured in "levelinfo.conf"
4489 char *leveldir_artwork_set =
4490 *LEVELDIR_ARTWORK_SET_PTR(leveldir_current, type);
4491 boolean has_leveldir_artwork_set =
4492 (leveldir_artwork_set != NULL && !strEqual(leveldir_artwork_set,
4493 classic_artwork_set));
4495 // check for custom artwork in sub-directory "graphics" etc.
4496 TreeInfo *artwork_first_node = ARTWORK_FIRST_NODE(artwork, type);
4497 char *leveldir_identifier = leveldir_current->identifier;
4498 boolean has_artwork_subdir =
4499 (getTreeInfoFromIdentifier(artwork_first_node,
4500 leveldir_identifier) != NULL);
4502 return (has_leveldir_artwork_set ? leveldir_artwork_set :
4503 has_artwork_subdir ? leveldir_identifier :
4504 classic_artwork_set);
4507 TreeInfo *getArtworkTreeInfoForUserLevelSet(int type)
4509 char *artwork_set = getArtworkIdentifierForUserLevelSet(type);
4510 TreeInfo *artwork_first_node = ARTWORK_FIRST_NODE(artwork, type);
4511 TreeInfo *ti = getTreeInfoFromIdentifier(artwork_first_node, artwork_set);
4515 ti = getTreeInfoFromIdentifier(artwork_first_node,
4516 ARTWORK_DEFAULT_SUBDIR(type));
4518 Fail("cannot find default graphics -- should not happen");
4524 boolean checkIfCustomArtworkExistsForCurrentLevelSet(void)
4526 char *graphics_set =
4527 getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_GRAPHICS);
4529 getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_SOUNDS);
4531 getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_MUSIC);
4533 return (!strEqual(graphics_set, GFX_CLASSIC_SUBDIR) ||
4534 !strEqual(sounds_set, SND_CLASSIC_SUBDIR) ||
4535 !strEqual(music_set, MUS_CLASSIC_SUBDIR));
4538 boolean UpdateUserLevelSet(char *level_subdir, char *level_name,
4539 char *level_author, int num_levels)
4541 char *filename = getPath2(getUserLevelDir(level_subdir), LEVELINFO_FILENAME);
4542 char *filename_tmp = getStringCat2(filename, ".tmp");
4544 FILE *file_tmp = NULL;
4545 char line[MAX_LINE_LEN];
4546 boolean success = FALSE;
4547 LevelDirTree *leveldir = getTreeInfoFromIdentifier(leveldir_first,
4549 // update values in level directory tree
4551 if (level_name != NULL)
4552 setString(&leveldir->name, level_name);
4554 if (level_author != NULL)
4555 setString(&leveldir->author, level_author);
4557 if (num_levels != -1)
4558 leveldir->levels = num_levels;
4560 // update values that depend on other values
4562 setString(&leveldir->name_sorting, leveldir->name);
4564 leveldir->last_level = leveldir->first_level + leveldir->levels - 1;
4566 // sort order of level sets may have changed
4567 sortTreeInfo(&leveldir_first);
4569 if ((file = fopen(filename, MODE_READ)) &&
4570 (file_tmp = fopen(filename_tmp, MODE_WRITE)))
4572 while (fgets(line, MAX_LINE_LEN, file))
4574 if (strPrefix(line, "name:") && level_name != NULL)
4575 fprintf(file_tmp, "%-32s%s\n", "name:", level_name);
4576 else if (strPrefix(line, "author:") && level_author != NULL)
4577 fprintf(file_tmp, "%-32s%s\n", "author:", level_author);
4578 else if (strPrefix(line, "levels:") && num_levels != -1)
4579 fprintf(file_tmp, "%-32s%d\n", "levels:", num_levels);
4581 fputs(line, file_tmp);
4594 success = (rename(filename_tmp, filename) == 0);
4602 boolean CreateUserLevelSet(char *level_subdir, char *level_name,
4603 char *level_author, int num_levels,
4604 boolean use_artwork_set)
4606 LevelDirTree *level_info;
4611 // create user level sub-directory, if needed
4612 createDirectory(getUserLevelDir(level_subdir), "user level", PERMS_PRIVATE);
4614 filename = getPath2(getUserLevelDir(level_subdir), LEVELINFO_FILENAME);
4616 if (!(file = fopen(filename, MODE_WRITE)))
4618 Warn("cannot write level info file '%s'", filename);
4625 level_info = newTreeInfo();
4627 // always start with reliable default values
4628 setTreeInfoToDefaults(level_info, TREE_TYPE_LEVEL_DIR);
4630 setString(&level_info->name, level_name);
4631 setString(&level_info->author, level_author);
4632 level_info->levels = num_levels;
4633 level_info->first_level = 1;
4634 level_info->sort_priority = LEVELCLASS_PRIVATE_START;
4635 level_info->readonly = FALSE;
4637 if (use_artwork_set)
4639 level_info->graphics_set =
4640 getStringCopy(getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_GRAPHICS));
4641 level_info->sounds_set =
4642 getStringCopy(getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_SOUNDS));
4643 level_info->music_set =
4644 getStringCopy(getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_MUSIC));
4647 token_value_position = TOKEN_VALUE_POSITION_SHORT;
4649 fprintFileHeader(file, LEVELINFO_FILENAME);
4652 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
4654 if (i == LEVELINFO_TOKEN_NAME ||
4655 i == LEVELINFO_TOKEN_AUTHOR ||
4656 i == LEVELINFO_TOKEN_LEVELS ||
4657 i == LEVELINFO_TOKEN_FIRST_LEVEL ||
4658 i == LEVELINFO_TOKEN_SORT_PRIORITY ||
4659 i == LEVELINFO_TOKEN_READONLY ||
4660 (use_artwork_set && (i == LEVELINFO_TOKEN_GRAPHICS_SET ||
4661 i == LEVELINFO_TOKEN_SOUNDS_SET ||
4662 i == LEVELINFO_TOKEN_MUSIC_SET)))
4663 fprintf(file, "%s\n", getSetupLine(levelinfo_tokens, "", i));
4665 // just to make things nicer :)
4666 if (i == LEVELINFO_TOKEN_AUTHOR ||
4667 i == LEVELINFO_TOKEN_FIRST_LEVEL ||
4668 (use_artwork_set && i == LEVELINFO_TOKEN_READONLY))
4669 fprintf(file, "\n");
4672 token_value_position = TOKEN_VALUE_POSITION_DEFAULT;
4676 SetFilePermissions(filename, PERMS_PRIVATE);
4678 freeTreeInfo(level_info);
4684 static void SaveUserLevelInfo(void)
4686 CreateUserLevelSet(getLoginName(), getLoginName(), getRealName(), 100, FALSE);
4689 char *getSetupValue(int type, void *value)
4691 static char value_string[MAX_LINE_LEN];
4699 strcpy(value_string, (*(boolean *)value ? "true" : "false"));
4703 strcpy(value_string, (*(boolean *)value ? "on" : "off"));
4707 strcpy(value_string, (*(int *)value == AUTO ? "auto" :
4708 *(int *)value == FALSE ? "off" : "on"));
4712 strcpy(value_string, (*(boolean *)value ? "yes" : "no"));
4715 case TYPE_YES_NO_AUTO:
4716 strcpy(value_string, (*(int *)value == AUTO ? "auto" :
4717 *(int *)value == FALSE ? "no" : "yes"));
4721 strcpy(value_string, (*(boolean *)value ? "AGA" : "ECS"));
4725 strcpy(value_string, getKeyNameFromKey(*(Key *)value));
4729 strcpy(value_string, getX11KeyNameFromKey(*(Key *)value));
4733 sprintf(value_string, "%d", *(int *)value);
4737 if (*(char **)value == NULL)
4740 strcpy(value_string, *(char **)value);
4744 sprintf(value_string, "player_%d", *(int *)value + 1);
4748 value_string[0] = '\0';
4752 if (type & TYPE_GHOSTED)
4753 strcpy(value_string, "n/a");
4755 return value_string;
4758 char *getSetupLine(struct TokenInfo *token_info, char *prefix, int token_nr)
4762 static char token_string[MAX_LINE_LEN];
4763 int token_type = token_info[token_nr].type;
4764 void *setup_value = token_info[token_nr].value;
4765 char *token_text = token_info[token_nr].text;
4766 char *value_string = getSetupValue(token_type, setup_value);
4768 // build complete token string
4769 sprintf(token_string, "%s%s", prefix, token_text);
4771 // build setup entry line
4772 line = getFormattedSetupEntry(token_string, value_string);
4774 if (token_type == TYPE_KEY_X11)
4776 Key key = *(Key *)setup_value;
4777 char *keyname = getKeyNameFromKey(key);
4779 // add comment, if useful
4780 if (!strEqual(keyname, "(undefined)") &&
4781 !strEqual(keyname, "(unknown)"))
4783 // add at least one whitespace
4785 for (i = strlen(line); i < token_comment_position; i++)
4789 strcat(line, keyname);
4796 static void InitLastPlayedLevels_ParentNode(void)
4798 LevelDirTree **leveldir_top = &leveldir_first->node_group->next;
4799 LevelDirTree *leveldir_new = NULL;
4801 // check if parent node for last played levels already exists
4802 if (strEqual((*leveldir_top)->identifier, TOKEN_STR_LAST_LEVEL_SERIES))
4805 leveldir_new = newTreeInfo();
4807 setTreeInfoToDefaultsFromParent(leveldir_new, leveldir_first);
4809 leveldir_new->level_group = TRUE;
4810 leveldir_new->sort_priority = LEVELCLASS_LAST_PLAYED_LEVEL;
4812 setString(&leveldir_new->identifier, TOKEN_STR_LAST_LEVEL_SERIES);
4813 setString(&leveldir_new->name, "<< (last played level sets)");
4814 setString(&leveldir_new->name_sorting, leveldir_new->name);
4816 pushTreeInfo(leveldir_top, leveldir_new);
4818 // create node to link back to current level directory
4819 createParentTreeInfoNode(leveldir_new);
4822 void UpdateLastPlayedLevels_TreeInfo(void)
4824 char **last_level_series = setup.level_setup.last_level_series;
4825 LevelDirTree *leveldir_last;
4826 TreeInfo **node_new = NULL;
4829 if (last_level_series[0] == NULL)
4832 InitLastPlayedLevels_ParentNode();
4834 leveldir_last = getTreeInfoFromIdentifierExt(leveldir_first,
4835 TOKEN_STR_LAST_LEVEL_SERIES,
4836 TREE_NODE_TYPE_GROUP);
4837 if (leveldir_last == NULL)
4840 node_new = &leveldir_last->node_group->next;
4842 freeTreeInfo(*node_new);
4846 for (i = 0; last_level_series[i] != NULL; i++)
4848 LevelDirTree *node_last = getTreeInfoFromIdentifier(leveldir_first,
4849 last_level_series[i]);
4850 if (node_last == NULL)
4853 *node_new = getTreeInfoCopy(node_last); // copy complete node
4855 (*node_new)->node_top = &leveldir_first; // correct top node link
4856 (*node_new)->node_parent = leveldir_last; // correct parent node link
4858 (*node_new)->is_copy = TRUE; // mark entry as node copy
4860 (*node_new)->node_group = NULL;
4861 (*node_new)->next = NULL;
4863 (*node_new)->cl_first = -1; // force setting tree cursor
4865 node_new = &((*node_new)->next);
4869 static void UpdateLastPlayedLevels_List(void)
4871 char **last_level_series = setup.level_setup.last_level_series;
4872 int pos = MAX_LEVELDIR_HISTORY - 1;
4875 // search for potentially already existing entry in list of level sets
4876 for (i = 0; last_level_series[i] != NULL; i++)
4877 if (strEqual(last_level_series[i], leveldir_current->identifier))
4880 // move list of level sets one entry down (using potentially free entry)
4881 for (i = pos; i > 0; i--)
4882 setString(&last_level_series[i], last_level_series[i - 1]);
4884 // put last played level set at top position
4885 setString(&last_level_series[0], leveldir_current->identifier);
4888 static TreeInfo *StoreOrRestoreLastPlayedLevels(TreeInfo *node, boolean store)
4890 static char *identifier = NULL;
4894 setString(&identifier, (node && node->is_copy ? node->identifier : NULL));
4896 return NULL; // not used
4900 TreeInfo *node_new = getTreeInfoFromIdentifierExt(leveldir_first,
4902 TREE_NODE_TYPE_COPY);
4903 return (node_new != NULL ? node_new : node);
4907 void StoreLastPlayedLevels(TreeInfo *node)
4909 StoreOrRestoreLastPlayedLevels(node, TRUE);
4912 void RestoreLastPlayedLevels(TreeInfo **node)
4914 *node = StoreOrRestoreLastPlayedLevels(*node, FALSE);
4917 void LoadLevelSetup_LastSeries(void)
4919 // --------------------------------------------------------------------------
4920 // ~/.<program>/levelsetup.conf
4921 // --------------------------------------------------------------------------
4923 char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
4924 SetupFileHash *level_setup_hash = NULL;
4928 // always start with reliable default values
4929 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
4931 // start with empty history of last played level sets
4932 setString(&setup.level_setup.last_level_series[0], NULL);
4934 if (!strEqual(DEFAULT_LEVELSET, UNDEFINED_LEVELSET))
4936 leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
4938 if (leveldir_current == NULL)
4939 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
4942 if ((level_setup_hash = loadSetupFileHash(filename)))
4944 char *last_level_series =
4945 getHashEntry(level_setup_hash, TOKEN_STR_LAST_LEVEL_SERIES);
4947 leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
4949 if (leveldir_current == NULL)
4950 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
4952 for (i = 0; i < MAX_LEVELDIR_HISTORY; i++)
4954 char token[strlen(TOKEN_STR_LAST_LEVEL_SERIES) + 10];
4955 LevelDirTree *leveldir_last;
4957 sprintf(token, "%s.%03d", TOKEN_STR_LAST_LEVEL_SERIES, i);
4959 last_level_series = getHashEntry(level_setup_hash, token);
4961 leveldir_last = getTreeInfoFromIdentifier(leveldir_first,
4963 if (leveldir_last != NULL)
4964 setString(&setup.level_setup.last_level_series[pos++],
4968 setString(&setup.level_setup.last_level_series[pos], NULL);
4970 freeSetupFileHash(level_setup_hash);
4974 Debug("setup", "using default setup values");
4980 static void SaveLevelSetup_LastSeries_Ext(boolean deactivate_last_level_series)
4982 // --------------------------------------------------------------------------
4983 // ~/.<program>/levelsetup.conf
4984 // --------------------------------------------------------------------------
4986 // check if the current level directory structure is available at this point
4987 if (leveldir_current == NULL)
4990 char **last_level_series = setup.level_setup.last_level_series;
4991 char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
4995 InitUserDataDirectory();
4997 UpdateLastPlayedLevels_List();
4999 if (!(file = fopen(filename, MODE_WRITE)))
5001 Warn("cannot write setup file '%s'", filename);
5008 fprintFileHeader(file, LEVELSETUP_FILENAME);
5010 if (deactivate_last_level_series)
5011 fprintf(file, "# %s\n# ", "the following level set may have caused a problem and was deactivated");
5013 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_LAST_LEVEL_SERIES,
5014 leveldir_current->identifier));
5016 for (i = 0; last_level_series[i] != NULL; i++)
5018 char token[strlen(TOKEN_STR_LAST_LEVEL_SERIES) + 1 + 10 + 1];
5020 sprintf(token, "%s.%03d", TOKEN_STR_LAST_LEVEL_SERIES, i);
5022 fprintf(file, "%s\n", getFormattedSetupEntry(token, last_level_series[i]));
5027 SetFilePermissions(filename, PERMS_PRIVATE);
5032 void SaveLevelSetup_LastSeries(void)
5034 SaveLevelSetup_LastSeries_Ext(FALSE);
5037 void SaveLevelSetup_LastSeries_Deactivate(void)
5039 SaveLevelSetup_LastSeries_Ext(TRUE);
5042 static void checkSeriesInfo(void)
5044 static char *level_directory = NULL;
5047 DirectoryEntry *dir_entry;
5050 checked_free(level_directory);
5052 // check for more levels besides the 'levels' field of 'levelinfo.conf'
5054 level_directory = getPath2((leveldir_current->in_user_dir ?
5055 getUserLevelDir(NULL) :
5056 options.level_directory),
5057 leveldir_current->fullpath);
5059 if ((dir = openDirectory(level_directory)) == NULL)
5061 Warn("cannot read level directory '%s'", level_directory);
5067 while ((dir_entry = readDirectory(dir)) != NULL) // loop all entries
5069 if (strlen(dir_entry->basename) > 4 &&
5070 dir_entry->basename[3] == '.' &&
5071 strEqual(&dir_entry->basename[4], LEVELFILE_EXTENSION))
5073 char levelnum_str[4];
5076 strncpy(levelnum_str, dir_entry->basename, 3);
5077 levelnum_str[3] = '\0';
5079 levelnum_value = atoi(levelnum_str);
5081 if (levelnum_value < leveldir_current->first_level)
5083 Warn("additional level %d found", levelnum_value);
5085 leveldir_current->first_level = levelnum_value;
5087 else if (levelnum_value > leveldir_current->last_level)
5089 Warn("additional level %d found", levelnum_value);
5091 leveldir_current->last_level = levelnum_value;
5097 closeDirectory(dir);
5100 void LoadLevelSetup_SeriesInfo(void)
5103 SetupFileHash *level_setup_hash = NULL;
5104 char *level_subdir = leveldir_current->subdir;
5107 // always start with reliable default values
5108 level_nr = leveldir_current->first_level;
5110 for (i = 0; i < MAX_LEVELS; i++)
5112 LevelStats_setPlayed(i, 0);
5113 LevelStats_setSolved(i, 0);
5118 // --------------------------------------------------------------------------
5119 // ~/.<program>/levelsetup/<level series>/levelsetup.conf
5120 // --------------------------------------------------------------------------
5122 level_subdir = leveldir_current->subdir;
5124 filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
5126 if ((level_setup_hash = loadSetupFileHash(filename)))
5130 // get last played level in this level set
5132 token_value = getHashEntry(level_setup_hash, TOKEN_STR_LAST_PLAYED_LEVEL);
5136 level_nr = atoi(token_value);
5138 if (level_nr < leveldir_current->first_level)
5139 level_nr = leveldir_current->first_level;
5140 if (level_nr > leveldir_current->last_level)
5141 level_nr = leveldir_current->last_level;
5144 // get handicap level in this level set
5146 token_value = getHashEntry(level_setup_hash, TOKEN_STR_HANDICAP_LEVEL);
5150 int level_nr = atoi(token_value);
5152 if (level_nr < leveldir_current->first_level)
5153 level_nr = leveldir_current->first_level;
5154 if (level_nr > leveldir_current->last_level + 1)
5155 level_nr = leveldir_current->last_level;
5157 if (leveldir_current->user_defined || !leveldir_current->handicap)
5158 level_nr = leveldir_current->last_level;
5160 leveldir_current->handicap_level = level_nr;
5163 // get number of played and solved levels in this level set
5165 BEGIN_HASH_ITERATION(level_setup_hash, itr)
5167 char *token = HASH_ITERATION_TOKEN(itr);
5168 char *value = HASH_ITERATION_VALUE(itr);
5170 if (strlen(token) == 3 &&
5171 token[0] >= '0' && token[0] <= '9' &&
5172 token[1] >= '0' && token[1] <= '9' &&
5173 token[2] >= '0' && token[2] <= '9')
5175 int level_nr = atoi(token);
5178 LevelStats_setPlayed(level_nr, atoi(value)); // read 1st column
5180 value = strchr(value, ' ');
5183 LevelStats_setSolved(level_nr, atoi(value)); // read 2nd column
5186 END_HASH_ITERATION(hash, itr)
5188 freeSetupFileHash(level_setup_hash);
5192 Debug("setup", "using default setup values");
5198 void SaveLevelSetup_SeriesInfo(void)
5201 char *level_subdir = leveldir_current->subdir;
5202 char *level_nr_str = int2str(level_nr, 0);
5203 char *handicap_level_str = int2str(leveldir_current->handicap_level, 0);
5207 // --------------------------------------------------------------------------
5208 // ~/.<program>/levelsetup/<level series>/levelsetup.conf
5209 // --------------------------------------------------------------------------
5211 InitLevelSetupDirectory(level_subdir);
5213 filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
5215 if (!(file = fopen(filename, MODE_WRITE)))
5217 Warn("cannot write setup file '%s'", filename);
5224 fprintFileHeader(file, LEVELSETUP_FILENAME);
5226 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_PLAYED_LEVEL,
5228 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_HANDICAP_LEVEL,
5229 handicap_level_str));
5231 for (i = leveldir_current->first_level; i <= leveldir_current->last_level;
5234 if (LevelStats_getPlayed(i) > 0 ||
5235 LevelStats_getSolved(i) > 0)
5240 sprintf(token, "%03d", i);
5241 sprintf(value, "%d %d", LevelStats_getPlayed(i), LevelStats_getSolved(i));
5243 fprintf(file, "%s\n", getFormattedSetupEntry(token, value));
5249 SetFilePermissions(filename, PERMS_PRIVATE);
5254 int LevelStats_getPlayed(int nr)
5256 return (nr >= 0 && nr < MAX_LEVELS ? level_stats[nr].played : 0);
5259 int LevelStats_getSolved(int nr)
5261 return (nr >= 0 && nr < MAX_LEVELS ? level_stats[nr].solved : 0);
5264 void LevelStats_setPlayed(int nr, int value)
5266 if (nr >= 0 && nr < MAX_LEVELS)
5267 level_stats[nr].played = value;
5270 void LevelStats_setSolved(int nr, int value)
5272 if (nr >= 0 && nr < MAX_LEVELS)
5273 level_stats[nr].solved = value;
5276 void LevelStats_incPlayed(int nr)
5278 if (nr >= 0 && nr < MAX_LEVELS)
5279 level_stats[nr].played++;
5282 void LevelStats_incSolved(int nr)
5284 if (nr >= 0 && nr < MAX_LEVELS)
5285 level_stats[nr].solved++;
5288 void LoadUserSetup(void)
5290 // --------------------------------------------------------------------------
5291 // ~/.<program>/usersetup.conf
5292 // --------------------------------------------------------------------------
5294 char *filename = getPath2(getMainUserGameDataDir(), USERSETUP_FILENAME);
5295 SetupFileHash *user_setup_hash = NULL;
5297 // always start with reliable default values
5300 if ((user_setup_hash = loadSetupFileHash(filename)))
5304 // get last selected user number
5305 token_value = getHashEntry(user_setup_hash, TOKEN_STR_LAST_USER);
5308 user.nr = MIN(MAX(0, atoi(token_value)), MAX_PLAYER_NAMES - 1);
5310 freeSetupFileHash(user_setup_hash);
5314 Debug("setup", "using default setup values");
5320 void SaveUserSetup(void)
5322 // --------------------------------------------------------------------------
5323 // ~/.<program>/usersetup.conf
5324 // --------------------------------------------------------------------------
5326 char *filename = getPath2(getMainUserGameDataDir(), USERSETUP_FILENAME);
5329 InitMainUserDataDirectory();
5331 if (!(file = fopen(filename, MODE_WRITE)))
5333 Warn("cannot write setup file '%s'", filename);
5340 fprintFileHeader(file, USERSETUP_FILENAME);
5342 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_USER,
5346 SetFilePermissions(filename, PERMS_PRIVATE);