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>
27 #include "zip/miniunz.h"
30 #define ENABLE_UNUSED_CODE FALSE // for currently unused functions
31 #define DEBUG_NO_CONFIG_FILE FALSE // for extra-verbose debug output
33 #define NUM_LEVELCLASS_DESC 8
35 static char *levelclass_desc[NUM_LEVELCLASS_DESC] =
47 #define TOKEN_VALUE_POSITION_SHORT 32
48 #define TOKEN_VALUE_POSITION_DEFAULT 40
49 #define TOKEN_COMMENT_POSITION_DEFAULT 60
51 #define TREE_NODE_TYPE_DEFAULT 0
52 #define TREE_NODE_TYPE_PARENT 1
53 #define TREE_NODE_TYPE_GROUP 2
54 #define TREE_NODE_TYPE_COPY 3
56 #define TREE_NODE_TYPE(ti) (ti->node_group ? TREE_NODE_TYPE_GROUP : \
57 ti->parent_link ? TREE_NODE_TYPE_PARENT : \
58 ti->is_copy ? TREE_NODE_TYPE_COPY : \
59 TREE_NODE_TYPE_DEFAULT)
62 static void setTreeInfoToDefaults(TreeInfo *, int);
63 static TreeInfo *getTreeInfoCopy(TreeInfo *ti);
64 static int compareTreeInfoEntries(const void *, const void *);
66 static int token_value_position = TOKEN_VALUE_POSITION_DEFAULT;
67 static int token_comment_position = TOKEN_COMMENT_POSITION_DEFAULT;
69 static SetupFileHash *artworkinfo_cache_old = NULL;
70 static SetupFileHash *artworkinfo_cache_new = NULL;
71 static SetupFileHash *optional_tokens_hash = NULL;
72 static SetupFileHash *missing_file_hash = NULL;
73 static boolean use_artworkinfo_cache = TRUE;
74 static boolean update_artworkinfo_cache = FALSE;
77 // ----------------------------------------------------------------------------
79 // ----------------------------------------------------------------------------
81 static void WarnUsingFallback(char *filename)
83 if (getHashEntry(missing_file_hash, filename) == NULL)
85 setHashEntry(missing_file_hash, filename, "");
87 Debug("setup", "cannot find artwork file '%s' (using fallback)", filename);
91 static char *getLevelClassDescription(TreeInfo *ti)
93 int position = ti->sort_priority / 100;
95 if (position >= 0 && position < NUM_LEVELCLASS_DESC)
96 return levelclass_desc[position];
98 return "Unknown Level Class";
101 static char *getCacheDir(void)
103 static char *cache_dir = NULL;
105 if (cache_dir == NULL)
106 cache_dir = getPath2(getMainUserGameDataDir(), CACHE_DIRECTORY);
111 static char *getScoreDir(char *level_subdir)
113 static char *score_dir = NULL;
114 static char *score_level_dir = NULL;
115 char *score_subdir = SCORES_DIRECTORY;
117 if (score_dir == NULL)
118 score_dir = getPath2(getMainUserGameDataDir(), score_subdir);
120 if (level_subdir != NULL)
122 checked_free(score_level_dir);
124 score_level_dir = getPath2(score_dir, level_subdir);
126 return score_level_dir;
132 static char *getScoreCacheDir(char *level_subdir)
134 static char *score_dir = NULL;
135 static char *score_level_dir = NULL;
136 char *score_subdir = SCORES_DIRECTORY;
138 if (score_dir == NULL)
139 score_dir = getPath2(getCacheDir(), score_subdir);
141 if (level_subdir != NULL)
143 checked_free(score_level_dir);
145 score_level_dir = getPath2(score_dir, level_subdir);
147 return score_level_dir;
153 static char *getScoreTapeDir(char *level_subdir, int nr)
155 static char *score_tape_dir = NULL;
156 char tape_subdir[MAX_FILENAME_LEN];
158 checked_free(score_tape_dir);
160 sprintf(tape_subdir, "%03d", nr);
161 score_tape_dir = getPath2(getScoreDir(level_subdir), tape_subdir);
163 return score_tape_dir;
166 static char *getScoreCacheTapeDir(char *level_subdir, int nr)
168 static char *score_cache_tape_dir = NULL;
169 char tape_subdir[MAX_FILENAME_LEN];
171 checked_free(score_cache_tape_dir);
173 sprintf(tape_subdir, "%03d", nr);
174 score_cache_tape_dir = getPath2(getScoreCacheDir(level_subdir), tape_subdir);
176 return score_cache_tape_dir;
179 static char *getUserSubdir(int nr)
181 static char user_subdir[16] = { 0 };
183 sprintf(user_subdir, "%03d", nr);
188 static char *getUserDir(int nr)
190 static char *user_dir = NULL;
191 char *main_data_dir = getMainUserGameDataDir();
192 char *users_subdir = USERS_DIRECTORY;
193 char *user_subdir = getUserSubdir(nr);
195 checked_free(user_dir);
198 user_dir = getPath3(main_data_dir, users_subdir, user_subdir);
200 user_dir = getPath2(main_data_dir, users_subdir);
205 static char *getLevelSetupDir(char *level_subdir)
207 static char *levelsetup_dir = NULL;
208 char *data_dir = getUserGameDataDir();
209 char *levelsetup_subdir = LEVELSETUP_DIRECTORY;
211 checked_free(levelsetup_dir);
213 if (level_subdir != NULL)
214 levelsetup_dir = getPath3(data_dir, levelsetup_subdir, level_subdir);
216 levelsetup_dir = getPath2(data_dir, levelsetup_subdir);
218 return levelsetup_dir;
221 static char *getNetworkDir(void)
223 static char *network_dir = NULL;
225 if (network_dir == NULL)
226 network_dir = getPath2(getMainUserGameDataDir(), NETWORK_DIRECTORY);
231 char *getLevelDirFromTreeInfo(TreeInfo *node)
233 static char *level_dir = NULL;
236 return options.level_directory;
238 checked_free(level_dir);
240 level_dir = getPath2((node->in_user_dir ? getUserLevelDir(NULL) :
241 options.level_directory), node->fullpath);
246 char *getUserLevelDir(char *level_subdir)
248 static char *userlevel_dir = NULL;
249 char *data_dir = getMainUserGameDataDir();
250 char *userlevel_subdir = LEVELS_DIRECTORY;
252 checked_free(userlevel_dir);
254 if (level_subdir != NULL)
255 userlevel_dir = getPath3(data_dir, userlevel_subdir, level_subdir);
257 userlevel_dir = getPath2(data_dir, userlevel_subdir);
259 return userlevel_dir;
262 char *getNetworkLevelDir(char *level_subdir)
264 static char *network_level_dir = NULL;
265 char *data_dir = getNetworkDir();
266 char *networklevel_subdir = LEVELS_DIRECTORY;
268 checked_free(network_level_dir);
270 if (level_subdir != NULL)
271 network_level_dir = getPath3(data_dir, networklevel_subdir, level_subdir);
273 network_level_dir = getPath2(data_dir, networklevel_subdir);
275 return network_level_dir;
278 char *getCurrentLevelDir(void)
280 return getLevelDirFromTreeInfo(leveldir_current);
283 char *getNewUserLevelSubdir(void)
285 static char *new_level_subdir = NULL;
286 char *subdir_prefix = getLoginName();
287 char subdir_suffix[10];
288 int max_suffix_number = 1000;
291 while (++i < max_suffix_number)
293 sprintf(subdir_suffix, "_%d", i);
295 checked_free(new_level_subdir);
296 new_level_subdir = getStringCat2(subdir_prefix, subdir_suffix);
298 if (!directoryExists(getUserLevelDir(new_level_subdir)))
302 return new_level_subdir;
305 char *getTapeDir(char *level_subdir)
307 static char *tape_dir = NULL;
308 char *data_dir = getUserGameDataDir();
309 char *tape_subdir = TAPES_DIRECTORY;
311 checked_free(tape_dir);
313 if (level_subdir != NULL)
314 tape_dir = getPath3(data_dir, tape_subdir, level_subdir);
316 tape_dir = getPath2(data_dir, tape_subdir);
321 static char *getSolutionTapeDir(void)
323 static char *tape_dir = NULL;
324 char *data_dir = getCurrentLevelDir();
325 char *tape_subdir = TAPES_DIRECTORY;
327 checked_free(tape_dir);
329 tape_dir = getPath2(data_dir, tape_subdir);
334 static char *getDefaultGraphicsDir(char *graphics_subdir)
336 static char *graphics_dir = NULL;
338 if (graphics_subdir == NULL)
339 return options.graphics_directory;
341 checked_free(graphics_dir);
343 graphics_dir = getPath2(options.graphics_directory, graphics_subdir);
348 static char *getDefaultSoundsDir(char *sounds_subdir)
350 static char *sounds_dir = NULL;
352 if (sounds_subdir == NULL)
353 return options.sounds_directory;
355 checked_free(sounds_dir);
357 sounds_dir = getPath2(options.sounds_directory, sounds_subdir);
362 static char *getDefaultMusicDir(char *music_subdir)
364 static char *music_dir = NULL;
366 if (music_subdir == NULL)
367 return options.music_directory;
369 checked_free(music_dir);
371 music_dir = getPath2(options.music_directory, music_subdir);
376 static char *getClassicArtworkSet(int type)
378 return (type == TREE_TYPE_GRAPHICS_DIR ? GFX_CLASSIC_SUBDIR :
379 type == TREE_TYPE_SOUNDS_DIR ? SND_CLASSIC_SUBDIR :
380 type == TREE_TYPE_MUSIC_DIR ? MUS_CLASSIC_SUBDIR : "");
383 static char *getClassicArtworkDir(int type)
385 return (type == TREE_TYPE_GRAPHICS_DIR ?
386 getDefaultGraphicsDir(GFX_CLASSIC_SUBDIR) :
387 type == TREE_TYPE_SOUNDS_DIR ?
388 getDefaultSoundsDir(SND_CLASSIC_SUBDIR) :
389 type == TREE_TYPE_MUSIC_DIR ?
390 getDefaultMusicDir(MUS_CLASSIC_SUBDIR) : "");
393 char *getUserGraphicsDir(void)
395 static char *usergraphics_dir = NULL;
397 if (usergraphics_dir == NULL)
398 usergraphics_dir = getPath2(getMainUserGameDataDir(), GRAPHICS_DIRECTORY);
400 return usergraphics_dir;
403 char *getUserSoundsDir(void)
405 static char *usersounds_dir = NULL;
407 if (usersounds_dir == NULL)
408 usersounds_dir = getPath2(getMainUserGameDataDir(), SOUNDS_DIRECTORY);
410 return usersounds_dir;
413 char *getUserMusicDir(void)
415 static char *usermusic_dir = NULL;
417 if (usermusic_dir == NULL)
418 usermusic_dir = getPath2(getMainUserGameDataDir(), MUSIC_DIRECTORY);
420 return usermusic_dir;
423 static char *getSetupArtworkDir(TreeInfo *ti)
425 static char *artwork_dir = NULL;
430 checked_free(artwork_dir);
432 artwork_dir = getPath2(ti->basepath, ti->fullpath);
437 char *setLevelArtworkDir(TreeInfo *ti)
439 char **artwork_path_ptr, **artwork_set_ptr;
440 TreeInfo *level_artwork;
442 if (ti == NULL || leveldir_current == NULL)
445 artwork_path_ptr = LEVELDIR_ARTWORK_PATH_PTR(leveldir_current, ti->type);
446 artwork_set_ptr = LEVELDIR_ARTWORK_SET_PTR( leveldir_current, ti->type);
448 checked_free(*artwork_path_ptr);
450 if ((level_artwork = getTreeInfoFromIdentifier(ti, *artwork_set_ptr)))
452 *artwork_path_ptr = getStringCopy(getSetupArtworkDir(level_artwork));
457 No (or non-existing) artwork configured in "levelinfo.conf". This would
458 normally result in using the artwork configured in the setup menu. But
459 if an artwork subdirectory exists (which might contain custom artwork
460 or an artwork configuration file), this level artwork must be treated
461 as relative to the default "classic" artwork, not to the artwork that
462 is currently configured in the setup menu.
464 Update: For "special" versions of R'n'D (like "R'n'D jue"), do not use
465 the "default" artwork (which would be "jue0" for "R'n'D jue"), but use
466 the real "classic" artwork from the original R'n'D (like "gfx_classic").
469 char *dir = getPath2(getCurrentLevelDir(), ARTWORK_DIRECTORY(ti->type));
471 checked_free(*artwork_set_ptr);
473 if (directoryExists(dir))
475 *artwork_path_ptr = getStringCopy(getClassicArtworkDir(ti->type));
476 *artwork_set_ptr = getStringCopy(getClassicArtworkSet(ti->type));
480 *artwork_path_ptr = getStringCopy(UNDEFINED_FILENAME);
481 *artwork_set_ptr = NULL;
487 return *artwork_set_ptr;
490 static char *getLevelArtworkSet(int type)
492 if (leveldir_current == NULL)
495 return LEVELDIR_ARTWORK_SET(leveldir_current, type);
498 static char *getLevelArtworkDir(int type)
500 if (leveldir_current == NULL)
501 return UNDEFINED_FILENAME;
503 return LEVELDIR_ARTWORK_PATH(leveldir_current, type);
506 char *getProgramMainDataPath(char *command_filename, char *base_path)
508 // check if the program's main data base directory is configured
509 if (!strEqual(base_path, "."))
510 return getStringCopy(base_path);
512 /* if the program is configured to start from current directory (default),
513 determine program package directory from program binary (some versions
514 of KDE/Konqueror and Mac OS X (especially "Mavericks") apparently do not
515 set the current working directory to the program package directory) */
516 char *main_data_path = getBasePath(command_filename);
518 #if defined(PLATFORM_MAC)
519 if (strSuffix(main_data_path, MAC_APP_BINARY_SUBDIR))
521 char *main_data_path_old = main_data_path;
523 // cut relative path to Mac OS X application binary directory from path
524 main_data_path[strlen(main_data_path) -
525 strlen(MAC_APP_BINARY_SUBDIR)] = '\0';
527 // cut trailing path separator from path (but not if path is root directory)
528 if (strSuffix(main_data_path, "/") && !strEqual(main_data_path, "/"))
529 main_data_path[strlen(main_data_path) - 1] = '\0';
531 // replace empty path with current directory
532 if (strEqual(main_data_path, ""))
533 main_data_path = ".";
535 // add relative path to Mac OS X application resources directory to path
536 main_data_path = getPath2(main_data_path, MAC_APP_FILES_SUBDIR);
538 free(main_data_path_old);
542 return main_data_path;
545 char *getProgramConfigFilename(char *command_filename)
547 static char *config_filename_1 = NULL;
548 static char *config_filename_2 = NULL;
549 static char *config_filename_3 = NULL;
550 static boolean initialized = FALSE;
554 char *command_filename_1 = getStringCopy(command_filename);
556 // strip trailing executable suffix from command filename
557 if (strSuffix(command_filename_1, ".exe"))
558 command_filename_1[strlen(command_filename_1) - 4] = '\0';
560 char *base_path = getProgramMainDataPath(command_filename, BASE_PATH);
561 char *conf_directory = getPath2(base_path, CONF_DIRECTORY);
563 char *command_basepath = getBasePath(command_filename);
564 char *command_basename = getBaseNameNoSuffix(command_filename);
565 char *command_filename_2 = getPath2(command_basepath, command_basename);
567 config_filename_1 = getStringCat2(command_filename_1, ".conf");
568 config_filename_2 = getStringCat2(command_filename_2, ".conf");
569 config_filename_3 = getPath2(conf_directory, SETUP_FILENAME);
571 checked_free(base_path);
572 checked_free(conf_directory);
574 checked_free(command_basepath);
575 checked_free(command_basename);
577 checked_free(command_filename_1);
578 checked_free(command_filename_2);
583 // 1st try: look for config file that exactly matches the binary filename
584 if (fileExists(config_filename_1))
585 return config_filename_1;
587 // 2nd try: look for config file that matches binary filename without suffix
588 if (fileExists(config_filename_2))
589 return config_filename_2;
591 // 3rd try: return setup config filename in global program config directory
592 return config_filename_3;
595 static char *getPlatformConfigFilename(char *config_filename)
597 static char *platform_config_filename = NULL;
598 static boolean initialized = FALSE;
602 char *config_basepath = getBasePath(config_filename);
603 char *config_basename = getBaseNameNoSuffix(config_filename);
604 char *config_filename_prefix = getPath2(config_basepath, config_basename);
605 char *platform_string_lower = getStringToLower(PLATFORM_STRING);
606 char *platform_suffix = getStringCat2("-", platform_string_lower);
608 platform_config_filename = getStringCat3(config_filename_prefix,
609 platform_suffix, ".conf");
611 checked_free(config_basepath);
612 checked_free(config_basename);
613 checked_free(config_filename_prefix);
614 checked_free(platform_string_lower);
615 checked_free(platform_suffix);
620 return platform_config_filename;
623 char *getTapeFilename(int nr)
625 static char *filename = NULL;
626 char basename[MAX_FILENAME_LEN];
628 checked_free(filename);
630 sprintf(basename, "%03d.%s", nr, TAPEFILE_EXTENSION);
631 filename = getPath2(getTapeDir(leveldir_current->subdir), basename);
636 char *getTemporaryTapeFilename(void)
638 static char *filename = NULL;
639 char basename[MAX_FILENAME_LEN];
641 checked_free(filename);
643 sprintf(basename, "tmp.%s", TAPEFILE_EXTENSION);
644 filename = getPath2(getTapeDir(NULL), basename);
649 char *getDefaultSolutionTapeFilename(int nr)
651 static char *filename = NULL;
652 char basename[MAX_FILENAME_LEN];
654 checked_free(filename);
656 sprintf(basename, "%03d.%s", nr, TAPEFILE_EXTENSION);
657 filename = getPath2(getSolutionTapeDir(), basename);
662 char *getSokobanSolutionTapeFilename(int nr)
664 static char *filename = NULL;
665 char basename[MAX_FILENAME_LEN];
667 checked_free(filename);
669 sprintf(basename, "%03d.sln", nr);
670 filename = getPath2(getSolutionTapeDir(), basename);
675 char *getSolutionTapeFilename(int nr)
677 char *filename = getDefaultSolutionTapeFilename(nr);
679 if (!fileExists(filename))
681 char *filename2 = getSokobanSolutionTapeFilename(nr);
683 if (fileExists(filename2))
690 char *getScoreFilename(int nr)
692 static char *filename = NULL;
693 char basename[MAX_FILENAME_LEN];
695 checked_free(filename);
697 sprintf(basename, "%03d.%s", nr, SCOREFILE_EXTENSION);
699 // used instead of "leveldir_current->subdir" (for network games)
700 filename = getPath2(getScoreDir(levelset.identifier), basename);
705 char *getScoreCacheFilename(int nr)
707 static char *filename = NULL;
708 char basename[MAX_FILENAME_LEN];
710 checked_free(filename);
712 sprintf(basename, "%03d.%s", nr, SCOREFILE_EXTENSION);
714 // used instead of "leveldir_current->subdir" (for network games)
715 filename = getPath2(getScoreCacheDir(levelset.identifier), basename);
720 char *getScoreTapeBasename(char *name)
722 static char basename[MAX_FILENAME_LEN];
723 char basename_raw[MAX_FILENAME_LEN];
726 sprintf(timestamp, "%s", getCurrentTimestamp());
727 sprintf(basename_raw, "%s-%s", timestamp, name);
728 sprintf(basename, "%s-%08x", timestamp, get_hash_from_string(basename_raw));
733 char *getScoreTapeFilename(char *basename_no_ext, int nr)
735 static char *filename = NULL;
736 char basename[MAX_FILENAME_LEN];
738 checked_free(filename);
740 sprintf(basename, "%s.%s", basename_no_ext, TAPEFILE_EXTENSION);
742 // used instead of "leveldir_current->subdir" (for network games)
743 filename = getPath2(getScoreTapeDir(levelset.identifier, nr), basename);
748 char *getScoreCacheTapeFilename(char *basename_no_ext, int nr)
750 static char *filename = NULL;
751 char basename[MAX_FILENAME_LEN];
753 checked_free(filename);
755 sprintf(basename, "%s.%s", basename_no_ext, TAPEFILE_EXTENSION);
757 // used instead of "leveldir_current->subdir" (for network games)
758 filename = getPath2(getScoreCacheTapeDir(levelset.identifier, nr), basename);
763 char *getSetupFilename(void)
765 static char *filename = NULL;
767 checked_free(filename);
769 filename = getPath2(getSetupDir(), SETUP_FILENAME);
774 char *getDefaultSetupFilename(void)
776 return program.config_filename;
779 char *getPlatformSetupFilename(void)
781 return getPlatformConfigFilename(program.config_filename);
784 char *getEditorSetupFilename(void)
786 static char *filename = NULL;
788 checked_free(filename);
789 filename = getPath2(getCurrentLevelDir(), EDITORSETUP_FILENAME);
791 if (fileExists(filename))
794 checked_free(filename);
795 filename = getPath2(getSetupDir(), EDITORSETUP_FILENAME);
800 char *getHelpAnimFilename(void)
802 static char *filename = NULL;
804 checked_free(filename);
806 filename = getPath2(getCurrentLevelDir(), HELPANIM_FILENAME);
811 char *getHelpTextFilename(void)
813 static char *filename = NULL;
815 checked_free(filename);
817 filename = getPath2(getCurrentLevelDir(), HELPTEXT_FILENAME);
822 static char *getLevelSetInfoBasename(int nr)
824 static char basename[32];
826 sprintf(basename, "levelset_%d.txt", nr + 1);
831 char *getLevelSetInfoFilename(int nr)
833 char *basename = getLevelSetInfoBasename(nr);
834 static char *info_subdir = NULL;
835 static char *filename = NULL;
837 if (info_subdir == NULL)
838 info_subdir = getPath2(DOCS_DIRECTORY, LEVELSET_INFO_DIRECTORY);
840 checked_free(filename);
842 // look for level set info file the current level set directory
843 filename = getPath3(getCurrentLevelDir(), info_subdir, basename);
844 if (fileExists(filename))
864 for (i = 0; basenames[i] != NULL; i++)
866 checked_free(filename);
867 filename = getPath2(getCurrentLevelDir(), basenames[i]);
869 if (fileExists(filename))
876 static char *getLevelSetTitleMessageBasename(int nr, boolean initial)
878 static char basename[32];
880 sprintf(basename, "%s_%d.txt",
881 (initial ? "titlemessage_initial" : "titlemessage"), nr + 1);
886 char *getLevelSetTitleMessageFilename(int nr, boolean initial)
888 static char *filename = NULL;
890 boolean skip_setup_artwork = FALSE;
892 checked_free(filename);
894 basename = getLevelSetTitleMessageBasename(nr, initial);
896 if (!gfx.override_level_graphics)
898 // 1st try: look for special artwork in current level series directory
899 filename = getPath3(getCurrentLevelDir(), GRAPHICS_DIRECTORY, basename);
900 if (fileExists(filename))
905 // 2nd try: look for message file in current level set directory
906 filename = getPath2(getCurrentLevelDir(), basename);
907 if (fileExists(filename))
912 // check if there is special artwork configured in level series config
913 if (getLevelArtworkSet(ARTWORK_TYPE_GRAPHICS) != NULL)
915 // 3rd try: look for special artwork configured in level series config
916 filename = getPath2(getLevelArtworkDir(ARTWORK_TYPE_GRAPHICS), basename);
917 if (fileExists(filename))
922 // take missing artwork configured in level set config from default
923 skip_setup_artwork = TRUE;
927 if (!skip_setup_artwork)
929 // 4th try: look for special artwork in configured artwork directory
930 filename = getPath2(getSetupArtworkDir(artwork.gfx_current), basename);
931 if (fileExists(filename))
937 // 5th try: look for default artwork in new default artwork directory
938 filename = getPath2(getDefaultGraphicsDir(GFX_DEFAULT_SUBDIR), basename);
939 if (fileExists(filename))
944 // 6th try: look for default artwork in old default artwork directory
945 filename = getPath2(options.graphics_directory, basename);
946 if (fileExists(filename))
949 return NULL; // cannot find specified artwork file anywhere
952 static char *getCreditsBasename(int nr)
954 static char basename[32];
956 sprintf(basename, "credits_%d.txt", nr + 1);
961 char *getCreditsFilename(int nr, boolean global)
963 char *basename = getCreditsBasename(nr);
964 char *basepath = NULL;
965 static char *credits_subdir = NULL;
966 static char *filename = NULL;
968 if (credits_subdir == NULL)
969 credits_subdir = getPath2(DOCS_DIRECTORY, CREDITS_DIRECTORY);
971 checked_free(filename);
973 // look for credits file in the game's base or current level set directory
974 basepath = (global ? options.base_directory : getCurrentLevelDir());
976 filename = getPath3(basepath, credits_subdir, basename);
977 if (fileExists(filename))
980 return NULL; // cannot find credits file
983 static char *getProgramInfoBasename(int nr)
985 static char basename[32];
987 sprintf(basename, "program_%d.txt", nr + 1);
992 char *getProgramInfoFilename(int nr)
994 char *basename = getProgramInfoBasename(nr);
995 static char *info_subdir = NULL;
996 static char *filename = NULL;
998 if (info_subdir == NULL)
999 info_subdir = getPath2(DOCS_DIRECTORY, PROGRAM_INFO_DIRECTORY);
1001 checked_free(filename);
1003 // look for program info file in the game's base directory
1004 filename = getPath3(options.base_directory, info_subdir, basename);
1005 if (fileExists(filename))
1008 return NULL; // cannot find program info file
1011 static char *getCorrectedArtworkBasename(char *basename)
1016 char *getCustomImageFilename(char *basename)
1018 static char *filename = NULL;
1019 boolean skip_setup_artwork = FALSE;
1021 checked_free(filename);
1023 basename = getCorrectedArtworkBasename(basename);
1025 if (!gfx.override_level_graphics)
1027 // 1st try: look for special artwork in current level series directory
1028 filename = getImg3(getCurrentLevelDir(), GRAPHICS_DIRECTORY, basename);
1029 if (fileExists(filename))
1034 // check if there is special artwork configured in level series config
1035 if (getLevelArtworkSet(ARTWORK_TYPE_GRAPHICS) != NULL)
1037 // 2nd try: look for special artwork configured in level series config
1038 filename = getImg2(getLevelArtworkDir(ARTWORK_TYPE_GRAPHICS), basename);
1039 if (fileExists(filename))
1044 // take missing artwork configured in level set config from default
1045 skip_setup_artwork = TRUE;
1049 if (!skip_setup_artwork)
1051 // 3rd try: look for special artwork in configured artwork directory
1052 filename = getImg2(getSetupArtworkDir(artwork.gfx_current), basename);
1053 if (fileExists(filename))
1059 // 4th try: look for default artwork in new default artwork directory
1060 filename = getImg2(getDefaultGraphicsDir(GFX_DEFAULT_SUBDIR), basename);
1061 if (fileExists(filename))
1066 // 5th try: look for default artwork in old default artwork directory
1067 filename = getImg2(options.graphics_directory, basename);
1068 if (fileExists(filename))
1071 if (!strEqual(GFX_FALLBACK_FILENAME, UNDEFINED_FILENAME))
1075 WarnUsingFallback(basename);
1077 // 6th try: look for fallback artwork in old default artwork directory
1078 // (needed to prevent errors when trying to access unused artwork files)
1079 filename = getImg2(options.graphics_directory, GFX_FALLBACK_FILENAME);
1080 if (fileExists(filename))
1084 return NULL; // cannot find specified artwork file anywhere
1087 char *getCustomSoundFilename(char *basename)
1089 static char *filename = NULL;
1090 boolean skip_setup_artwork = FALSE;
1092 checked_free(filename);
1094 basename = getCorrectedArtworkBasename(basename);
1096 if (!gfx.override_level_sounds)
1098 // 1st try: look for special artwork in current level series directory
1099 filename = getPath3(getCurrentLevelDir(), SOUNDS_DIRECTORY, basename);
1100 if (fileExists(filename))
1105 // check if there is special artwork configured in level series config
1106 if (getLevelArtworkSet(ARTWORK_TYPE_SOUNDS) != NULL)
1108 // 2nd try: look for special artwork configured in level series config
1109 filename = getPath2(getLevelArtworkDir(TREE_TYPE_SOUNDS_DIR), basename);
1110 if (fileExists(filename))
1115 // take missing artwork configured in level set config from default
1116 skip_setup_artwork = TRUE;
1120 if (!skip_setup_artwork)
1122 // 3rd try: look for special artwork in configured artwork directory
1123 filename = getPath2(getSetupArtworkDir(artwork.snd_current), basename);
1124 if (fileExists(filename))
1130 // 4th try: look for default artwork in new default artwork directory
1131 filename = getPath2(getDefaultSoundsDir(SND_DEFAULT_SUBDIR), basename);
1132 if (fileExists(filename))
1137 // 5th try: look for default artwork in old default artwork directory
1138 filename = getPath2(options.sounds_directory, basename);
1139 if (fileExists(filename))
1142 if (!strEqual(SND_FALLBACK_FILENAME, UNDEFINED_FILENAME))
1146 WarnUsingFallback(basename);
1148 // 6th try: look for fallback artwork in old default artwork directory
1149 // (needed to prevent errors when trying to access unused artwork files)
1150 filename = getPath2(options.sounds_directory, SND_FALLBACK_FILENAME);
1151 if (fileExists(filename))
1155 return NULL; // cannot find specified artwork file anywhere
1158 char *getCustomMusicFilename(char *basename)
1160 static char *filename = NULL;
1161 boolean skip_setup_artwork = FALSE;
1163 checked_free(filename);
1165 basename = getCorrectedArtworkBasename(basename);
1167 if (!gfx.override_level_music)
1169 // 1st try: look for special artwork in current level series directory
1170 filename = getPath3(getCurrentLevelDir(), MUSIC_DIRECTORY, basename);
1171 if (fileExists(filename))
1176 // check if there is special artwork configured in level series config
1177 if (getLevelArtworkSet(ARTWORK_TYPE_MUSIC) != NULL)
1179 // 2nd try: look for special artwork configured in level series config
1180 filename = getPath2(getLevelArtworkDir(TREE_TYPE_MUSIC_DIR), basename);
1181 if (fileExists(filename))
1186 // take missing artwork configured in level set config from default
1187 skip_setup_artwork = TRUE;
1191 if (!skip_setup_artwork)
1193 // 3rd try: look for special artwork in configured artwork directory
1194 filename = getPath2(getSetupArtworkDir(artwork.mus_current), basename);
1195 if (fileExists(filename))
1201 // 4th try: look for default artwork in new default artwork directory
1202 filename = getPath2(getDefaultMusicDir(MUS_DEFAULT_SUBDIR), basename);
1203 if (fileExists(filename))
1208 // 5th try: look for default artwork in old default artwork directory
1209 filename = getPath2(options.music_directory, basename);
1210 if (fileExists(filename))
1213 if (!strEqual(MUS_FALLBACK_FILENAME, UNDEFINED_FILENAME))
1217 WarnUsingFallback(basename);
1219 // 6th try: look for fallback artwork in old default artwork directory
1220 // (needed to prevent errors when trying to access unused artwork files)
1221 filename = getPath2(options.music_directory, MUS_FALLBACK_FILENAME);
1222 if (fileExists(filename))
1226 return NULL; // cannot find specified artwork file anywhere
1229 char *getCustomArtworkFilename(char *basename, int type)
1231 if (type == ARTWORK_TYPE_GRAPHICS)
1232 return getCustomImageFilename(basename);
1233 else if (type == ARTWORK_TYPE_SOUNDS)
1234 return getCustomSoundFilename(basename);
1235 else if (type == ARTWORK_TYPE_MUSIC)
1236 return getCustomMusicFilename(basename);
1238 return UNDEFINED_FILENAME;
1241 char *getCustomArtworkConfigFilename(int type)
1243 return getCustomArtworkFilename(ARTWORKINFO_FILENAME(type), type);
1246 char *getCustomArtworkLevelConfigFilename(int type)
1248 static char *filename = NULL;
1250 checked_free(filename);
1252 filename = getPath2(getLevelArtworkDir(type), ARTWORKINFO_FILENAME(type));
1257 static boolean directoryExists_CheckMusic(char *directory, boolean check_music)
1259 if (!directoryExists(directory))
1266 DirectoryEntry *dir_entry;
1267 int num_music = getMusicListSize();
1268 boolean music_found = FALSE;
1270 if ((dir = openDirectory(directory)) == NULL)
1273 while ((dir_entry = readDirectory(dir)) != NULL) // loop all entries
1275 char *basename = dir_entry->basename;
1276 boolean music_already_used = FALSE;
1279 // skip all music files that are configured in music config file
1280 for (i = 0; i < num_music; i++)
1282 struct FileInfo *music = getMusicListEntry(i);
1284 if (strEqual(basename, music->filename))
1286 music_already_used = TRUE;
1292 if (music_already_used)
1295 if (FileIsMusic(dir_entry->filename))
1303 closeDirectory(dir);
1308 static char *getCustomMusicDirectoryExt(boolean check_music)
1310 static char *directory = NULL;
1311 boolean skip_setup_artwork = FALSE;
1313 checked_free(directory);
1315 if (!gfx.override_level_music)
1317 // 1st try: look for special artwork in current level series directory
1318 directory = getPath2(getCurrentLevelDir(), MUSIC_DIRECTORY);
1319 if (directoryExists_CheckMusic(directory, check_music))
1324 // check if there is special artwork configured in level series config
1325 if (getLevelArtworkSet(ARTWORK_TYPE_MUSIC) != NULL)
1327 // 2nd try: look for special artwork configured in level series config
1328 directory = getStringCopy(getLevelArtworkDir(TREE_TYPE_MUSIC_DIR));
1330 // directory also valid if no unconfigured music found (no game music)
1331 if (directoryExists_CheckMusic(directory, FALSE))
1336 // take missing artwork configured in level set config from default
1337 skip_setup_artwork = TRUE;
1341 if (!skip_setup_artwork)
1343 // 3rd try: look for special artwork in configured artwork directory
1344 directory = getStringCopy(getSetupArtworkDir(artwork.mus_current));
1346 // directory also valid if no unconfigured music found (no game music)
1347 if (directoryExists_CheckMusic(directory, FALSE))
1353 // 4th try: look for default artwork in new default artwork directory
1354 directory = getStringCopy(getDefaultMusicDir(MUS_DEFAULT_SUBDIR));
1355 if (directoryExists_CheckMusic(directory, check_music))
1360 // 5th try: look for default artwork in old default artwork directory
1361 directory = getStringCopy(options.music_directory);
1362 if (directoryExists_CheckMusic(directory, check_music))
1365 return NULL; // cannot find specified artwork file anywhere
1368 char *getCustomMusicDirectory(void)
1370 return getCustomMusicDirectoryExt(FALSE);
1373 char *getCustomMusicDirectory_NoConf(void)
1375 return getCustomMusicDirectoryExt(TRUE);
1378 void MarkTapeDirectoryUploadsAsComplete(char *level_subdir)
1380 char *filename = getPath2(getTapeDir(level_subdir), UPLOADED_FILENAME);
1382 touchFile(filename);
1384 checked_free(filename);
1387 void MarkTapeDirectoryUploadsAsIncomplete(char *level_subdir)
1389 char *filename = getPath2(getTapeDir(level_subdir), UPLOADED_FILENAME);
1393 checked_free(filename);
1396 boolean CheckTapeDirectoryUploadsComplete(char *level_subdir)
1398 char *filename = getPath2(getTapeDir(level_subdir), UPLOADED_FILENAME);
1399 boolean success = fileExists(filename);
1401 checked_free(filename);
1406 void InitMissingFileHash(void)
1408 if (missing_file_hash == NULL)
1409 freeSetupFileHash(missing_file_hash);
1411 missing_file_hash = newSetupFileHash();
1414 void InitTapeDirectory(char *level_subdir)
1416 boolean new_tape_dir = !directoryExists(getTapeDir(level_subdir));
1418 createDirectory(getUserGameDataDir(), "user data");
1419 createDirectory(getTapeDir(NULL), "main tape");
1420 createDirectory(getTapeDir(level_subdir), "level tape");
1423 MarkTapeDirectoryUploadsAsComplete(level_subdir);
1426 void InitScoreDirectory(char *level_subdir)
1428 createDirectory(getMainUserGameDataDir(), "main user data");
1429 createDirectory(getScoreDir(NULL), "main score");
1430 createDirectory(getScoreDir(level_subdir), "level score");
1433 void InitScoreCacheDirectory(char *level_subdir)
1435 createDirectory(getMainUserGameDataDir(), "main user data");
1436 createDirectory(getCacheDir(), "cache data");
1437 createDirectory(getScoreCacheDir(NULL), "main score");
1438 createDirectory(getScoreCacheDir(level_subdir), "level score");
1441 void InitScoreTapeDirectory(char *level_subdir, int nr)
1443 InitScoreDirectory(level_subdir);
1445 createDirectory(getScoreTapeDir(level_subdir, nr), "score tape");
1448 void InitScoreCacheTapeDirectory(char *level_subdir, int nr)
1450 InitScoreCacheDirectory(level_subdir);
1452 createDirectory(getScoreCacheTapeDir(level_subdir, nr), "score tape");
1455 static void SaveUserLevelInfo(void);
1457 void InitUserLevelDirectory(char *level_subdir)
1459 if (!directoryExists(getUserLevelDir(level_subdir)))
1461 createDirectory(getMainUserGameDataDir(), "main user data");
1462 createDirectory(getUserLevelDir(NULL), "main user level");
1464 if (setup.internal.create_user_levelset)
1466 createDirectory(getUserLevelDir(level_subdir), "user level");
1468 SaveUserLevelInfo();
1473 void InitNetworkLevelDirectory(char *level_subdir)
1475 if (!directoryExists(getNetworkLevelDir(level_subdir)))
1477 createDirectory(getMainUserGameDataDir(), "main user data");
1478 createDirectory(getNetworkDir(), "network data");
1479 createDirectory(getNetworkLevelDir(NULL), "main network level");
1480 createDirectory(getNetworkLevelDir(level_subdir), "network level");
1484 void InitLevelSetupDirectory(char *level_subdir)
1486 createDirectory(getUserGameDataDir(), "user data");
1487 createDirectory(getLevelSetupDir(NULL), "main level setup");
1488 createDirectory(getLevelSetupDir(level_subdir), "level setup");
1491 static void InitCacheDirectory(void)
1493 createDirectory(getMainUserGameDataDir(), "main user data");
1494 createDirectory(getCacheDir(), "cache data");
1498 // ----------------------------------------------------------------------------
1499 // some functions to handle lists of level and artwork directories
1500 // ----------------------------------------------------------------------------
1502 TreeInfo *newTreeInfo(void)
1504 return checked_calloc(sizeof(TreeInfo));
1507 TreeInfo *newTreeInfo_setDefaults(int type)
1509 TreeInfo *ti = newTreeInfo();
1511 setTreeInfoToDefaults(ti, type);
1516 void pushTreeInfo(TreeInfo **node_first, TreeInfo *node_new)
1518 node_new->next = *node_first;
1519 *node_first = node_new;
1522 void removeTreeInfo(TreeInfo **node_first)
1524 TreeInfo *node_old = *node_first;
1526 *node_first = node_old->next;
1527 node_old->next = NULL;
1529 freeTreeInfo(node_old);
1532 int numTreeInfo(TreeInfo *node)
1545 boolean validLevelSeries(TreeInfo *node)
1547 // in a number of cases, tree node is no valid level set
1548 if (node == NULL || node->node_group || node->parent_link || node->is_copy)
1554 TreeInfo *getValidLevelSeries(TreeInfo *node, TreeInfo *default_node)
1556 if (validLevelSeries(node))
1558 else if (node->is_copy)
1559 return getTreeInfoFromIdentifier(leveldir_first, node->identifier);
1561 return getFirstValidTreeInfoEntry(default_node);
1564 static TreeInfo *getValidTreeInfoEntryExt(TreeInfo *node, boolean get_next_node)
1569 if (node->node_group) // enter node group (step down into tree)
1570 return getFirstValidTreeInfoEntry(node->node_group);
1572 if (node->parent_link) // skip first node (back link) of node group
1573 get_next_node = TRUE;
1575 if (!get_next_node) // get current regular tree node
1578 // get next regular tree node, or step up until one is found
1579 while (node->next == NULL && node->node_parent != NULL)
1580 node = node->node_parent;
1582 return getFirstValidTreeInfoEntry(node->next);
1585 TreeInfo *getFirstValidTreeInfoEntry(TreeInfo *node)
1587 return getValidTreeInfoEntryExt(node, FALSE);
1590 TreeInfo *getNextValidTreeInfoEntry(TreeInfo *node)
1592 return getValidTreeInfoEntryExt(node, TRUE);
1595 TreeInfo *getTreeInfoFirstGroupEntry(TreeInfo *node)
1600 if (node->node_parent == NULL) // top level group
1601 return *node->node_top;
1602 else // sub level group
1603 return node->node_parent->node_group;
1606 int numTreeInfoInGroup(TreeInfo *node)
1608 return numTreeInfo(getTreeInfoFirstGroupEntry(node));
1611 int getPosFromTreeInfo(TreeInfo *node)
1613 TreeInfo *node_cmp = getTreeInfoFirstGroupEntry(node);
1618 if (node_cmp == node)
1622 node_cmp = node_cmp->next;
1628 TreeInfo *getTreeInfoFromPos(TreeInfo *node, int pos)
1630 TreeInfo *node_default = node;
1642 return node_default;
1645 static TreeInfo *getTreeInfoFromIdentifierExt(TreeInfo *node, char *identifier,
1646 int node_type_wanted)
1648 if (identifier == NULL)
1653 if (TREE_NODE_TYPE(node) == node_type_wanted &&
1654 strEqual(identifier, node->identifier))
1657 if (node->node_group)
1659 TreeInfo *node_group = getTreeInfoFromIdentifierExt(node->node_group,
1672 TreeInfo *getTreeInfoFromIdentifier(TreeInfo *node, char *identifier)
1674 return getTreeInfoFromIdentifierExt(node, identifier, TREE_NODE_TYPE_DEFAULT);
1677 static TreeInfo *cloneTreeNode(TreeInfo **node_top, TreeInfo *node_parent,
1678 TreeInfo *node, boolean skip_sets_without_levels)
1685 if (!node->parent_link && !node->level_group &&
1686 skip_sets_without_levels && node->levels == 0)
1687 return cloneTreeNode(node_top, node_parent, node->next,
1688 skip_sets_without_levels);
1690 node_new = getTreeInfoCopy(node); // copy complete node
1692 node_new->node_top = node_top; // correct top node link
1693 node_new->node_parent = node_parent; // correct parent node link
1695 if (node->level_group)
1696 node_new->node_group = cloneTreeNode(node_top, node_new, node->node_group,
1697 skip_sets_without_levels);
1699 node_new->next = cloneTreeNode(node_top, node_parent, node->next,
1700 skip_sets_without_levels);
1705 static void cloneTree(TreeInfo **ti_new, TreeInfo *ti, boolean skip_empty_sets)
1707 TreeInfo *ti_cloned = cloneTreeNode(ti_new, NULL, ti, skip_empty_sets);
1709 *ti_new = ti_cloned;
1712 static boolean adjustTreeArtworkForEMC(char **artwork_set_1,
1713 char **artwork_set_2,
1714 char **artwork_set, boolean prefer_2)
1716 // do nothing if neither special artwork set 1 nor 2 are defined
1717 if (!*artwork_set_1 && !*artwork_set_2)
1720 boolean want_1 = (prefer_2 == FALSE);
1721 boolean want_2 = (prefer_2 == TRUE);
1722 boolean has_only_1 = (!*artwork_set && !*artwork_set_2);
1723 boolean has_only_2 = (!*artwork_set && !*artwork_set_1);
1724 char *artwork_set_new = NULL;
1726 // replace missing special artwork 1 or 2 with (optional) standard artwork
1728 if (!*artwork_set_1)
1729 setString(artwork_set_1, *artwork_set);
1731 if (!*artwork_set_2)
1732 setString(artwork_set_2, *artwork_set);
1734 // set standard artwork to either special artwork 1 or 2, as requested
1736 if (*artwork_set_1 && (want_1 || has_only_1))
1737 artwork_set_new = *artwork_set_1;
1739 if (*artwork_set_2 && (want_2 || has_only_2))
1740 artwork_set_new = *artwork_set_2;
1742 if (artwork_set_new && !strEqual(*artwork_set, artwork_set_new))
1744 setString(artwork_set, artwork_set_new);
1752 static boolean adjustTreeGraphicsForEMC(TreeInfo *node)
1754 boolean settings_changed = FALSE;
1758 settings_changed |= adjustTreeArtworkForEMC(&node->graphics_set_ecs,
1759 &node->graphics_set_aga,
1760 &node->graphics_set,
1761 setup.prefer_aga_graphics);
1762 if (node->node_group != NULL)
1763 settings_changed |= adjustTreeGraphicsForEMC(node->node_group);
1768 return settings_changed;
1771 static boolean adjustTreeSoundsForEMC(TreeInfo *node)
1773 boolean settings_changed = FALSE;
1777 settings_changed |= adjustTreeArtworkForEMC(&node->sounds_set_default,
1778 &node->sounds_set_lowpass,
1780 setup.prefer_lowpass_sounds);
1781 if (node->node_group != NULL)
1782 settings_changed |= adjustTreeSoundsForEMC(node->node_group);
1787 return settings_changed;
1790 int dumpTreeInfo(TreeInfo *node, int depth)
1792 char bullet_list[] = { '-', '*', 'o' };
1793 int num_leaf_nodes = 0;
1797 Debug("tree", "Dumping TreeInfo:");
1801 char bullet = bullet_list[depth % ARRAY_SIZE(bullet_list)];
1803 for (i = 0; i < depth * 2; i++)
1804 DebugContinued("", " ");
1806 DebugContinued("tree", "%c '%s' ['%s] [PARENT: '%s'] %s\n",
1807 bullet, node->name, node->identifier,
1808 (node->node_parent ? node->node_parent->identifier : "-"),
1809 (node->node_group ? "[GROUP]" :
1810 node->is_copy ? "[COPY]" : ""));
1812 if (!node->node_group && !node->parent_link)
1816 // use for dumping artwork info tree
1817 Debug("tree", "subdir == '%s' ['%s', '%s'] [%d])",
1818 node->subdir, node->fullpath, node->basepath, node->in_user_dir);
1821 if (node->node_group != NULL)
1822 num_leaf_nodes += dumpTreeInfo(node->node_group, depth + 1);
1828 Debug("tree", "Summary: %d leaf nodes found", num_leaf_nodes);
1830 return num_leaf_nodes;
1833 void sortTreeInfoBySortFunction(TreeInfo **node_first,
1834 int (*compare_function)(const void *,
1837 int num_nodes = numTreeInfo(*node_first);
1838 TreeInfo **sort_array;
1839 TreeInfo *node = *node_first;
1845 // allocate array for sorting structure pointers
1846 sort_array = checked_calloc(num_nodes * sizeof(TreeInfo *));
1848 // writing structure pointers to sorting array
1849 while (i < num_nodes && node) // double boundary check...
1851 sort_array[i] = node;
1857 // sorting the structure pointers in the sorting array
1858 qsort(sort_array, num_nodes, sizeof(TreeInfo *),
1861 // update the linkage of list elements with the sorted node array
1862 for (i = 0; i < num_nodes - 1; i++)
1863 sort_array[i]->next = sort_array[i + 1];
1864 sort_array[num_nodes - 1]->next = NULL;
1866 // update the linkage of the main list anchor pointer
1867 *node_first = sort_array[0];
1871 // now recursively sort the level group structures
1875 if (node->node_group != NULL)
1876 sortTreeInfoBySortFunction(&node->node_group, compare_function);
1882 void sortTreeInfo(TreeInfo **node_first)
1884 sortTreeInfoBySortFunction(node_first, compareTreeInfoEntries);
1888 // ============================================================================
1889 // some stuff from "files.c"
1890 // ============================================================================
1892 #if defined(PLATFORM_WINDOWS)
1894 #define S_IRGRP S_IRUSR
1897 #define S_IROTH S_IRUSR
1900 #define S_IWGRP S_IWUSR
1903 #define S_IWOTH S_IWUSR
1906 #define S_IXGRP S_IXUSR
1909 #define S_IXOTH S_IXUSR
1912 #define S_IRWXG (S_IRGRP | S_IWGRP | S_IXGRP)
1917 #endif // PLATFORM_WINDOWS
1919 // file permissions for newly written files
1920 #define MODE_R_ALL (S_IRUSR | S_IRGRP | S_IROTH)
1921 #define MODE_W_ALL (S_IWUSR | S_IWGRP | S_IWOTH)
1922 #define MODE_X_ALL (S_IXUSR | S_IXGRP | S_IXOTH)
1924 #define MODE_W_PRIVATE (S_IWUSR)
1925 #define MODE_W_PUBLIC_FILE (S_IWUSR | S_IWGRP)
1926 #define MODE_W_PUBLIC_DIR (S_IWUSR | S_IWGRP | S_ISGID)
1928 #define DIR_PERMS_PRIVATE (MODE_R_ALL | MODE_X_ALL | MODE_W_PRIVATE)
1929 #define DIR_PERMS_PUBLIC (MODE_R_ALL | MODE_X_ALL | MODE_W_PUBLIC_DIR)
1930 #define DIR_PERMS_PUBLIC_ALL (MODE_R_ALL | MODE_X_ALL | MODE_W_ALL)
1932 #define FILE_PERMS_PRIVATE (MODE_R_ALL | MODE_W_PRIVATE)
1933 #define FILE_PERMS_PUBLIC (MODE_R_ALL | MODE_W_PUBLIC_FILE)
1934 #define FILE_PERMS_PUBLIC_ALL (MODE_R_ALL | MODE_W_ALL)
1937 char *getHomeDir(void)
1939 static char *dir = NULL;
1941 #if defined(PLATFORM_WINDOWS)
1944 dir = checked_malloc(MAX_PATH + 1);
1946 if (!SUCCEEDED(SHGetFolderPath(NULL, CSIDL_PERSONAL, NULL, 0, dir)))
1949 #elif defined(PLATFORM_EMSCRIPTEN)
1950 dir = PERSISTENT_DIRECTORY;
1951 #elif defined(PLATFORM_UNIX)
1954 if ((dir = getenv("HOME")) == NULL)
1956 dir = getUnixHomeDir();
1959 dir = getStringCopy(dir);
1971 char *getPersonalDataDir(void)
1973 static char *personal_data_dir = NULL;
1975 #if defined(PLATFORM_MAC)
1976 if (personal_data_dir == NULL)
1977 personal_data_dir = getPath2(getHomeDir(), "Documents");
1979 if (personal_data_dir == NULL)
1980 personal_data_dir = getHomeDir();
1983 return personal_data_dir;
1986 char *getMainUserGameDataDir(void)
1988 static char *main_user_data_dir = NULL;
1990 #if defined(PLATFORM_ANDROID)
1991 if (main_user_data_dir == NULL)
1992 main_user_data_dir = (char *)(SDL_AndroidGetExternalStorageState() &
1993 SDL_ANDROID_EXTERNAL_STORAGE_WRITE ?
1994 SDL_AndroidGetExternalStoragePath() :
1995 SDL_AndroidGetInternalStoragePath());
1997 if (main_user_data_dir == NULL)
1998 main_user_data_dir = getPath2(getPersonalDataDir(),
1999 program.userdata_subdir);
2002 return main_user_data_dir;
2005 char *getUserGameDataDir(void)
2008 return getMainUserGameDataDir();
2010 return getUserDir(user.nr);
2013 char *getSetupDir(void)
2015 return getUserGameDataDir();
2018 static mode_t posix_umask(mode_t mask)
2020 #if defined(PLATFORM_UNIX)
2027 static int posix_mkdir(const char *pathname, mode_t mode)
2029 #if defined(PLATFORM_WINDOWS)
2030 return mkdir(pathname);
2032 return mkdir(pathname, mode);
2036 static boolean posix_process_running_setgid(void)
2038 #if defined(PLATFORM_UNIX)
2039 return (getgid() != getegid());
2045 void createDirectory(char *dir, char *text)
2047 if (directoryExists(dir))
2050 // leave "other" permissions in umask untouched, but ensure group parts
2051 // of USERDATA_DIR_MODE are not masked
2052 int permission_class = PERMS_PRIVATE;
2053 mode_t dir_mode = (permission_class == PERMS_PRIVATE ?
2054 DIR_PERMS_PRIVATE : DIR_PERMS_PUBLIC);
2055 mode_t last_umask = posix_umask(0);
2056 mode_t group_umask = ~(dir_mode & S_IRWXG);
2057 int running_setgid = posix_process_running_setgid();
2059 if (permission_class == PERMS_PUBLIC)
2061 // if we're setgid, protect files against "other"
2062 // else keep umask(0) to make the dir world-writable
2065 posix_umask(last_umask & group_umask);
2067 dir_mode = DIR_PERMS_PUBLIC_ALL;
2070 if (posix_mkdir(dir, dir_mode) != 0)
2071 Warn("cannot create %s directory '%s': %s", text, dir, strerror(errno));
2073 if (permission_class == PERMS_PUBLIC && !running_setgid)
2074 chmod(dir, dir_mode);
2076 posix_umask(last_umask); // restore previous umask
2079 void InitMainUserDataDirectory(void)
2081 createDirectory(getMainUserGameDataDir(), "main user data");
2084 void InitUserDataDirectory(void)
2086 createDirectory(getMainUserGameDataDir(), "main user data");
2090 createDirectory(getUserDir(-1), "users");
2091 createDirectory(getUserDir(user.nr), "user data");
2095 void SetFilePermissions(char *filename, int permission_class)
2097 int running_setgid = posix_process_running_setgid();
2098 int perms = (permission_class == PERMS_PRIVATE ?
2099 FILE_PERMS_PRIVATE : FILE_PERMS_PUBLIC);
2101 if (permission_class == PERMS_PUBLIC && !running_setgid)
2102 perms = FILE_PERMS_PUBLIC_ALL;
2104 chmod(filename, perms);
2107 void fprintFileHeader(FILE *file, char *basename)
2109 char *prefix = "# ";
2112 fprintf_line_with_prefix(file, prefix, sep1, 77);
2113 fprintf(file, "%s%s\n", prefix, basename);
2114 fprintf_line_with_prefix(file, prefix, sep1, 77);
2115 fprintf(file, "\n");
2118 int getFileVersionFromCookieString(const char *cookie)
2120 const char *ptr_cookie1, *ptr_cookie2;
2121 const char *pattern1 = "_FILE_VERSION_";
2122 const char *pattern2 = "?.?";
2123 const int len_cookie = strlen(cookie);
2124 const int len_pattern1 = strlen(pattern1);
2125 const int len_pattern2 = strlen(pattern2);
2126 const int len_pattern = len_pattern1 + len_pattern2;
2127 int version_super, version_major;
2129 if (len_cookie <= len_pattern)
2132 ptr_cookie1 = &cookie[len_cookie - len_pattern];
2133 ptr_cookie2 = &cookie[len_cookie - len_pattern2];
2135 if (strncmp(ptr_cookie1, pattern1, len_pattern1) != 0)
2138 if (ptr_cookie2[0] < '0' || ptr_cookie2[0] > '9' ||
2139 ptr_cookie2[1] != '.' ||
2140 ptr_cookie2[2] < '0' || ptr_cookie2[2] > '9')
2143 version_super = ptr_cookie2[0] - '0';
2144 version_major = ptr_cookie2[2] - '0';
2146 return VERSION_IDENT(version_super, version_major, 0, 0);
2149 boolean checkCookieString(const char *cookie, const char *template)
2151 const char *pattern = "_FILE_VERSION_?.?";
2152 const int len_cookie = strlen(cookie);
2153 const int len_template = strlen(template);
2154 const int len_pattern = strlen(pattern);
2156 if (len_cookie != len_template)
2159 if (strncmp(cookie, template, len_cookie - len_pattern) != 0)
2166 // ----------------------------------------------------------------------------
2167 // setup file list and hash handling functions
2168 // ----------------------------------------------------------------------------
2170 char *getFormattedSetupEntry(char *token, char *value)
2173 static char entry[MAX_LINE_LEN];
2175 // if value is an empty string, just return token without value
2179 // start with the token and some spaces to format output line
2180 sprintf(entry, "%s:", token);
2181 for (i = strlen(entry); i < token_value_position; i++)
2184 // continue with the token's value
2185 strcat(entry, value);
2190 SetupFileList *newSetupFileList(char *token, char *value)
2192 SetupFileList *new = checked_malloc(sizeof(SetupFileList));
2194 new->token = getStringCopy(token);
2195 new->value = getStringCopy(value);
2202 void freeSetupFileList(SetupFileList *list)
2207 checked_free(list->token);
2208 checked_free(list->value);
2211 freeSetupFileList(list->next);
2216 char *getListEntry(SetupFileList *list, char *token)
2221 if (strEqual(list->token, token))
2224 return getListEntry(list->next, token);
2227 SetupFileList *setListEntry(SetupFileList *list, char *token, char *value)
2232 if (strEqual(list->token, token))
2234 checked_free(list->value);
2236 list->value = getStringCopy(value);
2240 else if (list->next == NULL)
2241 return (list->next = newSetupFileList(token, value));
2243 return setListEntry(list->next, token, value);
2246 SetupFileList *addListEntry(SetupFileList *list, char *token, char *value)
2251 if (list->next == NULL)
2252 return (list->next = newSetupFileList(token, value));
2254 return addListEntry(list->next, token, value);
2257 #if ENABLE_UNUSED_CODE
2259 static void printSetupFileList(SetupFileList *list)
2264 Debug("setup:printSetupFileList", "token: '%s'", list->token);
2265 Debug("setup:printSetupFileList", "value: '%s'", list->value);
2267 printSetupFileList(list->next);
2273 DEFINE_HASHTABLE_INSERT(insert_hash_entry, char, char);
2274 DEFINE_HASHTABLE_SEARCH(search_hash_entry, char, char);
2275 DEFINE_HASHTABLE_CHANGE(change_hash_entry, char, char);
2276 DEFINE_HASHTABLE_REMOVE(remove_hash_entry, char, char);
2278 #define insert_hash_entry hashtable_insert
2279 #define search_hash_entry hashtable_search
2280 #define change_hash_entry hashtable_change
2281 #define remove_hash_entry hashtable_remove
2284 unsigned int get_hash_from_string(void *key)
2289 This algorithm (k=33) was first reported by Dan Bernstein many years ago in
2290 'comp.lang.c'. Another version of this algorithm (now favored by Bernstein)
2291 uses XOR: hash(i) = hash(i - 1) * 33 ^ str[i]; the magic of number 33 (why
2292 it works better than many other constants, prime or not) has never been
2293 adequately explained.
2295 If you just want to have a good hash function, and cannot wait, djb2
2296 is one of the best string hash functions i know. It has excellent
2297 distribution and speed on many different sets of keys and table sizes.
2298 You are not likely to do better with one of the "well known" functions
2299 such as PJW, K&R, etc.
2301 Ozan (oz) Yigit [http://www.cs.yorku.ca/~oz/hash.html]
2304 char *str = (char *)key;
2305 unsigned int hash = 5381;
2308 while ((c = *str++))
2309 hash = ((hash << 5) + hash) + c; // hash * 33 + c
2314 unsigned int get_hash_from_integer(void *key)
2316 unsigned int hash = PTR_TO_UINT(key);
2321 int hash_key_strings_are_equal(void *key1, void *key2)
2323 return (strEqual((char *)key1, (char *)key2));
2326 int hash_key_integers_are_equal(void *key1, void *key2)
2328 return (key1 == key2);
2331 SetupFileHash *newSetupFileHash(void)
2333 SetupFileHash *new_hash =
2334 create_hashtable(get_hash_from_string, hash_key_strings_are_equal, free, free);
2336 if (new_hash == NULL)
2337 Fail("create_hashtable() failed -- out of memory");
2342 void freeSetupFileHash(SetupFileHash *hash)
2347 hashtable_destroy(hash);
2350 char *getHashEntry(SetupFileHash *hash, char *token)
2355 return search_hash_entry(hash, token);
2358 void setHashEntry(SetupFileHash *hash, char *token, char *value)
2365 value_copy = getStringCopy(value);
2367 // change value; if it does not exist, insert it as new
2368 if (!change_hash_entry(hash, token, value_copy))
2369 if (!insert_hash_entry(hash, getStringCopy(token), value_copy))
2370 Fail("cannot insert into hash -- aborting");
2373 void removeHashEntry(SetupFileHash *hash, char *token)
2378 remove_hash_entry(hash, token);
2381 #if ENABLE_UNUSED_CODE
2383 static void printSetupFileHash(SetupFileHash *hash)
2385 BEGIN_HASH_ITERATION(hash, itr)
2387 Debug("setup:printSetupFileHash", "token: '%s'", HASH_ITERATION_TOKEN(itr));
2388 Debug("setup:printSetupFileHash", "value: '%s'", HASH_ITERATION_VALUE(itr));
2390 END_HASH_ITERATION(hash, itr)
2395 #define ALLOW_TOKEN_VALUE_SEPARATOR_BEING_WHITESPACE 1
2396 #define CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING 0
2397 #define CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH 0
2399 static boolean token_value_separator_found = FALSE;
2400 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2401 static boolean token_value_separator_warning = FALSE;
2403 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2404 static boolean token_already_exists_warning = FALSE;
2407 static boolean getTokenValueFromSetupLineExt(char *line,
2408 char **token_ptr, char **value_ptr,
2409 char *filename, char *line_raw,
2411 boolean separator_required)
2413 static char line_copy[MAX_LINE_LEN + 1], line_raw_copy[MAX_LINE_LEN + 1];
2414 char *token, *value, *line_ptr;
2416 // when externally invoked via ReadTokenValueFromLine(), copy line buffers
2417 if (line_raw == NULL)
2419 strncpy(line_copy, line, MAX_LINE_LEN);
2420 line_copy[MAX_LINE_LEN] = '\0';
2423 strcpy(line_raw_copy, line_copy);
2424 line_raw = line_raw_copy;
2427 // cut trailing comment from input line
2428 for (line_ptr = line; *line_ptr; line_ptr++)
2430 if (*line_ptr == '#')
2437 // cut trailing whitespaces from input line
2438 for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
2439 if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
2442 // ignore empty lines
2446 // cut leading whitespaces from token
2447 for (token = line; *token; token++)
2448 if (*token != ' ' && *token != '\t')
2451 // start with empty value as reliable default
2454 token_value_separator_found = FALSE;
2456 // find end of token to determine start of value
2457 for (line_ptr = token; *line_ptr; line_ptr++)
2459 // first look for an explicit token/value separator, like ':' or '='
2460 if (*line_ptr == ':' || *line_ptr == '=')
2462 *line_ptr = '\0'; // terminate token string
2463 value = line_ptr + 1; // set beginning of value
2465 token_value_separator_found = TRUE;
2471 #if ALLOW_TOKEN_VALUE_SEPARATOR_BEING_WHITESPACE
2472 // fallback: if no token/value separator found, also allow whitespaces
2473 if (!token_value_separator_found && !separator_required)
2475 for (line_ptr = token; *line_ptr; line_ptr++)
2477 if (*line_ptr == ' ' || *line_ptr == '\t')
2479 *line_ptr = '\0'; // terminate token string
2480 value = line_ptr + 1; // set beginning of value
2482 token_value_separator_found = TRUE;
2488 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2489 if (token_value_separator_found)
2491 if (!token_value_separator_warning)
2493 Debug("setup", "---");
2495 if (filename != NULL)
2497 Debug("setup", "missing token/value separator(s) in config file:");
2498 Debug("setup", "- config file: '%s'", filename);
2502 Debug("setup", "missing token/value separator(s):");
2505 token_value_separator_warning = TRUE;
2508 if (filename != NULL)
2509 Debug("setup", "- line %d: '%s'", line_nr, line_raw);
2511 Debug("setup", "- line: '%s'", line_raw);
2517 // cut trailing whitespaces from token
2518 for (line_ptr = &token[strlen(token)]; line_ptr >= token; line_ptr--)
2519 if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
2522 // cut leading whitespaces from value
2523 for (; *value; value++)
2524 if (*value != ' ' && *value != '\t')
2533 boolean getTokenValueFromSetupLine(char *line, char **token, char **value)
2535 // while the internal (old) interface does not require a token/value
2536 // separator (for downwards compatibility with existing files which
2537 // don't use them), it is mandatory for the external (new) interface
2539 return getTokenValueFromSetupLineExt(line, token, value, NULL, NULL, 0, TRUE);
2542 static boolean loadSetupFileData(void *setup_file_data, char *filename,
2543 boolean top_recursion_level, boolean is_hash)
2545 static SetupFileHash *include_filename_hash = NULL;
2546 char line[MAX_LINE_LEN], line_raw[MAX_LINE_LEN], previous_line[MAX_LINE_LEN];
2547 char *token, *value, *line_ptr;
2548 void *insert_ptr = NULL;
2549 boolean read_continued_line = FALSE;
2551 int line_nr = 0, token_count = 0, include_count = 0;
2553 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2554 token_value_separator_warning = FALSE;
2557 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2558 token_already_exists_warning = FALSE;
2561 if (!(file = openFile(filename, MODE_READ)))
2563 #if DEBUG_NO_CONFIG_FILE
2564 Debug("setup", "cannot open configuration file '%s'", filename);
2570 // use "insert pointer" to store list end for constant insertion complexity
2572 insert_ptr = setup_file_data;
2574 // on top invocation, create hash to mark included files (to prevent loops)
2575 if (top_recursion_level)
2576 include_filename_hash = newSetupFileHash();
2578 // mark this file as already included (to prevent including it again)
2579 setHashEntry(include_filename_hash, getBaseNamePtr(filename), "true");
2581 while (!checkEndOfFile(file))
2583 // read next line of input file
2584 if (!getStringFromFile(file, line, MAX_LINE_LEN))
2587 // check if line was completely read and is terminated by line break
2588 if (strlen(line) > 0 && line[strlen(line) - 1] == '\n')
2591 // cut trailing line break (this can be newline and/or carriage return)
2592 for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
2593 if ((*line_ptr == '\n' || *line_ptr == '\r') && *(line_ptr + 1) == '\0')
2596 // copy raw input line for later use (mainly debugging output)
2597 strcpy(line_raw, line);
2599 if (read_continued_line)
2601 // append new line to existing line, if there is enough space
2602 if (strlen(previous_line) + strlen(line_ptr) < MAX_LINE_LEN)
2603 strcat(previous_line, line_ptr);
2605 strcpy(line, previous_line); // copy storage buffer to line
2607 read_continued_line = FALSE;
2610 // if the last character is '\', continue at next line
2611 if (strlen(line) > 0 && line[strlen(line) - 1] == '\\')
2613 line[strlen(line) - 1] = '\0'; // cut off trailing backslash
2614 strcpy(previous_line, line); // copy line to storage buffer
2616 read_continued_line = TRUE;
2621 if (!getTokenValueFromSetupLineExt(line, &token, &value, filename,
2622 line_raw, line_nr, FALSE))
2627 if (strEqual(token, "include"))
2629 if (getHashEntry(include_filename_hash, value) == NULL)
2631 char *basepath = getBasePath(filename);
2632 char *basename = getBaseName(value);
2633 char *filename_include = getPath2(basepath, basename);
2635 loadSetupFileData(setup_file_data, filename_include, FALSE, is_hash);
2639 free(filename_include);
2645 Warn("ignoring already processed file '%s'", value);
2652 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2654 getHashEntry((SetupFileHash *)setup_file_data, token);
2656 if (old_value != NULL)
2658 if (!token_already_exists_warning)
2660 Debug("setup", "---");
2661 Debug("setup", "duplicate token(s) found in config file:");
2662 Debug("setup", "- config file: '%s'", filename);
2664 token_already_exists_warning = TRUE;
2667 Debug("setup", "- token: '%s' (in line %d)", token, line_nr);
2668 Debug("setup", " old value: '%s'", old_value);
2669 Debug("setup", " new value: '%s'", value);
2673 setHashEntry((SetupFileHash *)setup_file_data, token, value);
2677 insert_ptr = addListEntry((SetupFileList *)insert_ptr, token, value);
2687 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2688 if (token_value_separator_warning)
2689 Debug("setup", "---");
2692 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2693 if (token_already_exists_warning)
2694 Debug("setup", "---");
2697 if (token_count == 0 && include_count == 0)
2698 Warn("configuration file '%s' is empty", filename);
2700 if (top_recursion_level)
2701 freeSetupFileHash(include_filename_hash);
2706 static int compareSetupFileData(const void *object1, const void *object2)
2708 const struct ConfigInfo *entry1 = (struct ConfigInfo *)object1;
2709 const struct ConfigInfo *entry2 = (struct ConfigInfo *)object2;
2711 return strcmp(entry1->token, entry2->token);
2714 static void saveSetupFileHash(SetupFileHash *hash, char *filename)
2716 int item_count = hashtable_count(hash);
2717 int item_size = sizeof(struct ConfigInfo);
2718 struct ConfigInfo *sort_array = checked_malloc(item_count * item_size);
2722 // copy string pointers from hash to array
2723 BEGIN_HASH_ITERATION(hash, itr)
2725 sort_array[i].token = HASH_ITERATION_TOKEN(itr);
2726 sort_array[i].value = HASH_ITERATION_VALUE(itr);
2730 if (i > item_count) // should never happen
2733 END_HASH_ITERATION(hash, itr)
2735 // sort string pointers from hash in array
2736 qsort(sort_array, item_count, item_size, compareSetupFileData);
2738 if (!(file = fopen(filename, MODE_WRITE)))
2740 Warn("cannot write configuration file '%s'", filename);
2745 fprintf(file, "%s\n\n", getFormattedSetupEntry("program.version",
2746 program.version_string));
2747 for (i = 0; i < item_count; i++)
2748 fprintf(file, "%s\n", getFormattedSetupEntry(sort_array[i].token,
2749 sort_array[i].value));
2752 checked_free(sort_array);
2755 SetupFileList *loadSetupFileList(char *filename)
2757 SetupFileList *setup_file_list = newSetupFileList("", "");
2758 SetupFileList *first_valid_list_entry;
2760 if (!loadSetupFileData(setup_file_list, filename, TRUE, FALSE))
2762 freeSetupFileList(setup_file_list);
2767 first_valid_list_entry = setup_file_list->next;
2769 // free empty list header
2770 setup_file_list->next = NULL;
2771 freeSetupFileList(setup_file_list);
2773 return first_valid_list_entry;
2776 SetupFileHash *loadSetupFileHash(char *filename)
2778 SetupFileHash *setup_file_hash = newSetupFileHash();
2780 if (!loadSetupFileData(setup_file_hash, filename, TRUE, TRUE))
2782 freeSetupFileHash(setup_file_hash);
2787 return setup_file_hash;
2791 // ============================================================================
2793 // ============================================================================
2795 #define TOKEN_STR_LAST_LEVEL_SERIES "last_level_series"
2796 #define TOKEN_STR_LAST_PLAYED_MENU_USED "last_played_menu_used"
2797 #define TOKEN_STR_LAST_PLAYED_LEVEL "last_played_level"
2798 #define TOKEN_STR_HANDICAP_LEVEL "handicap_level"
2799 #define TOKEN_STR_LAST_USER "last_user"
2801 // level directory info
2802 #define LEVELINFO_TOKEN_IDENTIFIER 0
2803 #define LEVELINFO_TOKEN_NAME 1
2804 #define LEVELINFO_TOKEN_NAME_SORTING 2
2805 #define LEVELINFO_TOKEN_AUTHOR 3
2806 #define LEVELINFO_TOKEN_YEAR 4
2807 #define LEVELINFO_TOKEN_PROGRAM_TITLE 5
2808 #define LEVELINFO_TOKEN_PROGRAM_COPYRIGHT 6
2809 #define LEVELINFO_TOKEN_PROGRAM_COMPANY 7
2810 #define LEVELINFO_TOKEN_IMPORTED_FROM 8
2811 #define LEVELINFO_TOKEN_IMPORTED_BY 9
2812 #define LEVELINFO_TOKEN_TESTED_BY 10
2813 #define LEVELINFO_TOKEN_LEVELS 11
2814 #define LEVELINFO_TOKEN_FIRST_LEVEL 12
2815 #define LEVELINFO_TOKEN_SORT_PRIORITY 13
2816 #define LEVELINFO_TOKEN_LATEST_ENGINE 14
2817 #define LEVELINFO_TOKEN_LEVEL_GROUP 15
2818 #define LEVELINFO_TOKEN_READONLY 16
2819 #define LEVELINFO_TOKEN_GRAPHICS_SET_ECS 17
2820 #define LEVELINFO_TOKEN_GRAPHICS_SET_AGA 18
2821 #define LEVELINFO_TOKEN_GRAPHICS_SET 19
2822 #define LEVELINFO_TOKEN_SOUNDS_SET_DEFAULT 20
2823 #define LEVELINFO_TOKEN_SOUNDS_SET_LOWPASS 21
2824 #define LEVELINFO_TOKEN_SOUNDS_SET 22
2825 #define LEVELINFO_TOKEN_MUSIC_SET 23
2826 #define LEVELINFO_TOKEN_FILENAME 24
2827 #define LEVELINFO_TOKEN_FILETYPE 25
2828 #define LEVELINFO_TOKEN_SPECIAL_FLAGS 26
2829 #define LEVELINFO_TOKEN_EMPTY_LEVEL_NAME 27
2830 #define LEVELINFO_TOKEN_FORCE_LEVEL_NAME 28
2831 #define LEVELINFO_TOKEN_HANDICAP 29
2832 #define LEVELINFO_TOKEN_TIME_LIMIT 30
2833 #define LEVELINFO_TOKEN_SKIP_LEVELS 31
2834 #define LEVELINFO_TOKEN_ALLOW_SKIPPING_LEVELS 32
2835 #define LEVELINFO_TOKEN_USE_EMC_TILES 33
2836 #define LEVELINFO_TOKEN_INFO_SCREENS_FROM_MAIN 34
2838 #define NUM_LEVELINFO_TOKENS 35
2840 static LevelDirTree ldi;
2842 static struct TokenInfo levelinfo_tokens[] =
2844 // level directory info
2845 { TYPE_STRING, &ldi.identifier, "identifier" },
2846 { TYPE_STRING, &ldi.name, "name" },
2847 { TYPE_STRING, &ldi.name_sorting, "name_sorting" },
2848 { TYPE_STRING, &ldi.author, "author" },
2849 { TYPE_STRING, &ldi.year, "year" },
2850 { TYPE_STRING, &ldi.program_title, "program_title" },
2851 { TYPE_STRING, &ldi.program_copyright, "program_copyright" },
2852 { TYPE_STRING, &ldi.program_company, "program_company" },
2853 { TYPE_STRING, &ldi.imported_from, "imported_from" },
2854 { TYPE_STRING, &ldi.imported_by, "imported_by" },
2855 { TYPE_STRING, &ldi.tested_by, "tested_by" },
2856 { TYPE_INTEGER, &ldi.levels, "levels" },
2857 { TYPE_INTEGER, &ldi.first_level, "first_level" },
2858 { TYPE_INTEGER, &ldi.sort_priority, "sort_priority" },
2859 { TYPE_BOOLEAN, &ldi.latest_engine, "latest_engine" },
2860 { TYPE_BOOLEAN, &ldi.level_group, "level_group" },
2861 { TYPE_BOOLEAN, &ldi.readonly, "readonly" },
2862 { TYPE_STRING, &ldi.graphics_set_ecs, "graphics_set.old" },
2863 { TYPE_STRING, &ldi.graphics_set_aga, "graphics_set.new" },
2864 { TYPE_STRING, &ldi.graphics_set_ecs, "graphics_set.ecs" },
2865 { TYPE_STRING, &ldi.graphics_set_aga, "graphics_set.aga" },
2866 { TYPE_STRING, &ldi.graphics_set, "graphics_set" },
2867 { TYPE_STRING, &ldi.sounds_set_default,"sounds_set.default" },
2868 { TYPE_STRING, &ldi.sounds_set_lowpass,"sounds_set.lowpass" },
2869 { TYPE_STRING, &ldi.sounds_set, "sounds_set" },
2870 { TYPE_STRING, &ldi.music_set, "music_set" },
2871 { TYPE_STRING, &ldi.level_filename, "filename" },
2872 { TYPE_STRING, &ldi.level_filetype, "filetype" },
2873 { TYPE_STRING, &ldi.special_flags, "special_flags" },
2874 { TYPE_STRING, &ldi.empty_level_name, "empty_level_name" },
2875 { TYPE_BOOLEAN, &ldi.force_level_name, "force_level_name" },
2876 { TYPE_BOOLEAN, &ldi.handicap, "handicap" },
2877 { TYPE_BOOLEAN, &ldi.time_limit, "time_limit" },
2878 { TYPE_BOOLEAN, &ldi.skip_levels, "skip_levels" },
2879 { TYPE_BOOLEAN, &ldi.use_emc_tiles, "use_emc_tiles" },
2880 { TYPE_BOOLEAN, &ldi.info_screens_from_main, "info_screens_from_main" }
2883 static struct TokenInfo artworkinfo_tokens[] =
2885 // artwork directory info
2886 { TYPE_STRING, &ldi.identifier, "identifier" },
2887 { TYPE_STRING, &ldi.subdir, "subdir" },
2888 { TYPE_STRING, &ldi.name, "name" },
2889 { TYPE_STRING, &ldi.name_sorting, "name_sorting" },
2890 { TYPE_STRING, &ldi.author, "author" },
2891 { TYPE_STRING, &ldi.program_title, "program_title" },
2892 { TYPE_STRING, &ldi.program_copyright, "program_copyright" },
2893 { TYPE_STRING, &ldi.program_company, "program_company" },
2894 { TYPE_INTEGER, &ldi.sort_priority, "sort_priority" },
2895 { TYPE_STRING, &ldi.basepath, "basepath" },
2896 { TYPE_STRING, &ldi.fullpath, "fullpath" },
2897 { TYPE_BOOLEAN, &ldi.in_user_dir, "in_user_dir" },
2898 { TYPE_STRING, &ldi.class_desc, "class_desc" },
2903 static char *optional_tokens[] =
2906 "program_copyright",
2912 static void setTreeInfoToDefaults(TreeInfo *ti, int type)
2916 ti->node_top = (ti->type == TREE_TYPE_LEVEL_DIR ? &leveldir_first :
2917 ti->type == TREE_TYPE_GRAPHICS_DIR ? &artwork.gfx_first :
2918 ti->type == TREE_TYPE_SOUNDS_DIR ? &artwork.snd_first :
2919 ti->type == TREE_TYPE_MUSIC_DIR ? &artwork.mus_first :
2922 ti->node_parent = NULL;
2923 ti->node_group = NULL;
2930 ti->fullpath = NULL;
2931 ti->basepath = NULL;
2932 ti->identifier = NULL;
2933 ti->name = getStringCopy(ANONYMOUS_NAME);
2934 ti->name_sorting = NULL;
2935 ti->author = getStringCopy(ANONYMOUS_NAME);
2938 ti->program_title = NULL;
2939 ti->program_copyright = NULL;
2940 ti->program_company = NULL;
2942 ti->sort_priority = LEVELCLASS_UNDEFINED; // default: least priority
2943 ti->latest_engine = FALSE; // default: get from level
2944 ti->parent_link = FALSE;
2945 ti->is_copy = FALSE;
2946 ti->in_user_dir = FALSE;
2947 ti->user_defined = FALSE;
2949 ti->class_desc = NULL;
2951 ti->infotext = getStringCopy(TREE_INFOTEXT(ti->type));
2953 if (ti->type == TREE_TYPE_LEVEL_DIR)
2955 ti->imported_from = NULL;
2956 ti->imported_by = NULL;
2957 ti->tested_by = NULL;
2959 ti->graphics_set_ecs = NULL;
2960 ti->graphics_set_aga = NULL;
2961 ti->graphics_set = NULL;
2962 ti->sounds_set_default = NULL;
2963 ti->sounds_set_lowpass = NULL;
2964 ti->sounds_set = NULL;
2965 ti->music_set = NULL;
2966 ti->graphics_path = getStringCopy(UNDEFINED_FILENAME);
2967 ti->sounds_path = getStringCopy(UNDEFINED_FILENAME);
2968 ti->music_path = getStringCopy(UNDEFINED_FILENAME);
2970 ti->level_filename = NULL;
2971 ti->level_filetype = NULL;
2973 ti->special_flags = NULL;
2975 ti->empty_level_name = NULL;
2976 ti->force_level_name = FALSE;
2979 ti->first_level = 0;
2981 ti->level_group = FALSE;
2982 ti->handicap_level = 0;
2983 ti->readonly = TRUE;
2984 ti->handicap = TRUE;
2985 ti->time_limit = TRUE;
2986 ti->skip_levels = FALSE;
2988 ti->use_emc_tiles = FALSE;
2989 ti->info_screens_from_main = FALSE;
2993 static void setTreeInfoToDefaultsFromParent(TreeInfo *ti, TreeInfo *parent)
2997 Warn("setTreeInfoToDefaultsFromParent(): parent == NULL");
2999 setTreeInfoToDefaults(ti, TREE_TYPE_UNDEFINED);
3004 // copy all values from the parent structure
3006 ti->type = parent->type;
3008 ti->node_top = parent->node_top;
3009 ti->node_parent = parent;
3010 ti->node_group = NULL;
3017 ti->fullpath = NULL;
3018 ti->basepath = NULL;
3019 ti->identifier = NULL;
3020 ti->name = getStringCopy(ANONYMOUS_NAME);
3021 ti->name_sorting = NULL;
3022 ti->author = getStringCopy(parent->author);
3023 ti->year = getStringCopy(parent->year);
3025 ti->program_title = getStringCopy(parent->program_title);
3026 ti->program_copyright = getStringCopy(parent->program_copyright);
3027 ti->program_company = getStringCopy(parent->program_company);
3029 ti->sort_priority = parent->sort_priority;
3030 ti->latest_engine = parent->latest_engine;
3031 ti->parent_link = FALSE;
3032 ti->is_copy = FALSE;
3033 ti->in_user_dir = parent->in_user_dir;
3034 ti->user_defined = parent->user_defined;
3035 ti->color = parent->color;
3036 ti->class_desc = getStringCopy(parent->class_desc);
3038 ti->infotext = getStringCopy(parent->infotext);
3040 if (ti->type == TREE_TYPE_LEVEL_DIR)
3042 ti->imported_from = getStringCopy(parent->imported_from);
3043 ti->imported_by = getStringCopy(parent->imported_by);
3044 ti->tested_by = getStringCopy(parent->tested_by);
3046 ti->graphics_set_ecs = getStringCopy(parent->graphics_set_ecs);
3047 ti->graphics_set_aga = getStringCopy(parent->graphics_set_aga);
3048 ti->graphics_set = getStringCopy(parent->graphics_set);
3049 ti->sounds_set_default = getStringCopy(parent->sounds_set_default);
3050 ti->sounds_set_lowpass = getStringCopy(parent->sounds_set_lowpass);
3051 ti->sounds_set = getStringCopy(parent->sounds_set);
3052 ti->music_set = getStringCopy(parent->music_set);
3053 ti->graphics_path = getStringCopy(UNDEFINED_FILENAME);
3054 ti->sounds_path = getStringCopy(UNDEFINED_FILENAME);
3055 ti->music_path = getStringCopy(UNDEFINED_FILENAME);
3057 ti->level_filename = getStringCopy(parent->level_filename);
3058 ti->level_filetype = getStringCopy(parent->level_filetype);
3060 ti->special_flags = getStringCopy(parent->special_flags);
3062 ti->empty_level_name = getStringCopy(parent->empty_level_name);
3063 ti->force_level_name = parent->force_level_name;
3065 ti->levels = parent->levels;
3066 ti->first_level = parent->first_level;
3067 ti->last_level = parent->last_level;
3068 ti->level_group = FALSE;
3069 ti->handicap_level = parent->handicap_level;
3070 ti->readonly = parent->readonly;
3071 ti->handicap = parent->handicap;
3072 ti->time_limit = parent->time_limit;
3073 ti->skip_levels = parent->skip_levels;
3075 ti->use_emc_tiles = parent->use_emc_tiles;
3076 ti->info_screens_from_main = parent->info_screens_from_main;
3080 static TreeInfo *getTreeInfoCopy(TreeInfo *ti)
3082 TreeInfo *ti_copy = newTreeInfo();
3084 // copy all values from the original structure
3086 ti_copy->type = ti->type;
3088 ti_copy->node_top = ti->node_top;
3089 ti_copy->node_parent = ti->node_parent;
3090 ti_copy->node_group = ti->node_group;
3091 ti_copy->next = ti->next;
3093 ti_copy->cl_first = ti->cl_first;
3094 ti_copy->cl_cursor = ti->cl_cursor;
3096 ti_copy->subdir = getStringCopy(ti->subdir);
3097 ti_copy->fullpath = getStringCopy(ti->fullpath);
3098 ti_copy->basepath = getStringCopy(ti->basepath);
3099 ti_copy->identifier = getStringCopy(ti->identifier);
3100 ti_copy->name = getStringCopy(ti->name);
3101 ti_copy->name_sorting = getStringCopy(ti->name_sorting);
3102 ti_copy->author = getStringCopy(ti->author);
3103 ti_copy->year = getStringCopy(ti->year);
3105 ti_copy->program_title = getStringCopy(ti->program_title);
3106 ti_copy->program_copyright = getStringCopy(ti->program_copyright);
3107 ti_copy->program_company = getStringCopy(ti->program_company);
3109 ti_copy->imported_from = getStringCopy(ti->imported_from);
3110 ti_copy->imported_by = getStringCopy(ti->imported_by);
3111 ti_copy->tested_by = getStringCopy(ti->tested_by);
3113 ti_copy->graphics_set_ecs = getStringCopy(ti->graphics_set_ecs);
3114 ti_copy->graphics_set_aga = getStringCopy(ti->graphics_set_aga);
3115 ti_copy->graphics_set = getStringCopy(ti->graphics_set);
3116 ti_copy->sounds_set_default = getStringCopy(ti->sounds_set_default);
3117 ti_copy->sounds_set_lowpass = getStringCopy(ti->sounds_set_lowpass);
3118 ti_copy->sounds_set = getStringCopy(ti->sounds_set);
3119 ti_copy->music_set = getStringCopy(ti->music_set);
3120 ti_copy->graphics_path = getStringCopy(ti->graphics_path);
3121 ti_copy->sounds_path = getStringCopy(ti->sounds_path);
3122 ti_copy->music_path = getStringCopy(ti->music_path);
3124 ti_copy->level_filename = getStringCopy(ti->level_filename);
3125 ti_copy->level_filetype = getStringCopy(ti->level_filetype);
3127 ti_copy->special_flags = getStringCopy(ti->special_flags);
3129 ti_copy->empty_level_name = getStringCopy(ti->empty_level_name);
3130 ti_copy->force_level_name = ti->force_level_name;
3132 ti_copy->levels = ti->levels;
3133 ti_copy->first_level = ti->first_level;
3134 ti_copy->last_level = ti->last_level;
3135 ti_copy->sort_priority = ti->sort_priority;
3137 ti_copy->latest_engine = ti->latest_engine;
3139 ti_copy->level_group = ti->level_group;
3140 ti_copy->parent_link = ti->parent_link;
3141 ti_copy->is_copy = ti->is_copy;
3142 ti_copy->in_user_dir = ti->in_user_dir;
3143 ti_copy->user_defined = ti->user_defined;
3144 ti_copy->readonly = ti->readonly;
3145 ti_copy->handicap = ti->handicap;
3146 ti_copy->time_limit = ti->time_limit;
3147 ti_copy->skip_levels = ti->skip_levels;
3149 ti_copy->use_emc_tiles = ti->use_emc_tiles;
3150 ti_copy->info_screens_from_main = ti->info_screens_from_main;
3152 ti_copy->color = ti->color;
3153 ti_copy->class_desc = getStringCopy(ti->class_desc);
3154 ti_copy->handicap_level = ti->handicap_level;
3156 ti_copy->infotext = getStringCopy(ti->infotext);
3161 void freeTreeInfo(TreeInfo *ti)
3166 checked_free(ti->subdir);
3167 checked_free(ti->fullpath);
3168 checked_free(ti->basepath);
3169 checked_free(ti->identifier);
3171 checked_free(ti->name);
3172 checked_free(ti->name_sorting);
3173 checked_free(ti->author);
3174 checked_free(ti->year);
3176 checked_free(ti->program_title);
3177 checked_free(ti->program_copyright);
3178 checked_free(ti->program_company);
3180 checked_free(ti->class_desc);
3182 checked_free(ti->infotext);
3184 if (ti->type == TREE_TYPE_LEVEL_DIR)
3186 checked_free(ti->imported_from);
3187 checked_free(ti->imported_by);
3188 checked_free(ti->tested_by);
3190 checked_free(ti->graphics_set_ecs);
3191 checked_free(ti->graphics_set_aga);
3192 checked_free(ti->graphics_set);
3193 checked_free(ti->sounds_set_default);
3194 checked_free(ti->sounds_set_lowpass);
3195 checked_free(ti->sounds_set);
3196 checked_free(ti->music_set);
3198 checked_free(ti->graphics_path);
3199 checked_free(ti->sounds_path);
3200 checked_free(ti->music_path);
3202 checked_free(ti->level_filename);
3203 checked_free(ti->level_filetype);
3205 checked_free(ti->special_flags);
3208 // recursively free child node
3210 freeTreeInfo(ti->node_group);
3212 // recursively free next node
3214 freeTreeInfo(ti->next);
3219 void setSetupInfo(struct TokenInfo *token_info,
3220 int token_nr, char *token_value)
3222 int token_type = token_info[token_nr].type;
3223 void *setup_value = token_info[token_nr].value;
3225 if (token_value == NULL)
3228 // set setup field to corresponding token value
3233 *(boolean *)setup_value = get_boolean_from_string(token_value);
3236 case TYPE_SWITCH_3_STATES:
3237 *(int *)setup_value = get_switch_3_state_from_string(token_value);
3241 *(Key *)setup_value = getKeyFromKeyName(token_value);
3245 *(Key *)setup_value = getKeyFromX11KeyName(token_value);
3249 *(int *)setup_value = get_integer_from_string(token_value);
3253 checked_free(*(char **)setup_value);
3254 *(char **)setup_value = getStringCopy(token_value);
3258 *(int *)setup_value = get_player_nr_from_string(token_value);
3266 static int compareTreeInfoEntries(const void *object1, const void *object2)
3268 const TreeInfo *entry1 = *((TreeInfo **)object1);
3269 const TreeInfo *entry2 = *((TreeInfo **)object2);
3270 int tree_sorting1 = TREE_SORTING(entry1);
3271 int tree_sorting2 = TREE_SORTING(entry2);
3273 if (tree_sorting1 != tree_sorting2)
3274 return (tree_sorting1 - tree_sorting2);
3276 return strcasecmp(entry1->name_sorting, entry2->name_sorting);
3279 static TreeInfo *createParentTreeInfoNode(TreeInfo *node_parent)
3283 if (node_parent == NULL)
3286 ti_new = newTreeInfo();
3287 setTreeInfoToDefaults(ti_new, node_parent->type);
3289 ti_new->node_parent = node_parent;
3290 ti_new->parent_link = TRUE;
3292 setString(&ti_new->identifier, node_parent->identifier);
3293 setString(&ti_new->name, BACKLINK_TEXT_PARENT);
3294 setString(&ti_new->name_sorting, ti_new->name);
3296 setString(&ti_new->subdir, STRING_PARENT_DIRECTORY);
3297 setString(&ti_new->fullpath, node_parent->fullpath);
3299 ti_new->sort_priority = LEVELCLASS_PARENT;
3300 ti_new->latest_engine = node_parent->latest_engine;
3302 setString(&ti_new->class_desc, getLevelClassDescription(ti_new));
3304 pushTreeInfo(&node_parent->node_group, ti_new);
3309 static TreeInfo *createTopTreeInfoNode(TreeInfo *node_first)
3311 if (node_first == NULL)
3314 TreeInfo *ti_new = newTreeInfo();
3315 int type = node_first->type;
3317 setTreeInfoToDefaults(ti_new, type);
3319 ti_new->node_parent = NULL;
3320 ti_new->parent_link = FALSE;
3322 setString(&ti_new->identifier, "top_tree_node");
3323 setString(&ti_new->name, TREE_INFOTEXT(type));
3324 setString(&ti_new->name_sorting, ti_new->name);
3326 setString(&ti_new->subdir, STRING_TOP_DIRECTORY);
3327 setString(&ti_new->fullpath, ".");
3329 ti_new->sort_priority = LEVELCLASS_TOP;
3330 ti_new->latest_engine = node_first->latest_engine;
3332 setString(&ti_new->class_desc, TREE_INFOTEXT(type));
3334 ti_new->node_group = node_first;
3335 ti_new->level_group = TRUE;
3337 TreeInfo *ti_new2 = createParentTreeInfoNode(ti_new);
3339 setString(&ti_new2->name, TREE_BACKLINK_TEXT(type));
3340 setString(&ti_new2->name_sorting, ti_new2->name);
3345 static void setTreeInfoParentNodes(TreeInfo *node, TreeInfo *node_parent)
3349 if (node->node_group)
3350 setTreeInfoParentNodes(node->node_group, node);
3352 node->node_parent = node_parent;
3358 TreeInfo *addTopTreeInfoNode(TreeInfo *node_first)
3360 // add top tree node with back link node in previous tree
3361 node_first = createTopTreeInfoNode(node_first);
3363 // set all parent links (back links) in complete tree
3364 setTreeInfoParentNodes(node_first, NULL);
3370 // ----------------------------------------------------------------------------
3371 // functions for handling level and custom artwork info cache
3372 // ----------------------------------------------------------------------------
3374 static void LoadArtworkInfoCache(void)
3376 InitCacheDirectory();
3378 if (artworkinfo_cache_old == NULL)
3380 char *filename = getPath2(getCacheDir(), ARTWORKINFO_CACHE_FILE);
3382 // try to load artwork info hash from already existing cache file
3383 artworkinfo_cache_old = loadSetupFileHash(filename);
3385 // try to get program version that artwork info cache was written with
3386 char *version = getHashEntry(artworkinfo_cache_old, "program.version");
3388 // check program version of artwork info cache against current version
3389 if (!strEqual(version, program.version_string))
3391 freeSetupFileHash(artworkinfo_cache_old);
3393 artworkinfo_cache_old = NULL;
3396 // if no artwork info cache file was found, start with empty hash
3397 if (artworkinfo_cache_old == NULL)
3398 artworkinfo_cache_old = newSetupFileHash();
3403 if (artworkinfo_cache_new == NULL)
3404 artworkinfo_cache_new = newSetupFileHash();
3406 update_artworkinfo_cache = FALSE;
3409 static void SaveArtworkInfoCache(void)
3411 if (!update_artworkinfo_cache)
3414 char *filename = getPath2(getCacheDir(), ARTWORKINFO_CACHE_FILE);
3416 InitCacheDirectory();
3418 saveSetupFileHash(artworkinfo_cache_new, filename);
3423 static char *getCacheTokenPrefix(char *prefix1, char *prefix2)
3425 static char *prefix = NULL;
3427 checked_free(prefix);
3429 prefix = getStringCat2WithSeparator(prefix1, prefix2, ".");
3434 // (identical to above function, but separate string buffer needed -- nasty)
3435 static char *getCacheToken(char *prefix, char *suffix)
3437 static char *token = NULL;
3439 checked_free(token);
3441 token = getStringCat2WithSeparator(prefix, suffix, ".");
3446 static char *getFileTimestampString(char *filename)
3448 return getStringCopy(i_to_a(getFileTimestampEpochSeconds(filename)));
3451 static boolean modifiedFileTimestamp(char *filename, char *timestamp_string)
3453 struct stat file_status;
3455 if (timestamp_string == NULL)
3458 if (!fileExists(filename)) // file does not exist
3459 return (atoi(timestamp_string) != 0);
3461 if (stat(filename, &file_status) != 0) // cannot stat file
3464 return (file_status.st_mtime != atoi(timestamp_string));
3467 static TreeInfo *getArtworkInfoCacheEntry(LevelDirTree *level_node, int type)
3469 char *identifier = level_node->subdir;
3470 char *type_string = ARTWORK_DIRECTORY(type);
3471 char *token_prefix = getCacheTokenPrefix(type_string, identifier);
3472 char *token_main = getCacheToken(token_prefix, "CACHED");
3473 char *cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
3474 boolean cached = (cache_entry != NULL && strEqual(cache_entry, "true"));
3475 TreeInfo *artwork_info = NULL;
3477 if (!use_artworkinfo_cache)
3480 if (optional_tokens_hash == NULL)
3484 // create hash from list of optional tokens (for quick access)
3485 optional_tokens_hash = newSetupFileHash();
3486 for (i = 0; optional_tokens[i] != NULL; i++)
3487 setHashEntry(optional_tokens_hash, optional_tokens[i], "");
3494 artwork_info = newTreeInfo();
3495 setTreeInfoToDefaults(artwork_info, type);
3497 // set all structure fields according to the token/value pairs
3498 ldi = *artwork_info;
3499 for (i = 0; artworkinfo_tokens[i].type != -1; i++)
3501 char *token_suffix = artworkinfo_tokens[i].text;
3502 char *token = getCacheToken(token_prefix, token_suffix);
3503 char *value = getHashEntry(artworkinfo_cache_old, token);
3505 (getHashEntry(optional_tokens_hash, token_suffix) != NULL);
3507 setSetupInfo(artworkinfo_tokens, i, value);
3509 // check if cache entry for this item is mandatory, but missing
3510 if (value == NULL && !optional)
3512 Warn("missing cache entry '%s'", token);
3518 *artwork_info = ldi;
3523 char *filename_levelinfo = getPath2(getLevelDirFromTreeInfo(level_node),
3524 LEVELINFO_FILENAME);
3525 char *filename_artworkinfo = getPath2(getSetupArtworkDir(artwork_info),
3526 ARTWORKINFO_FILENAME(type));
3528 // check if corresponding "levelinfo.conf" file has changed
3529 token_main = getCacheToken(token_prefix, "TIMESTAMP_LEVELINFO");
3530 cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
3532 if (modifiedFileTimestamp(filename_levelinfo, cache_entry))
3535 // check if corresponding "<artworkinfo>.conf" file has changed
3536 token_main = getCacheToken(token_prefix, "TIMESTAMP_ARTWORKINFO");
3537 cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
3539 if (modifiedFileTimestamp(filename_artworkinfo, cache_entry))
3542 checked_free(filename_levelinfo);
3543 checked_free(filename_artworkinfo);
3546 if (!cached && artwork_info != NULL)
3548 freeTreeInfo(artwork_info);
3553 return artwork_info;
3556 static void setArtworkInfoCacheEntry(TreeInfo *artwork_info,
3557 LevelDirTree *level_node, int type)
3559 char *identifier = level_node->subdir;
3560 char *type_string = ARTWORK_DIRECTORY(type);
3561 char *token_prefix = getCacheTokenPrefix(type_string, identifier);
3562 char *token_main = getCacheToken(token_prefix, "CACHED");
3563 boolean set_cache_timestamps = TRUE;
3566 setHashEntry(artworkinfo_cache_new, token_main, "true");
3568 if (set_cache_timestamps)
3570 char *filename_levelinfo = getPath2(getLevelDirFromTreeInfo(level_node),
3571 LEVELINFO_FILENAME);
3572 char *filename_artworkinfo = getPath2(getSetupArtworkDir(artwork_info),
3573 ARTWORKINFO_FILENAME(type));
3574 char *timestamp_levelinfo = getFileTimestampString(filename_levelinfo);
3575 char *timestamp_artworkinfo = getFileTimestampString(filename_artworkinfo);
3577 token_main = getCacheToken(token_prefix, "TIMESTAMP_LEVELINFO");
3578 setHashEntry(artworkinfo_cache_new, token_main, timestamp_levelinfo);
3580 token_main = getCacheToken(token_prefix, "TIMESTAMP_ARTWORKINFO");
3581 setHashEntry(artworkinfo_cache_new, token_main, timestamp_artworkinfo);
3583 checked_free(filename_levelinfo);
3584 checked_free(filename_artworkinfo);
3585 checked_free(timestamp_levelinfo);
3586 checked_free(timestamp_artworkinfo);
3589 ldi = *artwork_info;
3590 for (i = 0; artworkinfo_tokens[i].type != -1; i++)
3592 char *token = getCacheToken(token_prefix, artworkinfo_tokens[i].text);
3593 char *value = getSetupValue(artworkinfo_tokens[i].type,
3594 artworkinfo_tokens[i].value);
3596 setHashEntry(artworkinfo_cache_new, token, value);
3601 // ----------------------------------------------------------------------------
3602 // functions for loading level info and custom artwork info
3603 // ----------------------------------------------------------------------------
3605 int GetZipFileTreeType(char *zip_filename)
3607 static char *top_dir_path = NULL;
3608 static char *top_dir_conf_filename[NUM_BASE_TREE_TYPES] = { NULL };
3609 static char *conf_basename[NUM_BASE_TREE_TYPES] =
3611 GRAPHICSINFO_FILENAME,
3612 SOUNDSINFO_FILENAME,
3618 checked_free(top_dir_path);
3619 top_dir_path = NULL;
3621 for (j = 0; j < NUM_BASE_TREE_TYPES; j++)
3623 checked_free(top_dir_conf_filename[j]);
3624 top_dir_conf_filename[j] = NULL;
3627 char **zip_entries = zip_list(zip_filename);
3629 // check if zip file successfully opened
3630 if (zip_entries == NULL || zip_entries[0] == NULL)
3631 return TREE_TYPE_UNDEFINED;
3633 // first zip file entry is expected to be top level directory
3634 char *top_dir = zip_entries[0];
3636 // check if valid top level directory found in zip file
3637 if (!strSuffix(top_dir, "/"))
3638 return TREE_TYPE_UNDEFINED;
3640 // get filenames of valid configuration files in top level directory
3641 for (j = 0; j < NUM_BASE_TREE_TYPES; j++)
3642 top_dir_conf_filename[j] = getStringCat2(top_dir, conf_basename[j]);
3644 int tree_type = TREE_TYPE_UNDEFINED;
3647 while (zip_entries[e] != NULL)
3649 // check if every zip file entry is below top level directory
3650 if (!strPrefix(zip_entries[e], top_dir))
3651 return TREE_TYPE_UNDEFINED;
3653 // check if this zip file entry is a valid configuration filename
3654 for (j = 0; j < NUM_BASE_TREE_TYPES; j++)
3656 if (strEqual(zip_entries[e], top_dir_conf_filename[j]))
3658 // only exactly one valid configuration file allowed
3659 if (tree_type != TREE_TYPE_UNDEFINED)
3660 return TREE_TYPE_UNDEFINED;
3672 static boolean CheckZipFileForDirectory(char *zip_filename, char *directory,
3675 static char *top_dir_path = NULL;
3676 static char *top_dir_conf_filename = NULL;
3678 checked_free(top_dir_path);
3679 checked_free(top_dir_conf_filename);
3681 top_dir_path = NULL;
3682 top_dir_conf_filename = NULL;
3684 char *conf_basename = (tree_type == TREE_TYPE_LEVEL_DIR ? LEVELINFO_FILENAME :
3685 ARTWORKINFO_FILENAME(tree_type));
3687 // check if valid configuration filename determined
3688 if (conf_basename == NULL || strEqual(conf_basename, ""))
3691 char **zip_entries = zip_list(zip_filename);
3693 // check if zip file successfully opened
3694 if (zip_entries == NULL || zip_entries[0] == NULL)
3697 // first zip file entry is expected to be top level directory
3698 char *top_dir = zip_entries[0];
3700 // check if valid top level directory found in zip file
3701 if (!strSuffix(top_dir, "/"))
3704 // get path of extracted top level directory
3705 top_dir_path = getPath2(directory, top_dir);
3707 // remove trailing directory separator from top level directory path
3708 // (required to be able to check for file and directory in next step)
3709 top_dir_path[strlen(top_dir_path) - 1] = '\0';
3711 // check if zip file's top level directory already exists in target directory
3712 if (fileExists(top_dir_path)) // (checks for file and directory)
3715 // get filename of configuration file in top level directory
3716 top_dir_conf_filename = getStringCat2(top_dir, conf_basename);
3718 boolean found_top_dir_conf_filename = FALSE;
3721 while (zip_entries[i] != NULL)
3723 // check if every zip file entry is below top level directory
3724 if (!strPrefix(zip_entries[i], top_dir))
3727 // check if this zip file entry is the configuration filename
3728 if (strEqual(zip_entries[i], top_dir_conf_filename))
3729 found_top_dir_conf_filename = TRUE;
3734 // check if valid configuration filename was found in zip file
3735 if (!found_top_dir_conf_filename)
3741 char *ExtractZipFileIntoDirectory(char *zip_filename, char *directory,
3744 boolean zip_file_valid = CheckZipFileForDirectory(zip_filename, directory,
3747 if (!zip_file_valid)
3749 Warn("zip file '%s' rejected!", zip_filename);
3754 char **zip_entries = zip_extract(zip_filename, directory);
3756 if (zip_entries == NULL)
3758 Warn("zip file '%s' could not be extracted!", zip_filename);
3763 Info("zip file '%s' successfully extracted!", zip_filename);
3765 // first zip file entry contains top level directory
3766 char *top_dir = zip_entries[0];
3768 // remove trailing directory separator from top level directory
3769 top_dir[strlen(top_dir) - 1] = '\0';
3774 static void ProcessZipFilesInDirectory(char *directory, int tree_type)
3777 DirectoryEntry *dir_entry;
3779 if ((dir = openDirectory(directory)) == NULL)
3781 // display error if directory is main "options.graphics_directory" etc.
3782 if (tree_type == TREE_TYPE_LEVEL_DIR ||
3783 directory == OPTIONS_ARTWORK_DIRECTORY(tree_type))
3784 Warn("cannot read directory '%s'", directory);
3789 while ((dir_entry = readDirectory(dir)) != NULL) // loop all entries
3791 // skip non-zip files (and also directories with zip extension)
3792 if (!strSuffixLower(dir_entry->basename, ".zip") || dir_entry->is_directory)
3795 char *zip_filename = getPath2(directory, dir_entry->basename);
3796 char *zip_filename_extracted = getStringCat2(zip_filename, ".extracted");
3797 char *zip_filename_rejected = getStringCat2(zip_filename, ".rejected");
3799 // check if zip file hasn't already been extracted or rejected
3800 if (!fileExists(zip_filename_extracted) &&
3801 !fileExists(zip_filename_rejected))
3803 char *top_dir = ExtractZipFileIntoDirectory(zip_filename, directory,
3805 char *marker_filename = (top_dir != NULL ? zip_filename_extracted :
3806 zip_filename_rejected);
3809 // create empty file to mark zip file as extracted or rejected
3810 if ((marker_file = fopen(marker_filename, MODE_WRITE)))
3811 fclose(marker_file);
3814 free(zip_filename_extracted);
3815 free(zip_filename_rejected);
3819 closeDirectory(dir);
3822 // forward declaration for recursive call by "LoadLevelInfoFromLevelDir()"
3823 static void LoadLevelInfoFromLevelDir(TreeInfo **, TreeInfo *, char *);
3825 static boolean LoadLevelInfoFromLevelConf(TreeInfo **node_first,
3826 TreeInfo *node_parent,
3827 char *level_directory,
3828 char *directory_name)
3830 char *directory_path = getPath2(level_directory, directory_name);
3831 char *filename = getPath2(directory_path, LEVELINFO_FILENAME);
3832 SetupFileHash *setup_file_hash;
3833 LevelDirTree *leveldir_new = NULL;
3836 // unless debugging, silently ignore directories without "levelinfo.conf"
3837 if (!options.debug && !fileExists(filename))
3839 free(directory_path);
3845 setup_file_hash = loadSetupFileHash(filename);
3847 if (setup_file_hash == NULL)
3849 #if DEBUG_NO_CONFIG_FILE
3850 Debug("setup", "ignoring level directory '%s'", directory_path);
3853 free(directory_path);
3859 leveldir_new = newTreeInfo();
3862 setTreeInfoToDefaultsFromParent(leveldir_new, node_parent);
3864 setTreeInfoToDefaults(leveldir_new, TREE_TYPE_LEVEL_DIR);
3866 leveldir_new->subdir = getStringCopy(directory_name);
3868 // set all structure fields according to the token/value pairs
3869 ldi = *leveldir_new;
3870 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
3871 setSetupInfo(levelinfo_tokens, i,
3872 getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
3873 *leveldir_new = ldi;
3875 if (strEqual(leveldir_new->name, ANONYMOUS_NAME))
3876 setString(&leveldir_new->name, leveldir_new->subdir);
3878 if (leveldir_new->identifier == NULL)
3879 leveldir_new->identifier = getStringCopy(leveldir_new->subdir);
3881 if (leveldir_new->name_sorting == NULL)
3882 leveldir_new->name_sorting = getStringCopy(leveldir_new->name);
3884 if (node_parent == NULL) // top level group
3886 leveldir_new->basepath = getStringCopy(level_directory);
3887 leveldir_new->fullpath = getStringCopy(leveldir_new->subdir);
3889 else // sub level group
3891 leveldir_new->basepath = getStringCopy(node_parent->basepath);
3892 leveldir_new->fullpath = getPath2(node_parent->fullpath, directory_name);
3895 leveldir_new->last_level =
3896 leveldir_new->first_level + leveldir_new->levels - 1;
3898 leveldir_new->in_user_dir =
3899 (!strEqual(leveldir_new->basepath, options.level_directory));
3901 // adjust some settings if user's private level directory was detected
3902 if (leveldir_new->sort_priority == LEVELCLASS_UNDEFINED &&
3903 leveldir_new->in_user_dir &&
3904 (strEqual(leveldir_new->subdir, getLoginName()) ||
3905 strEqual(leveldir_new->name, getLoginName()) ||
3906 strEqual(leveldir_new->author, getRealName())))
3908 leveldir_new->sort_priority = LEVELCLASS_PRIVATE_START;
3909 leveldir_new->readonly = FALSE;
3912 leveldir_new->user_defined =
3913 (leveldir_new->in_user_dir && IS_LEVELCLASS_PRIVATE(leveldir_new));
3915 setString(&leveldir_new->class_desc, getLevelClassDescription(leveldir_new));
3917 leveldir_new->handicap_level = // set handicap to default value
3918 (leveldir_new->user_defined || !leveldir_new->handicap ?
3919 leveldir_new->last_level : leveldir_new->first_level);
3921 DrawInitTextItem(leveldir_new->name);
3923 pushTreeInfo(node_first, leveldir_new);
3925 freeSetupFileHash(setup_file_hash);
3927 if (leveldir_new->level_group)
3929 // create node to link back to current level directory
3930 createParentTreeInfoNode(leveldir_new);
3932 // recursively step into sub-directory and look for more level series
3933 LoadLevelInfoFromLevelDir(&leveldir_new->node_group,
3934 leveldir_new, directory_path);
3937 free(directory_path);
3943 static void LoadLevelInfoFromLevelDir(TreeInfo **node_first,
3944 TreeInfo *node_parent,
3945 char *level_directory)
3947 // ---------- 1st stage: process any level set zip files ----------
3949 ProcessZipFilesInDirectory(level_directory, TREE_TYPE_LEVEL_DIR);
3951 // ---------- 2nd stage: check for level set directories ----------
3954 DirectoryEntry *dir_entry;
3955 boolean valid_entry_found = FALSE;
3957 if ((dir = openDirectory(level_directory)) == NULL)
3959 Warn("cannot read level directory '%s'", level_directory);
3964 while ((dir_entry = readDirectory(dir)) != NULL) // loop all entries
3966 char *directory_name = dir_entry->basename;
3967 char *directory_path = getPath2(level_directory, directory_name);
3969 // skip entries for current and parent directory
3970 if (strEqual(directory_name, ".") ||
3971 strEqual(directory_name, ".."))
3973 free(directory_path);
3978 // find out if directory entry is itself a directory
3979 if (!dir_entry->is_directory) // not a directory
3981 free(directory_path);
3986 free(directory_path);
3988 if (strEqual(directory_name, GRAPHICS_DIRECTORY) ||
3989 strEqual(directory_name, SOUNDS_DIRECTORY) ||
3990 strEqual(directory_name, MUSIC_DIRECTORY))
3993 valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
3998 closeDirectory(dir);
4000 // special case: top level directory may directly contain "levelinfo.conf"
4001 if (node_parent == NULL && !valid_entry_found)
4003 // check if this directory directly contains a file "levelinfo.conf"
4004 valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
4005 level_directory, ".");
4008 boolean valid_entry_expected =
4009 (strEqual(level_directory, options.level_directory) ||
4010 setup.internal.create_user_levelset);
4012 if (valid_entry_expected && !valid_entry_found)
4013 Warn("cannot find any valid level series in directory '%s'",
4017 boolean AdjustGraphicsForEMC(void)
4019 boolean settings_changed = FALSE;
4021 settings_changed |= adjustTreeGraphicsForEMC(leveldir_first_all);
4022 settings_changed |= adjustTreeGraphicsForEMC(leveldir_first);
4024 return settings_changed;
4027 boolean AdjustSoundsForEMC(void)
4029 boolean settings_changed = FALSE;
4031 settings_changed |= adjustTreeSoundsForEMC(leveldir_first_all);
4032 settings_changed |= adjustTreeSoundsForEMC(leveldir_first);
4034 return settings_changed;
4037 void LoadLevelInfo(void)
4039 InitUserLevelDirectory(getLoginName());
4041 DrawInitTextHead("Loading level series");
4043 LoadLevelInfoFromLevelDir(&leveldir_first, NULL, options.level_directory);
4044 LoadLevelInfoFromLevelDir(&leveldir_first, NULL, getUserLevelDir(NULL));
4046 leveldir_first = createTopTreeInfoNode(leveldir_first);
4048 /* after loading all level set information, clone the level directory tree
4049 and remove all level sets without levels (these may still contain artwork
4050 to be offered in the setup menu as "custom artwork", and are therefore
4051 checked for existing artwork in the function "LoadLevelArtworkInfo()") */
4052 leveldir_first_all = leveldir_first;
4053 cloneTree(&leveldir_first, leveldir_first_all, TRUE);
4055 AdjustGraphicsForEMC();
4056 AdjustSoundsForEMC();
4058 // before sorting, the first entries will be from the user directory
4059 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
4061 if (leveldir_first == NULL)
4062 Fail("cannot find any valid level series in any directory");
4064 sortTreeInfo(&leveldir_first);
4066 #if ENABLE_UNUSED_CODE
4067 dumpTreeInfo(leveldir_first, 0);
4071 static boolean LoadArtworkInfoFromArtworkConf(TreeInfo **node_first,
4072 TreeInfo *node_parent,
4073 char *base_directory,
4074 char *directory_name, int type)
4076 char *directory_path = getPath2(base_directory, directory_name);
4077 char *filename = getPath2(directory_path, ARTWORKINFO_FILENAME(type));
4078 SetupFileHash *setup_file_hash = NULL;
4079 TreeInfo *artwork_new = NULL;
4082 if (fileExists(filename))
4083 setup_file_hash = loadSetupFileHash(filename);
4085 if (setup_file_hash == NULL) // no config file -- look for artwork files
4088 DirectoryEntry *dir_entry;
4089 boolean valid_file_found = FALSE;
4091 if ((dir = openDirectory(directory_path)) != NULL)
4093 while ((dir_entry = readDirectory(dir)) != NULL)
4095 if (FileIsArtworkType(dir_entry->filename, type))
4097 valid_file_found = TRUE;
4103 closeDirectory(dir);
4106 if (!valid_file_found)
4108 #if DEBUG_NO_CONFIG_FILE
4109 if (!strEqual(directory_name, "."))
4110 Debug("setup", "ignoring artwork directory '%s'", directory_path);
4113 free(directory_path);
4120 artwork_new = newTreeInfo();
4123 setTreeInfoToDefaultsFromParent(artwork_new, node_parent);
4125 setTreeInfoToDefaults(artwork_new, type);
4127 artwork_new->subdir = getStringCopy(directory_name);
4129 if (setup_file_hash) // (before defining ".color" and ".class_desc")
4131 // set all structure fields according to the token/value pairs
4133 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
4134 setSetupInfo(levelinfo_tokens, i,
4135 getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
4138 if (strEqual(artwork_new->name, ANONYMOUS_NAME))
4139 setString(&artwork_new->name, artwork_new->subdir);
4141 if (artwork_new->identifier == NULL)
4142 artwork_new->identifier = getStringCopy(artwork_new->subdir);
4144 if (artwork_new->name_sorting == NULL)
4145 artwork_new->name_sorting = getStringCopy(artwork_new->name);
4148 if (node_parent == NULL) // top level group
4150 artwork_new->basepath = getStringCopy(base_directory);
4151 artwork_new->fullpath = getStringCopy(artwork_new->subdir);
4153 else // sub level group
4155 artwork_new->basepath = getStringCopy(node_parent->basepath);
4156 artwork_new->fullpath = getPath2(node_parent->fullpath, directory_name);
4159 artwork_new->in_user_dir =
4160 (!strEqual(artwork_new->basepath, OPTIONS_ARTWORK_DIRECTORY(type)));
4162 setString(&artwork_new->class_desc, getLevelClassDescription(artwork_new));
4164 if (setup_file_hash == NULL) // (after determining ".user_defined")
4166 if (strEqual(artwork_new->subdir, "."))
4168 if (artwork_new->user_defined)
4170 setString(&artwork_new->identifier, "private");
4171 artwork_new->sort_priority = ARTWORKCLASS_PRIVATE;
4175 setString(&artwork_new->identifier, "classic");
4176 artwork_new->sort_priority = ARTWORKCLASS_CLASSICS;
4179 setString(&artwork_new->class_desc,
4180 getLevelClassDescription(artwork_new));
4184 setString(&artwork_new->identifier, artwork_new->subdir);
4187 setString(&artwork_new->name, artwork_new->identifier);
4188 setString(&artwork_new->name_sorting, artwork_new->name);
4191 pushTreeInfo(node_first, artwork_new);
4193 freeSetupFileHash(setup_file_hash);
4195 free(directory_path);
4201 static void LoadArtworkInfoFromArtworkDir(TreeInfo **node_first,
4202 TreeInfo *node_parent,
4203 char *base_directory, int type)
4205 // ---------- 1st stage: process any artwork set zip files ----------
4207 ProcessZipFilesInDirectory(base_directory, type);
4209 // ---------- 2nd stage: check for artwork set directories ----------
4212 DirectoryEntry *dir_entry;
4213 boolean valid_entry_found = FALSE;
4215 if ((dir = openDirectory(base_directory)) == NULL)
4217 // display error if directory is main "options.graphics_directory" etc.
4218 if (base_directory == OPTIONS_ARTWORK_DIRECTORY(type))
4219 Warn("cannot read directory '%s'", base_directory);
4224 while ((dir_entry = readDirectory(dir)) != NULL) // loop all entries
4226 char *directory_name = dir_entry->basename;
4227 char *directory_path = getPath2(base_directory, directory_name);
4229 // skip directory entries for current and parent directory
4230 if (strEqual(directory_name, ".") ||
4231 strEqual(directory_name, ".."))
4233 free(directory_path);
4238 // skip directory entries which are not a directory
4239 if (!dir_entry->is_directory) // not a directory
4241 free(directory_path);
4246 free(directory_path);
4248 // check if this directory contains artwork with or without config file
4249 valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
4251 directory_name, type);
4254 closeDirectory(dir);
4256 // check if this directory directly contains artwork itself
4257 valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
4258 base_directory, ".",
4260 if (!valid_entry_found)
4261 Warn("cannot find any valid artwork in directory '%s'", base_directory);
4264 static TreeInfo *getDummyArtworkInfo(int type)
4266 // this is only needed when there is completely no artwork available
4267 TreeInfo *artwork_new = newTreeInfo();
4269 setTreeInfoToDefaults(artwork_new, type);
4271 setString(&artwork_new->subdir, UNDEFINED_FILENAME);
4272 setString(&artwork_new->fullpath, UNDEFINED_FILENAME);
4273 setString(&artwork_new->basepath, UNDEFINED_FILENAME);
4275 setString(&artwork_new->identifier, UNDEFINED_FILENAME);
4276 setString(&artwork_new->name, UNDEFINED_FILENAME);
4277 setString(&artwork_new->name_sorting, UNDEFINED_FILENAME);
4282 void SetCurrentArtwork(int type)
4284 ArtworkDirTree **current_ptr = ARTWORK_CURRENT_PTR(artwork, type);
4285 ArtworkDirTree *first_node = ARTWORK_FIRST_NODE(artwork, type);
4286 char *setup_set = SETUP_ARTWORK_SET(setup, type);
4287 char *default_subdir = ARTWORK_DEFAULT_SUBDIR(type);
4289 // set current artwork to artwork configured in setup menu
4290 *current_ptr = getTreeInfoFromIdentifier(first_node, setup_set);
4292 // if not found, set current artwork to default artwork
4293 if (*current_ptr == NULL)
4294 *current_ptr = getTreeInfoFromIdentifier(first_node, default_subdir);
4296 // if not found, set current artwork to first artwork in tree
4297 if (*current_ptr == NULL)
4298 *current_ptr = getFirstValidTreeInfoEntry(first_node);
4301 void ChangeCurrentArtworkIfNeeded(int type)
4303 char *current_identifier = ARTWORK_CURRENT_IDENTIFIER(artwork, type);
4304 char *setup_set = SETUP_ARTWORK_SET(setup, type);
4306 if (!strEqual(current_identifier, setup_set))
4307 SetCurrentArtwork(type);
4310 void LoadArtworkInfo(void)
4312 LoadArtworkInfoCache();
4314 DrawInitTextHead("Looking for custom artwork");
4316 LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
4317 options.graphics_directory,
4318 TREE_TYPE_GRAPHICS_DIR);
4319 LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
4320 getUserGraphicsDir(),
4321 TREE_TYPE_GRAPHICS_DIR);
4323 LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
4324 options.sounds_directory,
4325 TREE_TYPE_SOUNDS_DIR);
4326 LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
4328 TREE_TYPE_SOUNDS_DIR);
4330 LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
4331 options.music_directory,
4332 TREE_TYPE_MUSIC_DIR);
4333 LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
4335 TREE_TYPE_MUSIC_DIR);
4337 if (artwork.gfx_first == NULL)
4338 artwork.gfx_first = getDummyArtworkInfo(TREE_TYPE_GRAPHICS_DIR);
4339 if (artwork.snd_first == NULL)
4340 artwork.snd_first = getDummyArtworkInfo(TREE_TYPE_SOUNDS_DIR);
4341 if (artwork.mus_first == NULL)
4342 artwork.mus_first = getDummyArtworkInfo(TREE_TYPE_MUSIC_DIR);
4344 // before sorting, the first entries will be from the user directory
4345 SetCurrentArtwork(ARTWORK_TYPE_GRAPHICS);
4346 SetCurrentArtwork(ARTWORK_TYPE_SOUNDS);
4347 SetCurrentArtwork(ARTWORK_TYPE_MUSIC);
4349 artwork.gfx_current_identifier = artwork.gfx_current->identifier;
4350 artwork.snd_current_identifier = artwork.snd_current->identifier;
4351 artwork.mus_current_identifier = artwork.mus_current->identifier;
4353 #if ENABLE_UNUSED_CODE
4354 Debug("setup:LoadArtworkInfo", "graphics set == %s",
4355 artwork.gfx_current_identifier);
4356 Debug("setup:LoadArtworkInfo", "sounds set == %s",
4357 artwork.snd_current_identifier);
4358 Debug("setup:LoadArtworkInfo", "music set == %s",
4359 artwork.mus_current_identifier);
4362 sortTreeInfo(&artwork.gfx_first);
4363 sortTreeInfo(&artwork.snd_first);
4364 sortTreeInfo(&artwork.mus_first);
4366 #if ENABLE_UNUSED_CODE
4367 dumpTreeInfo(artwork.gfx_first, 0);
4368 dumpTreeInfo(artwork.snd_first, 0);
4369 dumpTreeInfo(artwork.mus_first, 0);
4373 static void MoveArtworkInfoIntoSubTree(ArtworkDirTree **artwork_node)
4375 ArtworkDirTree *artwork_new = newTreeInfo();
4376 char *top_node_name = "standalone artwork";
4378 setTreeInfoToDefaults(artwork_new, (*artwork_node)->type);
4380 artwork_new->level_group = TRUE;
4382 setString(&artwork_new->identifier, top_node_name);
4383 setString(&artwork_new->name, top_node_name);
4384 setString(&artwork_new->name_sorting, top_node_name);
4386 // create node to link back to current custom artwork directory
4387 createParentTreeInfoNode(artwork_new);
4389 // move existing custom artwork tree into newly created sub-tree
4390 artwork_new->node_group->next = *artwork_node;
4392 // change custom artwork tree to contain only newly created node
4393 *artwork_node = artwork_new;
4396 static void LoadArtworkInfoFromLevelInfoExt(ArtworkDirTree **artwork_node,
4397 ArtworkDirTree *node_parent,
4398 LevelDirTree *level_node,
4399 boolean empty_level_set_mode)
4401 int type = (*artwork_node)->type;
4403 // recursively check all level directories for artwork sub-directories
4407 boolean empty_level_set = (level_node->levels == 0);
4409 // check all tree entries for artwork, but skip parent link entries
4410 if (!level_node->parent_link && empty_level_set == empty_level_set_mode)
4412 TreeInfo *artwork_new = getArtworkInfoCacheEntry(level_node, type);
4413 boolean cached = (artwork_new != NULL);
4417 pushTreeInfo(artwork_node, artwork_new);
4421 TreeInfo *topnode_last = *artwork_node;
4422 char *path = getPath2(getLevelDirFromTreeInfo(level_node),
4423 ARTWORK_DIRECTORY(type));
4425 LoadArtworkInfoFromArtworkDir(artwork_node, NULL, path, type);
4427 if (topnode_last != *artwork_node) // check for newly added node
4429 artwork_new = *artwork_node;
4431 setString(&artwork_new->identifier, level_node->subdir);
4432 setString(&artwork_new->name, level_node->name);
4433 setString(&artwork_new->name_sorting, level_node->name_sorting);
4435 artwork_new->sort_priority = level_node->sort_priority;
4436 artwork_new->in_user_dir = level_node->in_user_dir;
4438 update_artworkinfo_cache = TRUE;
4444 // insert artwork info (from old cache or filesystem) into new cache
4445 if (artwork_new != NULL)
4446 setArtworkInfoCacheEntry(artwork_new, level_node, type);
4449 DrawInitTextItem(level_node->name);
4451 if (level_node->node_group != NULL)
4453 TreeInfo *artwork_new = newTreeInfo();
4456 setTreeInfoToDefaultsFromParent(artwork_new, node_parent);
4458 setTreeInfoToDefaults(artwork_new, type);
4460 artwork_new->level_group = TRUE;
4462 setString(&artwork_new->identifier, level_node->subdir);
4464 if (node_parent == NULL) // check for top tree node
4466 char *top_node_name = (empty_level_set_mode ?
4467 "artwork for certain level sets" :
4468 "artwork included in level sets");
4470 setString(&artwork_new->name, top_node_name);
4471 setString(&artwork_new->name_sorting, top_node_name);
4475 setString(&artwork_new->name, level_node->name);
4476 setString(&artwork_new->name_sorting, level_node->name_sorting);
4479 pushTreeInfo(artwork_node, artwork_new);
4481 // create node to link back to current custom artwork directory
4482 createParentTreeInfoNode(artwork_new);
4484 // recursively step into sub-directory and look for more custom artwork
4485 LoadArtworkInfoFromLevelInfoExt(&artwork_new->node_group, artwork_new,
4486 level_node->node_group,
4487 empty_level_set_mode);
4489 // if sub-tree has no custom artwork at all, remove it
4490 if (artwork_new->node_group->next == NULL)
4491 removeTreeInfo(artwork_node);
4494 level_node = level_node->next;
4498 static void LoadArtworkInfoFromLevelInfo(ArtworkDirTree **artwork_node)
4500 // move peviously loaded artwork tree into separate sub-tree
4501 MoveArtworkInfoIntoSubTree(artwork_node);
4503 // load artwork from level sets into separate sub-trees
4504 LoadArtworkInfoFromLevelInfoExt(artwork_node, NULL, leveldir_first_all, TRUE);
4505 LoadArtworkInfoFromLevelInfoExt(artwork_node, NULL, leveldir_first_all, FALSE);
4507 // add top tree node over all sub-trees and set parent links
4508 *artwork_node = addTopTreeInfoNode(*artwork_node);
4511 void LoadLevelArtworkInfo(void)
4513 print_timestamp_init("LoadLevelArtworkInfo");
4515 DrawInitTextHead("Looking for custom level artwork");
4517 print_timestamp_time("DrawTimeText");
4519 LoadArtworkInfoFromLevelInfo(&artwork.gfx_first);
4520 print_timestamp_time("LoadArtworkInfoFromLevelInfo (gfx)");
4521 LoadArtworkInfoFromLevelInfo(&artwork.snd_first);
4522 print_timestamp_time("LoadArtworkInfoFromLevelInfo (snd)");
4523 LoadArtworkInfoFromLevelInfo(&artwork.mus_first);
4524 print_timestamp_time("LoadArtworkInfoFromLevelInfo (mus)");
4526 SaveArtworkInfoCache();
4528 print_timestamp_time("SaveArtworkInfoCache");
4530 // needed for reloading level artwork not known at ealier stage
4531 ChangeCurrentArtworkIfNeeded(ARTWORK_TYPE_GRAPHICS);
4532 ChangeCurrentArtworkIfNeeded(ARTWORK_TYPE_SOUNDS);
4533 ChangeCurrentArtworkIfNeeded(ARTWORK_TYPE_MUSIC);
4535 print_timestamp_time("getTreeInfoFromIdentifier");
4537 sortTreeInfo(&artwork.gfx_first);
4538 sortTreeInfo(&artwork.snd_first);
4539 sortTreeInfo(&artwork.mus_first);
4541 print_timestamp_time("sortTreeInfo");
4543 #if ENABLE_UNUSED_CODE
4544 dumpTreeInfo(artwork.gfx_first, 0);
4545 dumpTreeInfo(artwork.snd_first, 0);
4546 dumpTreeInfo(artwork.mus_first, 0);
4549 print_timestamp_done("LoadLevelArtworkInfo");
4552 static boolean AddTreeSetToTreeInfoExt(TreeInfo *tree_node_old, char *tree_dir,
4553 char *tree_subdir_new, int type)
4555 if (tree_node_old == NULL)
4557 if (type == TREE_TYPE_LEVEL_DIR)
4559 // get level info tree node of personal user level set
4560 tree_node_old = getTreeInfoFromIdentifier(leveldir_first, getLoginName());
4562 // this may happen if "setup.internal.create_user_levelset" is FALSE
4563 // or if file "levelinfo.conf" is missing in personal user level set
4564 if (tree_node_old == NULL)
4565 tree_node_old = leveldir_first->node_group;
4569 // get artwork info tree node of first artwork set
4570 tree_node_old = ARTWORK_FIRST_NODE(artwork, type);
4574 if (tree_dir == NULL)
4575 tree_dir = TREE_USERDIR(type);
4577 if (tree_node_old == NULL ||
4579 tree_subdir_new == NULL) // should not happen
4582 int draw_deactivation_mask = GetDrawDeactivationMask();
4584 // override draw deactivation mask (temporarily disable drawing)
4585 SetDrawDeactivationMask(REDRAW_ALL);
4587 if (type == TREE_TYPE_LEVEL_DIR)
4589 // load new level set config and add it next to first user level set
4590 LoadLevelInfoFromLevelConf(&tree_node_old->next,
4591 tree_node_old->node_parent,
4592 tree_dir, tree_subdir_new);
4596 // load new artwork set config and add it next to first artwork set
4597 LoadArtworkInfoFromArtworkConf(&tree_node_old->next,
4598 tree_node_old->node_parent,
4599 tree_dir, tree_subdir_new, type);
4602 // set draw deactivation mask to previous value
4603 SetDrawDeactivationMask(draw_deactivation_mask);
4605 // get first node of level or artwork info tree
4606 TreeInfo **tree_node_first = TREE_FIRST_NODE_PTR(type);
4608 // get tree info node of newly added level or artwork set
4609 TreeInfo *tree_node_new = getTreeInfoFromIdentifier(*tree_node_first,
4612 // if not found, check if added node is level group or artwork group
4613 if (tree_node_new == NULL)
4614 tree_node_new = getTreeInfoFromIdentifierExt(*tree_node_first,
4616 TREE_NODE_TYPE_GROUP);
4618 if (tree_node_new == NULL) // should not happen
4621 // correct top link and parent node link of newly created tree node
4622 tree_node_new->node_top = tree_node_old->node_top;
4623 tree_node_new->node_parent = tree_node_old->node_parent;
4625 // sort tree info to adjust position of newly added tree set
4626 sortTreeInfo(tree_node_first);
4631 void AddTreeSetToTreeInfo(TreeInfo *tree_node, char *tree_dir,
4632 char *tree_subdir_new, int type)
4634 if (!AddTreeSetToTreeInfoExt(tree_node, tree_dir, tree_subdir_new, type))
4635 Fail("internal tree info structure corrupted -- aborting");
4638 void AddUserLevelSetToLevelInfo(char *level_subdir_new)
4640 AddTreeSetToTreeInfo(NULL, NULL, level_subdir_new, TREE_TYPE_LEVEL_DIR);
4643 char *getArtworkIdentifierForUserLevelSet(int type)
4645 char *classic_artwork_set = getClassicArtworkSet(type);
4647 // check for custom artwork configured in "levelinfo.conf"
4648 char *leveldir_artwork_set =
4649 *LEVELDIR_ARTWORK_SET_PTR(leveldir_current, type);
4650 boolean has_leveldir_artwork_set =
4651 (leveldir_artwork_set != NULL && !strEqual(leveldir_artwork_set,
4652 classic_artwork_set));
4654 // check for custom artwork in sub-directory "graphics" etc.
4655 TreeInfo *artwork_first_node = ARTWORK_FIRST_NODE(artwork, type);
4656 char *leveldir_identifier = leveldir_current->identifier;
4657 boolean has_artwork_subdir =
4658 (getTreeInfoFromIdentifier(artwork_first_node,
4659 leveldir_identifier) != NULL);
4661 return (has_leveldir_artwork_set ? leveldir_artwork_set :
4662 has_artwork_subdir ? leveldir_identifier :
4663 classic_artwork_set);
4666 TreeInfo *getArtworkTreeInfoForUserLevelSet(int type)
4668 char *artwork_set = getArtworkIdentifierForUserLevelSet(type);
4669 TreeInfo *artwork_first_node = ARTWORK_FIRST_NODE(artwork, type);
4670 TreeInfo *ti = getTreeInfoFromIdentifier(artwork_first_node, artwork_set);
4674 ti = getTreeInfoFromIdentifier(artwork_first_node,
4675 ARTWORK_DEFAULT_SUBDIR(type));
4677 Fail("cannot find default graphics -- should not happen");
4683 boolean checkIfCustomArtworkExistsForCurrentLevelSet(void)
4685 char *graphics_set =
4686 getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_GRAPHICS);
4688 getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_SOUNDS);
4690 getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_MUSIC);
4692 return (!strEqual(graphics_set, GFX_CLASSIC_SUBDIR) ||
4693 !strEqual(sounds_set, SND_CLASSIC_SUBDIR) ||
4694 !strEqual(music_set, MUS_CLASSIC_SUBDIR));
4697 boolean UpdateUserLevelSet(char *level_subdir, char *level_name,
4698 char *level_author, int num_levels)
4700 char *filename = getPath2(getUserLevelDir(level_subdir), LEVELINFO_FILENAME);
4701 char *filename_tmp = getStringCat2(filename, ".tmp");
4703 FILE *file_tmp = NULL;
4704 char line[MAX_LINE_LEN];
4705 boolean success = FALSE;
4706 LevelDirTree *leveldir = getTreeInfoFromIdentifier(leveldir_first,
4708 // update values in level directory tree
4710 if (level_name != NULL)
4711 setString(&leveldir->name, level_name);
4713 if (level_author != NULL)
4714 setString(&leveldir->author, level_author);
4716 if (num_levels != -1)
4717 leveldir->levels = num_levels;
4719 // update values that depend on other values
4721 setString(&leveldir->name_sorting, leveldir->name);
4723 leveldir->last_level = leveldir->first_level + leveldir->levels - 1;
4725 // sort order of level sets may have changed
4726 sortTreeInfo(&leveldir_first);
4728 if ((file = fopen(filename, MODE_READ)) &&
4729 (file_tmp = fopen(filename_tmp, MODE_WRITE)))
4731 while (fgets(line, MAX_LINE_LEN, file))
4733 if (strPrefix(line, "name:") && level_name != NULL)
4734 fprintf(file_tmp, "%-32s%s\n", "name:", level_name);
4735 else if (strPrefix(line, "author:") && level_author != NULL)
4736 fprintf(file_tmp, "%-32s%s\n", "author:", level_author);
4737 else if (strPrefix(line, "levels:") && num_levels != -1)
4738 fprintf(file_tmp, "%-32s%d\n", "levels:", num_levels);
4740 fputs(line, file_tmp);
4753 success = (rename(filename_tmp, filename) == 0);
4761 boolean CreateUserLevelSet(char *level_subdir, char *level_name,
4762 char *level_author, int num_levels,
4763 boolean use_artwork_set)
4765 LevelDirTree *level_info;
4770 // create user level sub-directory, if needed
4771 createDirectory(getUserLevelDir(level_subdir), "user level");
4773 filename = getPath2(getUserLevelDir(level_subdir), LEVELINFO_FILENAME);
4775 if (!(file = fopen(filename, MODE_WRITE)))
4777 Warn("cannot write level info file '%s'", filename);
4784 level_info = newTreeInfo();
4786 // always start with reliable default values
4787 setTreeInfoToDefaults(level_info, TREE_TYPE_LEVEL_DIR);
4789 setString(&level_info->name, level_name);
4790 setString(&level_info->author, level_author);
4791 level_info->levels = num_levels;
4792 level_info->first_level = 1;
4793 level_info->sort_priority = LEVELCLASS_PRIVATE_START;
4794 level_info->readonly = FALSE;
4796 if (use_artwork_set)
4798 level_info->graphics_set =
4799 getStringCopy(getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_GRAPHICS));
4800 level_info->sounds_set =
4801 getStringCopy(getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_SOUNDS));
4802 level_info->music_set =
4803 getStringCopy(getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_MUSIC));
4806 token_value_position = TOKEN_VALUE_POSITION_SHORT;
4808 fprintFileHeader(file, LEVELINFO_FILENAME);
4811 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
4813 if (i == LEVELINFO_TOKEN_NAME ||
4814 i == LEVELINFO_TOKEN_AUTHOR ||
4815 i == LEVELINFO_TOKEN_LEVELS ||
4816 i == LEVELINFO_TOKEN_FIRST_LEVEL ||
4817 i == LEVELINFO_TOKEN_SORT_PRIORITY ||
4818 i == LEVELINFO_TOKEN_READONLY ||
4819 (use_artwork_set && (i == LEVELINFO_TOKEN_GRAPHICS_SET ||
4820 i == LEVELINFO_TOKEN_SOUNDS_SET ||
4821 i == LEVELINFO_TOKEN_MUSIC_SET)))
4822 fprintf(file, "%s\n", getSetupLine(levelinfo_tokens, "", i));
4824 // just to make things nicer :)
4825 if (i == LEVELINFO_TOKEN_AUTHOR ||
4826 i == LEVELINFO_TOKEN_FIRST_LEVEL ||
4827 (use_artwork_set && i == LEVELINFO_TOKEN_READONLY))
4828 fprintf(file, "\n");
4831 token_value_position = TOKEN_VALUE_POSITION_DEFAULT;
4835 SetFilePermissions(filename, PERMS_PRIVATE);
4837 freeTreeInfo(level_info);
4843 static void SaveUserLevelInfo(void)
4845 CreateUserLevelSet(getLoginName(), getLoginName(), getRealName(), 100, FALSE);
4848 char *getSetupValue(int type, void *value)
4850 static char value_string[MAX_LINE_LEN];
4858 strcpy(value_string, (*(boolean *)value ? "true" : "false"));
4862 strcpy(value_string, (*(boolean *)value ? "on" : "off"));
4865 case TYPE_SWITCH_3_STATES:
4866 strcpy(value_string, (*(int *)value == STATE_AUTO ? "auto" :
4867 *(int *)value == STATE_ASK ? "ask" :
4868 *(int *)value == STATE_FALSE ? "off" : "on"));
4872 strcpy(value_string, (*(boolean *)value ? "yes" : "no"));
4875 case TYPE_YES_NO_AUTO:
4876 strcpy(value_string, (*(int *)value == STATE_AUTO ? "auto" :
4877 *(int *)value == STATE_FALSE ? "no" : "yes"));
4880 case TYPE_YES_NO_ASK:
4881 strcpy(value_string, (*(int *)value == STATE_ASK ? "ask" :
4882 *(int *)value == STATE_FALSE ? "no" : "yes"));
4886 strcpy(value_string, (*(boolean *)value ? "new" : "old"));
4890 strcpy(value_string, getKeyNameFromKey(*(Key *)value));
4894 strcpy(value_string, getX11KeyNameFromKey(*(Key *)value));
4898 sprintf(value_string, "%d", *(int *)value);
4902 if (*(char **)value == NULL)
4905 strcpy(value_string, *(char **)value);
4909 sprintf(value_string, "player_%d", *(int *)value + 1);
4913 value_string[0] = '\0';
4917 if (type & TYPE_GHOSTED)
4918 strcpy(value_string, "n/a");
4920 return value_string;
4923 char *getSetupLine(struct TokenInfo *token_info, char *prefix, int token_nr)
4927 static char token_string[MAX_LINE_LEN];
4928 int token_type = token_info[token_nr].type;
4929 void *setup_value = token_info[token_nr].value;
4930 char *token_text = token_info[token_nr].text;
4931 char *value_string = getSetupValue(token_type, setup_value);
4933 // build complete token string
4934 sprintf(token_string, "%s%s", prefix, token_text);
4936 // build setup entry line
4937 line = getFormattedSetupEntry(token_string, value_string);
4939 if (token_type == TYPE_KEY_X11)
4941 Key key = *(Key *)setup_value;
4942 char *keyname = getKeyNameFromKey(key);
4944 // add comment, if useful
4945 if (!strEqual(keyname, "(undefined)") &&
4946 !strEqual(keyname, "(unknown)"))
4948 // add at least one whitespace
4950 for (i = strlen(line); i < token_comment_position; i++)
4954 strcat(line, keyname);
4961 static void InitLastPlayedLevels_ParentNode(void)
4963 LevelDirTree **leveldir_top = &leveldir_first->node_group->next;
4964 LevelDirTree *leveldir_new = NULL;
4966 // check if parent node for last played levels already exists
4967 if (strEqual((*leveldir_top)->identifier, TOKEN_STR_LAST_LEVEL_SERIES))
4970 leveldir_new = newTreeInfo();
4972 setTreeInfoToDefaultsFromParent(leveldir_new, leveldir_first);
4974 leveldir_new->level_group = TRUE;
4975 leveldir_new->sort_priority = LEVELCLASS_LAST_PLAYED_LEVEL;
4977 setString(&leveldir_new->identifier, TOKEN_STR_LAST_LEVEL_SERIES);
4978 setString(&leveldir_new->name, "<< (last played level sets)");
4979 setString(&leveldir_new->name_sorting, leveldir_new->name);
4981 pushTreeInfo(leveldir_top, leveldir_new);
4983 // create node to link back to current level directory
4984 createParentTreeInfoNode(leveldir_new);
4987 void UpdateLastPlayedLevels_TreeInfo(void)
4989 char **last_level_series = setup.level_setup.last_level_series;
4990 LevelDirTree *leveldir_last;
4991 TreeInfo **node_new = NULL;
4994 if (last_level_series[0] == NULL)
4997 InitLastPlayedLevels_ParentNode();
4999 leveldir_last = getTreeInfoFromIdentifierExt(leveldir_first,
5000 TOKEN_STR_LAST_LEVEL_SERIES,
5001 TREE_NODE_TYPE_GROUP);
5002 if (leveldir_last == NULL)
5005 node_new = &leveldir_last->node_group->next;
5007 freeTreeInfo(*node_new);
5011 for (i = 0; last_level_series[i] != NULL; i++)
5013 LevelDirTree *node_last = getTreeInfoFromIdentifier(leveldir_first,
5014 last_level_series[i]);
5015 if (node_last == NULL)
5018 *node_new = getTreeInfoCopy(node_last); // copy complete node
5020 (*node_new)->node_top = &leveldir_first; // correct top node link
5021 (*node_new)->node_parent = leveldir_last; // correct parent node link
5023 (*node_new)->is_copy = TRUE; // mark entry as node copy
5025 (*node_new)->node_group = NULL;
5026 (*node_new)->next = NULL;
5028 (*node_new)->cl_first = -1; // force setting tree cursor
5030 node_new = &((*node_new)->next);
5034 static void UpdateLastPlayedLevels_List(void)
5036 char **last_level_series = setup.level_setup.last_level_series;
5037 int pos = MAX_LEVELDIR_HISTORY - 1;
5040 // search for potentially already existing entry in list of level sets
5041 for (i = 0; last_level_series[i] != NULL; i++)
5042 if (strEqual(last_level_series[i], leveldir_current->identifier))
5045 // move list of level sets one entry down (using potentially free entry)
5046 for (i = pos; i > 0; i--)
5047 setString(&last_level_series[i], last_level_series[i - 1]);
5049 // put last played level set at top position
5050 setString(&last_level_series[0], leveldir_current->identifier);
5053 #define LAST_PLAYED_MODE_SET 1
5054 #define LAST_PLAYED_MODE_SET_FORCED 2
5055 #define LAST_PLAYED_MODE_GET 3
5057 static TreeInfo *StoreOrRestoreLastPlayedLevels(TreeInfo *node, int mode)
5059 static char *identifier = NULL;
5061 if (mode == LAST_PLAYED_MODE_SET)
5063 setString(&identifier, (node && node->is_copy ? node->identifier : NULL));
5065 else if (mode == LAST_PLAYED_MODE_SET_FORCED)
5067 setString(&identifier, (node ? node->identifier : NULL));
5069 else if (mode == LAST_PLAYED_MODE_GET)
5071 TreeInfo *node_new = getTreeInfoFromIdentifierExt(leveldir_first,
5073 TREE_NODE_TYPE_COPY);
5074 return (node_new != NULL ? node_new : node);
5077 return NULL; // not used
5080 void StoreLastPlayedLevels(TreeInfo *node)
5082 StoreOrRestoreLastPlayedLevels(node, LAST_PLAYED_MODE_SET);
5085 void ForcedStoreLastPlayedLevels(TreeInfo *node)
5087 StoreOrRestoreLastPlayedLevels(node, LAST_PLAYED_MODE_SET_FORCED);
5090 void RestoreLastPlayedLevels(TreeInfo **node)
5092 *node = StoreOrRestoreLastPlayedLevels(*node, LAST_PLAYED_MODE_GET);
5095 boolean CheckLastPlayedLevels(void)
5097 return (StoreOrRestoreLastPlayedLevels(NULL, LAST_PLAYED_MODE_GET) != NULL);
5100 void LoadLevelSetup_LastSeries(void)
5102 // --------------------------------------------------------------------------
5103 // ~/.<program>/levelsetup.conf
5104 // --------------------------------------------------------------------------
5106 char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
5107 SetupFileHash *level_setup_hash = NULL;
5111 // always start with reliable default values
5112 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
5114 // start with empty history of last played level sets
5115 setString(&setup.level_setup.last_level_series[0], NULL);
5117 if (!strEqual(DEFAULT_LEVELSET, UNDEFINED_LEVELSET))
5119 leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
5121 if (leveldir_current == NULL)
5122 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
5125 if ((level_setup_hash = loadSetupFileHash(filename)))
5127 char *last_level_series =
5128 getHashEntry(level_setup_hash, TOKEN_STR_LAST_LEVEL_SERIES);
5130 leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
5132 if (leveldir_current == NULL)
5133 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
5135 char *last_played_menu_used =
5136 getHashEntry(level_setup_hash, TOKEN_STR_LAST_PLAYED_MENU_USED);
5138 // store if last level set was selected from "last played" menu
5139 if (strEqual(last_played_menu_used, "true"))
5140 ForcedStoreLastPlayedLevels(leveldir_current);
5142 for (i = 0; i < MAX_LEVELDIR_HISTORY; i++)
5144 char token[strlen(TOKEN_STR_LAST_LEVEL_SERIES) + 10];
5145 LevelDirTree *leveldir_last;
5147 sprintf(token, "%s.%03d", TOKEN_STR_LAST_LEVEL_SERIES, i);
5149 last_level_series = getHashEntry(level_setup_hash, token);
5151 leveldir_last = getTreeInfoFromIdentifier(leveldir_first,
5153 if (leveldir_last != NULL)
5154 setString(&setup.level_setup.last_level_series[pos++],
5158 setString(&setup.level_setup.last_level_series[pos], NULL);
5160 freeSetupFileHash(level_setup_hash);
5164 Debug("setup", "using default setup values");
5170 static void SaveLevelSetup_LastSeries_Ext(boolean deactivate_last_level_series)
5172 // --------------------------------------------------------------------------
5173 // ~/.<program>/levelsetup.conf
5174 // --------------------------------------------------------------------------
5176 // check if the current level directory structure is available at this point
5177 if (leveldir_current == NULL)
5180 char **last_level_series = setup.level_setup.last_level_series;
5181 char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
5185 InitUserDataDirectory();
5187 UpdateLastPlayedLevels_List();
5189 if (!(file = fopen(filename, MODE_WRITE)))
5191 Warn("cannot write setup file '%s'", filename);
5198 fprintFileHeader(file, LEVELSETUP_FILENAME);
5200 if (deactivate_last_level_series)
5201 fprintf(file, "# %s\n# ", "the following level set may have caused a problem and was deactivated");
5203 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_LAST_LEVEL_SERIES,
5204 leveldir_current->identifier));
5206 // store if last level set was selected from "last played" menu
5207 boolean last_played_menu_used = CheckLastPlayedLevels();
5208 char *setup_value = getSetupValue(TYPE_BOOLEAN, &last_played_menu_used);
5210 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_LAST_PLAYED_MENU_USED,
5213 for (i = 0; last_level_series[i] != NULL; i++)
5215 char token[strlen(TOKEN_STR_LAST_LEVEL_SERIES) + 1 + 10 + 1];
5217 sprintf(token, "%s.%03d", TOKEN_STR_LAST_LEVEL_SERIES, i);
5219 fprintf(file, "%s\n", getFormattedSetupEntry(token, last_level_series[i]));
5224 SetFilePermissions(filename, PERMS_PRIVATE);
5229 void SaveLevelSetup_LastSeries(void)
5231 SaveLevelSetup_LastSeries_Ext(FALSE);
5234 void SaveLevelSetup_LastSeries_Deactivate(void)
5236 SaveLevelSetup_LastSeries_Ext(TRUE);
5239 static void checkSeriesInfo(void)
5241 static char *level_directory = NULL;
5244 DirectoryEntry *dir_entry;
5247 checked_free(level_directory);
5249 // check for more levels besides the 'levels' field of 'levelinfo.conf'
5251 level_directory = getPath2((leveldir_current->in_user_dir ?
5252 getUserLevelDir(NULL) :
5253 options.level_directory),
5254 leveldir_current->fullpath);
5256 if ((dir = openDirectory(level_directory)) == NULL)
5258 Warn("cannot read level directory '%s'", level_directory);
5264 while ((dir_entry = readDirectory(dir)) != NULL) // loop all entries
5266 if (strlen(dir_entry->basename) > 4 &&
5267 dir_entry->basename[3] == '.' &&
5268 strEqual(&dir_entry->basename[4], LEVELFILE_EXTENSION))
5270 char levelnum_str[4];
5273 strncpy(levelnum_str, dir_entry->basename, 3);
5274 levelnum_str[3] = '\0';
5276 levelnum_value = atoi(levelnum_str);
5278 if (levelnum_value < leveldir_current->first_level)
5280 Warn("additional level %d found", levelnum_value);
5282 leveldir_current->first_level = levelnum_value;
5284 else if (levelnum_value > leveldir_current->last_level)
5286 Warn("additional level %d found", levelnum_value);
5288 leveldir_current->last_level = levelnum_value;
5294 closeDirectory(dir);
5297 void LoadLevelSetup_SeriesInfo(void)
5300 SetupFileHash *level_setup_hash = NULL;
5301 char *level_subdir = leveldir_current->subdir;
5304 // always start with reliable default values
5305 level_nr = leveldir_current->first_level;
5307 for (i = 0; i < MAX_LEVELS; i++)
5309 LevelStats_setPlayed(i, 0);
5310 LevelStats_setSolved(i, 0);
5315 // --------------------------------------------------------------------------
5316 // ~/.<program>/levelsetup/<level series>/levelsetup.conf
5317 // --------------------------------------------------------------------------
5319 level_subdir = leveldir_current->subdir;
5321 filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
5323 if ((level_setup_hash = loadSetupFileHash(filename)))
5327 // get last played level in this level set
5329 token_value = getHashEntry(level_setup_hash, TOKEN_STR_LAST_PLAYED_LEVEL);
5333 level_nr = atoi(token_value);
5335 if (level_nr < leveldir_current->first_level)
5336 level_nr = leveldir_current->first_level;
5337 if (level_nr > leveldir_current->last_level)
5338 level_nr = leveldir_current->last_level;
5341 // get handicap level in this level set
5343 token_value = getHashEntry(level_setup_hash, TOKEN_STR_HANDICAP_LEVEL);
5347 int level_nr = atoi(token_value);
5349 if (level_nr < leveldir_current->first_level)
5350 level_nr = leveldir_current->first_level;
5351 if (level_nr > leveldir_current->last_level + 1)
5352 level_nr = leveldir_current->last_level;
5354 if (leveldir_current->user_defined || !leveldir_current->handicap)
5355 level_nr = leveldir_current->last_level;
5357 leveldir_current->handicap_level = level_nr;
5360 // get number of played and solved levels in this level set
5362 BEGIN_HASH_ITERATION(level_setup_hash, itr)
5364 char *token = HASH_ITERATION_TOKEN(itr);
5365 char *value = HASH_ITERATION_VALUE(itr);
5367 if (strlen(token) == 3 &&
5368 token[0] >= '0' && token[0] <= '9' &&
5369 token[1] >= '0' && token[1] <= '9' &&
5370 token[2] >= '0' && token[2] <= '9')
5372 int level_nr = atoi(token);
5375 LevelStats_setPlayed(level_nr, atoi(value)); // read 1st column
5377 value = strchr(value, ' ');
5380 LevelStats_setSolved(level_nr, atoi(value)); // read 2nd column
5383 END_HASH_ITERATION(hash, itr)
5385 freeSetupFileHash(level_setup_hash);
5389 Debug("setup", "using default setup values");
5395 void SaveLevelSetup_SeriesInfo(void)
5398 char *level_subdir = leveldir_current->subdir;
5399 char *level_nr_str = int2str(level_nr, 0);
5400 char *handicap_level_str = int2str(leveldir_current->handicap_level, 0);
5404 // --------------------------------------------------------------------------
5405 // ~/.<program>/levelsetup/<level series>/levelsetup.conf
5406 // --------------------------------------------------------------------------
5408 InitLevelSetupDirectory(level_subdir);
5410 filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
5412 if (!(file = fopen(filename, MODE_WRITE)))
5414 Warn("cannot write setup file '%s'", filename);
5421 fprintFileHeader(file, LEVELSETUP_FILENAME);
5423 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_PLAYED_LEVEL,
5425 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_HANDICAP_LEVEL,
5426 handicap_level_str));
5428 for (i = leveldir_current->first_level; i <= leveldir_current->last_level;
5431 if (LevelStats_getPlayed(i) > 0 ||
5432 LevelStats_getSolved(i) > 0)
5437 sprintf(token, "%03d", i);
5438 sprintf(value, "%d %d", LevelStats_getPlayed(i), LevelStats_getSolved(i));
5440 fprintf(file, "%s\n", getFormattedSetupEntry(token, value));
5446 SetFilePermissions(filename, PERMS_PRIVATE);
5451 int LevelStats_getPlayed(int nr)
5453 return (nr >= 0 && nr < MAX_LEVELS ? level_stats[nr].played : 0);
5456 int LevelStats_getSolved(int nr)
5458 return (nr >= 0 && nr < MAX_LEVELS ? level_stats[nr].solved : 0);
5461 void LevelStats_setPlayed(int nr, int value)
5463 if (nr >= 0 && nr < MAX_LEVELS)
5464 level_stats[nr].played = value;
5467 void LevelStats_setSolved(int nr, int value)
5469 if (nr >= 0 && nr < MAX_LEVELS)
5470 level_stats[nr].solved = value;
5473 void LevelStats_incPlayed(int nr)
5475 if (nr >= 0 && nr < MAX_LEVELS)
5476 level_stats[nr].played++;
5479 void LevelStats_incSolved(int nr)
5481 if (nr >= 0 && nr < MAX_LEVELS)
5482 level_stats[nr].solved++;
5485 void LoadUserSetup(void)
5487 // --------------------------------------------------------------------------
5488 // ~/.<program>/usersetup.conf
5489 // --------------------------------------------------------------------------
5491 char *filename = getPath2(getMainUserGameDataDir(), USERSETUP_FILENAME);
5492 SetupFileHash *user_setup_hash = NULL;
5494 // always start with reliable default values
5497 if ((user_setup_hash = loadSetupFileHash(filename)))
5501 // get last selected user number
5502 token_value = getHashEntry(user_setup_hash, TOKEN_STR_LAST_USER);
5505 user.nr = MIN(MAX(0, atoi(token_value)), MAX_PLAYER_NAMES - 1);
5507 freeSetupFileHash(user_setup_hash);
5511 Debug("setup", "using default setup values");
5517 void SaveUserSetup(void)
5519 // --------------------------------------------------------------------------
5520 // ~/.<program>/usersetup.conf
5521 // --------------------------------------------------------------------------
5523 char *filename = getPath2(getMainUserGameDataDir(), USERSETUP_FILENAME);
5526 InitMainUserDataDirectory();
5528 if (!(file = fopen(filename, MODE_WRITE)))
5530 Warn("cannot write setup file '%s'", filename);
5537 fprintFileHeader(file, USERSETUP_FILENAME);
5539 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_USER,
5543 SetFilePermissions(filename, PERMS_PRIVATE);