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_key(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 boolean want_1 = (prefer_2 == FALSE);
1717 boolean want_2 = (prefer_2 == TRUE);
1718 boolean has_only_1 = (!*artwork_set && !*artwork_set_2);
1719 boolean has_only_2 = (!*artwork_set && !*artwork_set_1);
1720 char *artwork_set_new = NULL;
1722 if (*artwork_set_1 && (want_1 || has_only_1))
1723 artwork_set_new = *artwork_set_1;
1725 if (*artwork_set_2 && (want_2 || has_only_2))
1726 artwork_set_new = *artwork_set_2;
1728 if (artwork_set_new && !strEqual(*artwork_set, artwork_set_new))
1730 setString(artwork_set, artwork_set_new);
1738 static boolean adjustTreeGraphicsForEMC(TreeInfo *node)
1740 boolean settings_changed = FALSE;
1744 settings_changed |= adjustTreeArtworkForEMC(&node->graphics_set_ecs,
1745 &node->graphics_set_aga,
1746 &node->graphics_set,
1747 setup.prefer_aga_graphics);
1748 if (node->node_group != NULL)
1749 settings_changed |= adjustTreeGraphicsForEMC(node->node_group);
1754 return settings_changed;
1757 static boolean adjustTreeSoundsForEMC(TreeInfo *node)
1759 boolean settings_changed = FALSE;
1763 settings_changed |= adjustTreeArtworkForEMC(&node->sounds_set_default,
1764 &node->sounds_set_lowpass,
1766 setup.prefer_lowpass_sounds);
1767 if (node->node_group != NULL)
1768 settings_changed |= adjustTreeSoundsForEMC(node->node_group);
1773 return settings_changed;
1776 int dumpTreeInfo(TreeInfo *node, int depth)
1778 char bullet_list[] = { '-', '*', 'o' };
1779 int num_leaf_nodes = 0;
1783 Debug("tree", "Dumping TreeInfo:");
1787 char bullet = bullet_list[depth % ARRAY_SIZE(bullet_list)];
1789 for (i = 0; i < depth * 2; i++)
1790 DebugContinued("", " ");
1792 DebugContinued("tree", "%c '%s' ['%s] [PARENT: '%s'] %s\n",
1793 bullet, node->name, node->identifier,
1794 (node->node_parent ? node->node_parent->identifier : "-"),
1795 (node->node_group ? "[GROUP]" :
1796 node->is_copy ? "[COPY]" : ""));
1798 if (!node->node_group && !node->parent_link)
1802 // use for dumping artwork info tree
1803 Debug("tree", "subdir == '%s' ['%s', '%s'] [%d])",
1804 node->subdir, node->fullpath, node->basepath, node->in_user_dir);
1807 if (node->node_group != NULL)
1808 num_leaf_nodes += dumpTreeInfo(node->node_group, depth + 1);
1814 Debug("tree", "Summary: %d leaf nodes found", num_leaf_nodes);
1816 return num_leaf_nodes;
1819 void sortTreeInfoBySortFunction(TreeInfo **node_first,
1820 int (*compare_function)(const void *,
1823 int num_nodes = numTreeInfo(*node_first);
1824 TreeInfo **sort_array;
1825 TreeInfo *node = *node_first;
1831 // allocate array for sorting structure pointers
1832 sort_array = checked_calloc(num_nodes * sizeof(TreeInfo *));
1834 // writing structure pointers to sorting array
1835 while (i < num_nodes && node) // double boundary check...
1837 sort_array[i] = node;
1843 // sorting the structure pointers in the sorting array
1844 qsort(sort_array, num_nodes, sizeof(TreeInfo *),
1847 // update the linkage of list elements with the sorted node array
1848 for (i = 0; i < num_nodes - 1; i++)
1849 sort_array[i]->next = sort_array[i + 1];
1850 sort_array[num_nodes - 1]->next = NULL;
1852 // update the linkage of the main list anchor pointer
1853 *node_first = sort_array[0];
1857 // now recursively sort the level group structures
1861 if (node->node_group != NULL)
1862 sortTreeInfoBySortFunction(&node->node_group, compare_function);
1868 void sortTreeInfo(TreeInfo **node_first)
1870 sortTreeInfoBySortFunction(node_first, compareTreeInfoEntries);
1874 // ============================================================================
1875 // some stuff from "files.c"
1876 // ============================================================================
1878 #if defined(PLATFORM_WINDOWS)
1880 #define S_IRGRP S_IRUSR
1883 #define S_IROTH S_IRUSR
1886 #define S_IWGRP S_IWUSR
1889 #define S_IWOTH S_IWUSR
1892 #define S_IXGRP S_IXUSR
1895 #define S_IXOTH S_IXUSR
1898 #define S_IRWXG (S_IRGRP | S_IWGRP | S_IXGRP)
1903 #endif // PLATFORM_WINDOWS
1905 // file permissions for newly written files
1906 #define MODE_R_ALL (S_IRUSR | S_IRGRP | S_IROTH)
1907 #define MODE_W_ALL (S_IWUSR | S_IWGRP | S_IWOTH)
1908 #define MODE_X_ALL (S_IXUSR | S_IXGRP | S_IXOTH)
1910 #define MODE_W_PRIVATE (S_IWUSR)
1911 #define MODE_W_PUBLIC_FILE (S_IWUSR | S_IWGRP)
1912 #define MODE_W_PUBLIC_DIR (S_IWUSR | S_IWGRP | S_ISGID)
1914 #define DIR_PERMS_PRIVATE (MODE_R_ALL | MODE_X_ALL | MODE_W_PRIVATE)
1915 #define DIR_PERMS_PUBLIC (MODE_R_ALL | MODE_X_ALL | MODE_W_PUBLIC_DIR)
1916 #define DIR_PERMS_PUBLIC_ALL (MODE_R_ALL | MODE_X_ALL | MODE_W_ALL)
1918 #define FILE_PERMS_PRIVATE (MODE_R_ALL | MODE_W_PRIVATE)
1919 #define FILE_PERMS_PUBLIC (MODE_R_ALL | MODE_W_PUBLIC_FILE)
1920 #define FILE_PERMS_PUBLIC_ALL (MODE_R_ALL | MODE_W_ALL)
1923 char *getHomeDir(void)
1925 static char *dir = NULL;
1927 #if defined(PLATFORM_WINDOWS)
1930 dir = checked_malloc(MAX_PATH + 1);
1932 if (!SUCCEEDED(SHGetFolderPath(NULL, CSIDL_PERSONAL, NULL, 0, dir)))
1935 #elif defined(PLATFORM_EMSCRIPTEN)
1936 dir = PERSISTENT_DIRECTORY;
1937 #elif defined(PLATFORM_UNIX)
1940 if ((dir = getenv("HOME")) == NULL)
1942 dir = getUnixHomeDir();
1945 dir = getStringCopy(dir);
1957 char *getPersonalDataDir(void)
1959 static char *personal_data_dir = NULL;
1961 #if defined(PLATFORM_MAC)
1962 if (personal_data_dir == NULL)
1963 personal_data_dir = getPath2(getHomeDir(), "Documents");
1965 if (personal_data_dir == NULL)
1966 personal_data_dir = getHomeDir();
1969 return personal_data_dir;
1972 char *getMainUserGameDataDir(void)
1974 static char *main_user_data_dir = NULL;
1976 #if defined(PLATFORM_ANDROID)
1977 if (main_user_data_dir == NULL)
1978 main_user_data_dir = (char *)(SDL_AndroidGetExternalStorageState() &
1979 SDL_ANDROID_EXTERNAL_STORAGE_WRITE ?
1980 SDL_AndroidGetExternalStoragePath() :
1981 SDL_AndroidGetInternalStoragePath());
1983 if (main_user_data_dir == NULL)
1984 main_user_data_dir = getPath2(getPersonalDataDir(),
1985 program.userdata_subdir);
1988 return main_user_data_dir;
1991 char *getUserGameDataDir(void)
1994 return getMainUserGameDataDir();
1996 return getUserDir(user.nr);
1999 char *getSetupDir(void)
2001 return getUserGameDataDir();
2004 static mode_t posix_umask(mode_t mask)
2006 #if defined(PLATFORM_UNIX)
2013 static int posix_mkdir(const char *pathname, mode_t mode)
2015 #if defined(PLATFORM_WINDOWS)
2016 return mkdir(pathname);
2018 return mkdir(pathname, mode);
2022 static boolean posix_process_running_setgid(void)
2024 #if defined(PLATFORM_UNIX)
2025 return (getgid() != getegid());
2031 void createDirectory(char *dir, char *text)
2033 if (directoryExists(dir))
2036 // leave "other" permissions in umask untouched, but ensure group parts
2037 // of USERDATA_DIR_MODE are not masked
2038 int permission_class = PERMS_PRIVATE;
2039 mode_t dir_mode = (permission_class == PERMS_PRIVATE ?
2040 DIR_PERMS_PRIVATE : DIR_PERMS_PUBLIC);
2041 mode_t last_umask = posix_umask(0);
2042 mode_t group_umask = ~(dir_mode & S_IRWXG);
2043 int running_setgid = posix_process_running_setgid();
2045 if (permission_class == PERMS_PUBLIC)
2047 // if we're setgid, protect files against "other"
2048 // else keep umask(0) to make the dir world-writable
2051 posix_umask(last_umask & group_umask);
2053 dir_mode = DIR_PERMS_PUBLIC_ALL;
2056 if (posix_mkdir(dir, dir_mode) != 0)
2057 Warn("cannot create %s directory '%s': %s", text, dir, strerror(errno));
2059 if (permission_class == PERMS_PUBLIC && !running_setgid)
2060 chmod(dir, dir_mode);
2062 posix_umask(last_umask); // restore previous umask
2065 void InitMainUserDataDirectory(void)
2067 createDirectory(getMainUserGameDataDir(), "main user data");
2070 void InitUserDataDirectory(void)
2072 createDirectory(getMainUserGameDataDir(), "main user data");
2076 createDirectory(getUserDir(-1), "users");
2077 createDirectory(getUserDir(user.nr), "user data");
2081 void SetFilePermissions(char *filename, int permission_class)
2083 int running_setgid = posix_process_running_setgid();
2084 int perms = (permission_class == PERMS_PRIVATE ?
2085 FILE_PERMS_PRIVATE : FILE_PERMS_PUBLIC);
2087 if (permission_class == PERMS_PUBLIC && !running_setgid)
2088 perms = FILE_PERMS_PUBLIC_ALL;
2090 chmod(filename, perms);
2093 void fprintFileHeader(FILE *file, char *basename)
2095 char *prefix = "# ";
2098 fprintf_line_with_prefix(file, prefix, sep1, 77);
2099 fprintf(file, "%s%s\n", prefix, basename);
2100 fprintf_line_with_prefix(file, prefix, sep1, 77);
2101 fprintf(file, "\n");
2104 int getFileVersionFromCookieString(const char *cookie)
2106 const char *ptr_cookie1, *ptr_cookie2;
2107 const char *pattern1 = "_FILE_VERSION_";
2108 const char *pattern2 = "?.?";
2109 const int len_cookie = strlen(cookie);
2110 const int len_pattern1 = strlen(pattern1);
2111 const int len_pattern2 = strlen(pattern2);
2112 const int len_pattern = len_pattern1 + len_pattern2;
2113 int version_super, version_major;
2115 if (len_cookie <= len_pattern)
2118 ptr_cookie1 = &cookie[len_cookie - len_pattern];
2119 ptr_cookie2 = &cookie[len_cookie - len_pattern2];
2121 if (strncmp(ptr_cookie1, pattern1, len_pattern1) != 0)
2124 if (ptr_cookie2[0] < '0' || ptr_cookie2[0] > '9' ||
2125 ptr_cookie2[1] != '.' ||
2126 ptr_cookie2[2] < '0' || ptr_cookie2[2] > '9')
2129 version_super = ptr_cookie2[0] - '0';
2130 version_major = ptr_cookie2[2] - '0';
2132 return VERSION_IDENT(version_super, version_major, 0, 0);
2135 boolean checkCookieString(const char *cookie, const char *template)
2137 const char *pattern = "_FILE_VERSION_?.?";
2138 const int len_cookie = strlen(cookie);
2139 const int len_template = strlen(template);
2140 const int len_pattern = strlen(pattern);
2142 if (len_cookie != len_template)
2145 if (strncmp(cookie, template, len_cookie - len_pattern) != 0)
2152 // ----------------------------------------------------------------------------
2153 // setup file list and hash handling functions
2154 // ----------------------------------------------------------------------------
2156 char *getFormattedSetupEntry(char *token, char *value)
2159 static char entry[MAX_LINE_LEN];
2161 // if value is an empty string, just return token without value
2165 // start with the token and some spaces to format output line
2166 sprintf(entry, "%s:", token);
2167 for (i = strlen(entry); i < token_value_position; i++)
2170 // continue with the token's value
2171 strcat(entry, value);
2176 SetupFileList *newSetupFileList(char *token, char *value)
2178 SetupFileList *new = checked_malloc(sizeof(SetupFileList));
2180 new->token = getStringCopy(token);
2181 new->value = getStringCopy(value);
2188 void freeSetupFileList(SetupFileList *list)
2193 checked_free(list->token);
2194 checked_free(list->value);
2197 freeSetupFileList(list->next);
2202 char *getListEntry(SetupFileList *list, char *token)
2207 if (strEqual(list->token, token))
2210 return getListEntry(list->next, token);
2213 SetupFileList *setListEntry(SetupFileList *list, char *token, char *value)
2218 if (strEqual(list->token, token))
2220 checked_free(list->value);
2222 list->value = getStringCopy(value);
2226 else if (list->next == NULL)
2227 return (list->next = newSetupFileList(token, value));
2229 return setListEntry(list->next, token, value);
2232 SetupFileList *addListEntry(SetupFileList *list, char *token, char *value)
2237 if (list->next == NULL)
2238 return (list->next = newSetupFileList(token, value));
2240 return addListEntry(list->next, token, value);
2243 #if ENABLE_UNUSED_CODE
2245 static void printSetupFileList(SetupFileList *list)
2250 Debug("setup:printSetupFileList", "token: '%s'", list->token);
2251 Debug("setup:printSetupFileList", "value: '%s'", list->value);
2253 printSetupFileList(list->next);
2259 DEFINE_HASHTABLE_INSERT(insert_hash_entry, char, char);
2260 DEFINE_HASHTABLE_SEARCH(search_hash_entry, char, char);
2261 DEFINE_HASHTABLE_CHANGE(change_hash_entry, char, char);
2262 DEFINE_HASHTABLE_REMOVE(remove_hash_entry, char, char);
2264 #define insert_hash_entry hashtable_insert
2265 #define search_hash_entry hashtable_search
2266 #define change_hash_entry hashtable_change
2267 #define remove_hash_entry hashtable_remove
2270 unsigned int get_hash_from_key(void *key)
2275 This algorithm (k=33) was first reported by Dan Bernstein many years ago in
2276 'comp.lang.c'. Another version of this algorithm (now favored by Bernstein)
2277 uses XOR: hash(i) = hash(i - 1) * 33 ^ str[i]; the magic of number 33 (why
2278 it works better than many other constants, prime or not) has never been
2279 adequately explained.
2281 If you just want to have a good hash function, and cannot wait, djb2
2282 is one of the best string hash functions i know. It has excellent
2283 distribution and speed on many different sets of keys and table sizes.
2284 You are not likely to do better with one of the "well known" functions
2285 such as PJW, K&R, etc.
2287 Ozan (oz) Yigit [http://www.cs.yorku.ca/~oz/hash.html]
2290 char *str = (char *)key;
2291 unsigned int hash = 5381;
2294 while ((c = *str++))
2295 hash = ((hash << 5) + hash) + c; // hash * 33 + c
2300 int hash_keys_are_equal(void *key1, void *key2)
2302 return (strEqual((char *)key1, (char *)key2));
2305 SetupFileHash *newSetupFileHash(void)
2307 SetupFileHash *new_hash =
2308 create_hashtable(16, 0.75, get_hash_from_key, hash_keys_are_equal);
2310 if (new_hash == NULL)
2311 Fail("create_hashtable() failed -- out of memory");
2316 void freeSetupFileHash(SetupFileHash *hash)
2321 hashtable_destroy(hash, 1); // 1 == also free values stored in hash
2324 char *getHashEntry(SetupFileHash *hash, char *token)
2329 return search_hash_entry(hash, token);
2332 void setHashEntry(SetupFileHash *hash, char *token, char *value)
2339 value_copy = getStringCopy(value);
2341 // change value; if it does not exist, insert it as new
2342 if (!change_hash_entry(hash, token, value_copy))
2343 if (!insert_hash_entry(hash, getStringCopy(token), value_copy))
2344 Fail("cannot insert into hash -- aborting");
2347 char *removeHashEntry(SetupFileHash *hash, char *token)
2352 return remove_hash_entry(hash, token);
2355 #if ENABLE_UNUSED_CODE
2357 static void printSetupFileHash(SetupFileHash *hash)
2359 BEGIN_HASH_ITERATION(hash, itr)
2361 Debug("setup:printSetupFileHash", "token: '%s'", HASH_ITERATION_TOKEN(itr));
2362 Debug("setup:printSetupFileHash", "value: '%s'", HASH_ITERATION_VALUE(itr));
2364 END_HASH_ITERATION(hash, itr)
2369 #define ALLOW_TOKEN_VALUE_SEPARATOR_BEING_WHITESPACE 1
2370 #define CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING 0
2371 #define CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH 0
2373 static boolean token_value_separator_found = FALSE;
2374 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2375 static boolean token_value_separator_warning = FALSE;
2377 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2378 static boolean token_already_exists_warning = FALSE;
2381 static boolean getTokenValueFromSetupLineExt(char *line,
2382 char **token_ptr, char **value_ptr,
2383 char *filename, char *line_raw,
2385 boolean separator_required)
2387 static char line_copy[MAX_LINE_LEN + 1], line_raw_copy[MAX_LINE_LEN + 1];
2388 char *token, *value, *line_ptr;
2390 // when externally invoked via ReadTokenValueFromLine(), copy line buffers
2391 if (line_raw == NULL)
2393 strncpy(line_copy, line, MAX_LINE_LEN);
2394 line_copy[MAX_LINE_LEN] = '\0';
2397 strcpy(line_raw_copy, line_copy);
2398 line_raw = line_raw_copy;
2401 // cut trailing comment from input line
2402 for (line_ptr = line; *line_ptr; line_ptr++)
2404 if (*line_ptr == '#')
2411 // cut trailing whitespaces from input line
2412 for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
2413 if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
2416 // ignore empty lines
2420 // cut leading whitespaces from token
2421 for (token = line; *token; token++)
2422 if (*token != ' ' && *token != '\t')
2425 // start with empty value as reliable default
2428 token_value_separator_found = FALSE;
2430 // find end of token to determine start of value
2431 for (line_ptr = token; *line_ptr; line_ptr++)
2433 // first look for an explicit token/value separator, like ':' or '='
2434 if (*line_ptr == ':' || *line_ptr == '=')
2436 *line_ptr = '\0'; // terminate token string
2437 value = line_ptr + 1; // set beginning of value
2439 token_value_separator_found = TRUE;
2445 #if ALLOW_TOKEN_VALUE_SEPARATOR_BEING_WHITESPACE
2446 // fallback: if no token/value separator found, also allow whitespaces
2447 if (!token_value_separator_found && !separator_required)
2449 for (line_ptr = token; *line_ptr; line_ptr++)
2451 if (*line_ptr == ' ' || *line_ptr == '\t')
2453 *line_ptr = '\0'; // terminate token string
2454 value = line_ptr + 1; // set beginning of value
2456 token_value_separator_found = TRUE;
2462 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2463 if (token_value_separator_found)
2465 if (!token_value_separator_warning)
2467 Debug("setup", "---");
2469 if (filename != NULL)
2471 Debug("setup", "missing token/value separator(s) in config file:");
2472 Debug("setup", "- config file: '%s'", filename);
2476 Debug("setup", "missing token/value separator(s):");
2479 token_value_separator_warning = TRUE;
2482 if (filename != NULL)
2483 Debug("setup", "- line %d: '%s'", line_nr, line_raw);
2485 Debug("setup", "- line: '%s'", line_raw);
2491 // cut trailing whitespaces from token
2492 for (line_ptr = &token[strlen(token)]; line_ptr >= token; line_ptr--)
2493 if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
2496 // cut leading whitespaces from value
2497 for (; *value; value++)
2498 if (*value != ' ' && *value != '\t')
2507 boolean getTokenValueFromSetupLine(char *line, char **token, char **value)
2509 // while the internal (old) interface does not require a token/value
2510 // separator (for downwards compatibility with existing files which
2511 // don't use them), it is mandatory for the external (new) interface
2513 return getTokenValueFromSetupLineExt(line, token, value, NULL, NULL, 0, TRUE);
2516 static boolean loadSetupFileData(void *setup_file_data, char *filename,
2517 boolean top_recursion_level, boolean is_hash)
2519 static SetupFileHash *include_filename_hash = NULL;
2520 char line[MAX_LINE_LEN], line_raw[MAX_LINE_LEN], previous_line[MAX_LINE_LEN];
2521 char *token, *value, *line_ptr;
2522 void *insert_ptr = NULL;
2523 boolean read_continued_line = FALSE;
2525 int line_nr = 0, token_count = 0, include_count = 0;
2527 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2528 token_value_separator_warning = FALSE;
2531 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2532 token_already_exists_warning = FALSE;
2535 if (!(file = openFile(filename, MODE_READ)))
2537 #if DEBUG_NO_CONFIG_FILE
2538 Debug("setup", "cannot open configuration file '%s'", filename);
2544 // use "insert pointer" to store list end for constant insertion complexity
2546 insert_ptr = setup_file_data;
2548 // on top invocation, create hash to mark included files (to prevent loops)
2549 if (top_recursion_level)
2550 include_filename_hash = newSetupFileHash();
2552 // mark this file as already included (to prevent including it again)
2553 setHashEntry(include_filename_hash, getBaseNamePtr(filename), "true");
2555 while (!checkEndOfFile(file))
2557 // read next line of input file
2558 if (!getStringFromFile(file, line, MAX_LINE_LEN))
2561 // check if line was completely read and is terminated by line break
2562 if (strlen(line) > 0 && line[strlen(line) - 1] == '\n')
2565 // cut trailing line break (this can be newline and/or carriage return)
2566 for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
2567 if ((*line_ptr == '\n' || *line_ptr == '\r') && *(line_ptr + 1) == '\0')
2570 // copy raw input line for later use (mainly debugging output)
2571 strcpy(line_raw, line);
2573 if (read_continued_line)
2575 // append new line to existing line, if there is enough space
2576 if (strlen(previous_line) + strlen(line_ptr) < MAX_LINE_LEN)
2577 strcat(previous_line, line_ptr);
2579 strcpy(line, previous_line); // copy storage buffer to line
2581 read_continued_line = FALSE;
2584 // if the last character is '\', continue at next line
2585 if (strlen(line) > 0 && line[strlen(line) - 1] == '\\')
2587 line[strlen(line) - 1] = '\0'; // cut off trailing backslash
2588 strcpy(previous_line, line); // copy line to storage buffer
2590 read_continued_line = TRUE;
2595 if (!getTokenValueFromSetupLineExt(line, &token, &value, filename,
2596 line_raw, line_nr, FALSE))
2601 if (strEqual(token, "include"))
2603 if (getHashEntry(include_filename_hash, value) == NULL)
2605 char *basepath = getBasePath(filename);
2606 char *basename = getBaseName(value);
2607 char *filename_include = getPath2(basepath, basename);
2609 loadSetupFileData(setup_file_data, filename_include, FALSE, is_hash);
2613 free(filename_include);
2619 Warn("ignoring already processed file '%s'", value);
2626 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2628 getHashEntry((SetupFileHash *)setup_file_data, token);
2630 if (old_value != NULL)
2632 if (!token_already_exists_warning)
2634 Debug("setup", "---");
2635 Debug("setup", "duplicate token(s) found in config file:");
2636 Debug("setup", "- config file: '%s'", filename);
2638 token_already_exists_warning = TRUE;
2641 Debug("setup", "- token: '%s' (in line %d)", token, line_nr);
2642 Debug("setup", " old value: '%s'", old_value);
2643 Debug("setup", " new value: '%s'", value);
2647 setHashEntry((SetupFileHash *)setup_file_data, token, value);
2651 insert_ptr = addListEntry((SetupFileList *)insert_ptr, token, value);
2661 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2662 if (token_value_separator_warning)
2663 Debug("setup", "---");
2666 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2667 if (token_already_exists_warning)
2668 Debug("setup", "---");
2671 if (token_count == 0 && include_count == 0)
2672 Warn("configuration file '%s' is empty", filename);
2674 if (top_recursion_level)
2675 freeSetupFileHash(include_filename_hash);
2680 static int compareSetupFileData(const void *object1, const void *object2)
2682 const struct ConfigInfo *entry1 = (struct ConfigInfo *)object1;
2683 const struct ConfigInfo *entry2 = (struct ConfigInfo *)object2;
2685 return strcmp(entry1->token, entry2->token);
2688 static void saveSetupFileHash(SetupFileHash *hash, char *filename)
2690 int item_count = hashtable_count(hash);
2691 int item_size = sizeof(struct ConfigInfo);
2692 struct ConfigInfo *sort_array = checked_malloc(item_count * item_size);
2696 // copy string pointers from hash to array
2697 BEGIN_HASH_ITERATION(hash, itr)
2699 sort_array[i].token = HASH_ITERATION_TOKEN(itr);
2700 sort_array[i].value = HASH_ITERATION_VALUE(itr);
2704 if (i > item_count) // should never happen
2707 END_HASH_ITERATION(hash, itr)
2709 // sort string pointers from hash in array
2710 qsort(sort_array, item_count, item_size, compareSetupFileData);
2712 if (!(file = fopen(filename, MODE_WRITE)))
2714 Warn("cannot write configuration file '%s'", filename);
2719 fprintf(file, "%s\n\n", getFormattedSetupEntry("program.version",
2720 program.version_string));
2721 for (i = 0; i < item_count; i++)
2722 fprintf(file, "%s\n", getFormattedSetupEntry(sort_array[i].token,
2723 sort_array[i].value));
2726 checked_free(sort_array);
2729 SetupFileList *loadSetupFileList(char *filename)
2731 SetupFileList *setup_file_list = newSetupFileList("", "");
2732 SetupFileList *first_valid_list_entry;
2734 if (!loadSetupFileData(setup_file_list, filename, TRUE, FALSE))
2736 freeSetupFileList(setup_file_list);
2741 first_valid_list_entry = setup_file_list->next;
2743 // free empty list header
2744 setup_file_list->next = NULL;
2745 freeSetupFileList(setup_file_list);
2747 return first_valid_list_entry;
2750 SetupFileHash *loadSetupFileHash(char *filename)
2752 SetupFileHash *setup_file_hash = newSetupFileHash();
2754 if (!loadSetupFileData(setup_file_hash, filename, TRUE, TRUE))
2756 freeSetupFileHash(setup_file_hash);
2761 return setup_file_hash;
2765 // ============================================================================
2767 // ============================================================================
2769 #define TOKEN_STR_LAST_LEVEL_SERIES "last_level_series"
2770 #define TOKEN_STR_LAST_PLAYED_MENU_USED "last_played_menu_used"
2771 #define TOKEN_STR_LAST_PLAYED_LEVEL "last_played_level"
2772 #define TOKEN_STR_HANDICAP_LEVEL "handicap_level"
2773 #define TOKEN_STR_LAST_USER "last_user"
2775 // level directory info
2776 #define LEVELINFO_TOKEN_IDENTIFIER 0
2777 #define LEVELINFO_TOKEN_NAME 1
2778 #define LEVELINFO_TOKEN_NAME_SORTING 2
2779 #define LEVELINFO_TOKEN_AUTHOR 3
2780 #define LEVELINFO_TOKEN_YEAR 4
2781 #define LEVELINFO_TOKEN_PROGRAM_TITLE 5
2782 #define LEVELINFO_TOKEN_PROGRAM_COPYRIGHT 6
2783 #define LEVELINFO_TOKEN_PROGRAM_COMPANY 7
2784 #define LEVELINFO_TOKEN_IMPORTED_FROM 8
2785 #define LEVELINFO_TOKEN_IMPORTED_BY 9
2786 #define LEVELINFO_TOKEN_TESTED_BY 10
2787 #define LEVELINFO_TOKEN_LEVELS 11
2788 #define LEVELINFO_TOKEN_FIRST_LEVEL 12
2789 #define LEVELINFO_TOKEN_SORT_PRIORITY 13
2790 #define LEVELINFO_TOKEN_LATEST_ENGINE 14
2791 #define LEVELINFO_TOKEN_LEVEL_GROUP 15
2792 #define LEVELINFO_TOKEN_READONLY 16
2793 #define LEVELINFO_TOKEN_GRAPHICS_SET_ECS 17
2794 #define LEVELINFO_TOKEN_GRAPHICS_SET_AGA 18
2795 #define LEVELINFO_TOKEN_GRAPHICS_SET 19
2796 #define LEVELINFO_TOKEN_SOUNDS_SET_DEFAULT 20
2797 #define LEVELINFO_TOKEN_SOUNDS_SET_LOWPASS 21
2798 #define LEVELINFO_TOKEN_SOUNDS_SET 22
2799 #define LEVELINFO_TOKEN_MUSIC_SET 23
2800 #define LEVELINFO_TOKEN_FILENAME 24
2801 #define LEVELINFO_TOKEN_FILETYPE 25
2802 #define LEVELINFO_TOKEN_SPECIAL_FLAGS 26
2803 #define LEVELINFO_TOKEN_EMPTY_LEVEL_NAME 27
2804 #define LEVELINFO_TOKEN_FORCE_LEVEL_NAME 28
2805 #define LEVELINFO_TOKEN_HANDICAP 29
2806 #define LEVELINFO_TOKEN_TIME_LIMIT 30
2807 #define LEVELINFO_TOKEN_SKIP_LEVELS 31
2808 #define LEVELINFO_TOKEN_USE_EMC_TILES 32
2809 #define LEVELINFO_TOKEN_INFO_SCREENS_FROM_MAIN 33
2811 #define NUM_LEVELINFO_TOKENS 34
2813 static LevelDirTree ldi;
2815 static struct TokenInfo levelinfo_tokens[] =
2817 // level directory info
2818 { TYPE_STRING, &ldi.identifier, "identifier" },
2819 { TYPE_STRING, &ldi.name, "name" },
2820 { TYPE_STRING, &ldi.name_sorting, "name_sorting" },
2821 { TYPE_STRING, &ldi.author, "author" },
2822 { TYPE_STRING, &ldi.year, "year" },
2823 { TYPE_STRING, &ldi.program_title, "program_title" },
2824 { TYPE_STRING, &ldi.program_copyright, "program_copyright" },
2825 { TYPE_STRING, &ldi.program_company, "program_company" },
2826 { TYPE_STRING, &ldi.imported_from, "imported_from" },
2827 { TYPE_STRING, &ldi.imported_by, "imported_by" },
2828 { TYPE_STRING, &ldi.tested_by, "tested_by" },
2829 { TYPE_INTEGER, &ldi.levels, "levels" },
2830 { TYPE_INTEGER, &ldi.first_level, "first_level" },
2831 { TYPE_INTEGER, &ldi.sort_priority, "sort_priority" },
2832 { TYPE_BOOLEAN, &ldi.latest_engine, "latest_engine" },
2833 { TYPE_BOOLEAN, &ldi.level_group, "level_group" },
2834 { TYPE_BOOLEAN, &ldi.readonly, "readonly" },
2835 { TYPE_STRING, &ldi.graphics_set_ecs, "graphics_set.ecs" },
2836 { TYPE_STRING, &ldi.graphics_set_aga, "graphics_set.aga" },
2837 { TYPE_STRING, &ldi.graphics_set, "graphics_set" },
2838 { TYPE_STRING, &ldi.sounds_set_default,"sounds_set.default" },
2839 { TYPE_STRING, &ldi.sounds_set_lowpass,"sounds_set.lowpass" },
2840 { TYPE_STRING, &ldi.sounds_set, "sounds_set" },
2841 { TYPE_STRING, &ldi.music_set, "music_set" },
2842 { TYPE_STRING, &ldi.level_filename, "filename" },
2843 { TYPE_STRING, &ldi.level_filetype, "filetype" },
2844 { TYPE_STRING, &ldi.special_flags, "special_flags" },
2845 { TYPE_STRING, &ldi.empty_level_name, "empty_level_name" },
2846 { TYPE_BOOLEAN, &ldi.force_level_name, "force_level_name" },
2847 { TYPE_BOOLEAN, &ldi.handicap, "handicap" },
2848 { TYPE_BOOLEAN, &ldi.time_limit, "time_limit" },
2849 { TYPE_BOOLEAN, &ldi.skip_levels, "skip_levels" },
2850 { TYPE_BOOLEAN, &ldi.use_emc_tiles, "use_emc_tiles" },
2851 { TYPE_BOOLEAN, &ldi.info_screens_from_main, "info_screens_from_main" }
2854 static struct TokenInfo artworkinfo_tokens[] =
2856 // artwork directory info
2857 { TYPE_STRING, &ldi.identifier, "identifier" },
2858 { TYPE_STRING, &ldi.subdir, "subdir" },
2859 { TYPE_STRING, &ldi.name, "name" },
2860 { TYPE_STRING, &ldi.name_sorting, "name_sorting" },
2861 { TYPE_STRING, &ldi.author, "author" },
2862 { TYPE_STRING, &ldi.program_title, "program_title" },
2863 { TYPE_STRING, &ldi.program_copyright, "program_copyright" },
2864 { TYPE_STRING, &ldi.program_company, "program_company" },
2865 { TYPE_INTEGER, &ldi.sort_priority, "sort_priority" },
2866 { TYPE_STRING, &ldi.basepath, "basepath" },
2867 { TYPE_STRING, &ldi.fullpath, "fullpath" },
2868 { TYPE_BOOLEAN, &ldi.in_user_dir, "in_user_dir" },
2869 { TYPE_STRING, &ldi.class_desc, "class_desc" },
2874 static char *optional_tokens[] =
2877 "program_copyright",
2883 static void setTreeInfoToDefaults(TreeInfo *ti, int type)
2887 ti->node_top = (ti->type == TREE_TYPE_LEVEL_DIR ? &leveldir_first :
2888 ti->type == TREE_TYPE_GRAPHICS_DIR ? &artwork.gfx_first :
2889 ti->type == TREE_TYPE_SOUNDS_DIR ? &artwork.snd_first :
2890 ti->type == TREE_TYPE_MUSIC_DIR ? &artwork.mus_first :
2893 ti->node_parent = NULL;
2894 ti->node_group = NULL;
2901 ti->fullpath = NULL;
2902 ti->basepath = NULL;
2903 ti->identifier = NULL;
2904 ti->name = getStringCopy(ANONYMOUS_NAME);
2905 ti->name_sorting = NULL;
2906 ti->author = getStringCopy(ANONYMOUS_NAME);
2909 ti->program_title = NULL;
2910 ti->program_copyright = NULL;
2911 ti->program_company = NULL;
2913 ti->sort_priority = LEVELCLASS_UNDEFINED; // default: least priority
2914 ti->latest_engine = FALSE; // default: get from level
2915 ti->parent_link = FALSE;
2916 ti->is_copy = FALSE;
2917 ti->in_user_dir = FALSE;
2918 ti->user_defined = FALSE;
2920 ti->class_desc = NULL;
2922 ti->infotext = getStringCopy(TREE_INFOTEXT(ti->type));
2924 if (ti->type == TREE_TYPE_LEVEL_DIR)
2926 ti->imported_from = NULL;
2927 ti->imported_by = NULL;
2928 ti->tested_by = NULL;
2930 ti->graphics_set_ecs = NULL;
2931 ti->graphics_set_aga = NULL;
2932 ti->graphics_set = NULL;
2933 ti->sounds_set_default = NULL;
2934 ti->sounds_set_lowpass = NULL;
2935 ti->sounds_set = NULL;
2936 ti->music_set = NULL;
2937 ti->graphics_path = getStringCopy(UNDEFINED_FILENAME);
2938 ti->sounds_path = getStringCopy(UNDEFINED_FILENAME);
2939 ti->music_path = getStringCopy(UNDEFINED_FILENAME);
2941 ti->level_filename = NULL;
2942 ti->level_filetype = NULL;
2944 ti->special_flags = NULL;
2946 ti->empty_level_name = NULL;
2947 ti->force_level_name = FALSE;
2950 ti->first_level = 0;
2952 ti->level_group = FALSE;
2953 ti->handicap_level = 0;
2954 ti->readonly = TRUE;
2955 ti->handicap = TRUE;
2956 ti->time_limit = TRUE;
2957 ti->skip_levels = FALSE;
2959 ti->use_emc_tiles = FALSE;
2960 ti->info_screens_from_main = FALSE;
2964 static void setTreeInfoToDefaultsFromParent(TreeInfo *ti, TreeInfo *parent)
2968 Warn("setTreeInfoToDefaultsFromParent(): parent == NULL");
2970 setTreeInfoToDefaults(ti, TREE_TYPE_UNDEFINED);
2975 // copy all values from the parent structure
2977 ti->type = parent->type;
2979 ti->node_top = parent->node_top;
2980 ti->node_parent = parent;
2981 ti->node_group = NULL;
2988 ti->fullpath = NULL;
2989 ti->basepath = NULL;
2990 ti->identifier = NULL;
2991 ti->name = getStringCopy(ANONYMOUS_NAME);
2992 ti->name_sorting = NULL;
2993 ti->author = getStringCopy(parent->author);
2994 ti->year = getStringCopy(parent->year);
2996 ti->program_title = getStringCopy(parent->program_title);
2997 ti->program_copyright = getStringCopy(parent->program_copyright);
2998 ti->program_company = getStringCopy(parent->program_company);
3000 ti->sort_priority = parent->sort_priority;
3001 ti->latest_engine = parent->latest_engine;
3002 ti->parent_link = FALSE;
3003 ti->is_copy = FALSE;
3004 ti->in_user_dir = parent->in_user_dir;
3005 ti->user_defined = parent->user_defined;
3006 ti->color = parent->color;
3007 ti->class_desc = getStringCopy(parent->class_desc);
3009 ti->infotext = getStringCopy(parent->infotext);
3011 if (ti->type == TREE_TYPE_LEVEL_DIR)
3013 ti->imported_from = getStringCopy(parent->imported_from);
3014 ti->imported_by = getStringCopy(parent->imported_by);
3015 ti->tested_by = getStringCopy(parent->tested_by);
3017 ti->graphics_set_ecs = getStringCopy(parent->graphics_set_ecs);
3018 ti->graphics_set_aga = getStringCopy(parent->graphics_set_aga);
3019 ti->graphics_set = getStringCopy(parent->graphics_set);
3020 ti->sounds_set_default = getStringCopy(parent->sounds_set_default);
3021 ti->sounds_set_lowpass = getStringCopy(parent->sounds_set_lowpass);
3022 ti->sounds_set = getStringCopy(parent->sounds_set);
3023 ti->music_set = getStringCopy(parent->music_set);
3024 ti->graphics_path = getStringCopy(UNDEFINED_FILENAME);
3025 ti->sounds_path = getStringCopy(UNDEFINED_FILENAME);
3026 ti->music_path = getStringCopy(UNDEFINED_FILENAME);
3028 ti->level_filename = getStringCopy(parent->level_filename);
3029 ti->level_filetype = getStringCopy(parent->level_filetype);
3031 ti->special_flags = getStringCopy(parent->special_flags);
3033 ti->empty_level_name = getStringCopy(parent->empty_level_name);
3034 ti->force_level_name = parent->force_level_name;
3036 ti->levels = parent->levels;
3037 ti->first_level = parent->first_level;
3038 ti->last_level = parent->last_level;
3039 ti->level_group = FALSE;
3040 ti->handicap_level = parent->handicap_level;
3041 ti->readonly = parent->readonly;
3042 ti->handicap = parent->handicap;
3043 ti->time_limit = parent->time_limit;
3044 ti->skip_levels = parent->skip_levels;
3046 ti->use_emc_tiles = parent->use_emc_tiles;
3047 ti->info_screens_from_main = parent->info_screens_from_main;
3051 static TreeInfo *getTreeInfoCopy(TreeInfo *ti)
3053 TreeInfo *ti_copy = newTreeInfo();
3055 // copy all values from the original structure
3057 ti_copy->type = ti->type;
3059 ti_copy->node_top = ti->node_top;
3060 ti_copy->node_parent = ti->node_parent;
3061 ti_copy->node_group = ti->node_group;
3062 ti_copy->next = ti->next;
3064 ti_copy->cl_first = ti->cl_first;
3065 ti_copy->cl_cursor = ti->cl_cursor;
3067 ti_copy->subdir = getStringCopy(ti->subdir);
3068 ti_copy->fullpath = getStringCopy(ti->fullpath);
3069 ti_copy->basepath = getStringCopy(ti->basepath);
3070 ti_copy->identifier = getStringCopy(ti->identifier);
3071 ti_copy->name = getStringCopy(ti->name);
3072 ti_copy->name_sorting = getStringCopy(ti->name_sorting);
3073 ti_copy->author = getStringCopy(ti->author);
3074 ti_copy->year = getStringCopy(ti->year);
3076 ti_copy->program_title = getStringCopy(ti->program_title);
3077 ti_copy->program_copyright = getStringCopy(ti->program_copyright);
3078 ti_copy->program_company = getStringCopy(ti->program_company);
3080 ti_copy->imported_from = getStringCopy(ti->imported_from);
3081 ti_copy->imported_by = getStringCopy(ti->imported_by);
3082 ti_copy->tested_by = getStringCopy(ti->tested_by);
3084 ti_copy->graphics_set_ecs = getStringCopy(ti->graphics_set_ecs);
3085 ti_copy->graphics_set_aga = getStringCopy(ti->graphics_set_aga);
3086 ti_copy->graphics_set = getStringCopy(ti->graphics_set);
3087 ti_copy->sounds_set_default = getStringCopy(ti->sounds_set_default);
3088 ti_copy->sounds_set_lowpass = getStringCopy(ti->sounds_set_lowpass);
3089 ti_copy->sounds_set = getStringCopy(ti->sounds_set);
3090 ti_copy->music_set = getStringCopy(ti->music_set);
3091 ti_copy->graphics_path = getStringCopy(ti->graphics_path);
3092 ti_copy->sounds_path = getStringCopy(ti->sounds_path);
3093 ti_copy->music_path = getStringCopy(ti->music_path);
3095 ti_copy->level_filename = getStringCopy(ti->level_filename);
3096 ti_copy->level_filetype = getStringCopy(ti->level_filetype);
3098 ti_copy->special_flags = getStringCopy(ti->special_flags);
3100 ti_copy->empty_level_name = getStringCopy(ti->empty_level_name);
3101 ti_copy->force_level_name = ti->force_level_name;
3103 ti_copy->levels = ti->levels;
3104 ti_copy->first_level = ti->first_level;
3105 ti_copy->last_level = ti->last_level;
3106 ti_copy->sort_priority = ti->sort_priority;
3108 ti_copy->latest_engine = ti->latest_engine;
3110 ti_copy->level_group = ti->level_group;
3111 ti_copy->parent_link = ti->parent_link;
3112 ti_copy->is_copy = ti->is_copy;
3113 ti_copy->in_user_dir = ti->in_user_dir;
3114 ti_copy->user_defined = ti->user_defined;
3115 ti_copy->readonly = ti->readonly;
3116 ti_copy->handicap = ti->handicap;
3117 ti_copy->time_limit = ti->time_limit;
3118 ti_copy->skip_levels = ti->skip_levels;
3120 ti_copy->use_emc_tiles = ti->use_emc_tiles;
3121 ti_copy->info_screens_from_main = ti->info_screens_from_main;
3123 ti_copy->color = ti->color;
3124 ti_copy->class_desc = getStringCopy(ti->class_desc);
3125 ti_copy->handicap_level = ti->handicap_level;
3127 ti_copy->infotext = getStringCopy(ti->infotext);
3132 void freeTreeInfo(TreeInfo *ti)
3137 checked_free(ti->subdir);
3138 checked_free(ti->fullpath);
3139 checked_free(ti->basepath);
3140 checked_free(ti->identifier);
3142 checked_free(ti->name);
3143 checked_free(ti->name_sorting);
3144 checked_free(ti->author);
3145 checked_free(ti->year);
3147 checked_free(ti->program_title);
3148 checked_free(ti->program_copyright);
3149 checked_free(ti->program_company);
3151 checked_free(ti->class_desc);
3153 checked_free(ti->infotext);
3155 if (ti->type == TREE_TYPE_LEVEL_DIR)
3157 checked_free(ti->imported_from);
3158 checked_free(ti->imported_by);
3159 checked_free(ti->tested_by);
3161 checked_free(ti->graphics_set_ecs);
3162 checked_free(ti->graphics_set_aga);
3163 checked_free(ti->graphics_set);
3164 checked_free(ti->sounds_set_default);
3165 checked_free(ti->sounds_set_lowpass);
3166 checked_free(ti->sounds_set);
3167 checked_free(ti->music_set);
3169 checked_free(ti->graphics_path);
3170 checked_free(ti->sounds_path);
3171 checked_free(ti->music_path);
3173 checked_free(ti->level_filename);
3174 checked_free(ti->level_filetype);
3176 checked_free(ti->special_flags);
3179 // recursively free child node
3181 freeTreeInfo(ti->node_group);
3183 // recursively free next node
3185 freeTreeInfo(ti->next);
3190 void setSetupInfo(struct TokenInfo *token_info,
3191 int token_nr, char *token_value)
3193 int token_type = token_info[token_nr].type;
3194 void *setup_value = token_info[token_nr].value;
3196 if (token_value == NULL)
3199 // set setup field to corresponding token value
3204 *(boolean *)setup_value = get_boolean_from_string(token_value);
3208 *(int *)setup_value = get_switch3_from_string(token_value);
3212 *(Key *)setup_value = getKeyFromKeyName(token_value);
3216 *(Key *)setup_value = getKeyFromX11KeyName(token_value);
3220 *(int *)setup_value = get_integer_from_string(token_value);
3224 checked_free(*(char **)setup_value);
3225 *(char **)setup_value = getStringCopy(token_value);
3229 *(int *)setup_value = get_player_nr_from_string(token_value);
3237 static int compareTreeInfoEntries(const void *object1, const void *object2)
3239 const TreeInfo *entry1 = *((TreeInfo **)object1);
3240 const TreeInfo *entry2 = *((TreeInfo **)object2);
3241 int tree_sorting1 = TREE_SORTING(entry1);
3242 int tree_sorting2 = TREE_SORTING(entry2);
3244 if (tree_sorting1 != tree_sorting2)
3245 return (tree_sorting1 - tree_sorting2);
3247 return strcasecmp(entry1->name_sorting, entry2->name_sorting);
3250 static TreeInfo *createParentTreeInfoNode(TreeInfo *node_parent)
3254 if (node_parent == NULL)
3257 ti_new = newTreeInfo();
3258 setTreeInfoToDefaults(ti_new, node_parent->type);
3260 ti_new->node_parent = node_parent;
3261 ti_new->parent_link = TRUE;
3263 setString(&ti_new->identifier, node_parent->identifier);
3264 setString(&ti_new->name, BACKLINK_TEXT_PARENT);
3265 setString(&ti_new->name_sorting, ti_new->name);
3267 setString(&ti_new->subdir, STRING_PARENT_DIRECTORY);
3268 setString(&ti_new->fullpath, node_parent->fullpath);
3270 ti_new->sort_priority = LEVELCLASS_PARENT;
3271 ti_new->latest_engine = node_parent->latest_engine;
3273 setString(&ti_new->class_desc, getLevelClassDescription(ti_new));
3275 pushTreeInfo(&node_parent->node_group, ti_new);
3280 static TreeInfo *createTopTreeInfoNode(TreeInfo *node_first)
3282 if (node_first == NULL)
3285 TreeInfo *ti_new = newTreeInfo();
3286 int type = node_first->type;
3288 setTreeInfoToDefaults(ti_new, type);
3290 ti_new->node_parent = NULL;
3291 ti_new->parent_link = FALSE;
3293 setString(&ti_new->identifier, "top_tree_node");
3294 setString(&ti_new->name, TREE_INFOTEXT(type));
3295 setString(&ti_new->name_sorting, ti_new->name);
3297 setString(&ti_new->subdir, STRING_TOP_DIRECTORY);
3298 setString(&ti_new->fullpath, ".");
3300 ti_new->sort_priority = LEVELCLASS_TOP;
3301 ti_new->latest_engine = node_first->latest_engine;
3303 setString(&ti_new->class_desc, TREE_INFOTEXT(type));
3305 ti_new->node_group = node_first;
3306 ti_new->level_group = TRUE;
3308 TreeInfo *ti_new2 = createParentTreeInfoNode(ti_new);
3310 setString(&ti_new2->name, TREE_BACKLINK_TEXT(type));
3311 setString(&ti_new2->name_sorting, ti_new2->name);
3316 static void setTreeInfoParentNodes(TreeInfo *node, TreeInfo *node_parent)
3320 if (node->node_group)
3321 setTreeInfoParentNodes(node->node_group, node);
3323 node->node_parent = node_parent;
3329 TreeInfo *addTopTreeInfoNode(TreeInfo *node_first)
3331 // add top tree node with back link node in previous tree
3332 node_first = createTopTreeInfoNode(node_first);
3334 // set all parent links (back links) in complete tree
3335 setTreeInfoParentNodes(node_first, NULL);
3341 // ----------------------------------------------------------------------------
3342 // functions for handling level and custom artwork info cache
3343 // ----------------------------------------------------------------------------
3345 static void LoadArtworkInfoCache(void)
3347 InitCacheDirectory();
3349 if (artworkinfo_cache_old == NULL)
3351 char *filename = getPath2(getCacheDir(), ARTWORKINFO_CACHE_FILE);
3353 // try to load artwork info hash from already existing cache file
3354 artworkinfo_cache_old = loadSetupFileHash(filename);
3356 // try to get program version that artwork info cache was written with
3357 char *version = getHashEntry(artworkinfo_cache_old, "program.version");
3359 // check program version of artwork info cache against current version
3360 if (!strEqual(version, program.version_string))
3362 freeSetupFileHash(artworkinfo_cache_old);
3364 artworkinfo_cache_old = NULL;
3367 // if no artwork info cache file was found, start with empty hash
3368 if (artworkinfo_cache_old == NULL)
3369 artworkinfo_cache_old = newSetupFileHash();
3374 if (artworkinfo_cache_new == NULL)
3375 artworkinfo_cache_new = newSetupFileHash();
3377 update_artworkinfo_cache = FALSE;
3380 static void SaveArtworkInfoCache(void)
3382 if (!update_artworkinfo_cache)
3385 char *filename = getPath2(getCacheDir(), ARTWORKINFO_CACHE_FILE);
3387 InitCacheDirectory();
3389 saveSetupFileHash(artworkinfo_cache_new, filename);
3394 static char *getCacheTokenPrefix(char *prefix1, char *prefix2)
3396 static char *prefix = NULL;
3398 checked_free(prefix);
3400 prefix = getStringCat2WithSeparator(prefix1, prefix2, ".");
3405 // (identical to above function, but separate string buffer needed -- nasty)
3406 static char *getCacheToken(char *prefix, char *suffix)
3408 static char *token = NULL;
3410 checked_free(token);
3412 token = getStringCat2WithSeparator(prefix, suffix, ".");
3417 static char *getFileTimestampString(char *filename)
3419 return getStringCopy(i_to_a(getFileTimestampEpochSeconds(filename)));
3422 static boolean modifiedFileTimestamp(char *filename, char *timestamp_string)
3424 struct stat file_status;
3426 if (timestamp_string == NULL)
3429 if (!fileExists(filename)) // file does not exist
3430 return (atoi(timestamp_string) != 0);
3432 if (stat(filename, &file_status) != 0) // cannot stat file
3435 return (file_status.st_mtime != atoi(timestamp_string));
3438 static TreeInfo *getArtworkInfoCacheEntry(LevelDirTree *level_node, int type)
3440 char *identifier = level_node->subdir;
3441 char *type_string = ARTWORK_DIRECTORY(type);
3442 char *token_prefix = getCacheTokenPrefix(type_string, identifier);
3443 char *token_main = getCacheToken(token_prefix, "CACHED");
3444 char *cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
3445 boolean cached = (cache_entry != NULL && strEqual(cache_entry, "true"));
3446 TreeInfo *artwork_info = NULL;
3448 if (!use_artworkinfo_cache)
3451 if (optional_tokens_hash == NULL)
3455 // create hash from list of optional tokens (for quick access)
3456 optional_tokens_hash = newSetupFileHash();
3457 for (i = 0; optional_tokens[i] != NULL; i++)
3458 setHashEntry(optional_tokens_hash, optional_tokens[i], "");
3465 artwork_info = newTreeInfo();
3466 setTreeInfoToDefaults(artwork_info, type);
3468 // set all structure fields according to the token/value pairs
3469 ldi = *artwork_info;
3470 for (i = 0; artworkinfo_tokens[i].type != -1; i++)
3472 char *token_suffix = artworkinfo_tokens[i].text;
3473 char *token = getCacheToken(token_prefix, token_suffix);
3474 char *value = getHashEntry(artworkinfo_cache_old, token);
3476 (getHashEntry(optional_tokens_hash, token_suffix) != NULL);
3478 setSetupInfo(artworkinfo_tokens, i, value);
3480 // check if cache entry for this item is mandatory, but missing
3481 if (value == NULL && !optional)
3483 Warn("missing cache entry '%s'", token);
3489 *artwork_info = ldi;
3494 char *filename_levelinfo = getPath2(getLevelDirFromTreeInfo(level_node),
3495 LEVELINFO_FILENAME);
3496 char *filename_artworkinfo = getPath2(getSetupArtworkDir(artwork_info),
3497 ARTWORKINFO_FILENAME(type));
3499 // check if corresponding "levelinfo.conf" file has changed
3500 token_main = getCacheToken(token_prefix, "TIMESTAMP_LEVELINFO");
3501 cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
3503 if (modifiedFileTimestamp(filename_levelinfo, cache_entry))
3506 // check if corresponding "<artworkinfo>.conf" file has changed
3507 token_main = getCacheToken(token_prefix, "TIMESTAMP_ARTWORKINFO");
3508 cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
3510 if (modifiedFileTimestamp(filename_artworkinfo, cache_entry))
3513 checked_free(filename_levelinfo);
3514 checked_free(filename_artworkinfo);
3517 if (!cached && artwork_info != NULL)
3519 freeTreeInfo(artwork_info);
3524 return artwork_info;
3527 static void setArtworkInfoCacheEntry(TreeInfo *artwork_info,
3528 LevelDirTree *level_node, int type)
3530 char *identifier = level_node->subdir;
3531 char *type_string = ARTWORK_DIRECTORY(type);
3532 char *token_prefix = getCacheTokenPrefix(type_string, identifier);
3533 char *token_main = getCacheToken(token_prefix, "CACHED");
3534 boolean set_cache_timestamps = TRUE;
3537 setHashEntry(artworkinfo_cache_new, token_main, "true");
3539 if (set_cache_timestamps)
3541 char *filename_levelinfo = getPath2(getLevelDirFromTreeInfo(level_node),
3542 LEVELINFO_FILENAME);
3543 char *filename_artworkinfo = getPath2(getSetupArtworkDir(artwork_info),
3544 ARTWORKINFO_FILENAME(type));
3545 char *timestamp_levelinfo = getFileTimestampString(filename_levelinfo);
3546 char *timestamp_artworkinfo = getFileTimestampString(filename_artworkinfo);
3548 token_main = getCacheToken(token_prefix, "TIMESTAMP_LEVELINFO");
3549 setHashEntry(artworkinfo_cache_new, token_main, timestamp_levelinfo);
3551 token_main = getCacheToken(token_prefix, "TIMESTAMP_ARTWORKINFO");
3552 setHashEntry(artworkinfo_cache_new, token_main, timestamp_artworkinfo);
3554 checked_free(filename_levelinfo);
3555 checked_free(filename_artworkinfo);
3556 checked_free(timestamp_levelinfo);
3557 checked_free(timestamp_artworkinfo);
3560 ldi = *artwork_info;
3561 for (i = 0; artworkinfo_tokens[i].type != -1; i++)
3563 char *token = getCacheToken(token_prefix, artworkinfo_tokens[i].text);
3564 char *value = getSetupValue(artworkinfo_tokens[i].type,
3565 artworkinfo_tokens[i].value);
3567 setHashEntry(artworkinfo_cache_new, token, value);
3572 // ----------------------------------------------------------------------------
3573 // functions for loading level info and custom artwork info
3574 // ----------------------------------------------------------------------------
3576 int GetZipFileTreeType(char *zip_filename)
3578 static char *top_dir_path = NULL;
3579 static char *top_dir_conf_filename[NUM_BASE_TREE_TYPES] = { NULL };
3580 static char *conf_basename[NUM_BASE_TREE_TYPES] =
3582 GRAPHICSINFO_FILENAME,
3583 SOUNDSINFO_FILENAME,
3589 checked_free(top_dir_path);
3590 top_dir_path = NULL;
3592 for (j = 0; j < NUM_BASE_TREE_TYPES; j++)
3594 checked_free(top_dir_conf_filename[j]);
3595 top_dir_conf_filename[j] = NULL;
3598 char **zip_entries = zip_list(zip_filename);
3600 // check if zip file successfully opened
3601 if (zip_entries == NULL || zip_entries[0] == NULL)
3602 return TREE_TYPE_UNDEFINED;
3604 // first zip file entry is expected to be top level directory
3605 char *top_dir = zip_entries[0];
3607 // check if valid top level directory found in zip file
3608 if (!strSuffix(top_dir, "/"))
3609 return TREE_TYPE_UNDEFINED;
3611 // get filenames of valid configuration files in top level directory
3612 for (j = 0; j < NUM_BASE_TREE_TYPES; j++)
3613 top_dir_conf_filename[j] = getStringCat2(top_dir, conf_basename[j]);
3615 int tree_type = TREE_TYPE_UNDEFINED;
3618 while (zip_entries[e] != NULL)
3620 // check if every zip file entry is below top level directory
3621 if (!strPrefix(zip_entries[e], top_dir))
3622 return TREE_TYPE_UNDEFINED;
3624 // check if this zip file entry is a valid configuration filename
3625 for (j = 0; j < NUM_BASE_TREE_TYPES; j++)
3627 if (strEqual(zip_entries[e], top_dir_conf_filename[j]))
3629 // only exactly one valid configuration file allowed
3630 if (tree_type != TREE_TYPE_UNDEFINED)
3631 return TREE_TYPE_UNDEFINED;
3643 static boolean CheckZipFileForDirectory(char *zip_filename, char *directory,
3646 static char *top_dir_path = NULL;
3647 static char *top_dir_conf_filename = NULL;
3649 checked_free(top_dir_path);
3650 checked_free(top_dir_conf_filename);
3652 top_dir_path = NULL;
3653 top_dir_conf_filename = NULL;
3655 char *conf_basename = (tree_type == TREE_TYPE_LEVEL_DIR ? LEVELINFO_FILENAME :
3656 ARTWORKINFO_FILENAME(tree_type));
3658 // check if valid configuration filename determined
3659 if (conf_basename == NULL || strEqual(conf_basename, ""))
3662 char **zip_entries = zip_list(zip_filename);
3664 // check if zip file successfully opened
3665 if (zip_entries == NULL || zip_entries[0] == NULL)
3668 // first zip file entry is expected to be top level directory
3669 char *top_dir = zip_entries[0];
3671 // check if valid top level directory found in zip file
3672 if (!strSuffix(top_dir, "/"))
3675 // get path of extracted top level directory
3676 top_dir_path = getPath2(directory, top_dir);
3678 // remove trailing directory separator from top level directory path
3679 // (required to be able to check for file and directory in next step)
3680 top_dir_path[strlen(top_dir_path) - 1] = '\0';
3682 // check if zip file's top level directory already exists in target directory
3683 if (fileExists(top_dir_path)) // (checks for file and directory)
3686 // get filename of configuration file in top level directory
3687 top_dir_conf_filename = getStringCat2(top_dir, conf_basename);
3689 boolean found_top_dir_conf_filename = FALSE;
3692 while (zip_entries[i] != NULL)
3694 // check if every zip file entry is below top level directory
3695 if (!strPrefix(zip_entries[i], top_dir))
3698 // check if this zip file entry is the configuration filename
3699 if (strEqual(zip_entries[i], top_dir_conf_filename))
3700 found_top_dir_conf_filename = TRUE;
3705 // check if valid configuration filename was found in zip file
3706 if (!found_top_dir_conf_filename)
3712 char *ExtractZipFileIntoDirectory(char *zip_filename, char *directory,
3715 boolean zip_file_valid = CheckZipFileForDirectory(zip_filename, directory,
3718 if (!zip_file_valid)
3720 Warn("zip file '%s' rejected!", zip_filename);
3725 char **zip_entries = zip_extract(zip_filename, directory);
3727 if (zip_entries == NULL)
3729 Warn("zip file '%s' could not be extracted!", zip_filename);
3734 Info("zip file '%s' successfully extracted!", zip_filename);
3736 // first zip file entry contains top level directory
3737 char *top_dir = zip_entries[0];
3739 // remove trailing directory separator from top level directory
3740 top_dir[strlen(top_dir) - 1] = '\0';
3745 static void ProcessZipFilesInDirectory(char *directory, int tree_type)
3748 DirectoryEntry *dir_entry;
3750 if ((dir = openDirectory(directory)) == NULL)
3752 // display error if directory is main "options.graphics_directory" etc.
3753 if (tree_type == TREE_TYPE_LEVEL_DIR ||
3754 directory == OPTIONS_ARTWORK_DIRECTORY(tree_type))
3755 Warn("cannot read directory '%s'", directory);
3760 while ((dir_entry = readDirectory(dir)) != NULL) // loop all entries
3762 // skip non-zip files (and also directories with zip extension)
3763 if (!strSuffixLower(dir_entry->basename, ".zip") || dir_entry->is_directory)
3766 char *zip_filename = getPath2(directory, dir_entry->basename);
3767 char *zip_filename_extracted = getStringCat2(zip_filename, ".extracted");
3768 char *zip_filename_rejected = getStringCat2(zip_filename, ".rejected");
3770 // check if zip file hasn't already been extracted or rejected
3771 if (!fileExists(zip_filename_extracted) &&
3772 !fileExists(zip_filename_rejected))
3774 char *top_dir = ExtractZipFileIntoDirectory(zip_filename, directory,
3776 char *marker_filename = (top_dir != NULL ? zip_filename_extracted :
3777 zip_filename_rejected);
3780 // create empty file to mark zip file as extracted or rejected
3781 if ((marker_file = fopen(marker_filename, MODE_WRITE)))
3782 fclose(marker_file);
3785 free(zip_filename_extracted);
3786 free(zip_filename_rejected);
3790 closeDirectory(dir);
3793 // forward declaration for recursive call by "LoadLevelInfoFromLevelDir()"
3794 static void LoadLevelInfoFromLevelDir(TreeInfo **, TreeInfo *, char *);
3796 static boolean LoadLevelInfoFromLevelConf(TreeInfo **node_first,
3797 TreeInfo *node_parent,
3798 char *level_directory,
3799 char *directory_name)
3801 char *directory_path = getPath2(level_directory, directory_name);
3802 char *filename = getPath2(directory_path, LEVELINFO_FILENAME);
3803 SetupFileHash *setup_file_hash;
3804 LevelDirTree *leveldir_new = NULL;
3807 // unless debugging, silently ignore directories without "levelinfo.conf"
3808 if (!options.debug && !fileExists(filename))
3810 free(directory_path);
3816 setup_file_hash = loadSetupFileHash(filename);
3818 if (setup_file_hash == NULL)
3820 #if DEBUG_NO_CONFIG_FILE
3821 Debug("setup", "ignoring level directory '%s'", directory_path);
3824 free(directory_path);
3830 leveldir_new = newTreeInfo();
3833 setTreeInfoToDefaultsFromParent(leveldir_new, node_parent);
3835 setTreeInfoToDefaults(leveldir_new, TREE_TYPE_LEVEL_DIR);
3837 leveldir_new->subdir = getStringCopy(directory_name);
3839 // set all structure fields according to the token/value pairs
3840 ldi = *leveldir_new;
3841 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
3842 setSetupInfo(levelinfo_tokens, i,
3843 getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
3844 *leveldir_new = ldi;
3846 if (strEqual(leveldir_new->name, ANONYMOUS_NAME))
3847 setString(&leveldir_new->name, leveldir_new->subdir);
3849 if (leveldir_new->identifier == NULL)
3850 leveldir_new->identifier = getStringCopy(leveldir_new->subdir);
3852 if (leveldir_new->name_sorting == NULL)
3853 leveldir_new->name_sorting = getStringCopy(leveldir_new->name);
3855 if (node_parent == NULL) // top level group
3857 leveldir_new->basepath = getStringCopy(level_directory);
3858 leveldir_new->fullpath = getStringCopy(leveldir_new->subdir);
3860 else // sub level group
3862 leveldir_new->basepath = getStringCopy(node_parent->basepath);
3863 leveldir_new->fullpath = getPath2(node_parent->fullpath, directory_name);
3866 leveldir_new->last_level =
3867 leveldir_new->first_level + leveldir_new->levels - 1;
3869 leveldir_new->in_user_dir =
3870 (!strEqual(leveldir_new->basepath, options.level_directory));
3872 // adjust some settings if user's private level directory was detected
3873 if (leveldir_new->sort_priority == LEVELCLASS_UNDEFINED &&
3874 leveldir_new->in_user_dir &&
3875 (strEqual(leveldir_new->subdir, getLoginName()) ||
3876 strEqual(leveldir_new->name, getLoginName()) ||
3877 strEqual(leveldir_new->author, getRealName())))
3879 leveldir_new->sort_priority = LEVELCLASS_PRIVATE_START;
3880 leveldir_new->readonly = FALSE;
3883 leveldir_new->user_defined =
3884 (leveldir_new->in_user_dir && IS_LEVELCLASS_PRIVATE(leveldir_new));
3886 setString(&leveldir_new->class_desc, getLevelClassDescription(leveldir_new));
3888 leveldir_new->handicap_level = // set handicap to default value
3889 (leveldir_new->user_defined || !leveldir_new->handicap ?
3890 leveldir_new->last_level : leveldir_new->first_level);
3892 DrawInitTextItem(leveldir_new->name);
3894 pushTreeInfo(node_first, leveldir_new);
3896 freeSetupFileHash(setup_file_hash);
3898 if (leveldir_new->level_group)
3900 // create node to link back to current level directory
3901 createParentTreeInfoNode(leveldir_new);
3903 // recursively step into sub-directory and look for more level series
3904 LoadLevelInfoFromLevelDir(&leveldir_new->node_group,
3905 leveldir_new, directory_path);
3908 free(directory_path);
3914 static void LoadLevelInfoFromLevelDir(TreeInfo **node_first,
3915 TreeInfo *node_parent,
3916 char *level_directory)
3918 // ---------- 1st stage: process any level set zip files ----------
3920 ProcessZipFilesInDirectory(level_directory, TREE_TYPE_LEVEL_DIR);
3922 // ---------- 2nd stage: check for level set directories ----------
3925 DirectoryEntry *dir_entry;
3926 boolean valid_entry_found = FALSE;
3928 if ((dir = openDirectory(level_directory)) == NULL)
3930 Warn("cannot read level directory '%s'", level_directory);
3935 while ((dir_entry = readDirectory(dir)) != NULL) // loop all entries
3937 char *directory_name = dir_entry->basename;
3938 char *directory_path = getPath2(level_directory, directory_name);
3940 // skip entries for current and parent directory
3941 if (strEqual(directory_name, ".") ||
3942 strEqual(directory_name, ".."))
3944 free(directory_path);
3949 // find out if directory entry is itself a directory
3950 if (!dir_entry->is_directory) // not a directory
3952 free(directory_path);
3957 free(directory_path);
3959 if (strEqual(directory_name, GRAPHICS_DIRECTORY) ||
3960 strEqual(directory_name, SOUNDS_DIRECTORY) ||
3961 strEqual(directory_name, MUSIC_DIRECTORY))
3964 valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
3969 closeDirectory(dir);
3971 // special case: top level directory may directly contain "levelinfo.conf"
3972 if (node_parent == NULL && !valid_entry_found)
3974 // check if this directory directly contains a file "levelinfo.conf"
3975 valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
3976 level_directory, ".");
3979 boolean valid_entry_expected =
3980 (strEqual(level_directory, options.level_directory) ||
3981 setup.internal.create_user_levelset);
3983 if (valid_entry_expected && !valid_entry_found)
3984 Warn("cannot find any valid level series in directory '%s'",
3988 boolean AdjustGraphicsForEMC(void)
3990 boolean settings_changed = FALSE;
3992 settings_changed |= adjustTreeGraphicsForEMC(leveldir_first_all);
3993 settings_changed |= adjustTreeGraphicsForEMC(leveldir_first);
3995 return settings_changed;
3998 boolean AdjustSoundsForEMC(void)
4000 boolean settings_changed = FALSE;
4002 settings_changed |= adjustTreeSoundsForEMC(leveldir_first_all);
4003 settings_changed |= adjustTreeSoundsForEMC(leveldir_first);
4005 return settings_changed;
4008 void LoadLevelInfo(void)
4010 InitUserLevelDirectory(getLoginName());
4012 DrawInitTextHead("Loading level series");
4014 LoadLevelInfoFromLevelDir(&leveldir_first, NULL, options.level_directory);
4015 LoadLevelInfoFromLevelDir(&leveldir_first, NULL, getUserLevelDir(NULL));
4017 leveldir_first = createTopTreeInfoNode(leveldir_first);
4019 /* after loading all level set information, clone the level directory tree
4020 and remove all level sets without levels (these may still contain artwork
4021 to be offered in the setup menu as "custom artwork", and are therefore
4022 checked for existing artwork in the function "LoadLevelArtworkInfo()") */
4023 leveldir_first_all = leveldir_first;
4024 cloneTree(&leveldir_first, leveldir_first_all, TRUE);
4026 AdjustGraphicsForEMC();
4027 AdjustSoundsForEMC();
4029 // before sorting, the first entries will be from the user directory
4030 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
4032 if (leveldir_first == NULL)
4033 Fail("cannot find any valid level series in any directory");
4035 sortTreeInfo(&leveldir_first);
4037 #if ENABLE_UNUSED_CODE
4038 dumpTreeInfo(leveldir_first, 0);
4042 static boolean LoadArtworkInfoFromArtworkConf(TreeInfo **node_first,
4043 TreeInfo *node_parent,
4044 char *base_directory,
4045 char *directory_name, int type)
4047 char *directory_path = getPath2(base_directory, directory_name);
4048 char *filename = getPath2(directory_path, ARTWORKINFO_FILENAME(type));
4049 SetupFileHash *setup_file_hash = NULL;
4050 TreeInfo *artwork_new = NULL;
4053 if (fileExists(filename))
4054 setup_file_hash = loadSetupFileHash(filename);
4056 if (setup_file_hash == NULL) // no config file -- look for artwork files
4059 DirectoryEntry *dir_entry;
4060 boolean valid_file_found = FALSE;
4062 if ((dir = openDirectory(directory_path)) != NULL)
4064 while ((dir_entry = readDirectory(dir)) != NULL)
4066 if (FileIsArtworkType(dir_entry->filename, type))
4068 valid_file_found = TRUE;
4074 closeDirectory(dir);
4077 if (!valid_file_found)
4079 #if DEBUG_NO_CONFIG_FILE
4080 if (!strEqual(directory_name, "."))
4081 Debug("setup", "ignoring artwork directory '%s'", directory_path);
4084 free(directory_path);
4091 artwork_new = newTreeInfo();
4094 setTreeInfoToDefaultsFromParent(artwork_new, node_parent);
4096 setTreeInfoToDefaults(artwork_new, type);
4098 artwork_new->subdir = getStringCopy(directory_name);
4100 if (setup_file_hash) // (before defining ".color" and ".class_desc")
4102 // set all structure fields according to the token/value pairs
4104 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
4105 setSetupInfo(levelinfo_tokens, i,
4106 getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
4109 if (strEqual(artwork_new->name, ANONYMOUS_NAME))
4110 setString(&artwork_new->name, artwork_new->subdir);
4112 if (artwork_new->identifier == NULL)
4113 artwork_new->identifier = getStringCopy(artwork_new->subdir);
4115 if (artwork_new->name_sorting == NULL)
4116 artwork_new->name_sorting = getStringCopy(artwork_new->name);
4119 if (node_parent == NULL) // top level group
4121 artwork_new->basepath = getStringCopy(base_directory);
4122 artwork_new->fullpath = getStringCopy(artwork_new->subdir);
4124 else // sub level group
4126 artwork_new->basepath = getStringCopy(node_parent->basepath);
4127 artwork_new->fullpath = getPath2(node_parent->fullpath, directory_name);
4130 artwork_new->in_user_dir =
4131 (!strEqual(artwork_new->basepath, OPTIONS_ARTWORK_DIRECTORY(type)));
4133 setString(&artwork_new->class_desc, getLevelClassDescription(artwork_new));
4135 if (setup_file_hash == NULL) // (after determining ".user_defined")
4137 if (strEqual(artwork_new->subdir, "."))
4139 if (artwork_new->user_defined)
4141 setString(&artwork_new->identifier, "private");
4142 artwork_new->sort_priority = ARTWORKCLASS_PRIVATE;
4146 setString(&artwork_new->identifier, "classic");
4147 artwork_new->sort_priority = ARTWORKCLASS_CLASSICS;
4150 setString(&artwork_new->class_desc,
4151 getLevelClassDescription(artwork_new));
4155 setString(&artwork_new->identifier, artwork_new->subdir);
4158 setString(&artwork_new->name, artwork_new->identifier);
4159 setString(&artwork_new->name_sorting, artwork_new->name);
4162 pushTreeInfo(node_first, artwork_new);
4164 freeSetupFileHash(setup_file_hash);
4166 free(directory_path);
4172 static void LoadArtworkInfoFromArtworkDir(TreeInfo **node_first,
4173 TreeInfo *node_parent,
4174 char *base_directory, int type)
4176 // ---------- 1st stage: process any artwork set zip files ----------
4178 ProcessZipFilesInDirectory(base_directory, type);
4180 // ---------- 2nd stage: check for artwork set directories ----------
4183 DirectoryEntry *dir_entry;
4184 boolean valid_entry_found = FALSE;
4186 if ((dir = openDirectory(base_directory)) == NULL)
4188 // display error if directory is main "options.graphics_directory" etc.
4189 if (base_directory == OPTIONS_ARTWORK_DIRECTORY(type))
4190 Warn("cannot read directory '%s'", base_directory);
4195 while ((dir_entry = readDirectory(dir)) != NULL) // loop all entries
4197 char *directory_name = dir_entry->basename;
4198 char *directory_path = getPath2(base_directory, directory_name);
4200 // skip directory entries for current and parent directory
4201 if (strEqual(directory_name, ".") ||
4202 strEqual(directory_name, ".."))
4204 free(directory_path);
4209 // skip directory entries which are not a directory
4210 if (!dir_entry->is_directory) // not a directory
4212 free(directory_path);
4217 free(directory_path);
4219 // check if this directory contains artwork with or without config file
4220 valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
4222 directory_name, type);
4225 closeDirectory(dir);
4227 // check if this directory directly contains artwork itself
4228 valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
4229 base_directory, ".",
4231 if (!valid_entry_found)
4232 Warn("cannot find any valid artwork in directory '%s'", base_directory);
4235 static TreeInfo *getDummyArtworkInfo(int type)
4237 // this is only needed when there is completely no artwork available
4238 TreeInfo *artwork_new = newTreeInfo();
4240 setTreeInfoToDefaults(artwork_new, type);
4242 setString(&artwork_new->subdir, UNDEFINED_FILENAME);
4243 setString(&artwork_new->fullpath, UNDEFINED_FILENAME);
4244 setString(&artwork_new->basepath, UNDEFINED_FILENAME);
4246 setString(&artwork_new->identifier, UNDEFINED_FILENAME);
4247 setString(&artwork_new->name, UNDEFINED_FILENAME);
4248 setString(&artwork_new->name_sorting, UNDEFINED_FILENAME);
4253 void SetCurrentArtwork(int type)
4255 ArtworkDirTree **current_ptr = ARTWORK_CURRENT_PTR(artwork, type);
4256 ArtworkDirTree *first_node = ARTWORK_FIRST_NODE(artwork, type);
4257 char *setup_set = SETUP_ARTWORK_SET(setup, type);
4258 char *default_subdir = ARTWORK_DEFAULT_SUBDIR(type);
4260 // set current artwork to artwork configured in setup menu
4261 *current_ptr = getTreeInfoFromIdentifier(first_node, setup_set);
4263 // if not found, set current artwork to default artwork
4264 if (*current_ptr == NULL)
4265 *current_ptr = getTreeInfoFromIdentifier(first_node, default_subdir);
4267 // if not found, set current artwork to first artwork in tree
4268 if (*current_ptr == NULL)
4269 *current_ptr = getFirstValidTreeInfoEntry(first_node);
4272 void ChangeCurrentArtworkIfNeeded(int type)
4274 char *current_identifier = ARTWORK_CURRENT_IDENTIFIER(artwork, type);
4275 char *setup_set = SETUP_ARTWORK_SET(setup, type);
4277 if (!strEqual(current_identifier, setup_set))
4278 SetCurrentArtwork(type);
4281 void LoadArtworkInfo(void)
4283 LoadArtworkInfoCache();
4285 DrawInitTextHead("Looking for custom artwork");
4287 LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
4288 options.graphics_directory,
4289 TREE_TYPE_GRAPHICS_DIR);
4290 LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
4291 getUserGraphicsDir(),
4292 TREE_TYPE_GRAPHICS_DIR);
4294 LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
4295 options.sounds_directory,
4296 TREE_TYPE_SOUNDS_DIR);
4297 LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
4299 TREE_TYPE_SOUNDS_DIR);
4301 LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
4302 options.music_directory,
4303 TREE_TYPE_MUSIC_DIR);
4304 LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
4306 TREE_TYPE_MUSIC_DIR);
4308 if (artwork.gfx_first == NULL)
4309 artwork.gfx_first = getDummyArtworkInfo(TREE_TYPE_GRAPHICS_DIR);
4310 if (artwork.snd_first == NULL)
4311 artwork.snd_first = getDummyArtworkInfo(TREE_TYPE_SOUNDS_DIR);
4312 if (artwork.mus_first == NULL)
4313 artwork.mus_first = getDummyArtworkInfo(TREE_TYPE_MUSIC_DIR);
4315 // before sorting, the first entries will be from the user directory
4316 SetCurrentArtwork(ARTWORK_TYPE_GRAPHICS);
4317 SetCurrentArtwork(ARTWORK_TYPE_SOUNDS);
4318 SetCurrentArtwork(ARTWORK_TYPE_MUSIC);
4320 artwork.gfx_current_identifier = artwork.gfx_current->identifier;
4321 artwork.snd_current_identifier = artwork.snd_current->identifier;
4322 artwork.mus_current_identifier = artwork.mus_current->identifier;
4324 #if ENABLE_UNUSED_CODE
4325 Debug("setup:LoadArtworkInfo", "graphics set == %s",
4326 artwork.gfx_current_identifier);
4327 Debug("setup:LoadArtworkInfo", "sounds set == %s",
4328 artwork.snd_current_identifier);
4329 Debug("setup:LoadArtworkInfo", "music set == %s",
4330 artwork.mus_current_identifier);
4333 sortTreeInfo(&artwork.gfx_first);
4334 sortTreeInfo(&artwork.snd_first);
4335 sortTreeInfo(&artwork.mus_first);
4337 #if ENABLE_UNUSED_CODE
4338 dumpTreeInfo(artwork.gfx_first, 0);
4339 dumpTreeInfo(artwork.snd_first, 0);
4340 dumpTreeInfo(artwork.mus_first, 0);
4344 static void MoveArtworkInfoIntoSubTree(ArtworkDirTree **artwork_node)
4346 ArtworkDirTree *artwork_new = newTreeInfo();
4347 char *top_node_name = "standalone artwork";
4349 setTreeInfoToDefaults(artwork_new, (*artwork_node)->type);
4351 artwork_new->level_group = TRUE;
4353 setString(&artwork_new->identifier, top_node_name);
4354 setString(&artwork_new->name, top_node_name);
4355 setString(&artwork_new->name_sorting, top_node_name);
4357 // create node to link back to current custom artwork directory
4358 createParentTreeInfoNode(artwork_new);
4360 // move existing custom artwork tree into newly created sub-tree
4361 artwork_new->node_group->next = *artwork_node;
4363 // change custom artwork tree to contain only newly created node
4364 *artwork_node = artwork_new;
4367 static void LoadArtworkInfoFromLevelInfoExt(ArtworkDirTree **artwork_node,
4368 ArtworkDirTree *node_parent,
4369 LevelDirTree *level_node,
4370 boolean empty_level_set_mode)
4372 int type = (*artwork_node)->type;
4374 // recursively check all level directories for artwork sub-directories
4378 boolean empty_level_set = (level_node->levels == 0);
4380 // check all tree entries for artwork, but skip parent link entries
4381 if (!level_node->parent_link && empty_level_set == empty_level_set_mode)
4383 TreeInfo *artwork_new = getArtworkInfoCacheEntry(level_node, type);
4384 boolean cached = (artwork_new != NULL);
4388 pushTreeInfo(artwork_node, artwork_new);
4392 TreeInfo *topnode_last = *artwork_node;
4393 char *path = getPath2(getLevelDirFromTreeInfo(level_node),
4394 ARTWORK_DIRECTORY(type));
4396 LoadArtworkInfoFromArtworkDir(artwork_node, NULL, path, type);
4398 if (topnode_last != *artwork_node) // check for newly added node
4400 artwork_new = *artwork_node;
4402 setString(&artwork_new->identifier, level_node->subdir);
4403 setString(&artwork_new->name, level_node->name);
4404 setString(&artwork_new->name_sorting, level_node->name_sorting);
4406 artwork_new->sort_priority = level_node->sort_priority;
4407 artwork_new->in_user_dir = level_node->in_user_dir;
4409 update_artworkinfo_cache = TRUE;
4415 // insert artwork info (from old cache or filesystem) into new cache
4416 if (artwork_new != NULL)
4417 setArtworkInfoCacheEntry(artwork_new, level_node, type);
4420 DrawInitTextItem(level_node->name);
4422 if (level_node->node_group != NULL)
4424 TreeInfo *artwork_new = newTreeInfo();
4427 setTreeInfoToDefaultsFromParent(artwork_new, node_parent);
4429 setTreeInfoToDefaults(artwork_new, type);
4431 artwork_new->level_group = TRUE;
4433 setString(&artwork_new->identifier, level_node->subdir);
4435 if (node_parent == NULL) // check for top tree node
4437 char *top_node_name = (empty_level_set_mode ?
4438 "artwork for certain level sets" :
4439 "artwork included in level sets");
4441 setString(&artwork_new->name, top_node_name);
4442 setString(&artwork_new->name_sorting, top_node_name);
4446 setString(&artwork_new->name, level_node->name);
4447 setString(&artwork_new->name_sorting, level_node->name_sorting);
4450 pushTreeInfo(artwork_node, artwork_new);
4452 // create node to link back to current custom artwork directory
4453 createParentTreeInfoNode(artwork_new);
4455 // recursively step into sub-directory and look for more custom artwork
4456 LoadArtworkInfoFromLevelInfoExt(&artwork_new->node_group, artwork_new,
4457 level_node->node_group,
4458 empty_level_set_mode);
4460 // if sub-tree has no custom artwork at all, remove it
4461 if (artwork_new->node_group->next == NULL)
4462 removeTreeInfo(artwork_node);
4465 level_node = level_node->next;
4469 static void LoadArtworkInfoFromLevelInfo(ArtworkDirTree **artwork_node)
4471 // move peviously loaded artwork tree into separate sub-tree
4472 MoveArtworkInfoIntoSubTree(artwork_node);
4474 // load artwork from level sets into separate sub-trees
4475 LoadArtworkInfoFromLevelInfoExt(artwork_node, NULL, leveldir_first_all, TRUE);
4476 LoadArtworkInfoFromLevelInfoExt(artwork_node, NULL, leveldir_first_all, FALSE);
4478 // add top tree node over all sub-trees and set parent links
4479 *artwork_node = addTopTreeInfoNode(*artwork_node);
4482 void LoadLevelArtworkInfo(void)
4484 print_timestamp_init("LoadLevelArtworkInfo");
4486 DrawInitTextHead("Looking for custom level artwork");
4488 print_timestamp_time("DrawTimeText");
4490 LoadArtworkInfoFromLevelInfo(&artwork.gfx_first);
4491 print_timestamp_time("LoadArtworkInfoFromLevelInfo (gfx)");
4492 LoadArtworkInfoFromLevelInfo(&artwork.snd_first);
4493 print_timestamp_time("LoadArtworkInfoFromLevelInfo (snd)");
4494 LoadArtworkInfoFromLevelInfo(&artwork.mus_first);
4495 print_timestamp_time("LoadArtworkInfoFromLevelInfo (mus)");
4497 SaveArtworkInfoCache();
4499 print_timestamp_time("SaveArtworkInfoCache");
4501 // needed for reloading level artwork not known at ealier stage
4502 ChangeCurrentArtworkIfNeeded(ARTWORK_TYPE_GRAPHICS);
4503 ChangeCurrentArtworkIfNeeded(ARTWORK_TYPE_SOUNDS);
4504 ChangeCurrentArtworkIfNeeded(ARTWORK_TYPE_MUSIC);
4506 print_timestamp_time("getTreeInfoFromIdentifier");
4508 sortTreeInfo(&artwork.gfx_first);
4509 sortTreeInfo(&artwork.snd_first);
4510 sortTreeInfo(&artwork.mus_first);
4512 print_timestamp_time("sortTreeInfo");
4514 #if ENABLE_UNUSED_CODE
4515 dumpTreeInfo(artwork.gfx_first, 0);
4516 dumpTreeInfo(artwork.snd_first, 0);
4517 dumpTreeInfo(artwork.mus_first, 0);
4520 print_timestamp_done("LoadLevelArtworkInfo");
4523 static boolean AddTreeSetToTreeInfoExt(TreeInfo *tree_node_old, char *tree_dir,
4524 char *tree_subdir_new, int type)
4526 if (tree_node_old == NULL)
4528 if (type == TREE_TYPE_LEVEL_DIR)
4530 // get level info tree node of personal user level set
4531 tree_node_old = getTreeInfoFromIdentifier(leveldir_first, getLoginName());
4533 // this may happen if "setup.internal.create_user_levelset" is FALSE
4534 // or if file "levelinfo.conf" is missing in personal user level set
4535 if (tree_node_old == NULL)
4536 tree_node_old = leveldir_first->node_group;
4540 // get artwork info tree node of first artwork set
4541 tree_node_old = ARTWORK_FIRST_NODE(artwork, type);
4545 if (tree_dir == NULL)
4546 tree_dir = TREE_USERDIR(type);
4548 if (tree_node_old == NULL ||
4550 tree_subdir_new == NULL) // should not happen
4553 int draw_deactivation_mask = GetDrawDeactivationMask();
4555 // override draw deactivation mask (temporarily disable drawing)
4556 SetDrawDeactivationMask(REDRAW_ALL);
4558 if (type == TREE_TYPE_LEVEL_DIR)
4560 // load new level set config and add it next to first user level set
4561 LoadLevelInfoFromLevelConf(&tree_node_old->next,
4562 tree_node_old->node_parent,
4563 tree_dir, tree_subdir_new);
4567 // load new artwork set config and add it next to first artwork set
4568 LoadArtworkInfoFromArtworkConf(&tree_node_old->next,
4569 tree_node_old->node_parent,
4570 tree_dir, tree_subdir_new, type);
4573 // set draw deactivation mask to previous value
4574 SetDrawDeactivationMask(draw_deactivation_mask);
4576 // get first node of level or artwork info tree
4577 TreeInfo **tree_node_first = TREE_FIRST_NODE_PTR(type);
4579 // get tree info node of newly added level or artwork set
4580 TreeInfo *tree_node_new = getTreeInfoFromIdentifier(*tree_node_first,
4583 if (tree_node_new == NULL) // should not happen
4586 // correct top link and parent node link of newly created tree node
4587 tree_node_new->node_top = tree_node_old->node_top;
4588 tree_node_new->node_parent = tree_node_old->node_parent;
4590 // sort tree info to adjust position of newly added tree set
4591 sortTreeInfo(tree_node_first);
4596 void AddTreeSetToTreeInfo(TreeInfo *tree_node, char *tree_dir,
4597 char *tree_subdir_new, int type)
4599 if (!AddTreeSetToTreeInfoExt(tree_node, tree_dir, tree_subdir_new, type))
4600 Fail("internal tree info structure corrupted -- aborting");
4603 void AddUserLevelSetToLevelInfo(char *level_subdir_new)
4605 AddTreeSetToTreeInfo(NULL, NULL, level_subdir_new, TREE_TYPE_LEVEL_DIR);
4608 char *getArtworkIdentifierForUserLevelSet(int type)
4610 char *classic_artwork_set = getClassicArtworkSet(type);
4612 // check for custom artwork configured in "levelinfo.conf"
4613 char *leveldir_artwork_set =
4614 *LEVELDIR_ARTWORK_SET_PTR(leveldir_current, type);
4615 boolean has_leveldir_artwork_set =
4616 (leveldir_artwork_set != NULL && !strEqual(leveldir_artwork_set,
4617 classic_artwork_set));
4619 // check for custom artwork in sub-directory "graphics" etc.
4620 TreeInfo *artwork_first_node = ARTWORK_FIRST_NODE(artwork, type);
4621 char *leveldir_identifier = leveldir_current->identifier;
4622 boolean has_artwork_subdir =
4623 (getTreeInfoFromIdentifier(artwork_first_node,
4624 leveldir_identifier) != NULL);
4626 return (has_leveldir_artwork_set ? leveldir_artwork_set :
4627 has_artwork_subdir ? leveldir_identifier :
4628 classic_artwork_set);
4631 TreeInfo *getArtworkTreeInfoForUserLevelSet(int type)
4633 char *artwork_set = getArtworkIdentifierForUserLevelSet(type);
4634 TreeInfo *artwork_first_node = ARTWORK_FIRST_NODE(artwork, type);
4635 TreeInfo *ti = getTreeInfoFromIdentifier(artwork_first_node, artwork_set);
4639 ti = getTreeInfoFromIdentifier(artwork_first_node,
4640 ARTWORK_DEFAULT_SUBDIR(type));
4642 Fail("cannot find default graphics -- should not happen");
4648 boolean checkIfCustomArtworkExistsForCurrentLevelSet(void)
4650 char *graphics_set =
4651 getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_GRAPHICS);
4653 getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_SOUNDS);
4655 getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_MUSIC);
4657 return (!strEqual(graphics_set, GFX_CLASSIC_SUBDIR) ||
4658 !strEqual(sounds_set, SND_CLASSIC_SUBDIR) ||
4659 !strEqual(music_set, MUS_CLASSIC_SUBDIR));
4662 boolean UpdateUserLevelSet(char *level_subdir, char *level_name,
4663 char *level_author, int num_levels)
4665 char *filename = getPath2(getUserLevelDir(level_subdir), LEVELINFO_FILENAME);
4666 char *filename_tmp = getStringCat2(filename, ".tmp");
4668 FILE *file_tmp = NULL;
4669 char line[MAX_LINE_LEN];
4670 boolean success = FALSE;
4671 LevelDirTree *leveldir = getTreeInfoFromIdentifier(leveldir_first,
4673 // update values in level directory tree
4675 if (level_name != NULL)
4676 setString(&leveldir->name, level_name);
4678 if (level_author != NULL)
4679 setString(&leveldir->author, level_author);
4681 if (num_levels != -1)
4682 leveldir->levels = num_levels;
4684 // update values that depend on other values
4686 setString(&leveldir->name_sorting, leveldir->name);
4688 leveldir->last_level = leveldir->first_level + leveldir->levels - 1;
4690 // sort order of level sets may have changed
4691 sortTreeInfo(&leveldir_first);
4693 if ((file = fopen(filename, MODE_READ)) &&
4694 (file_tmp = fopen(filename_tmp, MODE_WRITE)))
4696 while (fgets(line, MAX_LINE_LEN, file))
4698 if (strPrefix(line, "name:") && level_name != NULL)
4699 fprintf(file_tmp, "%-32s%s\n", "name:", level_name);
4700 else if (strPrefix(line, "author:") && level_author != NULL)
4701 fprintf(file_tmp, "%-32s%s\n", "author:", level_author);
4702 else if (strPrefix(line, "levels:") && num_levels != -1)
4703 fprintf(file_tmp, "%-32s%d\n", "levels:", num_levels);
4705 fputs(line, file_tmp);
4718 success = (rename(filename_tmp, filename) == 0);
4726 boolean CreateUserLevelSet(char *level_subdir, char *level_name,
4727 char *level_author, int num_levels,
4728 boolean use_artwork_set)
4730 LevelDirTree *level_info;
4735 // create user level sub-directory, if needed
4736 createDirectory(getUserLevelDir(level_subdir), "user level");
4738 filename = getPath2(getUserLevelDir(level_subdir), LEVELINFO_FILENAME);
4740 if (!(file = fopen(filename, MODE_WRITE)))
4742 Warn("cannot write level info file '%s'", filename);
4749 level_info = newTreeInfo();
4751 // always start with reliable default values
4752 setTreeInfoToDefaults(level_info, TREE_TYPE_LEVEL_DIR);
4754 setString(&level_info->name, level_name);
4755 setString(&level_info->author, level_author);
4756 level_info->levels = num_levels;
4757 level_info->first_level = 1;
4758 level_info->sort_priority = LEVELCLASS_PRIVATE_START;
4759 level_info->readonly = FALSE;
4761 if (use_artwork_set)
4763 level_info->graphics_set =
4764 getStringCopy(getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_GRAPHICS));
4765 level_info->sounds_set =
4766 getStringCopy(getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_SOUNDS));
4767 level_info->music_set =
4768 getStringCopy(getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_MUSIC));
4771 token_value_position = TOKEN_VALUE_POSITION_SHORT;
4773 fprintFileHeader(file, LEVELINFO_FILENAME);
4776 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
4778 if (i == LEVELINFO_TOKEN_NAME ||
4779 i == LEVELINFO_TOKEN_AUTHOR ||
4780 i == LEVELINFO_TOKEN_LEVELS ||
4781 i == LEVELINFO_TOKEN_FIRST_LEVEL ||
4782 i == LEVELINFO_TOKEN_SORT_PRIORITY ||
4783 i == LEVELINFO_TOKEN_READONLY ||
4784 (use_artwork_set && (i == LEVELINFO_TOKEN_GRAPHICS_SET ||
4785 i == LEVELINFO_TOKEN_SOUNDS_SET ||
4786 i == LEVELINFO_TOKEN_MUSIC_SET)))
4787 fprintf(file, "%s\n", getSetupLine(levelinfo_tokens, "", i));
4789 // just to make things nicer :)
4790 if (i == LEVELINFO_TOKEN_AUTHOR ||
4791 i == LEVELINFO_TOKEN_FIRST_LEVEL ||
4792 (use_artwork_set && i == LEVELINFO_TOKEN_READONLY))
4793 fprintf(file, "\n");
4796 token_value_position = TOKEN_VALUE_POSITION_DEFAULT;
4800 SetFilePermissions(filename, PERMS_PRIVATE);
4802 freeTreeInfo(level_info);
4808 static void SaveUserLevelInfo(void)
4810 CreateUserLevelSet(getLoginName(), getLoginName(), getRealName(), 100, FALSE);
4813 char *getSetupValue(int type, void *value)
4815 static char value_string[MAX_LINE_LEN];
4823 strcpy(value_string, (*(boolean *)value ? "true" : "false"));
4827 strcpy(value_string, (*(boolean *)value ? "on" : "off"));
4831 strcpy(value_string, (*(int *)value == AUTO ? "auto" :
4832 *(int *)value == FALSE ? "off" : "on"));
4836 strcpy(value_string, (*(boolean *)value ? "yes" : "no"));
4839 case TYPE_YES_NO_AUTO:
4840 strcpy(value_string, (*(int *)value == AUTO ? "auto" :
4841 *(int *)value == FALSE ? "no" : "yes"));
4845 strcpy(value_string, (*(boolean *)value ? "AGA" : "ECS"));
4849 strcpy(value_string, getKeyNameFromKey(*(Key *)value));
4853 strcpy(value_string, getX11KeyNameFromKey(*(Key *)value));
4857 sprintf(value_string, "%d", *(int *)value);
4861 if (*(char **)value == NULL)
4864 strcpy(value_string, *(char **)value);
4868 sprintf(value_string, "player_%d", *(int *)value + 1);
4872 value_string[0] = '\0';
4876 if (type & TYPE_GHOSTED)
4877 strcpy(value_string, "n/a");
4879 return value_string;
4882 char *getSetupLine(struct TokenInfo *token_info, char *prefix, int token_nr)
4886 static char token_string[MAX_LINE_LEN];
4887 int token_type = token_info[token_nr].type;
4888 void *setup_value = token_info[token_nr].value;
4889 char *token_text = token_info[token_nr].text;
4890 char *value_string = getSetupValue(token_type, setup_value);
4892 // build complete token string
4893 sprintf(token_string, "%s%s", prefix, token_text);
4895 // build setup entry line
4896 line = getFormattedSetupEntry(token_string, value_string);
4898 if (token_type == TYPE_KEY_X11)
4900 Key key = *(Key *)setup_value;
4901 char *keyname = getKeyNameFromKey(key);
4903 // add comment, if useful
4904 if (!strEqual(keyname, "(undefined)") &&
4905 !strEqual(keyname, "(unknown)"))
4907 // add at least one whitespace
4909 for (i = strlen(line); i < token_comment_position; i++)
4913 strcat(line, keyname);
4920 static void InitLastPlayedLevels_ParentNode(void)
4922 LevelDirTree **leveldir_top = &leveldir_first->node_group->next;
4923 LevelDirTree *leveldir_new = NULL;
4925 // check if parent node for last played levels already exists
4926 if (strEqual((*leveldir_top)->identifier, TOKEN_STR_LAST_LEVEL_SERIES))
4929 leveldir_new = newTreeInfo();
4931 setTreeInfoToDefaultsFromParent(leveldir_new, leveldir_first);
4933 leveldir_new->level_group = TRUE;
4934 leveldir_new->sort_priority = LEVELCLASS_LAST_PLAYED_LEVEL;
4936 setString(&leveldir_new->identifier, TOKEN_STR_LAST_LEVEL_SERIES);
4937 setString(&leveldir_new->name, "<< (last played level sets)");
4938 setString(&leveldir_new->name_sorting, leveldir_new->name);
4940 pushTreeInfo(leveldir_top, leveldir_new);
4942 // create node to link back to current level directory
4943 createParentTreeInfoNode(leveldir_new);
4946 void UpdateLastPlayedLevels_TreeInfo(void)
4948 char **last_level_series = setup.level_setup.last_level_series;
4949 LevelDirTree *leveldir_last;
4950 TreeInfo **node_new = NULL;
4953 if (last_level_series[0] == NULL)
4956 InitLastPlayedLevels_ParentNode();
4958 leveldir_last = getTreeInfoFromIdentifierExt(leveldir_first,
4959 TOKEN_STR_LAST_LEVEL_SERIES,
4960 TREE_NODE_TYPE_GROUP);
4961 if (leveldir_last == NULL)
4964 node_new = &leveldir_last->node_group->next;
4966 freeTreeInfo(*node_new);
4970 for (i = 0; last_level_series[i] != NULL; i++)
4972 LevelDirTree *node_last = getTreeInfoFromIdentifier(leveldir_first,
4973 last_level_series[i]);
4974 if (node_last == NULL)
4977 *node_new = getTreeInfoCopy(node_last); // copy complete node
4979 (*node_new)->node_top = &leveldir_first; // correct top node link
4980 (*node_new)->node_parent = leveldir_last; // correct parent node link
4982 (*node_new)->is_copy = TRUE; // mark entry as node copy
4984 (*node_new)->node_group = NULL;
4985 (*node_new)->next = NULL;
4987 (*node_new)->cl_first = -1; // force setting tree cursor
4989 node_new = &((*node_new)->next);
4993 static void UpdateLastPlayedLevels_List(void)
4995 char **last_level_series = setup.level_setup.last_level_series;
4996 int pos = MAX_LEVELDIR_HISTORY - 1;
4999 // search for potentially already existing entry in list of level sets
5000 for (i = 0; last_level_series[i] != NULL; i++)
5001 if (strEqual(last_level_series[i], leveldir_current->identifier))
5004 // move list of level sets one entry down (using potentially free entry)
5005 for (i = pos; i > 0; i--)
5006 setString(&last_level_series[i], last_level_series[i - 1]);
5008 // put last played level set at top position
5009 setString(&last_level_series[0], leveldir_current->identifier);
5012 #define LAST_PLAYED_MODE_SET 1
5013 #define LAST_PLAYED_MODE_SET_FORCED 2
5014 #define LAST_PLAYED_MODE_GET 3
5016 static TreeInfo *StoreOrRestoreLastPlayedLevels(TreeInfo *node, int mode)
5018 static char *identifier = NULL;
5020 if (mode == LAST_PLAYED_MODE_SET)
5022 setString(&identifier, (node && node->is_copy ? node->identifier : NULL));
5024 else if (mode == LAST_PLAYED_MODE_SET_FORCED)
5026 setString(&identifier, (node ? node->identifier : NULL));
5028 else if (mode == LAST_PLAYED_MODE_GET)
5030 TreeInfo *node_new = getTreeInfoFromIdentifierExt(leveldir_first,
5032 TREE_NODE_TYPE_COPY);
5033 return (node_new != NULL ? node_new : node);
5036 return NULL; // not used
5039 void StoreLastPlayedLevels(TreeInfo *node)
5041 StoreOrRestoreLastPlayedLevels(node, LAST_PLAYED_MODE_SET);
5044 void ForcedStoreLastPlayedLevels(TreeInfo *node)
5046 StoreOrRestoreLastPlayedLevels(node, LAST_PLAYED_MODE_SET_FORCED);
5049 void RestoreLastPlayedLevels(TreeInfo **node)
5051 *node = StoreOrRestoreLastPlayedLevels(*node, LAST_PLAYED_MODE_GET);
5054 boolean CheckLastPlayedLevels(void)
5056 return (StoreOrRestoreLastPlayedLevels(NULL, LAST_PLAYED_MODE_GET) != NULL);
5059 void LoadLevelSetup_LastSeries(void)
5061 // --------------------------------------------------------------------------
5062 // ~/.<program>/levelsetup.conf
5063 // --------------------------------------------------------------------------
5065 char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
5066 SetupFileHash *level_setup_hash = NULL;
5070 // always start with reliable default values
5071 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
5073 // start with empty history of last played level sets
5074 setString(&setup.level_setup.last_level_series[0], NULL);
5076 if (!strEqual(DEFAULT_LEVELSET, UNDEFINED_LEVELSET))
5078 leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
5080 if (leveldir_current == NULL)
5081 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
5084 if ((level_setup_hash = loadSetupFileHash(filename)))
5086 char *last_level_series =
5087 getHashEntry(level_setup_hash, TOKEN_STR_LAST_LEVEL_SERIES);
5089 leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
5091 if (leveldir_current == NULL)
5092 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
5094 char *last_played_menu_used =
5095 getHashEntry(level_setup_hash, TOKEN_STR_LAST_PLAYED_MENU_USED);
5097 // store if last level set was selected from "last played" menu
5098 if (strEqual(last_played_menu_used, "true"))
5099 ForcedStoreLastPlayedLevels(leveldir_current);
5101 for (i = 0; i < MAX_LEVELDIR_HISTORY; i++)
5103 char token[strlen(TOKEN_STR_LAST_LEVEL_SERIES) + 10];
5104 LevelDirTree *leveldir_last;
5106 sprintf(token, "%s.%03d", TOKEN_STR_LAST_LEVEL_SERIES, i);
5108 last_level_series = getHashEntry(level_setup_hash, token);
5110 leveldir_last = getTreeInfoFromIdentifier(leveldir_first,
5112 if (leveldir_last != NULL)
5113 setString(&setup.level_setup.last_level_series[pos++],
5117 setString(&setup.level_setup.last_level_series[pos], NULL);
5119 freeSetupFileHash(level_setup_hash);
5123 Debug("setup", "using default setup values");
5129 static void SaveLevelSetup_LastSeries_Ext(boolean deactivate_last_level_series)
5131 // --------------------------------------------------------------------------
5132 // ~/.<program>/levelsetup.conf
5133 // --------------------------------------------------------------------------
5135 // check if the current level directory structure is available at this point
5136 if (leveldir_current == NULL)
5139 char **last_level_series = setup.level_setup.last_level_series;
5140 char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
5144 InitUserDataDirectory();
5146 UpdateLastPlayedLevels_List();
5148 if (!(file = fopen(filename, MODE_WRITE)))
5150 Warn("cannot write setup file '%s'", filename);
5157 fprintFileHeader(file, LEVELSETUP_FILENAME);
5159 if (deactivate_last_level_series)
5160 fprintf(file, "# %s\n# ", "the following level set may have caused a problem and was deactivated");
5162 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_LAST_LEVEL_SERIES,
5163 leveldir_current->identifier));
5165 // store if last level set was selected from "last played" menu
5166 boolean last_played_menu_used = CheckLastPlayedLevels();
5167 char *setup_value = getSetupValue(TYPE_BOOLEAN, &last_played_menu_used);
5169 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_LAST_PLAYED_MENU_USED,
5172 for (i = 0; last_level_series[i] != NULL; i++)
5174 char token[strlen(TOKEN_STR_LAST_LEVEL_SERIES) + 1 + 10 + 1];
5176 sprintf(token, "%s.%03d", TOKEN_STR_LAST_LEVEL_SERIES, i);
5178 fprintf(file, "%s\n", getFormattedSetupEntry(token, last_level_series[i]));
5183 SetFilePermissions(filename, PERMS_PRIVATE);
5188 void SaveLevelSetup_LastSeries(void)
5190 SaveLevelSetup_LastSeries_Ext(FALSE);
5193 void SaveLevelSetup_LastSeries_Deactivate(void)
5195 SaveLevelSetup_LastSeries_Ext(TRUE);
5198 static void checkSeriesInfo(void)
5200 static char *level_directory = NULL;
5203 DirectoryEntry *dir_entry;
5206 checked_free(level_directory);
5208 // check for more levels besides the 'levels' field of 'levelinfo.conf'
5210 level_directory = getPath2((leveldir_current->in_user_dir ?
5211 getUserLevelDir(NULL) :
5212 options.level_directory),
5213 leveldir_current->fullpath);
5215 if ((dir = openDirectory(level_directory)) == NULL)
5217 Warn("cannot read level directory '%s'", level_directory);
5223 while ((dir_entry = readDirectory(dir)) != NULL) // loop all entries
5225 if (strlen(dir_entry->basename) > 4 &&
5226 dir_entry->basename[3] == '.' &&
5227 strEqual(&dir_entry->basename[4], LEVELFILE_EXTENSION))
5229 char levelnum_str[4];
5232 strncpy(levelnum_str, dir_entry->basename, 3);
5233 levelnum_str[3] = '\0';
5235 levelnum_value = atoi(levelnum_str);
5237 if (levelnum_value < leveldir_current->first_level)
5239 Warn("additional level %d found", levelnum_value);
5241 leveldir_current->first_level = levelnum_value;
5243 else if (levelnum_value > leveldir_current->last_level)
5245 Warn("additional level %d found", levelnum_value);
5247 leveldir_current->last_level = levelnum_value;
5253 closeDirectory(dir);
5256 void LoadLevelSetup_SeriesInfo(void)
5259 SetupFileHash *level_setup_hash = NULL;
5260 char *level_subdir = leveldir_current->subdir;
5263 // always start with reliable default values
5264 level_nr = leveldir_current->first_level;
5266 for (i = 0; i < MAX_LEVELS; i++)
5268 LevelStats_setPlayed(i, 0);
5269 LevelStats_setSolved(i, 0);
5274 // --------------------------------------------------------------------------
5275 // ~/.<program>/levelsetup/<level series>/levelsetup.conf
5276 // --------------------------------------------------------------------------
5278 level_subdir = leveldir_current->subdir;
5280 filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
5282 if ((level_setup_hash = loadSetupFileHash(filename)))
5286 // get last played level in this level set
5288 token_value = getHashEntry(level_setup_hash, TOKEN_STR_LAST_PLAYED_LEVEL);
5292 level_nr = atoi(token_value);
5294 if (level_nr < leveldir_current->first_level)
5295 level_nr = leveldir_current->first_level;
5296 if (level_nr > leveldir_current->last_level)
5297 level_nr = leveldir_current->last_level;
5300 // get handicap level in this level set
5302 token_value = getHashEntry(level_setup_hash, TOKEN_STR_HANDICAP_LEVEL);
5306 int level_nr = atoi(token_value);
5308 if (level_nr < leveldir_current->first_level)
5309 level_nr = leveldir_current->first_level;
5310 if (level_nr > leveldir_current->last_level + 1)
5311 level_nr = leveldir_current->last_level;
5313 if (leveldir_current->user_defined || !leveldir_current->handicap)
5314 level_nr = leveldir_current->last_level;
5316 leveldir_current->handicap_level = level_nr;
5319 // get number of played and solved levels in this level set
5321 BEGIN_HASH_ITERATION(level_setup_hash, itr)
5323 char *token = HASH_ITERATION_TOKEN(itr);
5324 char *value = HASH_ITERATION_VALUE(itr);
5326 if (strlen(token) == 3 &&
5327 token[0] >= '0' && token[0] <= '9' &&
5328 token[1] >= '0' && token[1] <= '9' &&
5329 token[2] >= '0' && token[2] <= '9')
5331 int level_nr = atoi(token);
5334 LevelStats_setPlayed(level_nr, atoi(value)); // read 1st column
5336 value = strchr(value, ' ');
5339 LevelStats_setSolved(level_nr, atoi(value)); // read 2nd column
5342 END_HASH_ITERATION(hash, itr)
5344 freeSetupFileHash(level_setup_hash);
5348 Debug("setup", "using default setup values");
5354 void SaveLevelSetup_SeriesInfo(void)
5357 char *level_subdir = leveldir_current->subdir;
5358 char *level_nr_str = int2str(level_nr, 0);
5359 char *handicap_level_str = int2str(leveldir_current->handicap_level, 0);
5363 // --------------------------------------------------------------------------
5364 // ~/.<program>/levelsetup/<level series>/levelsetup.conf
5365 // --------------------------------------------------------------------------
5367 InitLevelSetupDirectory(level_subdir);
5369 filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
5371 if (!(file = fopen(filename, MODE_WRITE)))
5373 Warn("cannot write setup file '%s'", filename);
5380 fprintFileHeader(file, LEVELSETUP_FILENAME);
5382 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_PLAYED_LEVEL,
5384 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_HANDICAP_LEVEL,
5385 handicap_level_str));
5387 for (i = leveldir_current->first_level; i <= leveldir_current->last_level;
5390 if (LevelStats_getPlayed(i) > 0 ||
5391 LevelStats_getSolved(i) > 0)
5396 sprintf(token, "%03d", i);
5397 sprintf(value, "%d %d", LevelStats_getPlayed(i), LevelStats_getSolved(i));
5399 fprintf(file, "%s\n", getFormattedSetupEntry(token, value));
5405 SetFilePermissions(filename, PERMS_PRIVATE);
5410 int LevelStats_getPlayed(int nr)
5412 return (nr >= 0 && nr < MAX_LEVELS ? level_stats[nr].played : 0);
5415 int LevelStats_getSolved(int nr)
5417 return (nr >= 0 && nr < MAX_LEVELS ? level_stats[nr].solved : 0);
5420 void LevelStats_setPlayed(int nr, int value)
5422 if (nr >= 0 && nr < MAX_LEVELS)
5423 level_stats[nr].played = value;
5426 void LevelStats_setSolved(int nr, int value)
5428 if (nr >= 0 && nr < MAX_LEVELS)
5429 level_stats[nr].solved = value;
5432 void LevelStats_incPlayed(int nr)
5434 if (nr >= 0 && nr < MAX_LEVELS)
5435 level_stats[nr].played++;
5438 void LevelStats_incSolved(int nr)
5440 if (nr >= 0 && nr < MAX_LEVELS)
5441 level_stats[nr].solved++;
5444 void LoadUserSetup(void)
5446 // --------------------------------------------------------------------------
5447 // ~/.<program>/usersetup.conf
5448 // --------------------------------------------------------------------------
5450 char *filename = getPath2(getMainUserGameDataDir(), USERSETUP_FILENAME);
5451 SetupFileHash *user_setup_hash = NULL;
5453 // always start with reliable default values
5456 if ((user_setup_hash = loadSetupFileHash(filename)))
5460 // get last selected user number
5461 token_value = getHashEntry(user_setup_hash, TOKEN_STR_LAST_USER);
5464 user.nr = MIN(MAX(0, atoi(token_value)), MAX_PLAYER_NAMES - 1);
5466 freeSetupFileHash(user_setup_hash);
5470 Debug("setup", "using default setup values");
5476 void SaveUserSetup(void)
5478 // --------------------------------------------------------------------------
5479 // ~/.<program>/usersetup.conf
5480 // --------------------------------------------------------------------------
5482 char *filename = getPath2(getMainUserGameDataDir(), USERSETUP_FILENAME);
5485 InitMainUserDataDirectory();
5487 if (!(file = fopen(filename, MODE_WRITE)))
5489 Warn("cannot write setup file '%s'", filename);
5496 fprintFileHeader(file, USERSETUP_FILENAME);
5498 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_USER,
5502 SetFilePermissions(filename, PERMS_PRIVATE);