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));
1329 if (directoryExists_CheckMusic(directory, check_music))
1334 // take missing artwork configured in level set config from default
1335 skip_setup_artwork = TRUE;
1339 if (!skip_setup_artwork)
1341 // 3rd try: look for special artwork in configured artwork directory
1342 directory = getStringCopy(getSetupArtworkDir(artwork.mus_current));
1343 if (directoryExists_CheckMusic(directory, check_music))
1349 // 4th try: look for default artwork in new default artwork directory
1350 directory = getStringCopy(getDefaultMusicDir(MUS_DEFAULT_SUBDIR));
1351 if (directoryExists_CheckMusic(directory, check_music))
1356 // 5th try: look for default artwork in old default artwork directory
1357 directory = getStringCopy(options.music_directory);
1358 if (directoryExists_CheckMusic(directory, check_music))
1361 return NULL; // cannot find specified artwork file anywhere
1364 char *getCustomMusicDirectory(void)
1366 return getCustomMusicDirectoryExt(FALSE);
1369 char *getCustomMusicDirectory_NoConf(void)
1371 return getCustomMusicDirectoryExt(TRUE);
1374 void MarkTapeDirectoryUploadsAsComplete(char *level_subdir)
1376 char *filename = getPath2(getTapeDir(level_subdir), UPLOADED_FILENAME);
1378 touchFile(filename);
1380 checked_free(filename);
1383 void MarkTapeDirectoryUploadsAsIncomplete(char *level_subdir)
1385 char *filename = getPath2(getTapeDir(level_subdir), UPLOADED_FILENAME);
1389 checked_free(filename);
1392 boolean CheckTapeDirectoryUploadsComplete(char *level_subdir)
1394 char *filename = getPath2(getTapeDir(level_subdir), UPLOADED_FILENAME);
1395 boolean success = fileExists(filename);
1397 checked_free(filename);
1402 void InitMissingFileHash(void)
1404 if (missing_file_hash == NULL)
1405 freeSetupFileHash(missing_file_hash);
1407 missing_file_hash = newSetupFileHash();
1410 void InitTapeDirectory(char *level_subdir)
1412 boolean new_tape_dir = !directoryExists(getTapeDir(level_subdir));
1414 createDirectory(getUserGameDataDir(), "user data");
1415 createDirectory(getTapeDir(NULL), "main tape");
1416 createDirectory(getTapeDir(level_subdir), "level tape");
1419 MarkTapeDirectoryUploadsAsComplete(level_subdir);
1422 void InitScoreDirectory(char *level_subdir)
1424 createDirectory(getMainUserGameDataDir(), "main user data");
1425 createDirectory(getScoreDir(NULL), "main score");
1426 createDirectory(getScoreDir(level_subdir), "level score");
1429 void InitScoreCacheDirectory(char *level_subdir)
1431 createDirectory(getMainUserGameDataDir(), "main user data");
1432 createDirectory(getCacheDir(), "cache data");
1433 createDirectory(getScoreCacheDir(NULL), "main score");
1434 createDirectory(getScoreCacheDir(level_subdir), "level score");
1437 void InitScoreTapeDirectory(char *level_subdir, int nr)
1439 InitScoreDirectory(level_subdir);
1441 createDirectory(getScoreTapeDir(level_subdir, nr), "score tape");
1444 void InitScoreCacheTapeDirectory(char *level_subdir, int nr)
1446 InitScoreCacheDirectory(level_subdir);
1448 createDirectory(getScoreCacheTapeDir(level_subdir, nr), "score tape");
1451 static void SaveUserLevelInfo(void);
1453 void InitUserLevelDirectory(char *level_subdir)
1455 if (!directoryExists(getUserLevelDir(level_subdir)))
1457 createDirectory(getMainUserGameDataDir(), "main user data");
1458 createDirectory(getUserLevelDir(NULL), "main user level");
1460 if (setup.internal.create_user_levelset)
1462 createDirectory(getUserLevelDir(level_subdir), "user level");
1464 SaveUserLevelInfo();
1469 void InitNetworkLevelDirectory(char *level_subdir)
1471 if (!directoryExists(getNetworkLevelDir(level_subdir)))
1473 createDirectory(getMainUserGameDataDir(), "main user data");
1474 createDirectory(getNetworkDir(), "network data");
1475 createDirectory(getNetworkLevelDir(NULL), "main network level");
1476 createDirectory(getNetworkLevelDir(level_subdir), "network level");
1480 void InitLevelSetupDirectory(char *level_subdir)
1482 createDirectory(getUserGameDataDir(), "user data");
1483 createDirectory(getLevelSetupDir(NULL), "main level setup");
1484 createDirectory(getLevelSetupDir(level_subdir), "level setup");
1487 static void InitCacheDirectory(void)
1489 createDirectory(getMainUserGameDataDir(), "main user data");
1490 createDirectory(getCacheDir(), "cache data");
1494 // ----------------------------------------------------------------------------
1495 // some functions to handle lists of level and artwork directories
1496 // ----------------------------------------------------------------------------
1498 TreeInfo *newTreeInfo(void)
1500 return checked_calloc(sizeof(TreeInfo));
1503 TreeInfo *newTreeInfo_setDefaults(int type)
1505 TreeInfo *ti = newTreeInfo();
1507 setTreeInfoToDefaults(ti, type);
1512 void pushTreeInfo(TreeInfo **node_first, TreeInfo *node_new)
1514 node_new->next = *node_first;
1515 *node_first = node_new;
1518 void removeTreeInfo(TreeInfo **node_first)
1520 TreeInfo *node_old = *node_first;
1522 *node_first = node_old->next;
1523 node_old->next = NULL;
1525 freeTreeInfo(node_old);
1528 int numTreeInfo(TreeInfo *node)
1541 boolean validLevelSeries(TreeInfo *node)
1543 // in a number of cases, tree node is no valid level set
1544 if (node == NULL || node->node_group || node->parent_link || node->is_copy)
1550 TreeInfo *getValidLevelSeries(TreeInfo *node, TreeInfo *default_node)
1552 if (validLevelSeries(node))
1554 else if (node->is_copy)
1555 return getTreeInfoFromIdentifier(leveldir_first, node->identifier);
1557 return getFirstValidTreeInfoEntry(default_node);
1560 static TreeInfo *getValidTreeInfoEntryExt(TreeInfo *node, boolean get_next_node)
1565 if (node->node_group) // enter node group (step down into tree)
1566 return getFirstValidTreeInfoEntry(node->node_group);
1568 if (node->parent_link) // skip first node (back link) of node group
1569 get_next_node = TRUE;
1571 if (!get_next_node) // get current regular tree node
1574 // get next regular tree node, or step up until one is found
1575 while (node->next == NULL && node->node_parent != NULL)
1576 node = node->node_parent;
1578 return getFirstValidTreeInfoEntry(node->next);
1581 TreeInfo *getFirstValidTreeInfoEntry(TreeInfo *node)
1583 return getValidTreeInfoEntryExt(node, FALSE);
1586 TreeInfo *getNextValidTreeInfoEntry(TreeInfo *node)
1588 return getValidTreeInfoEntryExt(node, TRUE);
1591 TreeInfo *getTreeInfoFirstGroupEntry(TreeInfo *node)
1596 if (node->node_parent == NULL) // top level group
1597 return *node->node_top;
1598 else // sub level group
1599 return node->node_parent->node_group;
1602 int numTreeInfoInGroup(TreeInfo *node)
1604 return numTreeInfo(getTreeInfoFirstGroupEntry(node));
1607 int getPosFromTreeInfo(TreeInfo *node)
1609 TreeInfo *node_cmp = getTreeInfoFirstGroupEntry(node);
1614 if (node_cmp == node)
1618 node_cmp = node_cmp->next;
1624 TreeInfo *getTreeInfoFromPos(TreeInfo *node, int pos)
1626 TreeInfo *node_default = node;
1638 return node_default;
1641 static TreeInfo *getTreeInfoFromIdentifierExt(TreeInfo *node, char *identifier,
1642 int node_type_wanted)
1644 if (identifier == NULL)
1649 if (TREE_NODE_TYPE(node) == node_type_wanted &&
1650 strEqual(identifier, node->identifier))
1653 if (node->node_group)
1655 TreeInfo *node_group = getTreeInfoFromIdentifierExt(node->node_group,
1668 TreeInfo *getTreeInfoFromIdentifier(TreeInfo *node, char *identifier)
1670 return getTreeInfoFromIdentifierExt(node, identifier, TREE_NODE_TYPE_DEFAULT);
1673 static TreeInfo *cloneTreeNode(TreeInfo **node_top, TreeInfo *node_parent,
1674 TreeInfo *node, boolean skip_sets_without_levels)
1681 if (!node->parent_link && !node->level_group &&
1682 skip_sets_without_levels && node->levels == 0)
1683 return cloneTreeNode(node_top, node_parent, node->next,
1684 skip_sets_without_levels);
1686 node_new = getTreeInfoCopy(node); // copy complete node
1688 node_new->node_top = node_top; // correct top node link
1689 node_new->node_parent = node_parent; // correct parent node link
1691 if (node->level_group)
1692 node_new->node_group = cloneTreeNode(node_top, node_new, node->node_group,
1693 skip_sets_without_levels);
1695 node_new->next = cloneTreeNode(node_top, node_parent, node->next,
1696 skip_sets_without_levels);
1701 static void cloneTree(TreeInfo **ti_new, TreeInfo *ti, boolean skip_empty_sets)
1703 TreeInfo *ti_cloned = cloneTreeNode(ti_new, NULL, ti, skip_empty_sets);
1705 *ti_new = ti_cloned;
1708 static boolean adjustTreeGraphicsForEMC(TreeInfo *node)
1710 boolean settings_changed = FALSE;
1714 boolean want_ecs = (setup.prefer_aga_graphics == FALSE);
1715 boolean want_aga = (setup.prefer_aga_graphics == TRUE);
1716 boolean has_only_ecs = (!node->graphics_set && !node->graphics_set_aga);
1717 boolean has_only_aga = (!node->graphics_set && !node->graphics_set_ecs);
1718 char *graphics_set = NULL;
1720 if (node->graphics_set_ecs && (want_ecs || has_only_ecs))
1721 graphics_set = node->graphics_set_ecs;
1723 if (node->graphics_set_aga && (want_aga || has_only_aga))
1724 graphics_set = node->graphics_set_aga;
1726 if (graphics_set && !strEqual(node->graphics_set, graphics_set))
1728 setString(&node->graphics_set, graphics_set);
1729 settings_changed = TRUE;
1732 if (node->node_group != NULL)
1733 settings_changed |= adjustTreeGraphicsForEMC(node->node_group);
1738 return settings_changed;
1741 static boolean adjustTreeSoundsForEMC(TreeInfo *node)
1743 boolean settings_changed = FALSE;
1747 boolean want_default = (setup.prefer_lowpass_sounds == FALSE);
1748 boolean want_lowpass = (setup.prefer_lowpass_sounds == TRUE);
1749 boolean has_only_default = (!node->sounds_set && !node->sounds_set_lowpass);
1750 boolean has_only_lowpass = (!node->sounds_set && !node->sounds_set_default);
1751 char *sounds_set = NULL;
1753 if (node->sounds_set_default && (want_default || has_only_default))
1754 sounds_set = node->sounds_set_default;
1756 if (node->sounds_set_lowpass && (want_lowpass || has_only_lowpass))
1757 sounds_set = node->sounds_set_lowpass;
1759 if (sounds_set && !strEqual(node->sounds_set, sounds_set))
1761 setString(&node->sounds_set, sounds_set);
1762 settings_changed = TRUE;
1765 if (node->node_group != NULL)
1766 settings_changed |= adjustTreeSoundsForEMC(node->node_group);
1771 return settings_changed;
1774 int dumpTreeInfo(TreeInfo *node, int depth)
1776 char bullet_list[] = { '-', '*', 'o' };
1777 int num_leaf_nodes = 0;
1781 Debug("tree", "Dumping TreeInfo:");
1785 char bullet = bullet_list[depth % ARRAY_SIZE(bullet_list)];
1787 for (i = 0; i < depth * 2; i++)
1788 DebugContinued("", " ");
1790 DebugContinued("tree", "%c '%s' ['%s] [PARENT: '%s'] %s\n",
1791 bullet, node->name, node->identifier,
1792 (node->node_parent ? node->node_parent->identifier : "-"),
1793 (node->node_group ? "[GROUP]" :
1794 node->is_copy ? "[COPY]" : ""));
1796 if (!node->node_group && !node->parent_link)
1800 // use for dumping artwork info tree
1801 Debug("tree", "subdir == '%s' ['%s', '%s'] [%d])",
1802 node->subdir, node->fullpath, node->basepath, node->in_user_dir);
1805 if (node->node_group != NULL)
1806 num_leaf_nodes += dumpTreeInfo(node->node_group, depth + 1);
1812 Debug("tree", "Summary: %d leaf nodes found", num_leaf_nodes);
1814 return num_leaf_nodes;
1817 void sortTreeInfoBySortFunction(TreeInfo **node_first,
1818 int (*compare_function)(const void *,
1821 int num_nodes = numTreeInfo(*node_first);
1822 TreeInfo **sort_array;
1823 TreeInfo *node = *node_first;
1829 // allocate array for sorting structure pointers
1830 sort_array = checked_calloc(num_nodes * sizeof(TreeInfo *));
1832 // writing structure pointers to sorting array
1833 while (i < num_nodes && node) // double boundary check...
1835 sort_array[i] = node;
1841 // sorting the structure pointers in the sorting array
1842 qsort(sort_array, num_nodes, sizeof(TreeInfo *),
1845 // update the linkage of list elements with the sorted node array
1846 for (i = 0; i < num_nodes - 1; i++)
1847 sort_array[i]->next = sort_array[i + 1];
1848 sort_array[num_nodes - 1]->next = NULL;
1850 // update the linkage of the main list anchor pointer
1851 *node_first = sort_array[0];
1855 // now recursively sort the level group structures
1859 if (node->node_group != NULL)
1860 sortTreeInfoBySortFunction(&node->node_group, compare_function);
1866 void sortTreeInfo(TreeInfo **node_first)
1868 sortTreeInfoBySortFunction(node_first, compareTreeInfoEntries);
1872 // ============================================================================
1873 // some stuff from "files.c"
1874 // ============================================================================
1876 #if defined(PLATFORM_WINDOWS)
1878 #define S_IRGRP S_IRUSR
1881 #define S_IROTH S_IRUSR
1884 #define S_IWGRP S_IWUSR
1887 #define S_IWOTH S_IWUSR
1890 #define S_IXGRP S_IXUSR
1893 #define S_IXOTH S_IXUSR
1896 #define S_IRWXG (S_IRGRP | S_IWGRP | S_IXGRP)
1901 #endif // PLATFORM_WINDOWS
1903 // file permissions for newly written files
1904 #define MODE_R_ALL (S_IRUSR | S_IRGRP | S_IROTH)
1905 #define MODE_W_ALL (S_IWUSR | S_IWGRP | S_IWOTH)
1906 #define MODE_X_ALL (S_IXUSR | S_IXGRP | S_IXOTH)
1908 #define MODE_W_PRIVATE (S_IWUSR)
1909 #define MODE_W_PUBLIC_FILE (S_IWUSR | S_IWGRP)
1910 #define MODE_W_PUBLIC_DIR (S_IWUSR | S_IWGRP | S_ISGID)
1912 #define DIR_PERMS_PRIVATE (MODE_R_ALL | MODE_X_ALL | MODE_W_PRIVATE)
1913 #define DIR_PERMS_PUBLIC (MODE_R_ALL | MODE_X_ALL | MODE_W_PUBLIC_DIR)
1914 #define DIR_PERMS_PUBLIC_ALL (MODE_R_ALL | MODE_X_ALL | MODE_W_ALL)
1916 #define FILE_PERMS_PRIVATE (MODE_R_ALL | MODE_W_PRIVATE)
1917 #define FILE_PERMS_PUBLIC (MODE_R_ALL | MODE_W_PUBLIC_FILE)
1918 #define FILE_PERMS_PUBLIC_ALL (MODE_R_ALL | MODE_W_ALL)
1921 char *getHomeDir(void)
1923 static char *dir = NULL;
1925 #if defined(PLATFORM_WINDOWS)
1928 dir = checked_malloc(MAX_PATH + 1);
1930 if (!SUCCEEDED(SHGetFolderPath(NULL, CSIDL_PERSONAL, NULL, 0, dir)))
1933 #elif defined(PLATFORM_EMSCRIPTEN)
1934 dir = PERSISTENT_DIRECTORY;
1935 #elif defined(PLATFORM_UNIX)
1938 if ((dir = getenv("HOME")) == NULL)
1940 dir = getUnixHomeDir();
1943 dir = getStringCopy(dir);
1955 char *getPersonalDataDir(void)
1957 static char *personal_data_dir = NULL;
1959 #if defined(PLATFORM_MAC)
1960 if (personal_data_dir == NULL)
1961 personal_data_dir = getPath2(getHomeDir(), "Documents");
1963 if (personal_data_dir == NULL)
1964 personal_data_dir = getHomeDir();
1967 return personal_data_dir;
1970 char *getMainUserGameDataDir(void)
1972 static char *main_user_data_dir = NULL;
1974 #if defined(PLATFORM_ANDROID)
1975 if (main_user_data_dir == NULL)
1976 main_user_data_dir = (char *)(SDL_AndroidGetExternalStorageState() &
1977 SDL_ANDROID_EXTERNAL_STORAGE_WRITE ?
1978 SDL_AndroidGetExternalStoragePath() :
1979 SDL_AndroidGetInternalStoragePath());
1981 if (main_user_data_dir == NULL)
1982 main_user_data_dir = getPath2(getPersonalDataDir(),
1983 program.userdata_subdir);
1986 return main_user_data_dir;
1989 char *getUserGameDataDir(void)
1992 return getMainUserGameDataDir();
1994 return getUserDir(user.nr);
1997 char *getSetupDir(void)
1999 return getUserGameDataDir();
2002 static mode_t posix_umask(mode_t mask)
2004 #if defined(PLATFORM_UNIX)
2011 static int posix_mkdir(const char *pathname, mode_t mode)
2013 #if defined(PLATFORM_WINDOWS)
2014 return mkdir(pathname);
2016 return mkdir(pathname, mode);
2020 static boolean posix_process_running_setgid(void)
2022 #if defined(PLATFORM_UNIX)
2023 return (getgid() != getegid());
2029 void createDirectory(char *dir, char *text)
2031 if (directoryExists(dir))
2034 // leave "other" permissions in umask untouched, but ensure group parts
2035 // of USERDATA_DIR_MODE are not masked
2036 int permission_class = PERMS_PRIVATE;
2037 mode_t dir_mode = (permission_class == PERMS_PRIVATE ?
2038 DIR_PERMS_PRIVATE : DIR_PERMS_PUBLIC);
2039 mode_t last_umask = posix_umask(0);
2040 mode_t group_umask = ~(dir_mode & S_IRWXG);
2041 int running_setgid = posix_process_running_setgid();
2043 if (permission_class == PERMS_PUBLIC)
2045 // if we're setgid, protect files against "other"
2046 // else keep umask(0) to make the dir world-writable
2049 posix_umask(last_umask & group_umask);
2051 dir_mode = DIR_PERMS_PUBLIC_ALL;
2054 if (posix_mkdir(dir, dir_mode) != 0)
2055 Warn("cannot create %s directory '%s': %s", text, dir, strerror(errno));
2057 if (permission_class == PERMS_PUBLIC && !running_setgid)
2058 chmod(dir, dir_mode);
2060 posix_umask(last_umask); // restore previous umask
2063 void InitMainUserDataDirectory(void)
2065 createDirectory(getMainUserGameDataDir(), "main user data");
2068 void InitUserDataDirectory(void)
2070 createDirectory(getMainUserGameDataDir(), "main user data");
2074 createDirectory(getUserDir(-1), "users");
2075 createDirectory(getUserDir(user.nr), "user data");
2079 void SetFilePermissions(char *filename, int permission_class)
2081 int running_setgid = posix_process_running_setgid();
2082 int perms = (permission_class == PERMS_PRIVATE ?
2083 FILE_PERMS_PRIVATE : FILE_PERMS_PUBLIC);
2085 if (permission_class == PERMS_PUBLIC && !running_setgid)
2086 perms = FILE_PERMS_PUBLIC_ALL;
2088 chmod(filename, perms);
2091 void fprintFileHeader(FILE *file, char *basename)
2093 char *prefix = "# ";
2096 fprintf_line_with_prefix(file, prefix, sep1, 77);
2097 fprintf(file, "%s%s\n", prefix, basename);
2098 fprintf_line_with_prefix(file, prefix, sep1, 77);
2099 fprintf(file, "\n");
2102 int getFileVersionFromCookieString(const char *cookie)
2104 const char *ptr_cookie1, *ptr_cookie2;
2105 const char *pattern1 = "_FILE_VERSION_";
2106 const char *pattern2 = "?.?";
2107 const int len_cookie = strlen(cookie);
2108 const int len_pattern1 = strlen(pattern1);
2109 const int len_pattern2 = strlen(pattern2);
2110 const int len_pattern = len_pattern1 + len_pattern2;
2111 int version_super, version_major;
2113 if (len_cookie <= len_pattern)
2116 ptr_cookie1 = &cookie[len_cookie - len_pattern];
2117 ptr_cookie2 = &cookie[len_cookie - len_pattern2];
2119 if (strncmp(ptr_cookie1, pattern1, len_pattern1) != 0)
2122 if (ptr_cookie2[0] < '0' || ptr_cookie2[0] > '9' ||
2123 ptr_cookie2[1] != '.' ||
2124 ptr_cookie2[2] < '0' || ptr_cookie2[2] > '9')
2127 version_super = ptr_cookie2[0] - '0';
2128 version_major = ptr_cookie2[2] - '0';
2130 return VERSION_IDENT(version_super, version_major, 0, 0);
2133 boolean checkCookieString(const char *cookie, const char *template)
2135 const char *pattern = "_FILE_VERSION_?.?";
2136 const int len_cookie = strlen(cookie);
2137 const int len_template = strlen(template);
2138 const int len_pattern = strlen(pattern);
2140 if (len_cookie != len_template)
2143 if (strncmp(cookie, template, len_cookie - len_pattern) != 0)
2150 // ----------------------------------------------------------------------------
2151 // setup file list and hash handling functions
2152 // ----------------------------------------------------------------------------
2154 char *getFormattedSetupEntry(char *token, char *value)
2157 static char entry[MAX_LINE_LEN];
2159 // if value is an empty string, just return token without value
2163 // start with the token and some spaces to format output line
2164 sprintf(entry, "%s:", token);
2165 for (i = strlen(entry); i < token_value_position; i++)
2168 // continue with the token's value
2169 strcat(entry, value);
2174 SetupFileList *newSetupFileList(char *token, char *value)
2176 SetupFileList *new = checked_malloc(sizeof(SetupFileList));
2178 new->token = getStringCopy(token);
2179 new->value = getStringCopy(value);
2186 void freeSetupFileList(SetupFileList *list)
2191 checked_free(list->token);
2192 checked_free(list->value);
2195 freeSetupFileList(list->next);
2200 char *getListEntry(SetupFileList *list, char *token)
2205 if (strEqual(list->token, token))
2208 return getListEntry(list->next, token);
2211 SetupFileList *setListEntry(SetupFileList *list, char *token, char *value)
2216 if (strEqual(list->token, token))
2218 checked_free(list->value);
2220 list->value = getStringCopy(value);
2224 else if (list->next == NULL)
2225 return (list->next = newSetupFileList(token, value));
2227 return setListEntry(list->next, token, value);
2230 SetupFileList *addListEntry(SetupFileList *list, char *token, char *value)
2235 if (list->next == NULL)
2236 return (list->next = newSetupFileList(token, value));
2238 return addListEntry(list->next, token, value);
2241 #if ENABLE_UNUSED_CODE
2243 static void printSetupFileList(SetupFileList *list)
2248 Debug("setup:printSetupFileList", "token: '%s'", list->token);
2249 Debug("setup:printSetupFileList", "value: '%s'", list->value);
2251 printSetupFileList(list->next);
2257 DEFINE_HASHTABLE_INSERT(insert_hash_entry, char, char);
2258 DEFINE_HASHTABLE_SEARCH(search_hash_entry, char, char);
2259 DEFINE_HASHTABLE_CHANGE(change_hash_entry, char, char);
2260 DEFINE_HASHTABLE_REMOVE(remove_hash_entry, char, char);
2262 #define insert_hash_entry hashtable_insert
2263 #define search_hash_entry hashtable_search
2264 #define change_hash_entry hashtable_change
2265 #define remove_hash_entry hashtable_remove
2268 unsigned int get_hash_from_key(void *key)
2273 This algorithm (k=33) was first reported by Dan Bernstein many years ago in
2274 'comp.lang.c'. Another version of this algorithm (now favored by Bernstein)
2275 uses XOR: hash(i) = hash(i - 1) * 33 ^ str[i]; the magic of number 33 (why
2276 it works better than many other constants, prime or not) has never been
2277 adequately explained.
2279 If you just want to have a good hash function, and cannot wait, djb2
2280 is one of the best string hash functions i know. It has excellent
2281 distribution and speed on many different sets of keys and table sizes.
2282 You are not likely to do better with one of the "well known" functions
2283 such as PJW, K&R, etc.
2285 Ozan (oz) Yigit [http://www.cs.yorku.ca/~oz/hash.html]
2288 char *str = (char *)key;
2289 unsigned int hash = 5381;
2292 while ((c = *str++))
2293 hash = ((hash << 5) + hash) + c; // hash * 33 + c
2298 int hash_keys_are_equal(void *key1, void *key2)
2300 return (strEqual((char *)key1, (char *)key2));
2303 SetupFileHash *newSetupFileHash(void)
2305 SetupFileHash *new_hash =
2306 create_hashtable(16, 0.75, get_hash_from_key, hash_keys_are_equal);
2308 if (new_hash == NULL)
2309 Fail("create_hashtable() failed -- out of memory");
2314 void freeSetupFileHash(SetupFileHash *hash)
2319 hashtable_destroy(hash, 1); // 1 == also free values stored in hash
2322 char *getHashEntry(SetupFileHash *hash, char *token)
2327 return search_hash_entry(hash, token);
2330 void setHashEntry(SetupFileHash *hash, char *token, char *value)
2337 value_copy = getStringCopy(value);
2339 // change value; if it does not exist, insert it as new
2340 if (!change_hash_entry(hash, token, value_copy))
2341 if (!insert_hash_entry(hash, getStringCopy(token), value_copy))
2342 Fail("cannot insert into hash -- aborting");
2345 char *removeHashEntry(SetupFileHash *hash, char *token)
2350 return remove_hash_entry(hash, token);
2353 #if ENABLE_UNUSED_CODE
2355 static void printSetupFileHash(SetupFileHash *hash)
2357 BEGIN_HASH_ITERATION(hash, itr)
2359 Debug("setup:printSetupFileHash", "token: '%s'", HASH_ITERATION_TOKEN(itr));
2360 Debug("setup:printSetupFileHash", "value: '%s'", HASH_ITERATION_VALUE(itr));
2362 END_HASH_ITERATION(hash, itr)
2367 #define ALLOW_TOKEN_VALUE_SEPARATOR_BEING_WHITESPACE 1
2368 #define CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING 0
2369 #define CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH 0
2371 static boolean token_value_separator_found = FALSE;
2372 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2373 static boolean token_value_separator_warning = FALSE;
2375 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2376 static boolean token_already_exists_warning = FALSE;
2379 static boolean getTokenValueFromSetupLineExt(char *line,
2380 char **token_ptr, char **value_ptr,
2381 char *filename, char *line_raw,
2383 boolean separator_required)
2385 static char line_copy[MAX_LINE_LEN + 1], line_raw_copy[MAX_LINE_LEN + 1];
2386 char *token, *value, *line_ptr;
2388 // when externally invoked via ReadTokenValueFromLine(), copy line buffers
2389 if (line_raw == NULL)
2391 strncpy(line_copy, line, MAX_LINE_LEN);
2392 line_copy[MAX_LINE_LEN] = '\0';
2395 strcpy(line_raw_copy, line_copy);
2396 line_raw = line_raw_copy;
2399 // cut trailing comment from input line
2400 for (line_ptr = line; *line_ptr; line_ptr++)
2402 if (*line_ptr == '#')
2409 // cut trailing whitespaces from input line
2410 for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
2411 if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
2414 // ignore empty lines
2418 // cut leading whitespaces from token
2419 for (token = line; *token; token++)
2420 if (*token != ' ' && *token != '\t')
2423 // start with empty value as reliable default
2426 token_value_separator_found = FALSE;
2428 // find end of token to determine start of value
2429 for (line_ptr = token; *line_ptr; line_ptr++)
2431 // first look for an explicit token/value separator, like ':' or '='
2432 if (*line_ptr == ':' || *line_ptr == '=')
2434 *line_ptr = '\0'; // terminate token string
2435 value = line_ptr + 1; // set beginning of value
2437 token_value_separator_found = TRUE;
2443 #if ALLOW_TOKEN_VALUE_SEPARATOR_BEING_WHITESPACE
2444 // fallback: if no token/value separator found, also allow whitespaces
2445 if (!token_value_separator_found && !separator_required)
2447 for (line_ptr = token; *line_ptr; line_ptr++)
2449 if (*line_ptr == ' ' || *line_ptr == '\t')
2451 *line_ptr = '\0'; // terminate token string
2452 value = line_ptr + 1; // set beginning of value
2454 token_value_separator_found = TRUE;
2460 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2461 if (token_value_separator_found)
2463 if (!token_value_separator_warning)
2465 Debug("setup", "---");
2467 if (filename != NULL)
2469 Debug("setup", "missing token/value separator(s) in config file:");
2470 Debug("setup", "- config file: '%s'", filename);
2474 Debug("setup", "missing token/value separator(s):");
2477 token_value_separator_warning = TRUE;
2480 if (filename != NULL)
2481 Debug("setup", "- line %d: '%s'", line_nr, line_raw);
2483 Debug("setup", "- line: '%s'", line_raw);
2489 // cut trailing whitespaces from token
2490 for (line_ptr = &token[strlen(token)]; line_ptr >= token; line_ptr--)
2491 if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
2494 // cut leading whitespaces from value
2495 for (; *value; value++)
2496 if (*value != ' ' && *value != '\t')
2505 boolean getTokenValueFromSetupLine(char *line, char **token, char **value)
2507 // while the internal (old) interface does not require a token/value
2508 // separator (for downwards compatibility with existing files which
2509 // don't use them), it is mandatory for the external (new) interface
2511 return getTokenValueFromSetupLineExt(line, token, value, NULL, NULL, 0, TRUE);
2514 static boolean loadSetupFileData(void *setup_file_data, char *filename,
2515 boolean top_recursion_level, boolean is_hash)
2517 static SetupFileHash *include_filename_hash = NULL;
2518 char line[MAX_LINE_LEN], line_raw[MAX_LINE_LEN], previous_line[MAX_LINE_LEN];
2519 char *token, *value, *line_ptr;
2520 void *insert_ptr = NULL;
2521 boolean read_continued_line = FALSE;
2523 int line_nr = 0, token_count = 0, include_count = 0;
2525 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2526 token_value_separator_warning = FALSE;
2529 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2530 token_already_exists_warning = FALSE;
2533 if (!(file = openFile(filename, MODE_READ)))
2535 #if DEBUG_NO_CONFIG_FILE
2536 Debug("setup", "cannot open configuration file '%s'", filename);
2542 // use "insert pointer" to store list end for constant insertion complexity
2544 insert_ptr = setup_file_data;
2546 // on top invocation, create hash to mark included files (to prevent loops)
2547 if (top_recursion_level)
2548 include_filename_hash = newSetupFileHash();
2550 // mark this file as already included (to prevent including it again)
2551 setHashEntry(include_filename_hash, getBaseNamePtr(filename), "true");
2553 while (!checkEndOfFile(file))
2555 // read next line of input file
2556 if (!getStringFromFile(file, line, MAX_LINE_LEN))
2559 // check if line was completely read and is terminated by line break
2560 if (strlen(line) > 0 && line[strlen(line) - 1] == '\n')
2563 // cut trailing line break (this can be newline and/or carriage return)
2564 for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
2565 if ((*line_ptr == '\n' || *line_ptr == '\r') && *(line_ptr + 1) == '\0')
2568 // copy raw input line for later use (mainly debugging output)
2569 strcpy(line_raw, line);
2571 if (read_continued_line)
2573 // append new line to existing line, if there is enough space
2574 if (strlen(previous_line) + strlen(line_ptr) < MAX_LINE_LEN)
2575 strcat(previous_line, line_ptr);
2577 strcpy(line, previous_line); // copy storage buffer to line
2579 read_continued_line = FALSE;
2582 // if the last character is '\', continue at next line
2583 if (strlen(line) > 0 && line[strlen(line) - 1] == '\\')
2585 line[strlen(line) - 1] = '\0'; // cut off trailing backslash
2586 strcpy(previous_line, line); // copy line to storage buffer
2588 read_continued_line = TRUE;
2593 if (!getTokenValueFromSetupLineExt(line, &token, &value, filename,
2594 line_raw, line_nr, FALSE))
2599 if (strEqual(token, "include"))
2601 if (getHashEntry(include_filename_hash, value) == NULL)
2603 char *basepath = getBasePath(filename);
2604 char *basename = getBaseName(value);
2605 char *filename_include = getPath2(basepath, basename);
2607 loadSetupFileData(setup_file_data, filename_include, FALSE, is_hash);
2611 free(filename_include);
2617 Warn("ignoring already processed file '%s'", value);
2624 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2626 getHashEntry((SetupFileHash *)setup_file_data, token);
2628 if (old_value != NULL)
2630 if (!token_already_exists_warning)
2632 Debug("setup", "---");
2633 Debug("setup", "duplicate token(s) found in config file:");
2634 Debug("setup", "- config file: '%s'", filename);
2636 token_already_exists_warning = TRUE;
2639 Debug("setup", "- token: '%s' (in line %d)", token, line_nr);
2640 Debug("setup", " old value: '%s'", old_value);
2641 Debug("setup", " new value: '%s'", value);
2645 setHashEntry((SetupFileHash *)setup_file_data, token, value);
2649 insert_ptr = addListEntry((SetupFileList *)insert_ptr, token, value);
2659 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2660 if (token_value_separator_warning)
2661 Debug("setup", "---");
2664 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2665 if (token_already_exists_warning)
2666 Debug("setup", "---");
2669 if (token_count == 0 && include_count == 0)
2670 Warn("configuration file '%s' is empty", filename);
2672 if (top_recursion_level)
2673 freeSetupFileHash(include_filename_hash);
2678 static int compareSetupFileData(const void *object1, const void *object2)
2680 const struct ConfigInfo *entry1 = (struct ConfigInfo *)object1;
2681 const struct ConfigInfo *entry2 = (struct ConfigInfo *)object2;
2683 return strcmp(entry1->token, entry2->token);
2686 static void saveSetupFileHash(SetupFileHash *hash, char *filename)
2688 int item_count = hashtable_count(hash);
2689 int item_size = sizeof(struct ConfigInfo);
2690 struct ConfigInfo *sort_array = checked_malloc(item_count * item_size);
2694 // copy string pointers from hash to array
2695 BEGIN_HASH_ITERATION(hash, itr)
2697 sort_array[i].token = HASH_ITERATION_TOKEN(itr);
2698 sort_array[i].value = HASH_ITERATION_VALUE(itr);
2702 if (i > item_count) // should never happen
2705 END_HASH_ITERATION(hash, itr)
2707 // sort string pointers from hash in array
2708 qsort(sort_array, item_count, item_size, compareSetupFileData);
2710 if (!(file = fopen(filename, MODE_WRITE)))
2712 Warn("cannot write configuration file '%s'", filename);
2717 fprintf(file, "%s\n\n", getFormattedSetupEntry("program.version",
2718 program.version_string));
2719 for (i = 0; i < item_count; i++)
2720 fprintf(file, "%s\n", getFormattedSetupEntry(sort_array[i].token,
2721 sort_array[i].value));
2724 checked_free(sort_array);
2727 SetupFileList *loadSetupFileList(char *filename)
2729 SetupFileList *setup_file_list = newSetupFileList("", "");
2730 SetupFileList *first_valid_list_entry;
2732 if (!loadSetupFileData(setup_file_list, filename, TRUE, FALSE))
2734 freeSetupFileList(setup_file_list);
2739 first_valid_list_entry = setup_file_list->next;
2741 // free empty list header
2742 setup_file_list->next = NULL;
2743 freeSetupFileList(setup_file_list);
2745 return first_valid_list_entry;
2748 SetupFileHash *loadSetupFileHash(char *filename)
2750 SetupFileHash *setup_file_hash = newSetupFileHash();
2752 if (!loadSetupFileData(setup_file_hash, filename, TRUE, TRUE))
2754 freeSetupFileHash(setup_file_hash);
2759 return setup_file_hash;
2763 // ============================================================================
2765 // ============================================================================
2767 #define TOKEN_STR_LAST_LEVEL_SERIES "last_level_series"
2768 #define TOKEN_STR_LAST_PLAYED_MENU_USED "last_played_menu_used"
2769 #define TOKEN_STR_LAST_PLAYED_LEVEL "last_played_level"
2770 #define TOKEN_STR_HANDICAP_LEVEL "handicap_level"
2771 #define TOKEN_STR_LAST_USER "last_user"
2773 // level directory info
2774 #define LEVELINFO_TOKEN_IDENTIFIER 0
2775 #define LEVELINFO_TOKEN_NAME 1
2776 #define LEVELINFO_TOKEN_NAME_SORTING 2
2777 #define LEVELINFO_TOKEN_AUTHOR 3
2778 #define LEVELINFO_TOKEN_YEAR 4
2779 #define LEVELINFO_TOKEN_PROGRAM_TITLE 5
2780 #define LEVELINFO_TOKEN_PROGRAM_COPYRIGHT 6
2781 #define LEVELINFO_TOKEN_PROGRAM_COMPANY 7
2782 #define LEVELINFO_TOKEN_IMPORTED_FROM 8
2783 #define LEVELINFO_TOKEN_IMPORTED_BY 9
2784 #define LEVELINFO_TOKEN_TESTED_BY 10
2785 #define LEVELINFO_TOKEN_LEVELS 11
2786 #define LEVELINFO_TOKEN_FIRST_LEVEL 12
2787 #define LEVELINFO_TOKEN_SORT_PRIORITY 13
2788 #define LEVELINFO_TOKEN_LATEST_ENGINE 14
2789 #define LEVELINFO_TOKEN_LEVEL_GROUP 15
2790 #define LEVELINFO_TOKEN_READONLY 16
2791 #define LEVELINFO_TOKEN_GRAPHICS_SET_ECS 17
2792 #define LEVELINFO_TOKEN_GRAPHICS_SET_AGA 18
2793 #define LEVELINFO_TOKEN_GRAPHICS_SET 19
2794 #define LEVELINFO_TOKEN_SOUNDS_SET_DEFAULT 20
2795 #define LEVELINFO_TOKEN_SOUNDS_SET_LOWPASS 21
2796 #define LEVELINFO_TOKEN_SOUNDS_SET 22
2797 #define LEVELINFO_TOKEN_MUSIC_SET 23
2798 #define LEVELINFO_TOKEN_FILENAME 24
2799 #define LEVELINFO_TOKEN_FILETYPE 25
2800 #define LEVELINFO_TOKEN_SPECIAL_FLAGS 26
2801 #define LEVELINFO_TOKEN_EMPTY_LEVEL_NAME 27
2802 #define LEVELINFO_TOKEN_FORCE_LEVEL_NAME 28
2803 #define LEVELINFO_TOKEN_HANDICAP 29
2804 #define LEVELINFO_TOKEN_TIME_LIMIT 30
2805 #define LEVELINFO_TOKEN_SKIP_LEVELS 31
2806 #define LEVELINFO_TOKEN_USE_EMC_TILES 32
2807 #define LEVELINFO_TOKEN_INFO_SCREENS_FROM_MAIN 33
2809 #define NUM_LEVELINFO_TOKENS 34
2811 static LevelDirTree ldi;
2813 static struct TokenInfo levelinfo_tokens[] =
2815 // level directory info
2816 { TYPE_STRING, &ldi.identifier, "identifier" },
2817 { TYPE_STRING, &ldi.name, "name" },
2818 { TYPE_STRING, &ldi.name_sorting, "name_sorting" },
2819 { TYPE_STRING, &ldi.author, "author" },
2820 { TYPE_STRING, &ldi.year, "year" },
2821 { TYPE_STRING, &ldi.program_title, "program_title" },
2822 { TYPE_STRING, &ldi.program_copyright, "program_copyright" },
2823 { TYPE_STRING, &ldi.program_company, "program_company" },
2824 { TYPE_STRING, &ldi.imported_from, "imported_from" },
2825 { TYPE_STRING, &ldi.imported_by, "imported_by" },
2826 { TYPE_STRING, &ldi.tested_by, "tested_by" },
2827 { TYPE_INTEGER, &ldi.levels, "levels" },
2828 { TYPE_INTEGER, &ldi.first_level, "first_level" },
2829 { TYPE_INTEGER, &ldi.sort_priority, "sort_priority" },
2830 { TYPE_BOOLEAN, &ldi.latest_engine, "latest_engine" },
2831 { TYPE_BOOLEAN, &ldi.level_group, "level_group" },
2832 { TYPE_BOOLEAN, &ldi.readonly, "readonly" },
2833 { TYPE_STRING, &ldi.graphics_set_ecs, "graphics_set.ecs" },
2834 { TYPE_STRING, &ldi.graphics_set_aga, "graphics_set.aga" },
2835 { TYPE_STRING, &ldi.graphics_set, "graphics_set" },
2836 { TYPE_STRING, &ldi.sounds_set_default,"sounds_set.default" },
2837 { TYPE_STRING, &ldi.sounds_set_lowpass,"sounds_set.lowpass" },
2838 { TYPE_STRING, &ldi.sounds_set, "sounds_set" },
2839 { TYPE_STRING, &ldi.music_set, "music_set" },
2840 { TYPE_STRING, &ldi.level_filename, "filename" },
2841 { TYPE_STRING, &ldi.level_filetype, "filetype" },
2842 { TYPE_STRING, &ldi.special_flags, "special_flags" },
2843 { TYPE_STRING, &ldi.empty_level_name, "empty_level_name" },
2844 { TYPE_BOOLEAN, &ldi.force_level_name, "force_level_name" },
2845 { TYPE_BOOLEAN, &ldi.handicap, "handicap" },
2846 { TYPE_BOOLEAN, &ldi.time_limit, "time_limit" },
2847 { TYPE_BOOLEAN, &ldi.skip_levels, "skip_levels" },
2848 { TYPE_BOOLEAN, &ldi.use_emc_tiles, "use_emc_tiles" },
2849 { TYPE_BOOLEAN, &ldi.info_screens_from_main, "info_screens_from_main" }
2852 static struct TokenInfo artworkinfo_tokens[] =
2854 // artwork directory info
2855 { TYPE_STRING, &ldi.identifier, "identifier" },
2856 { TYPE_STRING, &ldi.subdir, "subdir" },
2857 { TYPE_STRING, &ldi.name, "name" },
2858 { TYPE_STRING, &ldi.name_sorting, "name_sorting" },
2859 { TYPE_STRING, &ldi.author, "author" },
2860 { TYPE_STRING, &ldi.program_title, "program_title" },
2861 { TYPE_STRING, &ldi.program_copyright, "program_copyright" },
2862 { TYPE_STRING, &ldi.program_company, "program_company" },
2863 { TYPE_INTEGER, &ldi.sort_priority, "sort_priority" },
2864 { TYPE_STRING, &ldi.basepath, "basepath" },
2865 { TYPE_STRING, &ldi.fullpath, "fullpath" },
2866 { TYPE_BOOLEAN, &ldi.in_user_dir, "in_user_dir" },
2867 { TYPE_STRING, &ldi.class_desc, "class_desc" },
2872 static char *optional_tokens[] =
2875 "program_copyright",
2881 static void setTreeInfoToDefaults(TreeInfo *ti, int type)
2885 ti->node_top = (ti->type == TREE_TYPE_LEVEL_DIR ? &leveldir_first :
2886 ti->type == TREE_TYPE_GRAPHICS_DIR ? &artwork.gfx_first :
2887 ti->type == TREE_TYPE_SOUNDS_DIR ? &artwork.snd_first :
2888 ti->type == TREE_TYPE_MUSIC_DIR ? &artwork.mus_first :
2891 ti->node_parent = NULL;
2892 ti->node_group = NULL;
2899 ti->fullpath = NULL;
2900 ti->basepath = NULL;
2901 ti->identifier = NULL;
2902 ti->name = getStringCopy(ANONYMOUS_NAME);
2903 ti->name_sorting = NULL;
2904 ti->author = getStringCopy(ANONYMOUS_NAME);
2907 ti->program_title = NULL;
2908 ti->program_copyright = NULL;
2909 ti->program_company = NULL;
2911 ti->sort_priority = LEVELCLASS_UNDEFINED; // default: least priority
2912 ti->latest_engine = FALSE; // default: get from level
2913 ti->parent_link = FALSE;
2914 ti->is_copy = FALSE;
2915 ti->in_user_dir = FALSE;
2916 ti->user_defined = FALSE;
2918 ti->class_desc = NULL;
2920 ti->infotext = getStringCopy(TREE_INFOTEXT(ti->type));
2922 if (ti->type == TREE_TYPE_LEVEL_DIR)
2924 ti->imported_from = NULL;
2925 ti->imported_by = NULL;
2926 ti->tested_by = NULL;
2928 ti->graphics_set_ecs = NULL;
2929 ti->graphics_set_aga = NULL;
2930 ti->graphics_set = NULL;
2931 ti->sounds_set_default = NULL;
2932 ti->sounds_set_lowpass = NULL;
2933 ti->sounds_set = NULL;
2934 ti->music_set = NULL;
2935 ti->graphics_path = getStringCopy(UNDEFINED_FILENAME);
2936 ti->sounds_path = getStringCopy(UNDEFINED_FILENAME);
2937 ti->music_path = getStringCopy(UNDEFINED_FILENAME);
2939 ti->level_filename = NULL;
2940 ti->level_filetype = NULL;
2942 ti->special_flags = NULL;
2944 ti->empty_level_name = NULL;
2945 ti->force_level_name = FALSE;
2948 ti->first_level = 0;
2950 ti->level_group = FALSE;
2951 ti->handicap_level = 0;
2952 ti->readonly = TRUE;
2953 ti->handicap = TRUE;
2954 ti->time_limit = TRUE;
2955 ti->skip_levels = FALSE;
2957 ti->use_emc_tiles = FALSE;
2958 ti->info_screens_from_main = FALSE;
2962 static void setTreeInfoToDefaultsFromParent(TreeInfo *ti, TreeInfo *parent)
2966 Warn("setTreeInfoToDefaultsFromParent(): parent == NULL");
2968 setTreeInfoToDefaults(ti, TREE_TYPE_UNDEFINED);
2973 // copy all values from the parent structure
2975 ti->type = parent->type;
2977 ti->node_top = parent->node_top;
2978 ti->node_parent = parent;
2979 ti->node_group = NULL;
2986 ti->fullpath = NULL;
2987 ti->basepath = NULL;
2988 ti->identifier = NULL;
2989 ti->name = getStringCopy(ANONYMOUS_NAME);
2990 ti->name_sorting = NULL;
2991 ti->author = getStringCopy(parent->author);
2992 ti->year = getStringCopy(parent->year);
2994 ti->program_title = getStringCopy(parent->program_title);
2995 ti->program_copyright = getStringCopy(parent->program_copyright);
2996 ti->program_company = getStringCopy(parent->program_company);
2998 ti->sort_priority = parent->sort_priority;
2999 ti->latest_engine = parent->latest_engine;
3000 ti->parent_link = FALSE;
3001 ti->is_copy = FALSE;
3002 ti->in_user_dir = parent->in_user_dir;
3003 ti->user_defined = parent->user_defined;
3004 ti->color = parent->color;
3005 ti->class_desc = getStringCopy(parent->class_desc);
3007 ti->infotext = getStringCopy(parent->infotext);
3009 if (ti->type == TREE_TYPE_LEVEL_DIR)
3011 ti->imported_from = getStringCopy(parent->imported_from);
3012 ti->imported_by = getStringCopy(parent->imported_by);
3013 ti->tested_by = getStringCopy(parent->tested_by);
3015 ti->graphics_set_ecs = getStringCopy(parent->graphics_set_ecs);
3016 ti->graphics_set_aga = getStringCopy(parent->graphics_set_aga);
3017 ti->graphics_set = getStringCopy(parent->graphics_set);
3018 ti->sounds_set_default = getStringCopy(parent->sounds_set_default);
3019 ti->sounds_set_lowpass = getStringCopy(parent->sounds_set_lowpass);
3020 ti->sounds_set = getStringCopy(parent->sounds_set);
3021 ti->music_set = getStringCopy(parent->music_set);
3022 ti->graphics_path = getStringCopy(UNDEFINED_FILENAME);
3023 ti->sounds_path = getStringCopy(UNDEFINED_FILENAME);
3024 ti->music_path = getStringCopy(UNDEFINED_FILENAME);
3026 ti->level_filename = getStringCopy(parent->level_filename);
3027 ti->level_filetype = getStringCopy(parent->level_filetype);
3029 ti->special_flags = getStringCopy(parent->special_flags);
3031 ti->empty_level_name = getStringCopy(parent->empty_level_name);
3032 ti->force_level_name = parent->force_level_name;
3034 ti->levels = parent->levels;
3035 ti->first_level = parent->first_level;
3036 ti->last_level = parent->last_level;
3037 ti->level_group = FALSE;
3038 ti->handicap_level = parent->handicap_level;
3039 ti->readonly = parent->readonly;
3040 ti->handicap = parent->handicap;
3041 ti->time_limit = parent->time_limit;
3042 ti->skip_levels = parent->skip_levels;
3044 ti->use_emc_tiles = parent->use_emc_tiles;
3045 ti->info_screens_from_main = parent->info_screens_from_main;
3049 static TreeInfo *getTreeInfoCopy(TreeInfo *ti)
3051 TreeInfo *ti_copy = newTreeInfo();
3053 // copy all values from the original structure
3055 ti_copy->type = ti->type;
3057 ti_copy->node_top = ti->node_top;
3058 ti_copy->node_parent = ti->node_parent;
3059 ti_copy->node_group = ti->node_group;
3060 ti_copy->next = ti->next;
3062 ti_copy->cl_first = ti->cl_first;
3063 ti_copy->cl_cursor = ti->cl_cursor;
3065 ti_copy->subdir = getStringCopy(ti->subdir);
3066 ti_copy->fullpath = getStringCopy(ti->fullpath);
3067 ti_copy->basepath = getStringCopy(ti->basepath);
3068 ti_copy->identifier = getStringCopy(ti->identifier);
3069 ti_copy->name = getStringCopy(ti->name);
3070 ti_copy->name_sorting = getStringCopy(ti->name_sorting);
3071 ti_copy->author = getStringCopy(ti->author);
3072 ti_copy->year = getStringCopy(ti->year);
3074 ti_copy->program_title = getStringCopy(ti->program_title);
3075 ti_copy->program_copyright = getStringCopy(ti->program_copyright);
3076 ti_copy->program_company = getStringCopy(ti->program_company);
3078 ti_copy->imported_from = getStringCopy(ti->imported_from);
3079 ti_copy->imported_by = getStringCopy(ti->imported_by);
3080 ti_copy->tested_by = getStringCopy(ti->tested_by);
3082 ti_copy->graphics_set_ecs = getStringCopy(ti->graphics_set_ecs);
3083 ti_copy->graphics_set_aga = getStringCopy(ti->graphics_set_aga);
3084 ti_copy->graphics_set = getStringCopy(ti->graphics_set);
3085 ti_copy->sounds_set_default = getStringCopy(ti->sounds_set_default);
3086 ti_copy->sounds_set_lowpass = getStringCopy(ti->sounds_set_lowpass);
3087 ti_copy->sounds_set = getStringCopy(ti->sounds_set);
3088 ti_copy->music_set = getStringCopy(ti->music_set);
3089 ti_copy->graphics_path = getStringCopy(ti->graphics_path);
3090 ti_copy->sounds_path = getStringCopy(ti->sounds_path);
3091 ti_copy->music_path = getStringCopy(ti->music_path);
3093 ti_copy->level_filename = getStringCopy(ti->level_filename);
3094 ti_copy->level_filetype = getStringCopy(ti->level_filetype);
3096 ti_copy->special_flags = getStringCopy(ti->special_flags);
3098 ti_copy->empty_level_name = getStringCopy(ti->empty_level_name);
3099 ti_copy->force_level_name = ti->force_level_name;
3101 ti_copy->levels = ti->levels;
3102 ti_copy->first_level = ti->first_level;
3103 ti_copy->last_level = ti->last_level;
3104 ti_copy->sort_priority = ti->sort_priority;
3106 ti_copy->latest_engine = ti->latest_engine;
3108 ti_copy->level_group = ti->level_group;
3109 ti_copy->parent_link = ti->parent_link;
3110 ti_copy->is_copy = ti->is_copy;
3111 ti_copy->in_user_dir = ti->in_user_dir;
3112 ti_copy->user_defined = ti->user_defined;
3113 ti_copy->readonly = ti->readonly;
3114 ti_copy->handicap = ti->handicap;
3115 ti_copy->time_limit = ti->time_limit;
3116 ti_copy->skip_levels = ti->skip_levels;
3118 ti_copy->use_emc_tiles = ti->use_emc_tiles;
3119 ti_copy->info_screens_from_main = ti->info_screens_from_main;
3121 ti_copy->color = ti->color;
3122 ti_copy->class_desc = getStringCopy(ti->class_desc);
3123 ti_copy->handicap_level = ti->handicap_level;
3125 ti_copy->infotext = getStringCopy(ti->infotext);
3130 void freeTreeInfo(TreeInfo *ti)
3135 checked_free(ti->subdir);
3136 checked_free(ti->fullpath);
3137 checked_free(ti->basepath);
3138 checked_free(ti->identifier);
3140 checked_free(ti->name);
3141 checked_free(ti->name_sorting);
3142 checked_free(ti->author);
3143 checked_free(ti->year);
3145 checked_free(ti->program_title);
3146 checked_free(ti->program_copyright);
3147 checked_free(ti->program_company);
3149 checked_free(ti->class_desc);
3151 checked_free(ti->infotext);
3153 if (ti->type == TREE_TYPE_LEVEL_DIR)
3155 checked_free(ti->imported_from);
3156 checked_free(ti->imported_by);
3157 checked_free(ti->tested_by);
3159 checked_free(ti->graphics_set_ecs);
3160 checked_free(ti->graphics_set_aga);
3161 checked_free(ti->graphics_set);
3162 checked_free(ti->sounds_set_default);
3163 checked_free(ti->sounds_set_lowpass);
3164 checked_free(ti->sounds_set);
3165 checked_free(ti->music_set);
3167 checked_free(ti->graphics_path);
3168 checked_free(ti->sounds_path);
3169 checked_free(ti->music_path);
3171 checked_free(ti->level_filename);
3172 checked_free(ti->level_filetype);
3174 checked_free(ti->special_flags);
3177 // recursively free child node
3179 freeTreeInfo(ti->node_group);
3181 // recursively free next node
3183 freeTreeInfo(ti->next);
3188 void setSetupInfo(struct TokenInfo *token_info,
3189 int token_nr, char *token_value)
3191 int token_type = token_info[token_nr].type;
3192 void *setup_value = token_info[token_nr].value;
3194 if (token_value == NULL)
3197 // set setup field to corresponding token value
3202 *(boolean *)setup_value = get_boolean_from_string(token_value);
3206 *(int *)setup_value = get_switch3_from_string(token_value);
3210 *(Key *)setup_value = getKeyFromKeyName(token_value);
3214 *(Key *)setup_value = getKeyFromX11KeyName(token_value);
3218 *(int *)setup_value = get_integer_from_string(token_value);
3222 checked_free(*(char **)setup_value);
3223 *(char **)setup_value = getStringCopy(token_value);
3227 *(int *)setup_value = get_player_nr_from_string(token_value);
3235 static int compareTreeInfoEntries(const void *object1, const void *object2)
3237 const TreeInfo *entry1 = *((TreeInfo **)object1);
3238 const TreeInfo *entry2 = *((TreeInfo **)object2);
3239 int tree_sorting1 = TREE_SORTING(entry1);
3240 int tree_sorting2 = TREE_SORTING(entry2);
3242 if (tree_sorting1 != tree_sorting2)
3243 return (tree_sorting1 - tree_sorting2);
3245 return strcasecmp(entry1->name_sorting, entry2->name_sorting);
3248 static TreeInfo *createParentTreeInfoNode(TreeInfo *node_parent)
3252 if (node_parent == NULL)
3255 ti_new = newTreeInfo();
3256 setTreeInfoToDefaults(ti_new, node_parent->type);
3258 ti_new->node_parent = node_parent;
3259 ti_new->parent_link = TRUE;
3261 setString(&ti_new->identifier, node_parent->identifier);
3262 setString(&ti_new->name, BACKLINK_TEXT_PARENT);
3263 setString(&ti_new->name_sorting, ti_new->name);
3265 setString(&ti_new->subdir, STRING_PARENT_DIRECTORY);
3266 setString(&ti_new->fullpath, node_parent->fullpath);
3268 ti_new->sort_priority = LEVELCLASS_PARENT;
3269 ti_new->latest_engine = node_parent->latest_engine;
3271 setString(&ti_new->class_desc, getLevelClassDescription(ti_new));
3273 pushTreeInfo(&node_parent->node_group, ti_new);
3278 static TreeInfo *createTopTreeInfoNode(TreeInfo *node_first)
3280 if (node_first == NULL)
3283 TreeInfo *ti_new = newTreeInfo();
3284 int type = node_first->type;
3286 setTreeInfoToDefaults(ti_new, type);
3288 ti_new->node_parent = NULL;
3289 ti_new->parent_link = FALSE;
3291 setString(&ti_new->identifier, "top_tree_node");
3292 setString(&ti_new->name, TREE_INFOTEXT(type));
3293 setString(&ti_new->name_sorting, ti_new->name);
3295 setString(&ti_new->subdir, STRING_TOP_DIRECTORY);
3296 setString(&ti_new->fullpath, ".");
3298 ti_new->sort_priority = LEVELCLASS_TOP;
3299 ti_new->latest_engine = node_first->latest_engine;
3301 setString(&ti_new->class_desc, TREE_INFOTEXT(type));
3303 ti_new->node_group = node_first;
3304 ti_new->level_group = TRUE;
3306 TreeInfo *ti_new2 = createParentTreeInfoNode(ti_new);
3308 setString(&ti_new2->name, TREE_BACKLINK_TEXT(type));
3309 setString(&ti_new2->name_sorting, ti_new2->name);
3314 static void setTreeInfoParentNodes(TreeInfo *node, TreeInfo *node_parent)
3318 if (node->node_group)
3319 setTreeInfoParentNodes(node->node_group, node);
3321 node->node_parent = node_parent;
3327 TreeInfo *addTopTreeInfoNode(TreeInfo *node_first)
3329 // add top tree node with back link node in previous tree
3330 node_first = createTopTreeInfoNode(node_first);
3332 // set all parent links (back links) in complete tree
3333 setTreeInfoParentNodes(node_first, NULL);
3339 // ----------------------------------------------------------------------------
3340 // functions for handling level and custom artwork info cache
3341 // ----------------------------------------------------------------------------
3343 static void LoadArtworkInfoCache(void)
3345 InitCacheDirectory();
3347 if (artworkinfo_cache_old == NULL)
3349 char *filename = getPath2(getCacheDir(), ARTWORKINFO_CACHE_FILE);
3351 // try to load artwork info hash from already existing cache file
3352 artworkinfo_cache_old = loadSetupFileHash(filename);
3354 // try to get program version that artwork info cache was written with
3355 char *version = getHashEntry(artworkinfo_cache_old, "program.version");
3357 // check program version of artwork info cache against current version
3358 if (!strEqual(version, program.version_string))
3360 freeSetupFileHash(artworkinfo_cache_old);
3362 artworkinfo_cache_old = NULL;
3365 // if no artwork info cache file was found, start with empty hash
3366 if (artworkinfo_cache_old == NULL)
3367 artworkinfo_cache_old = newSetupFileHash();
3372 if (artworkinfo_cache_new == NULL)
3373 artworkinfo_cache_new = newSetupFileHash();
3375 update_artworkinfo_cache = FALSE;
3378 static void SaveArtworkInfoCache(void)
3380 if (!update_artworkinfo_cache)
3383 char *filename = getPath2(getCacheDir(), ARTWORKINFO_CACHE_FILE);
3385 InitCacheDirectory();
3387 saveSetupFileHash(artworkinfo_cache_new, filename);
3392 static char *getCacheTokenPrefix(char *prefix1, char *prefix2)
3394 static char *prefix = NULL;
3396 checked_free(prefix);
3398 prefix = getStringCat2WithSeparator(prefix1, prefix2, ".");
3403 // (identical to above function, but separate string buffer needed -- nasty)
3404 static char *getCacheToken(char *prefix, char *suffix)
3406 static char *token = NULL;
3408 checked_free(token);
3410 token = getStringCat2WithSeparator(prefix, suffix, ".");
3415 static char *getFileTimestampString(char *filename)
3417 return getStringCopy(i_to_a(getFileTimestampEpochSeconds(filename)));
3420 static boolean modifiedFileTimestamp(char *filename, char *timestamp_string)
3422 struct stat file_status;
3424 if (timestamp_string == NULL)
3427 if (!fileExists(filename)) // file does not exist
3428 return (atoi(timestamp_string) != 0);
3430 if (stat(filename, &file_status) != 0) // cannot stat file
3433 return (file_status.st_mtime != atoi(timestamp_string));
3436 static TreeInfo *getArtworkInfoCacheEntry(LevelDirTree *level_node, int type)
3438 char *identifier = level_node->subdir;
3439 char *type_string = ARTWORK_DIRECTORY(type);
3440 char *token_prefix = getCacheTokenPrefix(type_string, identifier);
3441 char *token_main = getCacheToken(token_prefix, "CACHED");
3442 char *cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
3443 boolean cached = (cache_entry != NULL && strEqual(cache_entry, "true"));
3444 TreeInfo *artwork_info = NULL;
3446 if (!use_artworkinfo_cache)
3449 if (optional_tokens_hash == NULL)
3453 // create hash from list of optional tokens (for quick access)
3454 optional_tokens_hash = newSetupFileHash();
3455 for (i = 0; optional_tokens[i] != NULL; i++)
3456 setHashEntry(optional_tokens_hash, optional_tokens[i], "");
3463 artwork_info = newTreeInfo();
3464 setTreeInfoToDefaults(artwork_info, type);
3466 // set all structure fields according to the token/value pairs
3467 ldi = *artwork_info;
3468 for (i = 0; artworkinfo_tokens[i].type != -1; i++)
3470 char *token_suffix = artworkinfo_tokens[i].text;
3471 char *token = getCacheToken(token_prefix, token_suffix);
3472 char *value = getHashEntry(artworkinfo_cache_old, token);
3474 (getHashEntry(optional_tokens_hash, token_suffix) != NULL);
3476 setSetupInfo(artworkinfo_tokens, i, value);
3478 // check if cache entry for this item is mandatory, but missing
3479 if (value == NULL && !optional)
3481 Warn("missing cache entry '%s'", token);
3487 *artwork_info = ldi;
3492 char *filename_levelinfo = getPath2(getLevelDirFromTreeInfo(level_node),
3493 LEVELINFO_FILENAME);
3494 char *filename_artworkinfo = getPath2(getSetupArtworkDir(artwork_info),
3495 ARTWORKINFO_FILENAME(type));
3497 // check if corresponding "levelinfo.conf" file has changed
3498 token_main = getCacheToken(token_prefix, "TIMESTAMP_LEVELINFO");
3499 cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
3501 if (modifiedFileTimestamp(filename_levelinfo, cache_entry))
3504 // check if corresponding "<artworkinfo>.conf" file has changed
3505 token_main = getCacheToken(token_prefix, "TIMESTAMP_ARTWORKINFO");
3506 cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
3508 if (modifiedFileTimestamp(filename_artworkinfo, cache_entry))
3511 checked_free(filename_levelinfo);
3512 checked_free(filename_artworkinfo);
3515 if (!cached && artwork_info != NULL)
3517 freeTreeInfo(artwork_info);
3522 return artwork_info;
3525 static void setArtworkInfoCacheEntry(TreeInfo *artwork_info,
3526 LevelDirTree *level_node, int type)
3528 char *identifier = level_node->subdir;
3529 char *type_string = ARTWORK_DIRECTORY(type);
3530 char *token_prefix = getCacheTokenPrefix(type_string, identifier);
3531 char *token_main = getCacheToken(token_prefix, "CACHED");
3532 boolean set_cache_timestamps = TRUE;
3535 setHashEntry(artworkinfo_cache_new, token_main, "true");
3537 if (set_cache_timestamps)
3539 char *filename_levelinfo = getPath2(getLevelDirFromTreeInfo(level_node),
3540 LEVELINFO_FILENAME);
3541 char *filename_artworkinfo = getPath2(getSetupArtworkDir(artwork_info),
3542 ARTWORKINFO_FILENAME(type));
3543 char *timestamp_levelinfo = getFileTimestampString(filename_levelinfo);
3544 char *timestamp_artworkinfo = getFileTimestampString(filename_artworkinfo);
3546 token_main = getCacheToken(token_prefix, "TIMESTAMP_LEVELINFO");
3547 setHashEntry(artworkinfo_cache_new, token_main, timestamp_levelinfo);
3549 token_main = getCacheToken(token_prefix, "TIMESTAMP_ARTWORKINFO");
3550 setHashEntry(artworkinfo_cache_new, token_main, timestamp_artworkinfo);
3552 checked_free(filename_levelinfo);
3553 checked_free(filename_artworkinfo);
3554 checked_free(timestamp_levelinfo);
3555 checked_free(timestamp_artworkinfo);
3558 ldi = *artwork_info;
3559 for (i = 0; artworkinfo_tokens[i].type != -1; i++)
3561 char *token = getCacheToken(token_prefix, artworkinfo_tokens[i].text);
3562 char *value = getSetupValue(artworkinfo_tokens[i].type,
3563 artworkinfo_tokens[i].value);
3565 setHashEntry(artworkinfo_cache_new, token, value);
3570 // ----------------------------------------------------------------------------
3571 // functions for loading level info and custom artwork info
3572 // ----------------------------------------------------------------------------
3574 int GetZipFileTreeType(char *zip_filename)
3576 static char *top_dir_path = NULL;
3577 static char *top_dir_conf_filename[NUM_BASE_TREE_TYPES] = { NULL };
3578 static char *conf_basename[NUM_BASE_TREE_TYPES] =
3580 GRAPHICSINFO_FILENAME,
3581 SOUNDSINFO_FILENAME,
3587 checked_free(top_dir_path);
3588 top_dir_path = NULL;
3590 for (j = 0; j < NUM_BASE_TREE_TYPES; j++)
3592 checked_free(top_dir_conf_filename[j]);
3593 top_dir_conf_filename[j] = NULL;
3596 char **zip_entries = zip_list(zip_filename);
3598 // check if zip file successfully opened
3599 if (zip_entries == NULL || zip_entries[0] == NULL)
3600 return TREE_TYPE_UNDEFINED;
3602 // first zip file entry is expected to be top level directory
3603 char *top_dir = zip_entries[0];
3605 // check if valid top level directory found in zip file
3606 if (!strSuffix(top_dir, "/"))
3607 return TREE_TYPE_UNDEFINED;
3609 // get filenames of valid configuration files in top level directory
3610 for (j = 0; j < NUM_BASE_TREE_TYPES; j++)
3611 top_dir_conf_filename[j] = getStringCat2(top_dir, conf_basename[j]);
3613 int tree_type = TREE_TYPE_UNDEFINED;
3616 while (zip_entries[e] != NULL)
3618 // check if every zip file entry is below top level directory
3619 if (!strPrefix(zip_entries[e], top_dir))
3620 return TREE_TYPE_UNDEFINED;
3622 // check if this zip file entry is a valid configuration filename
3623 for (j = 0; j < NUM_BASE_TREE_TYPES; j++)
3625 if (strEqual(zip_entries[e], top_dir_conf_filename[j]))
3627 // only exactly one valid configuration file allowed
3628 if (tree_type != TREE_TYPE_UNDEFINED)
3629 return TREE_TYPE_UNDEFINED;
3641 static boolean CheckZipFileForDirectory(char *zip_filename, char *directory,
3644 static char *top_dir_path = NULL;
3645 static char *top_dir_conf_filename = NULL;
3647 checked_free(top_dir_path);
3648 checked_free(top_dir_conf_filename);
3650 top_dir_path = NULL;
3651 top_dir_conf_filename = NULL;
3653 char *conf_basename = (tree_type == TREE_TYPE_LEVEL_DIR ? LEVELINFO_FILENAME :
3654 ARTWORKINFO_FILENAME(tree_type));
3656 // check if valid configuration filename determined
3657 if (conf_basename == NULL || strEqual(conf_basename, ""))
3660 char **zip_entries = zip_list(zip_filename);
3662 // check if zip file successfully opened
3663 if (zip_entries == NULL || zip_entries[0] == NULL)
3666 // first zip file entry is expected to be top level directory
3667 char *top_dir = zip_entries[0];
3669 // check if valid top level directory found in zip file
3670 if (!strSuffix(top_dir, "/"))
3673 // get path of extracted top level directory
3674 top_dir_path = getPath2(directory, top_dir);
3676 // remove trailing directory separator from top level directory path
3677 // (required to be able to check for file and directory in next step)
3678 top_dir_path[strlen(top_dir_path) - 1] = '\0';
3680 // check if zip file's top level directory already exists in target directory
3681 if (fileExists(top_dir_path)) // (checks for file and directory)
3684 // get filename of configuration file in top level directory
3685 top_dir_conf_filename = getStringCat2(top_dir, conf_basename);
3687 boolean found_top_dir_conf_filename = FALSE;
3690 while (zip_entries[i] != NULL)
3692 // check if every zip file entry is below top level directory
3693 if (!strPrefix(zip_entries[i], top_dir))
3696 // check if this zip file entry is the configuration filename
3697 if (strEqual(zip_entries[i], top_dir_conf_filename))
3698 found_top_dir_conf_filename = TRUE;
3703 // check if valid configuration filename was found in zip file
3704 if (!found_top_dir_conf_filename)
3710 char *ExtractZipFileIntoDirectory(char *zip_filename, char *directory,
3713 boolean zip_file_valid = CheckZipFileForDirectory(zip_filename, directory,
3716 if (!zip_file_valid)
3718 Warn("zip file '%s' rejected!", zip_filename);
3723 char **zip_entries = zip_extract(zip_filename, directory);
3725 if (zip_entries == NULL)
3727 Warn("zip file '%s' could not be extracted!", zip_filename);
3732 Info("zip file '%s' successfully extracted!", zip_filename);
3734 // first zip file entry contains top level directory
3735 char *top_dir = zip_entries[0];
3737 // remove trailing directory separator from top level directory
3738 top_dir[strlen(top_dir) - 1] = '\0';
3743 static void ProcessZipFilesInDirectory(char *directory, int tree_type)
3746 DirectoryEntry *dir_entry;
3748 if ((dir = openDirectory(directory)) == NULL)
3750 // display error if directory is main "options.graphics_directory" etc.
3751 if (tree_type == TREE_TYPE_LEVEL_DIR ||
3752 directory == OPTIONS_ARTWORK_DIRECTORY(tree_type))
3753 Warn("cannot read directory '%s'", directory);
3758 while ((dir_entry = readDirectory(dir)) != NULL) // loop all entries
3760 // skip non-zip files (and also directories with zip extension)
3761 if (!strSuffixLower(dir_entry->basename, ".zip") || dir_entry->is_directory)
3764 char *zip_filename = getPath2(directory, dir_entry->basename);
3765 char *zip_filename_extracted = getStringCat2(zip_filename, ".extracted");
3766 char *zip_filename_rejected = getStringCat2(zip_filename, ".rejected");
3768 // check if zip file hasn't already been extracted or rejected
3769 if (!fileExists(zip_filename_extracted) &&
3770 !fileExists(zip_filename_rejected))
3772 char *top_dir = ExtractZipFileIntoDirectory(zip_filename, directory,
3774 char *marker_filename = (top_dir != NULL ? zip_filename_extracted :
3775 zip_filename_rejected);
3778 // create empty file to mark zip file as extracted or rejected
3779 if ((marker_file = fopen(marker_filename, MODE_WRITE)))
3780 fclose(marker_file);
3783 free(zip_filename_extracted);
3784 free(zip_filename_rejected);
3788 closeDirectory(dir);
3791 // forward declaration for recursive call by "LoadLevelInfoFromLevelDir()"
3792 static void LoadLevelInfoFromLevelDir(TreeInfo **, TreeInfo *, char *);
3794 static boolean LoadLevelInfoFromLevelConf(TreeInfo **node_first,
3795 TreeInfo *node_parent,
3796 char *level_directory,
3797 char *directory_name)
3799 char *directory_path = getPath2(level_directory, directory_name);
3800 char *filename = getPath2(directory_path, LEVELINFO_FILENAME);
3801 SetupFileHash *setup_file_hash;
3802 LevelDirTree *leveldir_new = NULL;
3805 // unless debugging, silently ignore directories without "levelinfo.conf"
3806 if (!options.debug && !fileExists(filename))
3808 free(directory_path);
3814 setup_file_hash = loadSetupFileHash(filename);
3816 if (setup_file_hash == NULL)
3818 #if DEBUG_NO_CONFIG_FILE
3819 Debug("setup", "ignoring level directory '%s'", directory_path);
3822 free(directory_path);
3828 leveldir_new = newTreeInfo();
3831 setTreeInfoToDefaultsFromParent(leveldir_new, node_parent);
3833 setTreeInfoToDefaults(leveldir_new, TREE_TYPE_LEVEL_DIR);
3835 leveldir_new->subdir = getStringCopy(directory_name);
3837 // set all structure fields according to the token/value pairs
3838 ldi = *leveldir_new;
3839 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
3840 setSetupInfo(levelinfo_tokens, i,
3841 getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
3842 *leveldir_new = ldi;
3844 if (strEqual(leveldir_new->name, ANONYMOUS_NAME))
3845 setString(&leveldir_new->name, leveldir_new->subdir);
3847 if (leveldir_new->identifier == NULL)
3848 leveldir_new->identifier = getStringCopy(leveldir_new->subdir);
3850 if (leveldir_new->name_sorting == NULL)
3851 leveldir_new->name_sorting = getStringCopy(leveldir_new->name);
3853 if (node_parent == NULL) // top level group
3855 leveldir_new->basepath = getStringCopy(level_directory);
3856 leveldir_new->fullpath = getStringCopy(leveldir_new->subdir);
3858 else // sub level group
3860 leveldir_new->basepath = getStringCopy(node_parent->basepath);
3861 leveldir_new->fullpath = getPath2(node_parent->fullpath, directory_name);
3864 leveldir_new->last_level =
3865 leveldir_new->first_level + leveldir_new->levels - 1;
3867 leveldir_new->in_user_dir =
3868 (!strEqual(leveldir_new->basepath, options.level_directory));
3870 // adjust some settings if user's private level directory was detected
3871 if (leveldir_new->sort_priority == LEVELCLASS_UNDEFINED &&
3872 leveldir_new->in_user_dir &&
3873 (strEqual(leveldir_new->subdir, getLoginName()) ||
3874 strEqual(leveldir_new->name, getLoginName()) ||
3875 strEqual(leveldir_new->author, getRealName())))
3877 leveldir_new->sort_priority = LEVELCLASS_PRIVATE_START;
3878 leveldir_new->readonly = FALSE;
3881 leveldir_new->user_defined =
3882 (leveldir_new->in_user_dir && IS_LEVELCLASS_PRIVATE(leveldir_new));
3884 setString(&leveldir_new->class_desc, getLevelClassDescription(leveldir_new));
3886 leveldir_new->handicap_level = // set handicap to default value
3887 (leveldir_new->user_defined || !leveldir_new->handicap ?
3888 leveldir_new->last_level : leveldir_new->first_level);
3890 DrawInitTextItem(leveldir_new->name);
3892 pushTreeInfo(node_first, leveldir_new);
3894 freeSetupFileHash(setup_file_hash);
3896 if (leveldir_new->level_group)
3898 // create node to link back to current level directory
3899 createParentTreeInfoNode(leveldir_new);
3901 // recursively step into sub-directory and look for more level series
3902 LoadLevelInfoFromLevelDir(&leveldir_new->node_group,
3903 leveldir_new, directory_path);
3906 free(directory_path);
3912 static void LoadLevelInfoFromLevelDir(TreeInfo **node_first,
3913 TreeInfo *node_parent,
3914 char *level_directory)
3916 // ---------- 1st stage: process any level set zip files ----------
3918 ProcessZipFilesInDirectory(level_directory, TREE_TYPE_LEVEL_DIR);
3920 // ---------- 2nd stage: check for level set directories ----------
3923 DirectoryEntry *dir_entry;
3924 boolean valid_entry_found = FALSE;
3926 if ((dir = openDirectory(level_directory)) == NULL)
3928 Warn("cannot read level directory '%s'", level_directory);
3933 while ((dir_entry = readDirectory(dir)) != NULL) // loop all entries
3935 char *directory_name = dir_entry->basename;
3936 char *directory_path = getPath2(level_directory, directory_name);
3938 // skip entries for current and parent directory
3939 if (strEqual(directory_name, ".") ||
3940 strEqual(directory_name, ".."))
3942 free(directory_path);
3947 // find out if directory entry is itself a directory
3948 if (!dir_entry->is_directory) // not a directory
3950 free(directory_path);
3955 free(directory_path);
3957 if (strEqual(directory_name, GRAPHICS_DIRECTORY) ||
3958 strEqual(directory_name, SOUNDS_DIRECTORY) ||
3959 strEqual(directory_name, MUSIC_DIRECTORY))
3962 valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
3967 closeDirectory(dir);
3969 // special case: top level directory may directly contain "levelinfo.conf"
3970 if (node_parent == NULL && !valid_entry_found)
3972 // check if this directory directly contains a file "levelinfo.conf"
3973 valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
3974 level_directory, ".");
3977 boolean valid_entry_expected =
3978 (strEqual(level_directory, options.level_directory) ||
3979 setup.internal.create_user_levelset);
3981 if (valid_entry_expected && !valid_entry_found)
3982 Warn("cannot find any valid level series in directory '%s'",
3986 boolean AdjustGraphicsForEMC(void)
3988 boolean settings_changed = FALSE;
3990 settings_changed |= adjustTreeGraphicsForEMC(leveldir_first_all);
3991 settings_changed |= adjustTreeGraphicsForEMC(leveldir_first);
3993 return settings_changed;
3996 boolean AdjustSoundsForEMC(void)
3998 boolean settings_changed = FALSE;
4000 settings_changed |= adjustTreeSoundsForEMC(leveldir_first_all);
4001 settings_changed |= adjustTreeSoundsForEMC(leveldir_first);
4003 return settings_changed;
4006 void LoadLevelInfo(void)
4008 InitUserLevelDirectory(getLoginName());
4010 DrawInitTextHead("Loading level series");
4012 LoadLevelInfoFromLevelDir(&leveldir_first, NULL, options.level_directory);
4013 LoadLevelInfoFromLevelDir(&leveldir_first, NULL, getUserLevelDir(NULL));
4015 leveldir_first = createTopTreeInfoNode(leveldir_first);
4017 /* after loading all level set information, clone the level directory tree
4018 and remove all level sets without levels (these may still contain artwork
4019 to be offered in the setup menu as "custom artwork", and are therefore
4020 checked for existing artwork in the function "LoadLevelArtworkInfo()") */
4021 leveldir_first_all = leveldir_first;
4022 cloneTree(&leveldir_first, leveldir_first_all, TRUE);
4024 AdjustGraphicsForEMC();
4025 AdjustSoundsForEMC();
4027 // before sorting, the first entries will be from the user directory
4028 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
4030 if (leveldir_first == NULL)
4031 Fail("cannot find any valid level series in any directory");
4033 sortTreeInfo(&leveldir_first);
4035 #if ENABLE_UNUSED_CODE
4036 dumpTreeInfo(leveldir_first, 0);
4040 static boolean LoadArtworkInfoFromArtworkConf(TreeInfo **node_first,
4041 TreeInfo *node_parent,
4042 char *base_directory,
4043 char *directory_name, int type)
4045 char *directory_path = getPath2(base_directory, directory_name);
4046 char *filename = getPath2(directory_path, ARTWORKINFO_FILENAME(type));
4047 SetupFileHash *setup_file_hash = NULL;
4048 TreeInfo *artwork_new = NULL;
4051 if (fileExists(filename))
4052 setup_file_hash = loadSetupFileHash(filename);
4054 if (setup_file_hash == NULL) // no config file -- look for artwork files
4057 DirectoryEntry *dir_entry;
4058 boolean valid_file_found = FALSE;
4060 if ((dir = openDirectory(directory_path)) != NULL)
4062 while ((dir_entry = readDirectory(dir)) != NULL)
4064 if (FileIsArtworkType(dir_entry->filename, type))
4066 valid_file_found = TRUE;
4072 closeDirectory(dir);
4075 if (!valid_file_found)
4077 #if DEBUG_NO_CONFIG_FILE
4078 if (!strEqual(directory_name, "."))
4079 Debug("setup", "ignoring artwork directory '%s'", directory_path);
4082 free(directory_path);
4089 artwork_new = newTreeInfo();
4092 setTreeInfoToDefaultsFromParent(artwork_new, node_parent);
4094 setTreeInfoToDefaults(artwork_new, type);
4096 artwork_new->subdir = getStringCopy(directory_name);
4098 if (setup_file_hash) // (before defining ".color" and ".class_desc")
4100 // set all structure fields according to the token/value pairs
4102 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
4103 setSetupInfo(levelinfo_tokens, i,
4104 getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
4107 if (strEqual(artwork_new->name, ANONYMOUS_NAME))
4108 setString(&artwork_new->name, artwork_new->subdir);
4110 if (artwork_new->identifier == NULL)
4111 artwork_new->identifier = getStringCopy(artwork_new->subdir);
4113 if (artwork_new->name_sorting == NULL)
4114 artwork_new->name_sorting = getStringCopy(artwork_new->name);
4117 if (node_parent == NULL) // top level group
4119 artwork_new->basepath = getStringCopy(base_directory);
4120 artwork_new->fullpath = getStringCopy(artwork_new->subdir);
4122 else // sub level group
4124 artwork_new->basepath = getStringCopy(node_parent->basepath);
4125 artwork_new->fullpath = getPath2(node_parent->fullpath, directory_name);
4128 artwork_new->in_user_dir =
4129 (!strEqual(artwork_new->basepath, OPTIONS_ARTWORK_DIRECTORY(type)));
4131 setString(&artwork_new->class_desc, getLevelClassDescription(artwork_new));
4133 if (setup_file_hash == NULL) // (after determining ".user_defined")
4135 if (strEqual(artwork_new->subdir, "."))
4137 if (artwork_new->user_defined)
4139 setString(&artwork_new->identifier, "private");
4140 artwork_new->sort_priority = ARTWORKCLASS_PRIVATE;
4144 setString(&artwork_new->identifier, "classic");
4145 artwork_new->sort_priority = ARTWORKCLASS_CLASSICS;
4148 setString(&artwork_new->class_desc,
4149 getLevelClassDescription(artwork_new));
4153 setString(&artwork_new->identifier, artwork_new->subdir);
4156 setString(&artwork_new->name, artwork_new->identifier);
4157 setString(&artwork_new->name_sorting, artwork_new->name);
4160 pushTreeInfo(node_first, artwork_new);
4162 freeSetupFileHash(setup_file_hash);
4164 free(directory_path);
4170 static void LoadArtworkInfoFromArtworkDir(TreeInfo **node_first,
4171 TreeInfo *node_parent,
4172 char *base_directory, int type)
4174 // ---------- 1st stage: process any artwork set zip files ----------
4176 ProcessZipFilesInDirectory(base_directory, type);
4178 // ---------- 2nd stage: check for artwork set directories ----------
4181 DirectoryEntry *dir_entry;
4182 boolean valid_entry_found = FALSE;
4184 if ((dir = openDirectory(base_directory)) == NULL)
4186 // display error if directory is main "options.graphics_directory" etc.
4187 if (base_directory == OPTIONS_ARTWORK_DIRECTORY(type))
4188 Warn("cannot read directory '%s'", base_directory);
4193 while ((dir_entry = readDirectory(dir)) != NULL) // loop all entries
4195 char *directory_name = dir_entry->basename;
4196 char *directory_path = getPath2(base_directory, directory_name);
4198 // skip directory entries for current and parent directory
4199 if (strEqual(directory_name, ".") ||
4200 strEqual(directory_name, ".."))
4202 free(directory_path);
4207 // skip directory entries which are not a directory
4208 if (!dir_entry->is_directory) // not a directory
4210 free(directory_path);
4215 free(directory_path);
4217 // check if this directory contains artwork with or without config file
4218 valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
4220 directory_name, type);
4223 closeDirectory(dir);
4225 // check if this directory directly contains artwork itself
4226 valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
4227 base_directory, ".",
4229 if (!valid_entry_found)
4230 Warn("cannot find any valid artwork in directory '%s'", base_directory);
4233 static TreeInfo *getDummyArtworkInfo(int type)
4235 // this is only needed when there is completely no artwork available
4236 TreeInfo *artwork_new = newTreeInfo();
4238 setTreeInfoToDefaults(artwork_new, type);
4240 setString(&artwork_new->subdir, UNDEFINED_FILENAME);
4241 setString(&artwork_new->fullpath, UNDEFINED_FILENAME);
4242 setString(&artwork_new->basepath, UNDEFINED_FILENAME);
4244 setString(&artwork_new->identifier, UNDEFINED_FILENAME);
4245 setString(&artwork_new->name, UNDEFINED_FILENAME);
4246 setString(&artwork_new->name_sorting, UNDEFINED_FILENAME);
4251 void SetCurrentArtwork(int type)
4253 ArtworkDirTree **current_ptr = ARTWORK_CURRENT_PTR(artwork, type);
4254 ArtworkDirTree *first_node = ARTWORK_FIRST_NODE(artwork, type);
4255 char *setup_set = SETUP_ARTWORK_SET(setup, type);
4256 char *default_subdir = ARTWORK_DEFAULT_SUBDIR(type);
4258 // set current artwork to artwork configured in setup menu
4259 *current_ptr = getTreeInfoFromIdentifier(first_node, setup_set);
4261 // if not found, set current artwork to default artwork
4262 if (*current_ptr == NULL)
4263 *current_ptr = getTreeInfoFromIdentifier(first_node, default_subdir);
4265 // if not found, set current artwork to first artwork in tree
4266 if (*current_ptr == NULL)
4267 *current_ptr = getFirstValidTreeInfoEntry(first_node);
4270 void ChangeCurrentArtworkIfNeeded(int type)
4272 char *current_identifier = ARTWORK_CURRENT_IDENTIFIER(artwork, type);
4273 char *setup_set = SETUP_ARTWORK_SET(setup, type);
4275 if (!strEqual(current_identifier, setup_set))
4276 SetCurrentArtwork(type);
4279 void LoadArtworkInfo(void)
4281 LoadArtworkInfoCache();
4283 DrawInitTextHead("Looking for custom artwork");
4285 LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
4286 options.graphics_directory,
4287 TREE_TYPE_GRAPHICS_DIR);
4288 LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
4289 getUserGraphicsDir(),
4290 TREE_TYPE_GRAPHICS_DIR);
4292 LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
4293 options.sounds_directory,
4294 TREE_TYPE_SOUNDS_DIR);
4295 LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
4297 TREE_TYPE_SOUNDS_DIR);
4299 LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
4300 options.music_directory,
4301 TREE_TYPE_MUSIC_DIR);
4302 LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
4304 TREE_TYPE_MUSIC_DIR);
4306 if (artwork.gfx_first == NULL)
4307 artwork.gfx_first = getDummyArtworkInfo(TREE_TYPE_GRAPHICS_DIR);
4308 if (artwork.snd_first == NULL)
4309 artwork.snd_first = getDummyArtworkInfo(TREE_TYPE_SOUNDS_DIR);
4310 if (artwork.mus_first == NULL)
4311 artwork.mus_first = getDummyArtworkInfo(TREE_TYPE_MUSIC_DIR);
4313 // before sorting, the first entries will be from the user directory
4314 SetCurrentArtwork(ARTWORK_TYPE_GRAPHICS);
4315 SetCurrentArtwork(ARTWORK_TYPE_SOUNDS);
4316 SetCurrentArtwork(ARTWORK_TYPE_MUSIC);
4318 artwork.gfx_current_identifier = artwork.gfx_current->identifier;
4319 artwork.snd_current_identifier = artwork.snd_current->identifier;
4320 artwork.mus_current_identifier = artwork.mus_current->identifier;
4322 #if ENABLE_UNUSED_CODE
4323 Debug("setup:LoadArtworkInfo", "graphics set == %s",
4324 artwork.gfx_current_identifier);
4325 Debug("setup:LoadArtworkInfo", "sounds set == %s",
4326 artwork.snd_current_identifier);
4327 Debug("setup:LoadArtworkInfo", "music set == %s",
4328 artwork.mus_current_identifier);
4331 sortTreeInfo(&artwork.gfx_first);
4332 sortTreeInfo(&artwork.snd_first);
4333 sortTreeInfo(&artwork.mus_first);
4335 #if ENABLE_UNUSED_CODE
4336 dumpTreeInfo(artwork.gfx_first, 0);
4337 dumpTreeInfo(artwork.snd_first, 0);
4338 dumpTreeInfo(artwork.mus_first, 0);
4342 static void MoveArtworkInfoIntoSubTree(ArtworkDirTree **artwork_node)
4344 ArtworkDirTree *artwork_new = newTreeInfo();
4345 char *top_node_name = "standalone artwork";
4347 setTreeInfoToDefaults(artwork_new, (*artwork_node)->type);
4349 artwork_new->level_group = TRUE;
4351 setString(&artwork_new->identifier, top_node_name);
4352 setString(&artwork_new->name, top_node_name);
4353 setString(&artwork_new->name_sorting, top_node_name);
4355 // create node to link back to current custom artwork directory
4356 createParentTreeInfoNode(artwork_new);
4358 // move existing custom artwork tree into newly created sub-tree
4359 artwork_new->node_group->next = *artwork_node;
4361 // change custom artwork tree to contain only newly created node
4362 *artwork_node = artwork_new;
4365 static void LoadArtworkInfoFromLevelInfoExt(ArtworkDirTree **artwork_node,
4366 ArtworkDirTree *node_parent,
4367 LevelDirTree *level_node,
4368 boolean empty_level_set_mode)
4370 int type = (*artwork_node)->type;
4372 // recursively check all level directories for artwork sub-directories
4376 boolean empty_level_set = (level_node->levels == 0);
4378 // check all tree entries for artwork, but skip parent link entries
4379 if (!level_node->parent_link && empty_level_set == empty_level_set_mode)
4381 TreeInfo *artwork_new = getArtworkInfoCacheEntry(level_node, type);
4382 boolean cached = (artwork_new != NULL);
4386 pushTreeInfo(artwork_node, artwork_new);
4390 TreeInfo *topnode_last = *artwork_node;
4391 char *path = getPath2(getLevelDirFromTreeInfo(level_node),
4392 ARTWORK_DIRECTORY(type));
4394 LoadArtworkInfoFromArtworkDir(artwork_node, NULL, path, type);
4396 if (topnode_last != *artwork_node) // check for newly added node
4398 artwork_new = *artwork_node;
4400 setString(&artwork_new->identifier, level_node->subdir);
4401 setString(&artwork_new->name, level_node->name);
4402 setString(&artwork_new->name_sorting, level_node->name_sorting);
4404 artwork_new->sort_priority = level_node->sort_priority;
4405 artwork_new->in_user_dir = level_node->in_user_dir;
4407 update_artworkinfo_cache = TRUE;
4413 // insert artwork info (from old cache or filesystem) into new cache
4414 if (artwork_new != NULL)
4415 setArtworkInfoCacheEntry(artwork_new, level_node, type);
4418 DrawInitTextItem(level_node->name);
4420 if (level_node->node_group != NULL)
4422 TreeInfo *artwork_new = newTreeInfo();
4425 setTreeInfoToDefaultsFromParent(artwork_new, node_parent);
4427 setTreeInfoToDefaults(artwork_new, type);
4429 artwork_new->level_group = TRUE;
4431 setString(&artwork_new->identifier, level_node->subdir);
4433 if (node_parent == NULL) // check for top tree node
4435 char *top_node_name = (empty_level_set_mode ?
4436 "artwork for certain level sets" :
4437 "artwork included in level sets");
4439 setString(&artwork_new->name, top_node_name);
4440 setString(&artwork_new->name_sorting, top_node_name);
4444 setString(&artwork_new->name, level_node->name);
4445 setString(&artwork_new->name_sorting, level_node->name_sorting);
4448 pushTreeInfo(artwork_node, artwork_new);
4450 // create node to link back to current custom artwork directory
4451 createParentTreeInfoNode(artwork_new);
4453 // recursively step into sub-directory and look for more custom artwork
4454 LoadArtworkInfoFromLevelInfoExt(&artwork_new->node_group, artwork_new,
4455 level_node->node_group,
4456 empty_level_set_mode);
4458 // if sub-tree has no custom artwork at all, remove it
4459 if (artwork_new->node_group->next == NULL)
4460 removeTreeInfo(artwork_node);
4463 level_node = level_node->next;
4467 static void LoadArtworkInfoFromLevelInfo(ArtworkDirTree **artwork_node)
4469 // move peviously loaded artwork tree into separate sub-tree
4470 MoveArtworkInfoIntoSubTree(artwork_node);
4472 // load artwork from level sets into separate sub-trees
4473 LoadArtworkInfoFromLevelInfoExt(artwork_node, NULL, leveldir_first_all, TRUE);
4474 LoadArtworkInfoFromLevelInfoExt(artwork_node, NULL, leveldir_first_all, FALSE);
4476 // add top tree node over all sub-trees and set parent links
4477 *artwork_node = addTopTreeInfoNode(*artwork_node);
4480 void LoadLevelArtworkInfo(void)
4482 print_timestamp_init("LoadLevelArtworkInfo");
4484 DrawInitTextHead("Looking for custom level artwork");
4486 print_timestamp_time("DrawTimeText");
4488 LoadArtworkInfoFromLevelInfo(&artwork.gfx_first);
4489 print_timestamp_time("LoadArtworkInfoFromLevelInfo (gfx)");
4490 LoadArtworkInfoFromLevelInfo(&artwork.snd_first);
4491 print_timestamp_time("LoadArtworkInfoFromLevelInfo (snd)");
4492 LoadArtworkInfoFromLevelInfo(&artwork.mus_first);
4493 print_timestamp_time("LoadArtworkInfoFromLevelInfo (mus)");
4495 SaveArtworkInfoCache();
4497 print_timestamp_time("SaveArtworkInfoCache");
4499 // needed for reloading level artwork not known at ealier stage
4500 ChangeCurrentArtworkIfNeeded(ARTWORK_TYPE_GRAPHICS);
4501 ChangeCurrentArtworkIfNeeded(ARTWORK_TYPE_SOUNDS);
4502 ChangeCurrentArtworkIfNeeded(ARTWORK_TYPE_MUSIC);
4504 print_timestamp_time("getTreeInfoFromIdentifier");
4506 sortTreeInfo(&artwork.gfx_first);
4507 sortTreeInfo(&artwork.snd_first);
4508 sortTreeInfo(&artwork.mus_first);
4510 print_timestamp_time("sortTreeInfo");
4512 #if ENABLE_UNUSED_CODE
4513 dumpTreeInfo(artwork.gfx_first, 0);
4514 dumpTreeInfo(artwork.snd_first, 0);
4515 dumpTreeInfo(artwork.mus_first, 0);
4518 print_timestamp_done("LoadLevelArtworkInfo");
4521 static boolean AddTreeSetToTreeInfoExt(TreeInfo *tree_node_old, char *tree_dir,
4522 char *tree_subdir_new, int type)
4524 if (tree_node_old == NULL)
4526 if (type == TREE_TYPE_LEVEL_DIR)
4528 // get level info tree node of personal user level set
4529 tree_node_old = getTreeInfoFromIdentifier(leveldir_first, getLoginName());
4531 // this may happen if "setup.internal.create_user_levelset" is FALSE
4532 // or if file "levelinfo.conf" is missing in personal user level set
4533 if (tree_node_old == NULL)
4534 tree_node_old = leveldir_first->node_group;
4538 // get artwork info tree node of first artwork set
4539 tree_node_old = ARTWORK_FIRST_NODE(artwork, type);
4543 if (tree_dir == NULL)
4544 tree_dir = TREE_USERDIR(type);
4546 if (tree_node_old == NULL ||
4548 tree_subdir_new == NULL) // should not happen
4551 int draw_deactivation_mask = GetDrawDeactivationMask();
4553 // override draw deactivation mask (temporarily disable drawing)
4554 SetDrawDeactivationMask(REDRAW_ALL);
4556 if (type == TREE_TYPE_LEVEL_DIR)
4558 // load new level set config and add it next to first user level set
4559 LoadLevelInfoFromLevelConf(&tree_node_old->next,
4560 tree_node_old->node_parent,
4561 tree_dir, tree_subdir_new);
4565 // load new artwork set config and add it next to first artwork set
4566 LoadArtworkInfoFromArtworkConf(&tree_node_old->next,
4567 tree_node_old->node_parent,
4568 tree_dir, tree_subdir_new, type);
4571 // set draw deactivation mask to previous value
4572 SetDrawDeactivationMask(draw_deactivation_mask);
4574 // get first node of level or artwork info tree
4575 TreeInfo **tree_node_first = TREE_FIRST_NODE_PTR(type);
4577 // get tree info node of newly added level or artwork set
4578 TreeInfo *tree_node_new = getTreeInfoFromIdentifier(*tree_node_first,
4581 if (tree_node_new == NULL) // should not happen
4584 // correct top link and parent node link of newly created tree node
4585 tree_node_new->node_top = tree_node_old->node_top;
4586 tree_node_new->node_parent = tree_node_old->node_parent;
4588 // sort tree info to adjust position of newly added tree set
4589 sortTreeInfo(tree_node_first);
4594 void AddTreeSetToTreeInfo(TreeInfo *tree_node, char *tree_dir,
4595 char *tree_subdir_new, int type)
4597 if (!AddTreeSetToTreeInfoExt(tree_node, tree_dir, tree_subdir_new, type))
4598 Fail("internal tree info structure corrupted -- aborting");
4601 void AddUserLevelSetToLevelInfo(char *level_subdir_new)
4603 AddTreeSetToTreeInfo(NULL, NULL, level_subdir_new, TREE_TYPE_LEVEL_DIR);
4606 char *getArtworkIdentifierForUserLevelSet(int type)
4608 char *classic_artwork_set = getClassicArtworkSet(type);
4610 // check for custom artwork configured in "levelinfo.conf"
4611 char *leveldir_artwork_set =
4612 *LEVELDIR_ARTWORK_SET_PTR(leveldir_current, type);
4613 boolean has_leveldir_artwork_set =
4614 (leveldir_artwork_set != NULL && !strEqual(leveldir_artwork_set,
4615 classic_artwork_set));
4617 // check for custom artwork in sub-directory "graphics" etc.
4618 TreeInfo *artwork_first_node = ARTWORK_FIRST_NODE(artwork, type);
4619 char *leveldir_identifier = leveldir_current->identifier;
4620 boolean has_artwork_subdir =
4621 (getTreeInfoFromIdentifier(artwork_first_node,
4622 leveldir_identifier) != NULL);
4624 return (has_leveldir_artwork_set ? leveldir_artwork_set :
4625 has_artwork_subdir ? leveldir_identifier :
4626 classic_artwork_set);
4629 TreeInfo *getArtworkTreeInfoForUserLevelSet(int type)
4631 char *artwork_set = getArtworkIdentifierForUserLevelSet(type);
4632 TreeInfo *artwork_first_node = ARTWORK_FIRST_NODE(artwork, type);
4633 TreeInfo *ti = getTreeInfoFromIdentifier(artwork_first_node, artwork_set);
4637 ti = getTreeInfoFromIdentifier(artwork_first_node,
4638 ARTWORK_DEFAULT_SUBDIR(type));
4640 Fail("cannot find default graphics -- should not happen");
4646 boolean checkIfCustomArtworkExistsForCurrentLevelSet(void)
4648 char *graphics_set =
4649 getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_GRAPHICS);
4651 getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_SOUNDS);
4653 getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_MUSIC);
4655 return (!strEqual(graphics_set, GFX_CLASSIC_SUBDIR) ||
4656 !strEqual(sounds_set, SND_CLASSIC_SUBDIR) ||
4657 !strEqual(music_set, MUS_CLASSIC_SUBDIR));
4660 boolean UpdateUserLevelSet(char *level_subdir, char *level_name,
4661 char *level_author, int num_levels)
4663 char *filename = getPath2(getUserLevelDir(level_subdir), LEVELINFO_FILENAME);
4664 char *filename_tmp = getStringCat2(filename, ".tmp");
4666 FILE *file_tmp = NULL;
4667 char line[MAX_LINE_LEN];
4668 boolean success = FALSE;
4669 LevelDirTree *leveldir = getTreeInfoFromIdentifier(leveldir_first,
4671 // update values in level directory tree
4673 if (level_name != NULL)
4674 setString(&leveldir->name, level_name);
4676 if (level_author != NULL)
4677 setString(&leveldir->author, level_author);
4679 if (num_levels != -1)
4680 leveldir->levels = num_levels;
4682 // update values that depend on other values
4684 setString(&leveldir->name_sorting, leveldir->name);
4686 leveldir->last_level = leveldir->first_level + leveldir->levels - 1;
4688 // sort order of level sets may have changed
4689 sortTreeInfo(&leveldir_first);
4691 if ((file = fopen(filename, MODE_READ)) &&
4692 (file_tmp = fopen(filename_tmp, MODE_WRITE)))
4694 while (fgets(line, MAX_LINE_LEN, file))
4696 if (strPrefix(line, "name:") && level_name != NULL)
4697 fprintf(file_tmp, "%-32s%s\n", "name:", level_name);
4698 else if (strPrefix(line, "author:") && level_author != NULL)
4699 fprintf(file_tmp, "%-32s%s\n", "author:", level_author);
4700 else if (strPrefix(line, "levels:") && num_levels != -1)
4701 fprintf(file_tmp, "%-32s%d\n", "levels:", num_levels);
4703 fputs(line, file_tmp);
4716 success = (rename(filename_tmp, filename) == 0);
4724 boolean CreateUserLevelSet(char *level_subdir, char *level_name,
4725 char *level_author, int num_levels,
4726 boolean use_artwork_set)
4728 LevelDirTree *level_info;
4733 // create user level sub-directory, if needed
4734 createDirectory(getUserLevelDir(level_subdir), "user level");
4736 filename = getPath2(getUserLevelDir(level_subdir), LEVELINFO_FILENAME);
4738 if (!(file = fopen(filename, MODE_WRITE)))
4740 Warn("cannot write level info file '%s'", filename);
4747 level_info = newTreeInfo();
4749 // always start with reliable default values
4750 setTreeInfoToDefaults(level_info, TREE_TYPE_LEVEL_DIR);
4752 setString(&level_info->name, level_name);
4753 setString(&level_info->author, level_author);
4754 level_info->levels = num_levels;
4755 level_info->first_level = 1;
4756 level_info->sort_priority = LEVELCLASS_PRIVATE_START;
4757 level_info->readonly = FALSE;
4759 if (use_artwork_set)
4761 level_info->graphics_set =
4762 getStringCopy(getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_GRAPHICS));
4763 level_info->sounds_set =
4764 getStringCopy(getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_SOUNDS));
4765 level_info->music_set =
4766 getStringCopy(getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_MUSIC));
4769 token_value_position = TOKEN_VALUE_POSITION_SHORT;
4771 fprintFileHeader(file, LEVELINFO_FILENAME);
4774 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
4776 if (i == LEVELINFO_TOKEN_NAME ||
4777 i == LEVELINFO_TOKEN_AUTHOR ||
4778 i == LEVELINFO_TOKEN_LEVELS ||
4779 i == LEVELINFO_TOKEN_FIRST_LEVEL ||
4780 i == LEVELINFO_TOKEN_SORT_PRIORITY ||
4781 i == LEVELINFO_TOKEN_READONLY ||
4782 (use_artwork_set && (i == LEVELINFO_TOKEN_GRAPHICS_SET ||
4783 i == LEVELINFO_TOKEN_SOUNDS_SET ||
4784 i == LEVELINFO_TOKEN_MUSIC_SET)))
4785 fprintf(file, "%s\n", getSetupLine(levelinfo_tokens, "", i));
4787 // just to make things nicer :)
4788 if (i == LEVELINFO_TOKEN_AUTHOR ||
4789 i == LEVELINFO_TOKEN_FIRST_LEVEL ||
4790 (use_artwork_set && i == LEVELINFO_TOKEN_READONLY))
4791 fprintf(file, "\n");
4794 token_value_position = TOKEN_VALUE_POSITION_DEFAULT;
4798 SetFilePermissions(filename, PERMS_PRIVATE);
4800 freeTreeInfo(level_info);
4806 static void SaveUserLevelInfo(void)
4808 CreateUserLevelSet(getLoginName(), getLoginName(), getRealName(), 100, FALSE);
4811 char *getSetupValue(int type, void *value)
4813 static char value_string[MAX_LINE_LEN];
4821 strcpy(value_string, (*(boolean *)value ? "true" : "false"));
4825 strcpy(value_string, (*(boolean *)value ? "on" : "off"));
4829 strcpy(value_string, (*(int *)value == AUTO ? "auto" :
4830 *(int *)value == FALSE ? "off" : "on"));
4834 strcpy(value_string, (*(boolean *)value ? "yes" : "no"));
4837 case TYPE_YES_NO_AUTO:
4838 strcpy(value_string, (*(int *)value == AUTO ? "auto" :
4839 *(int *)value == FALSE ? "no" : "yes"));
4843 strcpy(value_string, (*(boolean *)value ? "AGA" : "ECS"));
4847 strcpy(value_string, getKeyNameFromKey(*(Key *)value));
4851 strcpy(value_string, getX11KeyNameFromKey(*(Key *)value));
4855 sprintf(value_string, "%d", *(int *)value);
4859 if (*(char **)value == NULL)
4862 strcpy(value_string, *(char **)value);
4866 sprintf(value_string, "player_%d", *(int *)value + 1);
4870 value_string[0] = '\0';
4874 if (type & TYPE_GHOSTED)
4875 strcpy(value_string, "n/a");
4877 return value_string;
4880 char *getSetupLine(struct TokenInfo *token_info, char *prefix, int token_nr)
4884 static char token_string[MAX_LINE_LEN];
4885 int token_type = token_info[token_nr].type;
4886 void *setup_value = token_info[token_nr].value;
4887 char *token_text = token_info[token_nr].text;
4888 char *value_string = getSetupValue(token_type, setup_value);
4890 // build complete token string
4891 sprintf(token_string, "%s%s", prefix, token_text);
4893 // build setup entry line
4894 line = getFormattedSetupEntry(token_string, value_string);
4896 if (token_type == TYPE_KEY_X11)
4898 Key key = *(Key *)setup_value;
4899 char *keyname = getKeyNameFromKey(key);
4901 // add comment, if useful
4902 if (!strEqual(keyname, "(undefined)") &&
4903 !strEqual(keyname, "(unknown)"))
4905 // add at least one whitespace
4907 for (i = strlen(line); i < token_comment_position; i++)
4911 strcat(line, keyname);
4918 static void InitLastPlayedLevels_ParentNode(void)
4920 LevelDirTree **leveldir_top = &leveldir_first->node_group->next;
4921 LevelDirTree *leveldir_new = NULL;
4923 // check if parent node for last played levels already exists
4924 if (strEqual((*leveldir_top)->identifier, TOKEN_STR_LAST_LEVEL_SERIES))
4927 leveldir_new = newTreeInfo();
4929 setTreeInfoToDefaultsFromParent(leveldir_new, leveldir_first);
4931 leveldir_new->level_group = TRUE;
4932 leveldir_new->sort_priority = LEVELCLASS_LAST_PLAYED_LEVEL;
4934 setString(&leveldir_new->identifier, TOKEN_STR_LAST_LEVEL_SERIES);
4935 setString(&leveldir_new->name, "<< (last played level sets)");
4936 setString(&leveldir_new->name_sorting, leveldir_new->name);
4938 pushTreeInfo(leveldir_top, leveldir_new);
4940 // create node to link back to current level directory
4941 createParentTreeInfoNode(leveldir_new);
4944 void UpdateLastPlayedLevels_TreeInfo(void)
4946 char **last_level_series = setup.level_setup.last_level_series;
4947 LevelDirTree *leveldir_last;
4948 TreeInfo **node_new = NULL;
4951 if (last_level_series[0] == NULL)
4954 InitLastPlayedLevels_ParentNode();
4956 leveldir_last = getTreeInfoFromIdentifierExt(leveldir_first,
4957 TOKEN_STR_LAST_LEVEL_SERIES,
4958 TREE_NODE_TYPE_GROUP);
4959 if (leveldir_last == NULL)
4962 node_new = &leveldir_last->node_group->next;
4964 freeTreeInfo(*node_new);
4968 for (i = 0; last_level_series[i] != NULL; i++)
4970 LevelDirTree *node_last = getTreeInfoFromIdentifier(leveldir_first,
4971 last_level_series[i]);
4972 if (node_last == NULL)
4975 *node_new = getTreeInfoCopy(node_last); // copy complete node
4977 (*node_new)->node_top = &leveldir_first; // correct top node link
4978 (*node_new)->node_parent = leveldir_last; // correct parent node link
4980 (*node_new)->is_copy = TRUE; // mark entry as node copy
4982 (*node_new)->node_group = NULL;
4983 (*node_new)->next = NULL;
4985 (*node_new)->cl_first = -1; // force setting tree cursor
4987 node_new = &((*node_new)->next);
4991 static void UpdateLastPlayedLevels_List(void)
4993 char **last_level_series = setup.level_setup.last_level_series;
4994 int pos = MAX_LEVELDIR_HISTORY - 1;
4997 // search for potentially already existing entry in list of level sets
4998 for (i = 0; last_level_series[i] != NULL; i++)
4999 if (strEqual(last_level_series[i], leveldir_current->identifier))
5002 // move list of level sets one entry down (using potentially free entry)
5003 for (i = pos; i > 0; i--)
5004 setString(&last_level_series[i], last_level_series[i - 1]);
5006 // put last played level set at top position
5007 setString(&last_level_series[0], leveldir_current->identifier);
5010 #define LAST_PLAYED_MODE_SET 1
5011 #define LAST_PLAYED_MODE_SET_FORCED 2
5012 #define LAST_PLAYED_MODE_GET 3
5014 static TreeInfo *StoreOrRestoreLastPlayedLevels(TreeInfo *node, int mode)
5016 static char *identifier = NULL;
5018 if (mode == LAST_PLAYED_MODE_SET)
5020 setString(&identifier, (node && node->is_copy ? node->identifier : NULL));
5022 else if (mode == LAST_PLAYED_MODE_SET_FORCED)
5024 setString(&identifier, (node ? node->identifier : NULL));
5026 else if (mode == LAST_PLAYED_MODE_GET)
5028 TreeInfo *node_new = getTreeInfoFromIdentifierExt(leveldir_first,
5030 TREE_NODE_TYPE_COPY);
5031 return (node_new != NULL ? node_new : node);
5034 return NULL; // not used
5037 void StoreLastPlayedLevels(TreeInfo *node)
5039 StoreOrRestoreLastPlayedLevels(node, LAST_PLAYED_MODE_SET);
5042 void ForcedStoreLastPlayedLevels(TreeInfo *node)
5044 StoreOrRestoreLastPlayedLevels(node, LAST_PLAYED_MODE_SET_FORCED);
5047 void RestoreLastPlayedLevels(TreeInfo **node)
5049 *node = StoreOrRestoreLastPlayedLevels(*node, LAST_PLAYED_MODE_GET);
5052 boolean CheckLastPlayedLevels(void)
5054 return (StoreOrRestoreLastPlayedLevels(NULL, LAST_PLAYED_MODE_GET) != NULL);
5057 void LoadLevelSetup_LastSeries(void)
5059 // --------------------------------------------------------------------------
5060 // ~/.<program>/levelsetup.conf
5061 // --------------------------------------------------------------------------
5063 char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
5064 SetupFileHash *level_setup_hash = NULL;
5068 // always start with reliable default values
5069 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
5071 // start with empty history of last played level sets
5072 setString(&setup.level_setup.last_level_series[0], NULL);
5074 if (!strEqual(DEFAULT_LEVELSET, UNDEFINED_LEVELSET))
5076 leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
5078 if (leveldir_current == NULL)
5079 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
5082 if ((level_setup_hash = loadSetupFileHash(filename)))
5084 char *last_level_series =
5085 getHashEntry(level_setup_hash, TOKEN_STR_LAST_LEVEL_SERIES);
5087 leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
5089 if (leveldir_current == NULL)
5090 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
5092 char *last_played_menu_used =
5093 getHashEntry(level_setup_hash, TOKEN_STR_LAST_PLAYED_MENU_USED);
5095 // store if last level set was selected from "last played" menu
5096 if (strEqual(last_played_menu_used, "true"))
5097 ForcedStoreLastPlayedLevels(leveldir_current);
5099 for (i = 0; i < MAX_LEVELDIR_HISTORY; i++)
5101 char token[strlen(TOKEN_STR_LAST_LEVEL_SERIES) + 10];
5102 LevelDirTree *leveldir_last;
5104 sprintf(token, "%s.%03d", TOKEN_STR_LAST_LEVEL_SERIES, i);
5106 last_level_series = getHashEntry(level_setup_hash, token);
5108 leveldir_last = getTreeInfoFromIdentifier(leveldir_first,
5110 if (leveldir_last != NULL)
5111 setString(&setup.level_setup.last_level_series[pos++],
5115 setString(&setup.level_setup.last_level_series[pos], NULL);
5117 freeSetupFileHash(level_setup_hash);
5121 Debug("setup", "using default setup values");
5127 static void SaveLevelSetup_LastSeries_Ext(boolean deactivate_last_level_series)
5129 // --------------------------------------------------------------------------
5130 // ~/.<program>/levelsetup.conf
5131 // --------------------------------------------------------------------------
5133 // check if the current level directory structure is available at this point
5134 if (leveldir_current == NULL)
5137 char **last_level_series = setup.level_setup.last_level_series;
5138 char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
5142 InitUserDataDirectory();
5144 UpdateLastPlayedLevels_List();
5146 if (!(file = fopen(filename, MODE_WRITE)))
5148 Warn("cannot write setup file '%s'", filename);
5155 fprintFileHeader(file, LEVELSETUP_FILENAME);
5157 if (deactivate_last_level_series)
5158 fprintf(file, "# %s\n# ", "the following level set may have caused a problem and was deactivated");
5160 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_LAST_LEVEL_SERIES,
5161 leveldir_current->identifier));
5163 // store if last level set was selected from "last played" menu
5164 boolean last_played_menu_used = CheckLastPlayedLevels();
5165 char *setup_value = getSetupValue(TYPE_BOOLEAN, &last_played_menu_used);
5167 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_LAST_PLAYED_MENU_USED,
5170 for (i = 0; last_level_series[i] != NULL; i++)
5172 char token[strlen(TOKEN_STR_LAST_LEVEL_SERIES) + 1 + 10 + 1];
5174 sprintf(token, "%s.%03d", TOKEN_STR_LAST_LEVEL_SERIES, i);
5176 fprintf(file, "%s\n", getFormattedSetupEntry(token, last_level_series[i]));
5181 SetFilePermissions(filename, PERMS_PRIVATE);
5186 void SaveLevelSetup_LastSeries(void)
5188 SaveLevelSetup_LastSeries_Ext(FALSE);
5191 void SaveLevelSetup_LastSeries_Deactivate(void)
5193 SaveLevelSetup_LastSeries_Ext(TRUE);
5196 static void checkSeriesInfo(void)
5198 static char *level_directory = NULL;
5201 DirectoryEntry *dir_entry;
5204 checked_free(level_directory);
5206 // check for more levels besides the 'levels' field of 'levelinfo.conf'
5208 level_directory = getPath2((leveldir_current->in_user_dir ?
5209 getUserLevelDir(NULL) :
5210 options.level_directory),
5211 leveldir_current->fullpath);
5213 if ((dir = openDirectory(level_directory)) == NULL)
5215 Warn("cannot read level directory '%s'", level_directory);
5221 while ((dir_entry = readDirectory(dir)) != NULL) // loop all entries
5223 if (strlen(dir_entry->basename) > 4 &&
5224 dir_entry->basename[3] == '.' &&
5225 strEqual(&dir_entry->basename[4], LEVELFILE_EXTENSION))
5227 char levelnum_str[4];
5230 strncpy(levelnum_str, dir_entry->basename, 3);
5231 levelnum_str[3] = '\0';
5233 levelnum_value = atoi(levelnum_str);
5235 if (levelnum_value < leveldir_current->first_level)
5237 Warn("additional level %d found", levelnum_value);
5239 leveldir_current->first_level = levelnum_value;
5241 else if (levelnum_value > leveldir_current->last_level)
5243 Warn("additional level %d found", levelnum_value);
5245 leveldir_current->last_level = levelnum_value;
5251 closeDirectory(dir);
5254 void LoadLevelSetup_SeriesInfo(void)
5257 SetupFileHash *level_setup_hash = NULL;
5258 char *level_subdir = leveldir_current->subdir;
5261 // always start with reliable default values
5262 level_nr = leveldir_current->first_level;
5264 for (i = 0; i < MAX_LEVELS; i++)
5266 LevelStats_setPlayed(i, 0);
5267 LevelStats_setSolved(i, 0);
5272 // --------------------------------------------------------------------------
5273 // ~/.<program>/levelsetup/<level series>/levelsetup.conf
5274 // --------------------------------------------------------------------------
5276 level_subdir = leveldir_current->subdir;
5278 filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
5280 if ((level_setup_hash = loadSetupFileHash(filename)))
5284 // get last played level in this level set
5286 token_value = getHashEntry(level_setup_hash, TOKEN_STR_LAST_PLAYED_LEVEL);
5290 level_nr = atoi(token_value);
5292 if (level_nr < leveldir_current->first_level)
5293 level_nr = leveldir_current->first_level;
5294 if (level_nr > leveldir_current->last_level)
5295 level_nr = leveldir_current->last_level;
5298 // get handicap level in this level set
5300 token_value = getHashEntry(level_setup_hash, TOKEN_STR_HANDICAP_LEVEL);
5304 int level_nr = atoi(token_value);
5306 if (level_nr < leveldir_current->first_level)
5307 level_nr = leveldir_current->first_level;
5308 if (level_nr > leveldir_current->last_level + 1)
5309 level_nr = leveldir_current->last_level;
5311 if (leveldir_current->user_defined || !leveldir_current->handicap)
5312 level_nr = leveldir_current->last_level;
5314 leveldir_current->handicap_level = level_nr;
5317 // get number of played and solved levels in this level set
5319 BEGIN_HASH_ITERATION(level_setup_hash, itr)
5321 char *token = HASH_ITERATION_TOKEN(itr);
5322 char *value = HASH_ITERATION_VALUE(itr);
5324 if (strlen(token) == 3 &&
5325 token[0] >= '0' && token[0] <= '9' &&
5326 token[1] >= '0' && token[1] <= '9' &&
5327 token[2] >= '0' && token[2] <= '9')
5329 int level_nr = atoi(token);
5332 LevelStats_setPlayed(level_nr, atoi(value)); // read 1st column
5334 value = strchr(value, ' ');
5337 LevelStats_setSolved(level_nr, atoi(value)); // read 2nd column
5340 END_HASH_ITERATION(hash, itr)
5342 freeSetupFileHash(level_setup_hash);
5346 Debug("setup", "using default setup values");
5352 void SaveLevelSetup_SeriesInfo(void)
5355 char *level_subdir = leveldir_current->subdir;
5356 char *level_nr_str = int2str(level_nr, 0);
5357 char *handicap_level_str = int2str(leveldir_current->handicap_level, 0);
5361 // --------------------------------------------------------------------------
5362 // ~/.<program>/levelsetup/<level series>/levelsetup.conf
5363 // --------------------------------------------------------------------------
5365 InitLevelSetupDirectory(level_subdir);
5367 filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
5369 if (!(file = fopen(filename, MODE_WRITE)))
5371 Warn("cannot write setup file '%s'", filename);
5378 fprintFileHeader(file, LEVELSETUP_FILENAME);
5380 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_PLAYED_LEVEL,
5382 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_HANDICAP_LEVEL,
5383 handicap_level_str));
5385 for (i = leveldir_current->first_level; i <= leveldir_current->last_level;
5388 if (LevelStats_getPlayed(i) > 0 ||
5389 LevelStats_getSolved(i) > 0)
5394 sprintf(token, "%03d", i);
5395 sprintf(value, "%d %d", LevelStats_getPlayed(i), LevelStats_getSolved(i));
5397 fprintf(file, "%s\n", getFormattedSetupEntry(token, value));
5403 SetFilePermissions(filename, PERMS_PRIVATE);
5408 int LevelStats_getPlayed(int nr)
5410 return (nr >= 0 && nr < MAX_LEVELS ? level_stats[nr].played : 0);
5413 int LevelStats_getSolved(int nr)
5415 return (nr >= 0 && nr < MAX_LEVELS ? level_stats[nr].solved : 0);
5418 void LevelStats_setPlayed(int nr, int value)
5420 if (nr >= 0 && nr < MAX_LEVELS)
5421 level_stats[nr].played = value;
5424 void LevelStats_setSolved(int nr, int value)
5426 if (nr >= 0 && nr < MAX_LEVELS)
5427 level_stats[nr].solved = value;
5430 void LevelStats_incPlayed(int nr)
5432 if (nr >= 0 && nr < MAX_LEVELS)
5433 level_stats[nr].played++;
5436 void LevelStats_incSolved(int nr)
5438 if (nr >= 0 && nr < MAX_LEVELS)
5439 level_stats[nr].solved++;
5442 void LoadUserSetup(void)
5444 // --------------------------------------------------------------------------
5445 // ~/.<program>/usersetup.conf
5446 // --------------------------------------------------------------------------
5448 char *filename = getPath2(getMainUserGameDataDir(), USERSETUP_FILENAME);
5449 SetupFileHash *user_setup_hash = NULL;
5451 // always start with reliable default values
5454 if ((user_setup_hash = loadSetupFileHash(filename)))
5458 // get last selected user number
5459 token_value = getHashEntry(user_setup_hash, TOKEN_STR_LAST_USER);
5462 user.nr = MIN(MAX(0, atoi(token_value)), MAX_PLAYER_NAMES - 1);
5464 freeSetupFileHash(user_setup_hash);
5468 Debug("setup", "using default setup values");
5474 void SaveUserSetup(void)
5476 // --------------------------------------------------------------------------
5477 // ~/.<program>/usersetup.conf
5478 // --------------------------------------------------------------------------
5480 char *filename = getPath2(getMainUserGameDataDir(), USERSETUP_FILENAME);
5483 InitMainUserDataDirectory();
5485 if (!(file = fopen(filename, MODE_WRITE)))
5487 Warn("cannot write setup file '%s'", filename);
5494 fprintFileHeader(file, USERSETUP_FILENAME);
5496 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_USER,
5500 SetFilePermissions(filename, PERMS_PRIVATE);