1 // ============================================================================
2 // Artsoft Retro-Game Library
3 // ----------------------------------------------------------------------------
4 // (c) 1995-2014 by Artsoft Entertainment
7 // https://www.artsoft.org/
8 // ----------------------------------------------------------------------------
10 // ============================================================================
12 #include <sys/types.h>
27 #include "zip/miniunz.h"
30 #define ENABLE_UNUSED_CODE FALSE // for currently unused functions
31 #define DEBUG_NO_CONFIG_FILE FALSE // for extra-verbose debug output
33 #define NUM_LEVELCLASS_DESC 8
35 static char *levelclass_desc[NUM_LEVELCLASS_DESC] =
47 #define TOKEN_VALUE_POSITION_SHORT 32
48 #define TOKEN_VALUE_POSITION_DEFAULT 40
49 #define TOKEN_COMMENT_POSITION_DEFAULT 60
51 #define TREE_NODE_TYPE_DEFAULT 0
52 #define TREE_NODE_TYPE_PARENT 1
53 #define TREE_NODE_TYPE_GROUP 2
54 #define TREE_NODE_TYPE_COPY 3
56 #define TREE_NODE_TYPE(ti) (ti->node_group ? TREE_NODE_TYPE_GROUP : \
57 ti->parent_link ? TREE_NODE_TYPE_PARENT : \
58 ti->is_copy ? TREE_NODE_TYPE_COPY : \
59 TREE_NODE_TYPE_DEFAULT)
62 static void setTreeInfoToDefaults(TreeInfo *, int);
63 static TreeInfo *getTreeInfoCopy(TreeInfo *ti);
64 static int compareTreeInfoEntries(const void *, const void *);
66 static int token_value_position = TOKEN_VALUE_POSITION_DEFAULT;
67 static int token_comment_position = TOKEN_COMMENT_POSITION_DEFAULT;
69 static SetupFileHash *artworkinfo_cache_old = NULL;
70 static SetupFileHash *artworkinfo_cache_new = NULL;
71 static SetupFileHash *optional_tokens_hash = NULL;
72 static SetupFileHash *missing_file_hash = NULL;
73 static boolean use_artworkinfo_cache = TRUE;
74 static boolean update_artworkinfo_cache = FALSE;
77 // ----------------------------------------------------------------------------
79 // ----------------------------------------------------------------------------
81 static void WarnUsingFallback(char *filename)
83 if (getHashEntry(missing_file_hash, filename) == NULL)
85 setHashEntry(missing_file_hash, filename, "");
87 Debug("setup", "cannot find artwork file '%s' (using fallback)", filename);
91 static char *getLevelClassDescription(TreeInfo *ti)
93 int position = ti->sort_priority / 100;
95 if (position >= 0 && position < NUM_LEVELCLASS_DESC)
96 return levelclass_desc[position];
98 return "Unknown Level Class";
101 static char *getCacheDir(void)
103 static char *cache_dir = NULL;
105 if (cache_dir == NULL)
106 cache_dir = getPath2(getMainUserGameDataDir(), CACHE_DIRECTORY);
111 static char *getScoreDir(char *level_subdir)
113 static char *score_dir = NULL;
114 static char *score_level_dir = NULL;
115 char *score_subdir = SCORES_DIRECTORY;
117 if (score_dir == NULL)
118 score_dir = getPath2(getMainUserGameDataDir(), score_subdir);
120 if (level_subdir != NULL)
122 checked_free(score_level_dir);
124 score_level_dir = getPath2(score_dir, level_subdir);
126 return score_level_dir;
132 static char *getScoreCacheDir(char *level_subdir)
134 static char *score_dir = NULL;
135 static char *score_level_dir = NULL;
136 char *score_subdir = SCORES_DIRECTORY;
138 if (score_dir == NULL)
139 score_dir = getPath2(getCacheDir(), score_subdir);
141 if (level_subdir != NULL)
143 checked_free(score_level_dir);
145 score_level_dir = getPath2(score_dir, level_subdir);
147 return score_level_dir;
153 static char *getScoreTapeDir(char *level_subdir, int nr)
155 static char *score_tape_dir = NULL;
156 char tape_subdir[MAX_FILENAME_LEN];
158 checked_free(score_tape_dir);
160 sprintf(tape_subdir, "%03d", nr);
161 score_tape_dir = getPath2(getScoreDir(level_subdir), tape_subdir);
163 return score_tape_dir;
166 static char *getScoreCacheTapeDir(char *level_subdir, int nr)
168 static char *score_cache_tape_dir = NULL;
169 char tape_subdir[MAX_FILENAME_LEN];
171 checked_free(score_cache_tape_dir);
173 sprintf(tape_subdir, "%03d", nr);
174 score_cache_tape_dir = getPath2(getScoreCacheDir(level_subdir), tape_subdir);
176 return score_cache_tape_dir;
179 static char *getUserSubdir(int nr)
181 static char user_subdir[16] = { 0 };
183 sprintf(user_subdir, "%03d", nr);
188 static char *getUserDir(int nr)
190 static char *user_dir = NULL;
191 char *main_data_dir = getMainUserGameDataDir();
192 char *users_subdir = USERS_DIRECTORY;
193 char *user_subdir = getUserSubdir(nr);
195 checked_free(user_dir);
198 user_dir = getPath3(main_data_dir, users_subdir, user_subdir);
200 user_dir = getPath2(main_data_dir, users_subdir);
205 static char *getLevelSetupDir(char *level_subdir)
207 static char *levelsetup_dir = NULL;
208 char *data_dir = getUserGameDataDir();
209 char *levelsetup_subdir = LEVELSETUP_DIRECTORY;
211 checked_free(levelsetup_dir);
213 if (level_subdir != NULL)
214 levelsetup_dir = getPath3(data_dir, levelsetup_subdir, level_subdir);
216 levelsetup_dir = getPath2(data_dir, levelsetup_subdir);
218 return levelsetup_dir;
221 static char *getNetworkDir(void)
223 static char *network_dir = NULL;
225 if (network_dir == NULL)
226 network_dir = getPath2(getMainUserGameDataDir(), NETWORK_DIRECTORY);
231 char *getLevelDirFromTreeInfo(TreeInfo *node)
233 static char *level_dir = NULL;
236 return options.level_directory;
238 checked_free(level_dir);
240 level_dir = getPath2((node->in_user_dir ? getUserLevelDir(NULL) :
241 options.level_directory), node->fullpath);
246 char *getUserLevelDir(char *level_subdir)
248 static char *userlevel_dir = NULL;
249 char *data_dir = getMainUserGameDataDir();
250 char *userlevel_subdir = LEVELS_DIRECTORY;
252 checked_free(userlevel_dir);
254 if (level_subdir != NULL)
255 userlevel_dir = getPath3(data_dir, userlevel_subdir, level_subdir);
257 userlevel_dir = getPath2(data_dir, userlevel_subdir);
259 return userlevel_dir;
262 char *getNetworkLevelDir(char *level_subdir)
264 static char *network_level_dir = NULL;
265 char *data_dir = getNetworkDir();
266 char *networklevel_subdir = LEVELS_DIRECTORY;
268 checked_free(network_level_dir);
270 if (level_subdir != NULL)
271 network_level_dir = getPath3(data_dir, networklevel_subdir, level_subdir);
273 network_level_dir = getPath2(data_dir, networklevel_subdir);
275 return network_level_dir;
278 char *getCurrentLevelDir(void)
280 return getLevelDirFromTreeInfo(leveldir_current);
283 char *getNewUserLevelSubdir(void)
285 static char *new_level_subdir = NULL;
286 char *subdir_prefix = getLoginName();
287 char subdir_suffix[10];
288 int max_suffix_number = 1000;
291 while (++i < max_suffix_number)
293 sprintf(subdir_suffix, "_%d", i);
295 checked_free(new_level_subdir);
296 new_level_subdir = getStringCat2(subdir_prefix, subdir_suffix);
298 if (!directoryExists(getUserLevelDir(new_level_subdir)))
302 return new_level_subdir;
305 char *getTapeDir(char *level_subdir)
307 static char *tape_dir = NULL;
308 char *data_dir = getUserGameDataDir();
309 char *tape_subdir = TAPES_DIRECTORY;
311 checked_free(tape_dir);
313 if (level_subdir != NULL)
314 tape_dir = getPath3(data_dir, tape_subdir, level_subdir);
316 tape_dir = getPath2(data_dir, tape_subdir);
321 static char *getSolutionTapeDir(void)
323 static char *tape_dir = NULL;
324 char *data_dir = getCurrentLevelDir();
325 char *tape_subdir = TAPES_DIRECTORY;
327 checked_free(tape_dir);
329 tape_dir = getPath2(data_dir, tape_subdir);
334 static char *getDefaultGraphicsDir(char *graphics_subdir)
336 static char *graphics_dir = NULL;
338 if (graphics_subdir == NULL)
339 return options.graphics_directory;
341 checked_free(graphics_dir);
343 graphics_dir = getPath2(options.graphics_directory, graphics_subdir);
348 static char *getDefaultSoundsDir(char *sounds_subdir)
350 static char *sounds_dir = NULL;
352 if (sounds_subdir == NULL)
353 return options.sounds_directory;
355 checked_free(sounds_dir);
357 sounds_dir = getPath2(options.sounds_directory, sounds_subdir);
362 static char *getDefaultMusicDir(char *music_subdir)
364 static char *music_dir = NULL;
366 if (music_subdir == NULL)
367 return options.music_directory;
369 checked_free(music_dir);
371 music_dir = getPath2(options.music_directory, music_subdir);
376 static char *getClassicArtworkSet(int type)
378 return (type == TREE_TYPE_GRAPHICS_DIR ? GFX_CLASSIC_SUBDIR :
379 type == TREE_TYPE_SOUNDS_DIR ? SND_CLASSIC_SUBDIR :
380 type == TREE_TYPE_MUSIC_DIR ? MUS_CLASSIC_SUBDIR : "");
383 static char *getClassicArtworkDir(int type)
385 return (type == TREE_TYPE_GRAPHICS_DIR ?
386 getDefaultGraphicsDir(GFX_CLASSIC_SUBDIR) :
387 type == TREE_TYPE_SOUNDS_DIR ?
388 getDefaultSoundsDir(SND_CLASSIC_SUBDIR) :
389 type == TREE_TYPE_MUSIC_DIR ?
390 getDefaultMusicDir(MUS_CLASSIC_SUBDIR) : "");
393 char *getUserGraphicsDir(void)
395 static char *usergraphics_dir = NULL;
397 if (usergraphics_dir == NULL)
398 usergraphics_dir = getPath2(getMainUserGameDataDir(), GRAPHICS_DIRECTORY);
400 return usergraphics_dir;
403 char *getUserSoundsDir(void)
405 static char *usersounds_dir = NULL;
407 if (usersounds_dir == NULL)
408 usersounds_dir = getPath2(getMainUserGameDataDir(), SOUNDS_DIRECTORY);
410 return usersounds_dir;
413 char *getUserMusicDir(void)
415 static char *usermusic_dir = NULL;
417 if (usermusic_dir == NULL)
418 usermusic_dir = getPath2(getMainUserGameDataDir(), MUSIC_DIRECTORY);
420 return usermusic_dir;
423 static char *getSetupArtworkDir(TreeInfo *ti)
425 static char *artwork_dir = NULL;
430 checked_free(artwork_dir);
432 artwork_dir = getPath2(ti->basepath, ti->fullpath);
437 char *setLevelArtworkDir(TreeInfo *ti)
439 char **artwork_path_ptr, **artwork_set_ptr;
440 TreeInfo *level_artwork;
442 if (ti == NULL || leveldir_current == NULL)
445 artwork_path_ptr = LEVELDIR_ARTWORK_PATH_PTR(leveldir_current, ti->type);
446 artwork_set_ptr = LEVELDIR_ARTWORK_SET_PTR( leveldir_current, ti->type);
448 checked_free(*artwork_path_ptr);
450 if ((level_artwork = getTreeInfoFromIdentifier(ti, *artwork_set_ptr)))
452 *artwork_path_ptr = getStringCopy(getSetupArtworkDir(level_artwork));
457 No (or non-existing) artwork configured in "levelinfo.conf". This would
458 normally result in using the artwork configured in the setup menu. But
459 if an artwork subdirectory exists (which might contain custom artwork
460 or an artwork configuration file), this level artwork must be treated
461 as relative to the default "classic" artwork, not to the artwork that
462 is currently configured in the setup menu.
464 Update: For "special" versions of R'n'D (like "R'n'D jue"), do not use
465 the "default" artwork (which would be "jue0" for "R'n'D jue"), but use
466 the real "classic" artwork from the original R'n'D (like "gfx_classic").
469 char *dir = getPath2(getCurrentLevelDir(), ARTWORK_DIRECTORY(ti->type));
471 checked_free(*artwork_set_ptr);
473 if (directoryExists(dir))
475 *artwork_path_ptr = getStringCopy(getClassicArtworkDir(ti->type));
476 *artwork_set_ptr = getStringCopy(getClassicArtworkSet(ti->type));
480 *artwork_path_ptr = getStringCopy(UNDEFINED_FILENAME);
481 *artwork_set_ptr = NULL;
487 return *artwork_set_ptr;
490 static char *getLevelArtworkSet(int type)
492 if (leveldir_current == NULL)
495 return LEVELDIR_ARTWORK_SET(leveldir_current, type);
498 static char *getLevelArtworkDir(int type)
500 if (leveldir_current == NULL)
501 return UNDEFINED_FILENAME;
503 return LEVELDIR_ARTWORK_PATH(leveldir_current, type);
506 char *getProgramMainDataPath(char *command_filename, char *base_path)
508 // check if the program's main data base directory is configured
509 if (!strEqual(base_path, "."))
510 return getStringCopy(base_path);
512 /* if the program is configured to start from current directory (default),
513 determine program package directory from program binary (some versions
514 of KDE/Konqueror and Mac OS X (especially "Mavericks") apparently do not
515 set the current working directory to the program package directory) */
516 char *main_data_path = getBasePath(command_filename);
518 #if defined(PLATFORM_MAC)
519 if (strSuffix(main_data_path, MAC_APP_BINARY_SUBDIR))
521 char *main_data_path_old = main_data_path;
523 // cut relative path to Mac OS X application binary directory from path
524 main_data_path[strlen(main_data_path) -
525 strlen(MAC_APP_BINARY_SUBDIR)] = '\0';
527 // cut trailing path separator from path (but not if path is root directory)
528 if (strSuffix(main_data_path, "/") && !strEqual(main_data_path, "/"))
529 main_data_path[strlen(main_data_path) - 1] = '\0';
531 // replace empty path with current directory
532 if (strEqual(main_data_path, ""))
533 main_data_path = ".";
535 // add relative path to Mac OS X application resources directory to path
536 main_data_path = getPath2(main_data_path, MAC_APP_FILES_SUBDIR);
538 free(main_data_path_old);
542 return main_data_path;
545 char *getProgramConfigFilename(char *command_filename)
547 static char *config_filename_1 = NULL;
548 static char *config_filename_2 = NULL;
549 static char *config_filename_3 = NULL;
550 static boolean initialized = FALSE;
554 char *command_filename_1 = getStringCopy(command_filename);
556 // strip trailing executable suffix from command filename
557 if (strSuffix(command_filename_1, ".exe"))
558 command_filename_1[strlen(command_filename_1) - 4] = '\0';
560 char *base_path = getProgramMainDataPath(command_filename, BASE_PATH);
561 char *conf_directory = getPath2(base_path, CONF_DIRECTORY);
563 char *command_basepath = getBasePath(command_filename);
564 char *command_basename = getBaseNameNoSuffix(command_filename);
565 char *command_filename_2 = getPath2(command_basepath, command_basename);
567 config_filename_1 = getStringCat2(command_filename_1, ".conf");
568 config_filename_2 = getStringCat2(command_filename_2, ".conf");
569 config_filename_3 = getPath2(conf_directory, SETUP_FILENAME);
571 checked_free(base_path);
572 checked_free(conf_directory);
574 checked_free(command_basepath);
575 checked_free(command_basename);
577 checked_free(command_filename_1);
578 checked_free(command_filename_2);
583 // 1st try: look for config file that exactly matches the binary filename
584 if (fileExists(config_filename_1))
585 return config_filename_1;
587 // 2nd try: look for config file that matches binary filename without suffix
588 if (fileExists(config_filename_2))
589 return config_filename_2;
591 // 3rd try: return setup config filename in global program config directory
592 return config_filename_3;
595 static char *getPlatformConfigFilename(char *config_filename)
597 static char *platform_config_filename = NULL;
598 static boolean initialized = FALSE;
602 char *config_basepath = getBasePath(config_filename);
603 char *config_basename = getBaseNameNoSuffix(config_filename);
604 char *config_filename_prefix = getPath2(config_basepath, config_basename);
605 char *platform_string_lower = getStringToLower(PLATFORM_STRING);
606 char *platform_suffix = getStringCat2("-", platform_string_lower);
608 platform_config_filename = getStringCat3(config_filename_prefix,
609 platform_suffix, ".conf");
611 checked_free(config_basepath);
612 checked_free(config_basename);
613 checked_free(config_filename_prefix);
614 checked_free(platform_string_lower);
615 checked_free(platform_suffix);
620 return platform_config_filename;
623 char *getTapeFilename(int nr)
625 static char *filename = NULL;
626 char basename[MAX_FILENAME_LEN];
628 checked_free(filename);
630 sprintf(basename, "%03d.%s", nr, TAPEFILE_EXTENSION);
631 filename = getPath2(getTapeDir(leveldir_current->subdir), basename);
636 char *getTemporaryTapeFilename(void)
638 static char *filename = NULL;
639 char basename[MAX_FILENAME_LEN];
641 checked_free(filename);
643 sprintf(basename, "tmp.%s", TAPEFILE_EXTENSION);
644 filename = getPath2(getTapeDir(NULL), basename);
649 char *getDefaultSolutionTapeFilename(int nr)
651 static char *filename = NULL;
652 char basename[MAX_FILENAME_LEN];
654 checked_free(filename);
656 sprintf(basename, "%03d.%s", nr, TAPEFILE_EXTENSION);
657 filename = getPath2(getSolutionTapeDir(), basename);
662 char *getSokobanSolutionTapeFilename(int nr)
664 static char *filename = NULL;
665 char basename[MAX_FILENAME_LEN];
667 checked_free(filename);
669 sprintf(basename, "%03d.sln", nr);
670 filename = getPath2(getSolutionTapeDir(), basename);
675 char *getSolutionTapeFilename(int nr)
677 char *filename = getDefaultSolutionTapeFilename(nr);
679 if (!fileExists(filename))
681 char *filename2 = getSokobanSolutionTapeFilename(nr);
683 if (fileExists(filename2))
690 char *getScoreFilename(int nr)
692 static char *filename = NULL;
693 char basename[MAX_FILENAME_LEN];
695 checked_free(filename);
697 sprintf(basename, "%03d.%s", nr, SCOREFILE_EXTENSION);
699 // used instead of "leveldir_current->subdir" (for network games)
700 filename = getPath2(getScoreDir(levelset.identifier), basename);
705 char *getScoreCacheFilename(int nr)
707 static char *filename = NULL;
708 char basename[MAX_FILENAME_LEN];
710 checked_free(filename);
712 sprintf(basename, "%03d.%s", nr, SCOREFILE_EXTENSION);
714 // used instead of "leveldir_current->subdir" (for network games)
715 filename = getPath2(getScoreCacheDir(levelset.identifier), basename);
720 char *getScoreTapeBasename(char *name)
722 static char basename[MAX_FILENAME_LEN];
723 char basename_raw[MAX_FILENAME_LEN];
726 sprintf(timestamp, "%s", getCurrentTimestamp());
727 sprintf(basename_raw, "%s-%s", timestamp, name);
728 sprintf(basename, "%s-%08x", timestamp, get_hash_from_key(basename_raw));
733 char *getScoreTapeFilename(char *basename_no_ext, int nr)
735 static char *filename = NULL;
736 char basename[MAX_FILENAME_LEN];
738 checked_free(filename);
740 sprintf(basename, "%s.%s", basename_no_ext, TAPEFILE_EXTENSION);
742 // used instead of "leveldir_current->subdir" (for network games)
743 filename = getPath2(getScoreTapeDir(levelset.identifier, nr), basename);
748 char *getScoreCacheTapeFilename(char *basename_no_ext, int nr)
750 static char *filename = NULL;
751 char basename[MAX_FILENAME_LEN];
753 checked_free(filename);
755 sprintf(basename, "%s.%s", basename_no_ext, TAPEFILE_EXTENSION);
757 // used instead of "leveldir_current->subdir" (for network games)
758 filename = getPath2(getScoreCacheTapeDir(levelset.identifier, nr), basename);
763 char *getSetupFilename(void)
765 static char *filename = NULL;
767 checked_free(filename);
769 filename = getPath2(getSetupDir(), SETUP_FILENAME);
774 char *getDefaultSetupFilename(void)
776 return program.config_filename;
779 char *getPlatformSetupFilename(void)
781 return getPlatformConfigFilename(program.config_filename);
784 char *getEditorSetupFilename(void)
786 static char *filename = NULL;
788 checked_free(filename);
789 filename = getPath2(getCurrentLevelDir(), EDITORSETUP_FILENAME);
791 if (fileExists(filename))
794 checked_free(filename);
795 filename = getPath2(getSetupDir(), EDITORSETUP_FILENAME);
800 char *getHelpAnimFilename(void)
802 static char *filename = NULL;
804 checked_free(filename);
806 filename = getPath2(getCurrentLevelDir(), HELPANIM_FILENAME);
811 char *getHelpTextFilename(void)
813 static char *filename = NULL;
815 checked_free(filename);
817 filename = getPath2(getCurrentLevelDir(), HELPTEXT_FILENAME);
822 static char *getLevelSetInfoBasename(int nr)
824 static char basename[32];
826 sprintf(basename, "levelset_%d.txt", nr + 1);
831 char *getLevelSetInfoFilename(int nr)
833 char *basename = getLevelSetInfoBasename(nr);
834 static char *info_subdir = NULL;
835 static char *filename = NULL;
837 if (info_subdir == NULL)
838 info_subdir = getPath2(DOCS_DIRECTORY, LEVELSET_INFO_DIRECTORY);
840 checked_free(filename);
842 // look for level set info file the current level set directory
843 filename = getPath3(getCurrentLevelDir(), info_subdir, basename);
844 if (fileExists(filename))
864 for (i = 0; basenames[i] != NULL; i++)
866 checked_free(filename);
867 filename = getPath2(getCurrentLevelDir(), basenames[i]);
869 if (fileExists(filename))
876 static char *getLevelSetTitleMessageBasename(int nr, boolean initial)
878 static char basename[32];
880 sprintf(basename, "%s_%d.txt",
881 (initial ? "titlemessage_initial" : "titlemessage"), nr + 1);
886 char *getLevelSetTitleMessageFilename(int nr, boolean initial)
888 static char *filename = NULL;
890 boolean skip_setup_artwork = FALSE;
892 checked_free(filename);
894 basename = getLevelSetTitleMessageBasename(nr, initial);
896 if (!gfx.override_level_graphics)
898 // 1st try: look for special artwork in current level series directory
899 filename = getPath3(getCurrentLevelDir(), GRAPHICS_DIRECTORY, basename);
900 if (fileExists(filename))
905 // 2nd try: look for message file in current level set directory
906 filename = getPath2(getCurrentLevelDir(), basename);
907 if (fileExists(filename))
912 // check if there is special artwork configured in level series config
913 if (getLevelArtworkSet(ARTWORK_TYPE_GRAPHICS) != NULL)
915 // 3rd try: look for special artwork configured in level series config
916 filename = getPath2(getLevelArtworkDir(ARTWORK_TYPE_GRAPHICS), basename);
917 if (fileExists(filename))
922 // take missing artwork configured in level set config from default
923 skip_setup_artwork = TRUE;
927 if (!skip_setup_artwork)
929 // 4th try: look for special artwork in configured artwork directory
930 filename = getPath2(getSetupArtworkDir(artwork.gfx_current), basename);
931 if (fileExists(filename))
937 // 5th try: look for default artwork in new default artwork directory
938 filename = getPath2(getDefaultGraphicsDir(GFX_DEFAULT_SUBDIR), basename);
939 if (fileExists(filename))
944 // 6th try: look for default artwork in old default artwork directory
945 filename = getPath2(options.graphics_directory, basename);
946 if (fileExists(filename))
949 return NULL; // cannot find specified artwork file anywhere
952 static char *getCreditsBasename(int nr)
954 static char basename[32];
956 sprintf(basename, "credits_%d.txt", nr + 1);
961 char *getCreditsFilename(int nr, boolean global)
963 char *basename = getCreditsBasename(nr);
964 char *basepath = NULL;
965 static char *credits_subdir = NULL;
966 static char *filename = NULL;
968 if (credits_subdir == NULL)
969 credits_subdir = getPath2(DOCS_DIRECTORY, CREDITS_DIRECTORY);
971 checked_free(filename);
973 // look for credits file in the game's base or current level set directory
974 basepath = (global ? options.base_directory : getCurrentLevelDir());
976 filename = getPath3(basepath, credits_subdir, basename);
977 if (fileExists(filename))
980 return NULL; // cannot find credits file
983 static char *getProgramInfoBasename(int nr)
985 static char basename[32];
987 sprintf(basename, "program_%d.txt", nr + 1);
992 char *getProgramInfoFilename(int nr)
994 char *basename = getProgramInfoBasename(nr);
995 static char *info_subdir = NULL;
996 static char *filename = NULL;
998 if (info_subdir == NULL)
999 info_subdir = getPath2(DOCS_DIRECTORY, PROGRAM_INFO_DIRECTORY);
1001 checked_free(filename);
1003 // look for program info file in the game's base directory
1004 filename = getPath3(options.base_directory, info_subdir, basename);
1005 if (fileExists(filename))
1008 return NULL; // cannot find program info file
1011 static char *getCorrectedArtworkBasename(char *basename)
1016 char *getCustomImageFilename(char *basename)
1018 static char *filename = NULL;
1019 boolean skip_setup_artwork = FALSE;
1021 checked_free(filename);
1023 basename = getCorrectedArtworkBasename(basename);
1025 if (!gfx.override_level_graphics)
1027 // 1st try: look for special artwork in current level series directory
1028 filename = getImg3(getCurrentLevelDir(), GRAPHICS_DIRECTORY, basename);
1029 if (fileExists(filename))
1034 // check if there is special artwork configured in level series config
1035 if (getLevelArtworkSet(ARTWORK_TYPE_GRAPHICS) != NULL)
1037 // 2nd try: look for special artwork configured in level series config
1038 filename = getImg2(getLevelArtworkDir(ARTWORK_TYPE_GRAPHICS), basename);
1039 if (fileExists(filename))
1044 // take missing artwork configured in level set config from default
1045 skip_setup_artwork = TRUE;
1049 if (!skip_setup_artwork)
1051 // 3rd try: look for special artwork in configured artwork directory
1052 filename = getImg2(getSetupArtworkDir(artwork.gfx_current), basename);
1053 if (fileExists(filename))
1059 // 4th try: look for default artwork in new default artwork directory
1060 filename = getImg2(getDefaultGraphicsDir(GFX_DEFAULT_SUBDIR), basename);
1061 if (fileExists(filename))
1066 // 5th try: look for default artwork in old default artwork directory
1067 filename = getImg2(options.graphics_directory, basename);
1068 if (fileExists(filename))
1071 if (!strEqual(GFX_FALLBACK_FILENAME, UNDEFINED_FILENAME))
1075 WarnUsingFallback(basename);
1077 // 6th try: look for fallback artwork in old default artwork directory
1078 // (needed to prevent errors when trying to access unused artwork files)
1079 filename = getImg2(options.graphics_directory, GFX_FALLBACK_FILENAME);
1080 if (fileExists(filename))
1084 return NULL; // cannot find specified artwork file anywhere
1087 char *getCustomSoundFilename(char *basename)
1089 static char *filename = NULL;
1090 boolean skip_setup_artwork = FALSE;
1092 checked_free(filename);
1094 basename = getCorrectedArtworkBasename(basename);
1096 if (!gfx.override_level_sounds)
1098 // 1st try: look for special artwork in current level series directory
1099 filename = getPath3(getCurrentLevelDir(), SOUNDS_DIRECTORY, basename);
1100 if (fileExists(filename))
1105 // check if there is special artwork configured in level series config
1106 if (getLevelArtworkSet(ARTWORK_TYPE_SOUNDS) != NULL)
1108 // 2nd try: look for special artwork configured in level series config
1109 filename = getPath2(getLevelArtworkDir(TREE_TYPE_SOUNDS_DIR), basename);
1110 if (fileExists(filename))
1115 // take missing artwork configured in level set config from default
1116 skip_setup_artwork = TRUE;
1120 if (!skip_setup_artwork)
1122 // 3rd try: look for special artwork in configured artwork directory
1123 filename = getPath2(getSetupArtworkDir(artwork.snd_current), basename);
1124 if (fileExists(filename))
1130 // 4th try: look for default artwork in new default artwork directory
1131 filename = getPath2(getDefaultSoundsDir(SND_DEFAULT_SUBDIR), basename);
1132 if (fileExists(filename))
1137 // 5th try: look for default artwork in old default artwork directory
1138 filename = getPath2(options.sounds_directory, basename);
1139 if (fileExists(filename))
1142 if (!strEqual(SND_FALLBACK_FILENAME, UNDEFINED_FILENAME))
1146 WarnUsingFallback(basename);
1148 // 6th try: look for fallback artwork in old default artwork directory
1149 // (needed to prevent errors when trying to access unused artwork files)
1150 filename = getPath2(options.sounds_directory, SND_FALLBACK_FILENAME);
1151 if (fileExists(filename))
1155 return NULL; // cannot find specified artwork file anywhere
1158 char *getCustomMusicFilename(char *basename)
1160 static char *filename = NULL;
1161 boolean skip_setup_artwork = FALSE;
1163 checked_free(filename);
1165 basename = getCorrectedArtworkBasename(basename);
1167 if (!gfx.override_level_music)
1169 // 1st try: look for special artwork in current level series directory
1170 filename = getPath3(getCurrentLevelDir(), MUSIC_DIRECTORY, basename);
1171 if (fileExists(filename))
1176 // check if there is special artwork configured in level series config
1177 if (getLevelArtworkSet(ARTWORK_TYPE_MUSIC) != NULL)
1179 // 2nd try: look for special artwork configured in level series config
1180 filename = getPath2(getLevelArtworkDir(TREE_TYPE_MUSIC_DIR), basename);
1181 if (fileExists(filename))
1186 // take missing artwork configured in level set config from default
1187 skip_setup_artwork = TRUE;
1191 if (!skip_setup_artwork)
1193 // 3rd try: look for special artwork in configured artwork directory
1194 filename = getPath2(getSetupArtworkDir(artwork.mus_current), basename);
1195 if (fileExists(filename))
1201 // 4th try: look for default artwork in new default artwork directory
1202 filename = getPath2(getDefaultMusicDir(MUS_DEFAULT_SUBDIR), basename);
1203 if (fileExists(filename))
1208 // 5th try: look for default artwork in old default artwork directory
1209 filename = getPath2(options.music_directory, basename);
1210 if (fileExists(filename))
1213 if (!strEqual(MUS_FALLBACK_FILENAME, UNDEFINED_FILENAME))
1217 WarnUsingFallback(basename);
1219 // 6th try: look for fallback artwork in old default artwork directory
1220 // (needed to prevent errors when trying to access unused artwork files)
1221 filename = getPath2(options.music_directory, MUS_FALLBACK_FILENAME);
1222 if (fileExists(filename))
1226 return NULL; // cannot find specified artwork file anywhere
1229 char *getCustomArtworkFilename(char *basename, int type)
1231 if (type == ARTWORK_TYPE_GRAPHICS)
1232 return getCustomImageFilename(basename);
1233 else if (type == ARTWORK_TYPE_SOUNDS)
1234 return getCustomSoundFilename(basename);
1235 else if (type == ARTWORK_TYPE_MUSIC)
1236 return getCustomMusicFilename(basename);
1238 return UNDEFINED_FILENAME;
1241 char *getCustomArtworkConfigFilename(int type)
1243 return getCustomArtworkFilename(ARTWORKINFO_FILENAME(type), type);
1246 char *getCustomArtworkLevelConfigFilename(int type)
1248 static char *filename = NULL;
1250 checked_free(filename);
1252 filename = getPath2(getLevelArtworkDir(type), ARTWORKINFO_FILENAME(type));
1257 static boolean directoryExists_CheckMusic(char *directory, boolean check_music)
1259 if (!directoryExists(directory))
1266 DirectoryEntry *dir_entry;
1267 int num_music = getMusicListSize();
1268 boolean music_found = FALSE;
1270 if ((dir = openDirectory(directory)) == NULL)
1273 while ((dir_entry = readDirectory(dir)) != NULL) // loop all entries
1275 char *basename = dir_entry->basename;
1276 boolean music_already_used = FALSE;
1279 // skip all music files that are configured in music config file
1280 for (i = 0; i < num_music; i++)
1282 struct FileInfo *music = getMusicListEntry(i);
1284 if (strEqual(basename, music->filename))
1286 music_already_used = TRUE;
1292 if (music_already_used)
1295 if (FileIsMusic(dir_entry->filename))
1303 closeDirectory(dir);
1308 static char *getCustomMusicDirectoryExt(boolean check_music)
1310 static char *directory = NULL;
1311 boolean skip_setup_artwork = FALSE;
1313 checked_free(directory);
1315 if (!gfx.override_level_music)
1317 // 1st try: look for special artwork in current level series directory
1318 directory = getPath2(getCurrentLevelDir(), MUSIC_DIRECTORY);
1319 if (directoryExists_CheckMusic(directory, check_music))
1324 // check if there is special artwork configured in level series config
1325 if (getLevelArtworkSet(ARTWORK_TYPE_MUSIC) != NULL)
1327 // 2nd try: look for special artwork configured in level series config
1328 directory = getStringCopy(getLevelArtworkDir(TREE_TYPE_MUSIC_DIR));
1330 // directory also valid if no unconfigured music found (no game music)
1331 if (directoryExists_CheckMusic(directory, FALSE))
1336 // take missing artwork configured in level set config from default
1337 skip_setup_artwork = TRUE;
1341 if (!skip_setup_artwork)
1343 // 3rd try: look for special artwork in configured artwork directory
1344 directory = getStringCopy(getSetupArtworkDir(artwork.mus_current));
1346 // directory also valid if no unconfigured music found (no game music)
1347 if (directoryExists_CheckMusic(directory, FALSE))
1353 // 4th try: look for default artwork in new default artwork directory
1354 directory = getStringCopy(getDefaultMusicDir(MUS_DEFAULT_SUBDIR));
1355 if (directoryExists_CheckMusic(directory, check_music))
1360 // 5th try: look for default artwork in old default artwork directory
1361 directory = getStringCopy(options.music_directory);
1362 if (directoryExists_CheckMusic(directory, check_music))
1365 return NULL; // cannot find specified artwork file anywhere
1368 char *getCustomMusicDirectory(void)
1370 return getCustomMusicDirectoryExt(FALSE);
1373 char *getCustomMusicDirectory_NoConf(void)
1375 return getCustomMusicDirectoryExt(TRUE);
1378 void MarkTapeDirectoryUploadsAsComplete(char *level_subdir)
1380 char *filename = getPath2(getTapeDir(level_subdir), UPLOADED_FILENAME);
1382 touchFile(filename);
1384 checked_free(filename);
1387 void MarkTapeDirectoryUploadsAsIncomplete(char *level_subdir)
1389 char *filename = getPath2(getTapeDir(level_subdir), UPLOADED_FILENAME);
1393 checked_free(filename);
1396 boolean CheckTapeDirectoryUploadsComplete(char *level_subdir)
1398 char *filename = getPath2(getTapeDir(level_subdir), UPLOADED_FILENAME);
1399 boolean success = fileExists(filename);
1401 checked_free(filename);
1406 void InitMissingFileHash(void)
1408 if (missing_file_hash == NULL)
1409 freeSetupFileHash(missing_file_hash);
1411 missing_file_hash = newSetupFileHash();
1414 void InitTapeDirectory(char *level_subdir)
1416 boolean new_tape_dir = !directoryExists(getTapeDir(level_subdir));
1418 createDirectory(getUserGameDataDir(), "user data");
1419 createDirectory(getTapeDir(NULL), "main tape");
1420 createDirectory(getTapeDir(level_subdir), "level tape");
1423 MarkTapeDirectoryUploadsAsComplete(level_subdir);
1426 void InitScoreDirectory(char *level_subdir)
1428 createDirectory(getMainUserGameDataDir(), "main user data");
1429 createDirectory(getScoreDir(NULL), "main score");
1430 createDirectory(getScoreDir(level_subdir), "level score");
1433 void InitScoreCacheDirectory(char *level_subdir)
1435 createDirectory(getMainUserGameDataDir(), "main user data");
1436 createDirectory(getCacheDir(), "cache data");
1437 createDirectory(getScoreCacheDir(NULL), "main score");
1438 createDirectory(getScoreCacheDir(level_subdir), "level score");
1441 void InitScoreTapeDirectory(char *level_subdir, int nr)
1443 InitScoreDirectory(level_subdir);
1445 createDirectory(getScoreTapeDir(level_subdir, nr), "score tape");
1448 void InitScoreCacheTapeDirectory(char *level_subdir, int nr)
1450 InitScoreCacheDirectory(level_subdir);
1452 createDirectory(getScoreCacheTapeDir(level_subdir, nr), "score tape");
1455 static void SaveUserLevelInfo(void);
1457 void InitUserLevelDirectory(char *level_subdir)
1459 if (!directoryExists(getUserLevelDir(level_subdir)))
1461 createDirectory(getMainUserGameDataDir(), "main user data");
1462 createDirectory(getUserLevelDir(NULL), "main user level");
1464 if (setup.internal.create_user_levelset)
1466 createDirectory(getUserLevelDir(level_subdir), "user level");
1468 SaveUserLevelInfo();
1473 void InitNetworkLevelDirectory(char *level_subdir)
1475 if (!directoryExists(getNetworkLevelDir(level_subdir)))
1477 createDirectory(getMainUserGameDataDir(), "main user data");
1478 createDirectory(getNetworkDir(), "network data");
1479 createDirectory(getNetworkLevelDir(NULL), "main network level");
1480 createDirectory(getNetworkLevelDir(level_subdir), "network level");
1484 void InitLevelSetupDirectory(char *level_subdir)
1486 createDirectory(getUserGameDataDir(), "user data");
1487 createDirectory(getLevelSetupDir(NULL), "main level setup");
1488 createDirectory(getLevelSetupDir(level_subdir), "level setup");
1491 static void InitCacheDirectory(void)
1493 createDirectory(getMainUserGameDataDir(), "main user data");
1494 createDirectory(getCacheDir(), "cache data");
1498 // ----------------------------------------------------------------------------
1499 // some functions to handle lists of level and artwork directories
1500 // ----------------------------------------------------------------------------
1502 TreeInfo *newTreeInfo(void)
1504 return checked_calloc(sizeof(TreeInfo));
1507 TreeInfo *newTreeInfo_setDefaults(int type)
1509 TreeInfo *ti = newTreeInfo();
1511 setTreeInfoToDefaults(ti, type);
1516 void pushTreeInfo(TreeInfo **node_first, TreeInfo *node_new)
1518 node_new->next = *node_first;
1519 *node_first = node_new;
1522 void removeTreeInfo(TreeInfo **node_first)
1524 TreeInfo *node_old = *node_first;
1526 *node_first = node_old->next;
1527 node_old->next = NULL;
1529 freeTreeInfo(node_old);
1532 int numTreeInfo(TreeInfo *node)
1545 boolean validLevelSeries(TreeInfo *node)
1547 // in a number of cases, tree node is no valid level set
1548 if (node == NULL || node->node_group || node->parent_link || node->is_copy)
1554 TreeInfo *getValidLevelSeries(TreeInfo *node, TreeInfo *default_node)
1556 if (validLevelSeries(node))
1558 else if (node->is_copy)
1559 return getTreeInfoFromIdentifier(leveldir_first, node->identifier);
1561 return getFirstValidTreeInfoEntry(default_node);
1564 static TreeInfo *getValidTreeInfoEntryExt(TreeInfo *node, boolean get_next_node)
1569 if (node->node_group) // enter node group (step down into tree)
1570 return getFirstValidTreeInfoEntry(node->node_group);
1572 if (node->parent_link) // skip first node (back link) of node group
1573 get_next_node = TRUE;
1575 if (!get_next_node) // get current regular tree node
1578 // get next regular tree node, or step up until one is found
1579 while (node->next == NULL && node->node_parent != NULL)
1580 node = node->node_parent;
1582 return getFirstValidTreeInfoEntry(node->next);
1585 TreeInfo *getFirstValidTreeInfoEntry(TreeInfo *node)
1587 return getValidTreeInfoEntryExt(node, FALSE);
1590 TreeInfo *getNextValidTreeInfoEntry(TreeInfo *node)
1592 return getValidTreeInfoEntryExt(node, TRUE);
1595 TreeInfo *getTreeInfoFirstGroupEntry(TreeInfo *node)
1600 if (node->node_parent == NULL) // top level group
1601 return *node->node_top;
1602 else // sub level group
1603 return node->node_parent->node_group;
1606 int numTreeInfoInGroup(TreeInfo *node)
1608 return numTreeInfo(getTreeInfoFirstGroupEntry(node));
1611 int getPosFromTreeInfo(TreeInfo *node)
1613 TreeInfo *node_cmp = getTreeInfoFirstGroupEntry(node);
1618 if (node_cmp == node)
1622 node_cmp = node_cmp->next;
1628 TreeInfo *getTreeInfoFromPos(TreeInfo *node, int pos)
1630 TreeInfo *node_default = node;
1642 return node_default;
1645 static TreeInfo *getTreeInfoFromIdentifierExt(TreeInfo *node, char *identifier,
1646 int node_type_wanted)
1648 if (identifier == NULL)
1653 if (TREE_NODE_TYPE(node) == node_type_wanted &&
1654 strEqual(identifier, node->identifier))
1657 if (node->node_group)
1659 TreeInfo *node_group = getTreeInfoFromIdentifierExt(node->node_group,
1672 TreeInfo *getTreeInfoFromIdentifier(TreeInfo *node, char *identifier)
1674 return getTreeInfoFromIdentifierExt(node, identifier, TREE_NODE_TYPE_DEFAULT);
1677 static TreeInfo *cloneTreeNode(TreeInfo **node_top, TreeInfo *node_parent,
1678 TreeInfo *node, boolean skip_sets_without_levels)
1685 if (!node->parent_link && !node->level_group &&
1686 skip_sets_without_levels && node->levels == 0)
1687 return cloneTreeNode(node_top, node_parent, node->next,
1688 skip_sets_without_levels);
1690 node_new = getTreeInfoCopy(node); // copy complete node
1692 node_new->node_top = node_top; // correct top node link
1693 node_new->node_parent = node_parent; // correct parent node link
1695 if (node->level_group)
1696 node_new->node_group = cloneTreeNode(node_top, node_new, node->node_group,
1697 skip_sets_without_levels);
1699 node_new->next = cloneTreeNode(node_top, node_parent, node->next,
1700 skip_sets_without_levels);
1705 static void cloneTree(TreeInfo **ti_new, TreeInfo *ti, boolean skip_empty_sets)
1707 TreeInfo *ti_cloned = cloneTreeNode(ti_new, NULL, ti, skip_empty_sets);
1709 *ti_new = ti_cloned;
1712 static boolean adjustTreeGraphicsForEMC(TreeInfo *node)
1714 boolean settings_changed = FALSE;
1718 boolean want_ecs = (setup.prefer_aga_graphics == FALSE);
1719 boolean want_aga = (setup.prefer_aga_graphics == TRUE);
1720 boolean has_only_ecs = (!node->graphics_set && !node->graphics_set_aga);
1721 boolean has_only_aga = (!node->graphics_set && !node->graphics_set_ecs);
1722 char *graphics_set = NULL;
1724 if (node->graphics_set_ecs && (want_ecs || has_only_ecs))
1725 graphics_set = node->graphics_set_ecs;
1727 if (node->graphics_set_aga && (want_aga || has_only_aga))
1728 graphics_set = node->graphics_set_aga;
1730 if (graphics_set && !strEqual(node->graphics_set, graphics_set))
1732 setString(&node->graphics_set, graphics_set);
1733 settings_changed = TRUE;
1736 if (node->node_group != NULL)
1737 settings_changed |= adjustTreeGraphicsForEMC(node->node_group);
1742 return settings_changed;
1745 static boolean adjustTreeSoundsForEMC(TreeInfo *node)
1747 boolean settings_changed = FALSE;
1751 boolean want_default = (setup.prefer_lowpass_sounds == FALSE);
1752 boolean want_lowpass = (setup.prefer_lowpass_sounds == TRUE);
1753 boolean has_only_default = (!node->sounds_set && !node->sounds_set_lowpass);
1754 boolean has_only_lowpass = (!node->sounds_set && !node->sounds_set_default);
1755 char *sounds_set = NULL;
1757 if (node->sounds_set_default && (want_default || has_only_default))
1758 sounds_set = node->sounds_set_default;
1760 if (node->sounds_set_lowpass && (want_lowpass || has_only_lowpass))
1761 sounds_set = node->sounds_set_lowpass;
1763 if (sounds_set && !strEqual(node->sounds_set, sounds_set))
1765 setString(&node->sounds_set, sounds_set);
1766 settings_changed = TRUE;
1769 if (node->node_group != NULL)
1770 settings_changed |= adjustTreeSoundsForEMC(node->node_group);
1775 return settings_changed;
1778 int dumpTreeInfo(TreeInfo *node, int depth)
1780 char bullet_list[] = { '-', '*', 'o' };
1781 int num_leaf_nodes = 0;
1785 Debug("tree", "Dumping TreeInfo:");
1789 char bullet = bullet_list[depth % ARRAY_SIZE(bullet_list)];
1791 for (i = 0; i < depth * 2; i++)
1792 DebugContinued("", " ");
1794 DebugContinued("tree", "%c '%s' ['%s] [PARENT: '%s'] %s\n",
1795 bullet, node->name, node->identifier,
1796 (node->node_parent ? node->node_parent->identifier : "-"),
1797 (node->node_group ? "[GROUP]" :
1798 node->is_copy ? "[COPY]" : ""));
1800 if (!node->node_group && !node->parent_link)
1804 // use for dumping artwork info tree
1805 Debug("tree", "subdir == '%s' ['%s', '%s'] [%d])",
1806 node->subdir, node->fullpath, node->basepath, node->in_user_dir);
1809 if (node->node_group != NULL)
1810 num_leaf_nodes += dumpTreeInfo(node->node_group, depth + 1);
1816 Debug("tree", "Summary: %d leaf nodes found", num_leaf_nodes);
1818 return num_leaf_nodes;
1821 void sortTreeInfoBySortFunction(TreeInfo **node_first,
1822 int (*compare_function)(const void *,
1825 int num_nodes = numTreeInfo(*node_first);
1826 TreeInfo **sort_array;
1827 TreeInfo *node = *node_first;
1833 // allocate array for sorting structure pointers
1834 sort_array = checked_calloc(num_nodes * sizeof(TreeInfo *));
1836 // writing structure pointers to sorting array
1837 while (i < num_nodes && node) // double boundary check...
1839 sort_array[i] = node;
1845 // sorting the structure pointers in the sorting array
1846 qsort(sort_array, num_nodes, sizeof(TreeInfo *),
1849 // update the linkage of list elements with the sorted node array
1850 for (i = 0; i < num_nodes - 1; i++)
1851 sort_array[i]->next = sort_array[i + 1];
1852 sort_array[num_nodes - 1]->next = NULL;
1854 // update the linkage of the main list anchor pointer
1855 *node_first = sort_array[0];
1859 // now recursively sort the level group structures
1863 if (node->node_group != NULL)
1864 sortTreeInfoBySortFunction(&node->node_group, compare_function);
1870 void sortTreeInfo(TreeInfo **node_first)
1872 sortTreeInfoBySortFunction(node_first, compareTreeInfoEntries);
1876 // ============================================================================
1877 // some stuff from "files.c"
1878 // ============================================================================
1880 #if defined(PLATFORM_WINDOWS)
1882 #define S_IRGRP S_IRUSR
1885 #define S_IROTH S_IRUSR
1888 #define S_IWGRP S_IWUSR
1891 #define S_IWOTH S_IWUSR
1894 #define S_IXGRP S_IXUSR
1897 #define S_IXOTH S_IXUSR
1900 #define S_IRWXG (S_IRGRP | S_IWGRP | S_IXGRP)
1905 #endif // PLATFORM_WINDOWS
1907 // file permissions for newly written files
1908 #define MODE_R_ALL (S_IRUSR | S_IRGRP | S_IROTH)
1909 #define MODE_W_ALL (S_IWUSR | S_IWGRP | S_IWOTH)
1910 #define MODE_X_ALL (S_IXUSR | S_IXGRP | S_IXOTH)
1912 #define MODE_W_PRIVATE (S_IWUSR)
1913 #define MODE_W_PUBLIC_FILE (S_IWUSR | S_IWGRP)
1914 #define MODE_W_PUBLIC_DIR (S_IWUSR | S_IWGRP | S_ISGID)
1916 #define DIR_PERMS_PRIVATE (MODE_R_ALL | MODE_X_ALL | MODE_W_PRIVATE)
1917 #define DIR_PERMS_PUBLIC (MODE_R_ALL | MODE_X_ALL | MODE_W_PUBLIC_DIR)
1918 #define DIR_PERMS_PUBLIC_ALL (MODE_R_ALL | MODE_X_ALL | MODE_W_ALL)
1920 #define FILE_PERMS_PRIVATE (MODE_R_ALL | MODE_W_PRIVATE)
1921 #define FILE_PERMS_PUBLIC (MODE_R_ALL | MODE_W_PUBLIC_FILE)
1922 #define FILE_PERMS_PUBLIC_ALL (MODE_R_ALL | MODE_W_ALL)
1925 char *getHomeDir(void)
1927 static char *dir = NULL;
1929 #if defined(PLATFORM_WINDOWS)
1932 dir = checked_malloc(MAX_PATH + 1);
1934 if (!SUCCEEDED(SHGetFolderPath(NULL, CSIDL_PERSONAL, NULL, 0, dir)))
1937 #elif defined(PLATFORM_EMSCRIPTEN)
1938 dir = PERSISTENT_DIRECTORY;
1939 #elif defined(PLATFORM_UNIX)
1942 if ((dir = getenv("HOME")) == NULL)
1944 dir = getUnixHomeDir();
1947 dir = getStringCopy(dir);
1959 char *getPersonalDataDir(void)
1961 static char *personal_data_dir = NULL;
1963 #if defined(PLATFORM_MAC)
1964 if (personal_data_dir == NULL)
1965 personal_data_dir = getPath2(getHomeDir(), "Documents");
1967 if (personal_data_dir == NULL)
1968 personal_data_dir = getHomeDir();
1971 return personal_data_dir;
1974 char *getMainUserGameDataDir(void)
1976 static char *main_user_data_dir = NULL;
1978 #if defined(PLATFORM_ANDROID)
1979 if (main_user_data_dir == NULL)
1980 main_user_data_dir = (char *)(SDL_AndroidGetExternalStorageState() &
1981 SDL_ANDROID_EXTERNAL_STORAGE_WRITE ?
1982 SDL_AndroidGetExternalStoragePath() :
1983 SDL_AndroidGetInternalStoragePath());
1985 if (main_user_data_dir == NULL)
1986 main_user_data_dir = getPath2(getPersonalDataDir(),
1987 program.userdata_subdir);
1990 return main_user_data_dir;
1993 char *getUserGameDataDir(void)
1996 return getMainUserGameDataDir();
1998 return getUserDir(user.nr);
2001 char *getSetupDir(void)
2003 return getUserGameDataDir();
2006 static mode_t posix_umask(mode_t mask)
2008 #if defined(PLATFORM_UNIX)
2015 static int posix_mkdir(const char *pathname, mode_t mode)
2017 #if defined(PLATFORM_WINDOWS)
2018 return mkdir(pathname);
2020 return mkdir(pathname, mode);
2024 static boolean posix_process_running_setgid(void)
2026 #if defined(PLATFORM_UNIX)
2027 return (getgid() != getegid());
2033 void createDirectory(char *dir, char *text)
2035 if (directoryExists(dir))
2038 // leave "other" permissions in umask untouched, but ensure group parts
2039 // of USERDATA_DIR_MODE are not masked
2040 int permission_class = PERMS_PRIVATE;
2041 mode_t dir_mode = (permission_class == PERMS_PRIVATE ?
2042 DIR_PERMS_PRIVATE : DIR_PERMS_PUBLIC);
2043 mode_t last_umask = posix_umask(0);
2044 mode_t group_umask = ~(dir_mode & S_IRWXG);
2045 int running_setgid = posix_process_running_setgid();
2047 if (permission_class == PERMS_PUBLIC)
2049 // if we're setgid, protect files against "other"
2050 // else keep umask(0) to make the dir world-writable
2053 posix_umask(last_umask & group_umask);
2055 dir_mode = DIR_PERMS_PUBLIC_ALL;
2058 if (posix_mkdir(dir, dir_mode) != 0)
2059 Warn("cannot create %s directory '%s': %s", text, dir, strerror(errno));
2061 if (permission_class == PERMS_PUBLIC && !running_setgid)
2062 chmod(dir, dir_mode);
2064 posix_umask(last_umask); // restore previous umask
2067 void InitMainUserDataDirectory(void)
2069 createDirectory(getMainUserGameDataDir(), "main user data");
2072 void InitUserDataDirectory(void)
2074 createDirectory(getMainUserGameDataDir(), "main user data");
2078 createDirectory(getUserDir(-1), "users");
2079 createDirectory(getUserDir(user.nr), "user data");
2083 void SetFilePermissions(char *filename, int permission_class)
2085 int running_setgid = posix_process_running_setgid();
2086 int perms = (permission_class == PERMS_PRIVATE ?
2087 FILE_PERMS_PRIVATE : FILE_PERMS_PUBLIC);
2089 if (permission_class == PERMS_PUBLIC && !running_setgid)
2090 perms = FILE_PERMS_PUBLIC_ALL;
2092 chmod(filename, perms);
2095 void fprintFileHeader(FILE *file, char *basename)
2097 char *prefix = "# ";
2100 fprintf_line_with_prefix(file, prefix, sep1, 77);
2101 fprintf(file, "%s%s\n", prefix, basename);
2102 fprintf_line_with_prefix(file, prefix, sep1, 77);
2103 fprintf(file, "\n");
2106 int getFileVersionFromCookieString(const char *cookie)
2108 const char *ptr_cookie1, *ptr_cookie2;
2109 const char *pattern1 = "_FILE_VERSION_";
2110 const char *pattern2 = "?.?";
2111 const int len_cookie = strlen(cookie);
2112 const int len_pattern1 = strlen(pattern1);
2113 const int len_pattern2 = strlen(pattern2);
2114 const int len_pattern = len_pattern1 + len_pattern2;
2115 int version_super, version_major;
2117 if (len_cookie <= len_pattern)
2120 ptr_cookie1 = &cookie[len_cookie - len_pattern];
2121 ptr_cookie2 = &cookie[len_cookie - len_pattern2];
2123 if (strncmp(ptr_cookie1, pattern1, len_pattern1) != 0)
2126 if (ptr_cookie2[0] < '0' || ptr_cookie2[0] > '9' ||
2127 ptr_cookie2[1] != '.' ||
2128 ptr_cookie2[2] < '0' || ptr_cookie2[2] > '9')
2131 version_super = ptr_cookie2[0] - '0';
2132 version_major = ptr_cookie2[2] - '0';
2134 return VERSION_IDENT(version_super, version_major, 0, 0);
2137 boolean checkCookieString(const char *cookie, const char *template)
2139 const char *pattern = "_FILE_VERSION_?.?";
2140 const int len_cookie = strlen(cookie);
2141 const int len_template = strlen(template);
2142 const int len_pattern = strlen(pattern);
2144 if (len_cookie != len_template)
2147 if (strncmp(cookie, template, len_cookie - len_pattern) != 0)
2154 // ----------------------------------------------------------------------------
2155 // setup file list and hash handling functions
2156 // ----------------------------------------------------------------------------
2158 char *getFormattedSetupEntry(char *token, char *value)
2161 static char entry[MAX_LINE_LEN];
2163 // if value is an empty string, just return token without value
2167 // start with the token and some spaces to format output line
2168 sprintf(entry, "%s:", token);
2169 for (i = strlen(entry); i < token_value_position; i++)
2172 // continue with the token's value
2173 strcat(entry, value);
2178 SetupFileList *newSetupFileList(char *token, char *value)
2180 SetupFileList *new = checked_malloc(sizeof(SetupFileList));
2182 new->token = getStringCopy(token);
2183 new->value = getStringCopy(value);
2190 void freeSetupFileList(SetupFileList *list)
2195 checked_free(list->token);
2196 checked_free(list->value);
2199 freeSetupFileList(list->next);
2204 char *getListEntry(SetupFileList *list, char *token)
2209 if (strEqual(list->token, token))
2212 return getListEntry(list->next, token);
2215 SetupFileList *setListEntry(SetupFileList *list, char *token, char *value)
2220 if (strEqual(list->token, token))
2222 checked_free(list->value);
2224 list->value = getStringCopy(value);
2228 else if (list->next == NULL)
2229 return (list->next = newSetupFileList(token, value));
2231 return setListEntry(list->next, token, value);
2234 SetupFileList *addListEntry(SetupFileList *list, char *token, char *value)
2239 if (list->next == NULL)
2240 return (list->next = newSetupFileList(token, value));
2242 return addListEntry(list->next, token, value);
2245 #if ENABLE_UNUSED_CODE
2247 static void printSetupFileList(SetupFileList *list)
2252 Debug("setup:printSetupFileList", "token: '%s'", list->token);
2253 Debug("setup:printSetupFileList", "value: '%s'", list->value);
2255 printSetupFileList(list->next);
2261 DEFINE_HASHTABLE_INSERT(insert_hash_entry, char, char);
2262 DEFINE_HASHTABLE_SEARCH(search_hash_entry, char, char);
2263 DEFINE_HASHTABLE_CHANGE(change_hash_entry, char, char);
2264 DEFINE_HASHTABLE_REMOVE(remove_hash_entry, char, char);
2266 #define insert_hash_entry hashtable_insert
2267 #define search_hash_entry hashtable_search
2268 #define change_hash_entry hashtable_change
2269 #define remove_hash_entry hashtable_remove
2272 unsigned int get_hash_from_key(void *key)
2277 This algorithm (k=33) was first reported by Dan Bernstein many years ago in
2278 'comp.lang.c'. Another version of this algorithm (now favored by Bernstein)
2279 uses XOR: hash(i) = hash(i - 1) * 33 ^ str[i]; the magic of number 33 (why
2280 it works better than many other constants, prime or not) has never been
2281 adequately explained.
2283 If you just want to have a good hash function, and cannot wait, djb2
2284 is one of the best string hash functions i know. It has excellent
2285 distribution and speed on many different sets of keys and table sizes.
2286 You are not likely to do better with one of the "well known" functions
2287 such as PJW, K&R, etc.
2289 Ozan (oz) Yigit [http://www.cs.yorku.ca/~oz/hash.html]
2292 char *str = (char *)key;
2293 unsigned int hash = 5381;
2296 while ((c = *str++))
2297 hash = ((hash << 5) + hash) + c; // hash * 33 + c
2302 int hash_keys_are_equal(void *key1, void *key2)
2304 return (strEqual((char *)key1, (char *)key2));
2307 SetupFileHash *newSetupFileHash(void)
2309 SetupFileHash *new_hash =
2310 create_hashtable(16, 0.75, get_hash_from_key, hash_keys_are_equal);
2312 if (new_hash == NULL)
2313 Fail("create_hashtable() failed -- out of memory");
2318 void freeSetupFileHash(SetupFileHash *hash)
2323 hashtable_destroy(hash, 1); // 1 == also free values stored in hash
2326 char *getHashEntry(SetupFileHash *hash, char *token)
2331 return search_hash_entry(hash, token);
2334 void setHashEntry(SetupFileHash *hash, char *token, char *value)
2341 value_copy = getStringCopy(value);
2343 // change value; if it does not exist, insert it as new
2344 if (!change_hash_entry(hash, token, value_copy))
2345 if (!insert_hash_entry(hash, getStringCopy(token), value_copy))
2346 Fail("cannot insert into hash -- aborting");
2349 char *removeHashEntry(SetupFileHash *hash, char *token)
2354 return remove_hash_entry(hash, token);
2357 #if ENABLE_UNUSED_CODE
2359 static void printSetupFileHash(SetupFileHash *hash)
2361 BEGIN_HASH_ITERATION(hash, itr)
2363 Debug("setup:printSetupFileHash", "token: '%s'", HASH_ITERATION_TOKEN(itr));
2364 Debug("setup:printSetupFileHash", "value: '%s'", HASH_ITERATION_VALUE(itr));
2366 END_HASH_ITERATION(hash, itr)
2371 #define ALLOW_TOKEN_VALUE_SEPARATOR_BEING_WHITESPACE 1
2372 #define CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING 0
2373 #define CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH 0
2375 static boolean token_value_separator_found = FALSE;
2376 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2377 static boolean token_value_separator_warning = FALSE;
2379 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2380 static boolean token_already_exists_warning = FALSE;
2383 static boolean getTokenValueFromSetupLineExt(char *line,
2384 char **token_ptr, char **value_ptr,
2385 char *filename, char *line_raw,
2387 boolean separator_required)
2389 static char line_copy[MAX_LINE_LEN + 1], line_raw_copy[MAX_LINE_LEN + 1];
2390 char *token, *value, *line_ptr;
2392 // when externally invoked via ReadTokenValueFromLine(), copy line buffers
2393 if (line_raw == NULL)
2395 strncpy(line_copy, line, MAX_LINE_LEN);
2396 line_copy[MAX_LINE_LEN] = '\0';
2399 strcpy(line_raw_copy, line_copy);
2400 line_raw = line_raw_copy;
2403 // cut trailing comment from input line
2404 for (line_ptr = line; *line_ptr; line_ptr++)
2406 if (*line_ptr == '#')
2413 // cut trailing whitespaces from input line
2414 for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
2415 if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
2418 // ignore empty lines
2422 // cut leading whitespaces from token
2423 for (token = line; *token; token++)
2424 if (*token != ' ' && *token != '\t')
2427 // start with empty value as reliable default
2430 token_value_separator_found = FALSE;
2432 // find end of token to determine start of value
2433 for (line_ptr = token; *line_ptr; line_ptr++)
2435 // first look for an explicit token/value separator, like ':' or '='
2436 if (*line_ptr == ':' || *line_ptr == '=')
2438 *line_ptr = '\0'; // terminate token string
2439 value = line_ptr + 1; // set beginning of value
2441 token_value_separator_found = TRUE;
2447 #if ALLOW_TOKEN_VALUE_SEPARATOR_BEING_WHITESPACE
2448 // fallback: if no token/value separator found, also allow whitespaces
2449 if (!token_value_separator_found && !separator_required)
2451 for (line_ptr = token; *line_ptr; line_ptr++)
2453 if (*line_ptr == ' ' || *line_ptr == '\t')
2455 *line_ptr = '\0'; // terminate token string
2456 value = line_ptr + 1; // set beginning of value
2458 token_value_separator_found = TRUE;
2464 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2465 if (token_value_separator_found)
2467 if (!token_value_separator_warning)
2469 Debug("setup", "---");
2471 if (filename != NULL)
2473 Debug("setup", "missing token/value separator(s) in config file:");
2474 Debug("setup", "- config file: '%s'", filename);
2478 Debug("setup", "missing token/value separator(s):");
2481 token_value_separator_warning = TRUE;
2484 if (filename != NULL)
2485 Debug("setup", "- line %d: '%s'", line_nr, line_raw);
2487 Debug("setup", "- line: '%s'", line_raw);
2493 // cut trailing whitespaces from token
2494 for (line_ptr = &token[strlen(token)]; line_ptr >= token; line_ptr--)
2495 if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
2498 // cut leading whitespaces from value
2499 for (; *value; value++)
2500 if (*value != ' ' && *value != '\t')
2509 boolean getTokenValueFromSetupLine(char *line, char **token, char **value)
2511 // while the internal (old) interface does not require a token/value
2512 // separator (for downwards compatibility with existing files which
2513 // don't use them), it is mandatory for the external (new) interface
2515 return getTokenValueFromSetupLineExt(line, token, value, NULL, NULL, 0, TRUE);
2518 static boolean loadSetupFileData(void *setup_file_data, char *filename,
2519 boolean top_recursion_level, boolean is_hash)
2521 static SetupFileHash *include_filename_hash = NULL;
2522 char line[MAX_LINE_LEN], line_raw[MAX_LINE_LEN], previous_line[MAX_LINE_LEN];
2523 char *token, *value, *line_ptr;
2524 void *insert_ptr = NULL;
2525 boolean read_continued_line = FALSE;
2527 int line_nr = 0, token_count = 0, include_count = 0;
2529 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2530 token_value_separator_warning = FALSE;
2533 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2534 token_already_exists_warning = FALSE;
2537 if (!(file = openFile(filename, MODE_READ)))
2539 #if DEBUG_NO_CONFIG_FILE
2540 Debug("setup", "cannot open configuration file '%s'", filename);
2546 // use "insert pointer" to store list end for constant insertion complexity
2548 insert_ptr = setup_file_data;
2550 // on top invocation, create hash to mark included files (to prevent loops)
2551 if (top_recursion_level)
2552 include_filename_hash = newSetupFileHash();
2554 // mark this file as already included (to prevent including it again)
2555 setHashEntry(include_filename_hash, getBaseNamePtr(filename), "true");
2557 while (!checkEndOfFile(file))
2559 // read next line of input file
2560 if (!getStringFromFile(file, line, MAX_LINE_LEN))
2563 // check if line was completely read and is terminated by line break
2564 if (strlen(line) > 0 && line[strlen(line) - 1] == '\n')
2567 // cut trailing line break (this can be newline and/or carriage return)
2568 for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
2569 if ((*line_ptr == '\n' || *line_ptr == '\r') && *(line_ptr + 1) == '\0')
2572 // copy raw input line for later use (mainly debugging output)
2573 strcpy(line_raw, line);
2575 if (read_continued_line)
2577 // append new line to existing line, if there is enough space
2578 if (strlen(previous_line) + strlen(line_ptr) < MAX_LINE_LEN)
2579 strcat(previous_line, line_ptr);
2581 strcpy(line, previous_line); // copy storage buffer to line
2583 read_continued_line = FALSE;
2586 // if the last character is '\', continue at next line
2587 if (strlen(line) > 0 && line[strlen(line) - 1] == '\\')
2589 line[strlen(line) - 1] = '\0'; // cut off trailing backslash
2590 strcpy(previous_line, line); // copy line to storage buffer
2592 read_continued_line = TRUE;
2597 if (!getTokenValueFromSetupLineExt(line, &token, &value, filename,
2598 line_raw, line_nr, FALSE))
2603 if (strEqual(token, "include"))
2605 if (getHashEntry(include_filename_hash, value) == NULL)
2607 char *basepath = getBasePath(filename);
2608 char *basename = getBaseName(value);
2609 char *filename_include = getPath2(basepath, basename);
2611 loadSetupFileData(setup_file_data, filename_include, FALSE, is_hash);
2615 free(filename_include);
2621 Warn("ignoring already processed file '%s'", value);
2628 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2630 getHashEntry((SetupFileHash *)setup_file_data, token);
2632 if (old_value != NULL)
2634 if (!token_already_exists_warning)
2636 Debug("setup", "---");
2637 Debug("setup", "duplicate token(s) found in config file:");
2638 Debug("setup", "- config file: '%s'", filename);
2640 token_already_exists_warning = TRUE;
2643 Debug("setup", "- token: '%s' (in line %d)", token, line_nr);
2644 Debug("setup", " old value: '%s'", old_value);
2645 Debug("setup", " new value: '%s'", value);
2649 setHashEntry((SetupFileHash *)setup_file_data, token, value);
2653 insert_ptr = addListEntry((SetupFileList *)insert_ptr, token, value);
2663 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2664 if (token_value_separator_warning)
2665 Debug("setup", "---");
2668 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2669 if (token_already_exists_warning)
2670 Debug("setup", "---");
2673 if (token_count == 0 && include_count == 0)
2674 Warn("configuration file '%s' is empty", filename);
2676 if (top_recursion_level)
2677 freeSetupFileHash(include_filename_hash);
2682 static int compareSetupFileData(const void *object1, const void *object2)
2684 const struct ConfigInfo *entry1 = (struct ConfigInfo *)object1;
2685 const struct ConfigInfo *entry2 = (struct ConfigInfo *)object2;
2687 return strcmp(entry1->token, entry2->token);
2690 static void saveSetupFileHash(SetupFileHash *hash, char *filename)
2692 int item_count = hashtable_count(hash);
2693 int item_size = sizeof(struct ConfigInfo);
2694 struct ConfigInfo *sort_array = checked_malloc(item_count * item_size);
2698 // copy string pointers from hash to array
2699 BEGIN_HASH_ITERATION(hash, itr)
2701 sort_array[i].token = HASH_ITERATION_TOKEN(itr);
2702 sort_array[i].value = HASH_ITERATION_VALUE(itr);
2706 if (i > item_count) // should never happen
2709 END_HASH_ITERATION(hash, itr)
2711 // sort string pointers from hash in array
2712 qsort(sort_array, item_count, item_size, compareSetupFileData);
2714 if (!(file = fopen(filename, MODE_WRITE)))
2716 Warn("cannot write configuration file '%s'", filename);
2721 fprintf(file, "%s\n\n", getFormattedSetupEntry("program.version",
2722 program.version_string));
2723 for (i = 0; i < item_count; i++)
2724 fprintf(file, "%s\n", getFormattedSetupEntry(sort_array[i].token,
2725 sort_array[i].value));
2728 checked_free(sort_array);
2731 SetupFileList *loadSetupFileList(char *filename)
2733 SetupFileList *setup_file_list = newSetupFileList("", "");
2734 SetupFileList *first_valid_list_entry;
2736 if (!loadSetupFileData(setup_file_list, filename, TRUE, FALSE))
2738 freeSetupFileList(setup_file_list);
2743 first_valid_list_entry = setup_file_list->next;
2745 // free empty list header
2746 setup_file_list->next = NULL;
2747 freeSetupFileList(setup_file_list);
2749 return first_valid_list_entry;
2752 SetupFileHash *loadSetupFileHash(char *filename)
2754 SetupFileHash *setup_file_hash = newSetupFileHash();
2756 if (!loadSetupFileData(setup_file_hash, filename, TRUE, TRUE))
2758 freeSetupFileHash(setup_file_hash);
2763 return setup_file_hash;
2767 // ============================================================================
2769 // ============================================================================
2771 #define TOKEN_STR_LAST_LEVEL_SERIES "last_level_series"
2772 #define TOKEN_STR_LAST_PLAYED_MENU_USED "last_played_menu_used"
2773 #define TOKEN_STR_LAST_PLAYED_LEVEL "last_played_level"
2774 #define TOKEN_STR_HANDICAP_LEVEL "handicap_level"
2775 #define TOKEN_STR_LAST_USER "last_user"
2777 // level directory info
2778 #define LEVELINFO_TOKEN_IDENTIFIER 0
2779 #define LEVELINFO_TOKEN_NAME 1
2780 #define LEVELINFO_TOKEN_NAME_SORTING 2
2781 #define LEVELINFO_TOKEN_AUTHOR 3
2782 #define LEVELINFO_TOKEN_YEAR 4
2783 #define LEVELINFO_TOKEN_PROGRAM_TITLE 5
2784 #define LEVELINFO_TOKEN_PROGRAM_COPYRIGHT 6
2785 #define LEVELINFO_TOKEN_PROGRAM_COMPANY 7
2786 #define LEVELINFO_TOKEN_IMPORTED_FROM 8
2787 #define LEVELINFO_TOKEN_IMPORTED_BY 9
2788 #define LEVELINFO_TOKEN_TESTED_BY 10
2789 #define LEVELINFO_TOKEN_LEVELS 11
2790 #define LEVELINFO_TOKEN_FIRST_LEVEL 12
2791 #define LEVELINFO_TOKEN_SORT_PRIORITY 13
2792 #define LEVELINFO_TOKEN_LATEST_ENGINE 14
2793 #define LEVELINFO_TOKEN_LEVEL_GROUP 15
2794 #define LEVELINFO_TOKEN_READONLY 16
2795 #define LEVELINFO_TOKEN_GRAPHICS_SET_ECS 17
2796 #define LEVELINFO_TOKEN_GRAPHICS_SET_AGA 18
2797 #define LEVELINFO_TOKEN_GRAPHICS_SET 19
2798 #define LEVELINFO_TOKEN_SOUNDS_SET_DEFAULT 20
2799 #define LEVELINFO_TOKEN_SOUNDS_SET_LOWPASS 21
2800 #define LEVELINFO_TOKEN_SOUNDS_SET 22
2801 #define LEVELINFO_TOKEN_MUSIC_SET 23
2802 #define LEVELINFO_TOKEN_FILENAME 24
2803 #define LEVELINFO_TOKEN_FILETYPE 25
2804 #define LEVELINFO_TOKEN_SPECIAL_FLAGS 26
2805 #define LEVELINFO_TOKEN_EMPTY_LEVEL_NAME 27
2806 #define LEVELINFO_TOKEN_FORCE_LEVEL_NAME 28
2807 #define LEVELINFO_TOKEN_HANDICAP 29
2808 #define LEVELINFO_TOKEN_TIME_LIMIT 30
2809 #define LEVELINFO_TOKEN_SKIP_LEVELS 31
2810 #define LEVELINFO_TOKEN_USE_EMC_TILES 32
2811 #define LEVELINFO_TOKEN_INFO_SCREENS_FROM_MAIN 33
2813 #define NUM_LEVELINFO_TOKENS 34
2815 static LevelDirTree ldi;
2817 static struct TokenInfo levelinfo_tokens[] =
2819 // level directory info
2820 { TYPE_STRING, &ldi.identifier, "identifier" },
2821 { TYPE_STRING, &ldi.name, "name" },
2822 { TYPE_STRING, &ldi.name_sorting, "name_sorting" },
2823 { TYPE_STRING, &ldi.author, "author" },
2824 { TYPE_STRING, &ldi.year, "year" },
2825 { TYPE_STRING, &ldi.program_title, "program_title" },
2826 { TYPE_STRING, &ldi.program_copyright, "program_copyright" },
2827 { TYPE_STRING, &ldi.program_company, "program_company" },
2828 { TYPE_STRING, &ldi.imported_from, "imported_from" },
2829 { TYPE_STRING, &ldi.imported_by, "imported_by" },
2830 { TYPE_STRING, &ldi.tested_by, "tested_by" },
2831 { TYPE_INTEGER, &ldi.levels, "levels" },
2832 { TYPE_INTEGER, &ldi.first_level, "first_level" },
2833 { TYPE_INTEGER, &ldi.sort_priority, "sort_priority" },
2834 { TYPE_BOOLEAN, &ldi.latest_engine, "latest_engine" },
2835 { TYPE_BOOLEAN, &ldi.level_group, "level_group" },
2836 { TYPE_BOOLEAN, &ldi.readonly, "readonly" },
2837 { TYPE_STRING, &ldi.graphics_set_ecs, "graphics_set.ecs" },
2838 { TYPE_STRING, &ldi.graphics_set_aga, "graphics_set.aga" },
2839 { TYPE_STRING, &ldi.graphics_set, "graphics_set" },
2840 { TYPE_STRING, &ldi.sounds_set_default,"sounds_set.default" },
2841 { TYPE_STRING, &ldi.sounds_set_lowpass,"sounds_set.lowpass" },
2842 { TYPE_STRING, &ldi.sounds_set, "sounds_set" },
2843 { TYPE_STRING, &ldi.music_set, "music_set" },
2844 { TYPE_STRING, &ldi.level_filename, "filename" },
2845 { TYPE_STRING, &ldi.level_filetype, "filetype" },
2846 { TYPE_STRING, &ldi.special_flags, "special_flags" },
2847 { TYPE_STRING, &ldi.empty_level_name, "empty_level_name" },
2848 { TYPE_BOOLEAN, &ldi.force_level_name, "force_level_name" },
2849 { TYPE_BOOLEAN, &ldi.handicap, "handicap" },
2850 { TYPE_BOOLEAN, &ldi.time_limit, "time_limit" },
2851 { TYPE_BOOLEAN, &ldi.skip_levels, "skip_levels" },
2852 { TYPE_BOOLEAN, &ldi.use_emc_tiles, "use_emc_tiles" },
2853 { TYPE_BOOLEAN, &ldi.info_screens_from_main, "info_screens_from_main" }
2856 static struct TokenInfo artworkinfo_tokens[] =
2858 // artwork directory info
2859 { TYPE_STRING, &ldi.identifier, "identifier" },
2860 { TYPE_STRING, &ldi.subdir, "subdir" },
2861 { TYPE_STRING, &ldi.name, "name" },
2862 { TYPE_STRING, &ldi.name_sorting, "name_sorting" },
2863 { TYPE_STRING, &ldi.author, "author" },
2864 { TYPE_STRING, &ldi.program_title, "program_title" },
2865 { TYPE_STRING, &ldi.program_copyright, "program_copyright" },
2866 { TYPE_STRING, &ldi.program_company, "program_company" },
2867 { TYPE_INTEGER, &ldi.sort_priority, "sort_priority" },
2868 { TYPE_STRING, &ldi.basepath, "basepath" },
2869 { TYPE_STRING, &ldi.fullpath, "fullpath" },
2870 { TYPE_BOOLEAN, &ldi.in_user_dir, "in_user_dir" },
2871 { TYPE_STRING, &ldi.class_desc, "class_desc" },
2876 static char *optional_tokens[] =
2879 "program_copyright",
2885 static void setTreeInfoToDefaults(TreeInfo *ti, int type)
2889 ti->node_top = (ti->type == TREE_TYPE_LEVEL_DIR ? &leveldir_first :
2890 ti->type == TREE_TYPE_GRAPHICS_DIR ? &artwork.gfx_first :
2891 ti->type == TREE_TYPE_SOUNDS_DIR ? &artwork.snd_first :
2892 ti->type == TREE_TYPE_MUSIC_DIR ? &artwork.mus_first :
2895 ti->node_parent = NULL;
2896 ti->node_group = NULL;
2903 ti->fullpath = NULL;
2904 ti->basepath = NULL;
2905 ti->identifier = NULL;
2906 ti->name = getStringCopy(ANONYMOUS_NAME);
2907 ti->name_sorting = NULL;
2908 ti->author = getStringCopy(ANONYMOUS_NAME);
2911 ti->program_title = NULL;
2912 ti->program_copyright = NULL;
2913 ti->program_company = NULL;
2915 ti->sort_priority = LEVELCLASS_UNDEFINED; // default: least priority
2916 ti->latest_engine = FALSE; // default: get from level
2917 ti->parent_link = FALSE;
2918 ti->is_copy = FALSE;
2919 ti->in_user_dir = FALSE;
2920 ti->user_defined = FALSE;
2922 ti->class_desc = NULL;
2924 ti->infotext = getStringCopy(TREE_INFOTEXT(ti->type));
2926 if (ti->type == TREE_TYPE_LEVEL_DIR)
2928 ti->imported_from = NULL;
2929 ti->imported_by = NULL;
2930 ti->tested_by = NULL;
2932 ti->graphics_set_ecs = NULL;
2933 ti->graphics_set_aga = NULL;
2934 ti->graphics_set = NULL;
2935 ti->sounds_set_default = NULL;
2936 ti->sounds_set_lowpass = NULL;
2937 ti->sounds_set = NULL;
2938 ti->music_set = NULL;
2939 ti->graphics_path = getStringCopy(UNDEFINED_FILENAME);
2940 ti->sounds_path = getStringCopy(UNDEFINED_FILENAME);
2941 ti->music_path = getStringCopy(UNDEFINED_FILENAME);
2943 ti->level_filename = NULL;
2944 ti->level_filetype = NULL;
2946 ti->special_flags = NULL;
2948 ti->empty_level_name = NULL;
2949 ti->force_level_name = FALSE;
2952 ti->first_level = 0;
2954 ti->level_group = FALSE;
2955 ti->handicap_level = 0;
2956 ti->readonly = TRUE;
2957 ti->handicap = TRUE;
2958 ti->time_limit = TRUE;
2959 ti->skip_levels = FALSE;
2961 ti->use_emc_tiles = FALSE;
2962 ti->info_screens_from_main = FALSE;
2966 static void setTreeInfoToDefaultsFromParent(TreeInfo *ti, TreeInfo *parent)
2970 Warn("setTreeInfoToDefaultsFromParent(): parent == NULL");
2972 setTreeInfoToDefaults(ti, TREE_TYPE_UNDEFINED);
2977 // copy all values from the parent structure
2979 ti->type = parent->type;
2981 ti->node_top = parent->node_top;
2982 ti->node_parent = parent;
2983 ti->node_group = NULL;
2990 ti->fullpath = NULL;
2991 ti->basepath = NULL;
2992 ti->identifier = NULL;
2993 ti->name = getStringCopy(ANONYMOUS_NAME);
2994 ti->name_sorting = NULL;
2995 ti->author = getStringCopy(parent->author);
2996 ti->year = getStringCopy(parent->year);
2998 ti->program_title = getStringCopy(parent->program_title);
2999 ti->program_copyright = getStringCopy(parent->program_copyright);
3000 ti->program_company = getStringCopy(parent->program_company);
3002 ti->sort_priority = parent->sort_priority;
3003 ti->latest_engine = parent->latest_engine;
3004 ti->parent_link = FALSE;
3005 ti->is_copy = FALSE;
3006 ti->in_user_dir = parent->in_user_dir;
3007 ti->user_defined = parent->user_defined;
3008 ti->color = parent->color;
3009 ti->class_desc = getStringCopy(parent->class_desc);
3011 ti->infotext = getStringCopy(parent->infotext);
3013 if (ti->type == TREE_TYPE_LEVEL_DIR)
3015 ti->imported_from = getStringCopy(parent->imported_from);
3016 ti->imported_by = getStringCopy(parent->imported_by);
3017 ti->tested_by = getStringCopy(parent->tested_by);
3019 ti->graphics_set_ecs = getStringCopy(parent->graphics_set_ecs);
3020 ti->graphics_set_aga = getStringCopy(parent->graphics_set_aga);
3021 ti->graphics_set = getStringCopy(parent->graphics_set);
3022 ti->sounds_set_default = getStringCopy(parent->sounds_set_default);
3023 ti->sounds_set_lowpass = getStringCopy(parent->sounds_set_lowpass);
3024 ti->sounds_set = getStringCopy(parent->sounds_set);
3025 ti->music_set = getStringCopy(parent->music_set);
3026 ti->graphics_path = getStringCopy(UNDEFINED_FILENAME);
3027 ti->sounds_path = getStringCopy(UNDEFINED_FILENAME);
3028 ti->music_path = getStringCopy(UNDEFINED_FILENAME);
3030 ti->level_filename = getStringCopy(parent->level_filename);
3031 ti->level_filetype = getStringCopy(parent->level_filetype);
3033 ti->special_flags = getStringCopy(parent->special_flags);
3035 ti->empty_level_name = getStringCopy(parent->empty_level_name);
3036 ti->force_level_name = parent->force_level_name;
3038 ti->levels = parent->levels;
3039 ti->first_level = parent->first_level;
3040 ti->last_level = parent->last_level;
3041 ti->level_group = FALSE;
3042 ti->handicap_level = parent->handicap_level;
3043 ti->readonly = parent->readonly;
3044 ti->handicap = parent->handicap;
3045 ti->time_limit = parent->time_limit;
3046 ti->skip_levels = parent->skip_levels;
3048 ti->use_emc_tiles = parent->use_emc_tiles;
3049 ti->info_screens_from_main = parent->info_screens_from_main;
3053 static TreeInfo *getTreeInfoCopy(TreeInfo *ti)
3055 TreeInfo *ti_copy = newTreeInfo();
3057 // copy all values from the original structure
3059 ti_copy->type = ti->type;
3061 ti_copy->node_top = ti->node_top;
3062 ti_copy->node_parent = ti->node_parent;
3063 ti_copy->node_group = ti->node_group;
3064 ti_copy->next = ti->next;
3066 ti_copy->cl_first = ti->cl_first;
3067 ti_copy->cl_cursor = ti->cl_cursor;
3069 ti_copy->subdir = getStringCopy(ti->subdir);
3070 ti_copy->fullpath = getStringCopy(ti->fullpath);
3071 ti_copy->basepath = getStringCopy(ti->basepath);
3072 ti_copy->identifier = getStringCopy(ti->identifier);
3073 ti_copy->name = getStringCopy(ti->name);
3074 ti_copy->name_sorting = getStringCopy(ti->name_sorting);
3075 ti_copy->author = getStringCopy(ti->author);
3076 ti_copy->year = getStringCopy(ti->year);
3078 ti_copy->program_title = getStringCopy(ti->program_title);
3079 ti_copy->program_copyright = getStringCopy(ti->program_copyright);
3080 ti_copy->program_company = getStringCopy(ti->program_company);
3082 ti_copy->imported_from = getStringCopy(ti->imported_from);
3083 ti_copy->imported_by = getStringCopy(ti->imported_by);
3084 ti_copy->tested_by = getStringCopy(ti->tested_by);
3086 ti_copy->graphics_set_ecs = getStringCopy(ti->graphics_set_ecs);
3087 ti_copy->graphics_set_aga = getStringCopy(ti->graphics_set_aga);
3088 ti_copy->graphics_set = getStringCopy(ti->graphics_set);
3089 ti_copy->sounds_set_default = getStringCopy(ti->sounds_set_default);
3090 ti_copy->sounds_set_lowpass = getStringCopy(ti->sounds_set_lowpass);
3091 ti_copy->sounds_set = getStringCopy(ti->sounds_set);
3092 ti_copy->music_set = getStringCopy(ti->music_set);
3093 ti_copy->graphics_path = getStringCopy(ti->graphics_path);
3094 ti_copy->sounds_path = getStringCopy(ti->sounds_path);
3095 ti_copy->music_path = getStringCopy(ti->music_path);
3097 ti_copy->level_filename = getStringCopy(ti->level_filename);
3098 ti_copy->level_filetype = getStringCopy(ti->level_filetype);
3100 ti_copy->special_flags = getStringCopy(ti->special_flags);
3102 ti_copy->empty_level_name = getStringCopy(ti->empty_level_name);
3103 ti_copy->force_level_name = ti->force_level_name;
3105 ti_copy->levels = ti->levels;
3106 ti_copy->first_level = ti->first_level;
3107 ti_copy->last_level = ti->last_level;
3108 ti_copy->sort_priority = ti->sort_priority;
3110 ti_copy->latest_engine = ti->latest_engine;
3112 ti_copy->level_group = ti->level_group;
3113 ti_copy->parent_link = ti->parent_link;
3114 ti_copy->is_copy = ti->is_copy;
3115 ti_copy->in_user_dir = ti->in_user_dir;
3116 ti_copy->user_defined = ti->user_defined;
3117 ti_copy->readonly = ti->readonly;
3118 ti_copy->handicap = ti->handicap;
3119 ti_copy->time_limit = ti->time_limit;
3120 ti_copy->skip_levels = ti->skip_levels;
3122 ti_copy->use_emc_tiles = ti->use_emc_tiles;
3123 ti_copy->info_screens_from_main = ti->info_screens_from_main;
3125 ti_copy->color = ti->color;
3126 ti_copy->class_desc = getStringCopy(ti->class_desc);
3127 ti_copy->handicap_level = ti->handicap_level;
3129 ti_copy->infotext = getStringCopy(ti->infotext);
3134 void freeTreeInfo(TreeInfo *ti)
3139 checked_free(ti->subdir);
3140 checked_free(ti->fullpath);
3141 checked_free(ti->basepath);
3142 checked_free(ti->identifier);
3144 checked_free(ti->name);
3145 checked_free(ti->name_sorting);
3146 checked_free(ti->author);
3147 checked_free(ti->year);
3149 checked_free(ti->program_title);
3150 checked_free(ti->program_copyright);
3151 checked_free(ti->program_company);
3153 checked_free(ti->class_desc);
3155 checked_free(ti->infotext);
3157 if (ti->type == TREE_TYPE_LEVEL_DIR)
3159 checked_free(ti->imported_from);
3160 checked_free(ti->imported_by);
3161 checked_free(ti->tested_by);
3163 checked_free(ti->graphics_set_ecs);
3164 checked_free(ti->graphics_set_aga);
3165 checked_free(ti->graphics_set);
3166 checked_free(ti->sounds_set_default);
3167 checked_free(ti->sounds_set_lowpass);
3168 checked_free(ti->sounds_set);
3169 checked_free(ti->music_set);
3171 checked_free(ti->graphics_path);
3172 checked_free(ti->sounds_path);
3173 checked_free(ti->music_path);
3175 checked_free(ti->level_filename);
3176 checked_free(ti->level_filetype);
3178 checked_free(ti->special_flags);
3181 // recursively free child node
3183 freeTreeInfo(ti->node_group);
3185 // recursively free next node
3187 freeTreeInfo(ti->next);
3192 void setSetupInfo(struct TokenInfo *token_info,
3193 int token_nr, char *token_value)
3195 int token_type = token_info[token_nr].type;
3196 void *setup_value = token_info[token_nr].value;
3198 if (token_value == NULL)
3201 // set setup field to corresponding token value
3206 *(boolean *)setup_value = get_boolean_from_string(token_value);
3210 *(int *)setup_value = get_switch3_from_string(token_value);
3214 *(Key *)setup_value = getKeyFromKeyName(token_value);
3218 *(Key *)setup_value = getKeyFromX11KeyName(token_value);
3222 *(int *)setup_value = get_integer_from_string(token_value);
3226 checked_free(*(char **)setup_value);
3227 *(char **)setup_value = getStringCopy(token_value);
3231 *(int *)setup_value = get_player_nr_from_string(token_value);
3239 static int compareTreeInfoEntries(const void *object1, const void *object2)
3241 const TreeInfo *entry1 = *((TreeInfo **)object1);
3242 const TreeInfo *entry2 = *((TreeInfo **)object2);
3243 int tree_sorting1 = TREE_SORTING(entry1);
3244 int tree_sorting2 = TREE_SORTING(entry2);
3246 if (tree_sorting1 != tree_sorting2)
3247 return (tree_sorting1 - tree_sorting2);
3249 return strcasecmp(entry1->name_sorting, entry2->name_sorting);
3252 static TreeInfo *createParentTreeInfoNode(TreeInfo *node_parent)
3256 if (node_parent == NULL)
3259 ti_new = newTreeInfo();
3260 setTreeInfoToDefaults(ti_new, node_parent->type);
3262 ti_new->node_parent = node_parent;
3263 ti_new->parent_link = TRUE;
3265 setString(&ti_new->identifier, node_parent->identifier);
3266 setString(&ti_new->name, BACKLINK_TEXT_PARENT);
3267 setString(&ti_new->name_sorting, ti_new->name);
3269 setString(&ti_new->subdir, STRING_PARENT_DIRECTORY);
3270 setString(&ti_new->fullpath, node_parent->fullpath);
3272 ti_new->sort_priority = LEVELCLASS_PARENT;
3273 ti_new->latest_engine = node_parent->latest_engine;
3275 setString(&ti_new->class_desc, getLevelClassDescription(ti_new));
3277 pushTreeInfo(&node_parent->node_group, ti_new);
3282 static TreeInfo *createTopTreeInfoNode(TreeInfo *node_first)
3284 if (node_first == NULL)
3287 TreeInfo *ti_new = newTreeInfo();
3288 int type = node_first->type;
3290 setTreeInfoToDefaults(ti_new, type);
3292 ti_new->node_parent = NULL;
3293 ti_new->parent_link = FALSE;
3295 setString(&ti_new->identifier, "top_tree_node");
3296 setString(&ti_new->name, TREE_INFOTEXT(type));
3297 setString(&ti_new->name_sorting, ti_new->name);
3299 setString(&ti_new->subdir, STRING_TOP_DIRECTORY);
3300 setString(&ti_new->fullpath, ".");
3302 ti_new->sort_priority = LEVELCLASS_TOP;
3303 ti_new->latest_engine = node_first->latest_engine;
3305 setString(&ti_new->class_desc, TREE_INFOTEXT(type));
3307 ti_new->node_group = node_first;
3308 ti_new->level_group = TRUE;
3310 TreeInfo *ti_new2 = createParentTreeInfoNode(ti_new);
3312 setString(&ti_new2->name, TREE_BACKLINK_TEXT(type));
3313 setString(&ti_new2->name_sorting, ti_new2->name);
3318 static void setTreeInfoParentNodes(TreeInfo *node, TreeInfo *node_parent)
3322 if (node->node_group)
3323 setTreeInfoParentNodes(node->node_group, node);
3325 node->node_parent = node_parent;
3331 TreeInfo *addTopTreeInfoNode(TreeInfo *node_first)
3333 // add top tree node with back link node in previous tree
3334 node_first = createTopTreeInfoNode(node_first);
3336 // set all parent links (back links) in complete tree
3337 setTreeInfoParentNodes(node_first, NULL);
3343 // ----------------------------------------------------------------------------
3344 // functions for handling level and custom artwork info cache
3345 // ----------------------------------------------------------------------------
3347 static void LoadArtworkInfoCache(void)
3349 InitCacheDirectory();
3351 if (artworkinfo_cache_old == NULL)
3353 char *filename = getPath2(getCacheDir(), ARTWORKINFO_CACHE_FILE);
3355 // try to load artwork info hash from already existing cache file
3356 artworkinfo_cache_old = loadSetupFileHash(filename);
3358 // try to get program version that artwork info cache was written with
3359 char *version = getHashEntry(artworkinfo_cache_old, "program.version");
3361 // check program version of artwork info cache against current version
3362 if (!strEqual(version, program.version_string))
3364 freeSetupFileHash(artworkinfo_cache_old);
3366 artworkinfo_cache_old = NULL;
3369 // if no artwork info cache file was found, start with empty hash
3370 if (artworkinfo_cache_old == NULL)
3371 artworkinfo_cache_old = newSetupFileHash();
3376 if (artworkinfo_cache_new == NULL)
3377 artworkinfo_cache_new = newSetupFileHash();
3379 update_artworkinfo_cache = FALSE;
3382 static void SaveArtworkInfoCache(void)
3384 if (!update_artworkinfo_cache)
3387 char *filename = getPath2(getCacheDir(), ARTWORKINFO_CACHE_FILE);
3389 InitCacheDirectory();
3391 saveSetupFileHash(artworkinfo_cache_new, filename);
3396 static char *getCacheTokenPrefix(char *prefix1, char *prefix2)
3398 static char *prefix = NULL;
3400 checked_free(prefix);
3402 prefix = getStringCat2WithSeparator(prefix1, prefix2, ".");
3407 // (identical to above function, but separate string buffer needed -- nasty)
3408 static char *getCacheToken(char *prefix, char *suffix)
3410 static char *token = NULL;
3412 checked_free(token);
3414 token = getStringCat2WithSeparator(prefix, suffix, ".");
3419 static char *getFileTimestampString(char *filename)
3421 return getStringCopy(i_to_a(getFileTimestampEpochSeconds(filename)));
3424 static boolean modifiedFileTimestamp(char *filename, char *timestamp_string)
3426 struct stat file_status;
3428 if (timestamp_string == NULL)
3431 if (!fileExists(filename)) // file does not exist
3432 return (atoi(timestamp_string) != 0);
3434 if (stat(filename, &file_status) != 0) // cannot stat file
3437 return (file_status.st_mtime != atoi(timestamp_string));
3440 static TreeInfo *getArtworkInfoCacheEntry(LevelDirTree *level_node, int type)
3442 char *identifier = level_node->subdir;
3443 char *type_string = ARTWORK_DIRECTORY(type);
3444 char *token_prefix = getCacheTokenPrefix(type_string, identifier);
3445 char *token_main = getCacheToken(token_prefix, "CACHED");
3446 char *cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
3447 boolean cached = (cache_entry != NULL && strEqual(cache_entry, "true"));
3448 TreeInfo *artwork_info = NULL;
3450 if (!use_artworkinfo_cache)
3453 if (optional_tokens_hash == NULL)
3457 // create hash from list of optional tokens (for quick access)
3458 optional_tokens_hash = newSetupFileHash();
3459 for (i = 0; optional_tokens[i] != NULL; i++)
3460 setHashEntry(optional_tokens_hash, optional_tokens[i], "");
3467 artwork_info = newTreeInfo();
3468 setTreeInfoToDefaults(artwork_info, type);
3470 // set all structure fields according to the token/value pairs
3471 ldi = *artwork_info;
3472 for (i = 0; artworkinfo_tokens[i].type != -1; i++)
3474 char *token_suffix = artworkinfo_tokens[i].text;
3475 char *token = getCacheToken(token_prefix, token_suffix);
3476 char *value = getHashEntry(artworkinfo_cache_old, token);
3478 (getHashEntry(optional_tokens_hash, token_suffix) != NULL);
3480 setSetupInfo(artworkinfo_tokens, i, value);
3482 // check if cache entry for this item is mandatory, but missing
3483 if (value == NULL && !optional)
3485 Warn("missing cache entry '%s'", token);
3491 *artwork_info = ldi;
3496 char *filename_levelinfo = getPath2(getLevelDirFromTreeInfo(level_node),
3497 LEVELINFO_FILENAME);
3498 char *filename_artworkinfo = getPath2(getSetupArtworkDir(artwork_info),
3499 ARTWORKINFO_FILENAME(type));
3501 // check if corresponding "levelinfo.conf" file has changed
3502 token_main = getCacheToken(token_prefix, "TIMESTAMP_LEVELINFO");
3503 cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
3505 if (modifiedFileTimestamp(filename_levelinfo, cache_entry))
3508 // check if corresponding "<artworkinfo>.conf" file has changed
3509 token_main = getCacheToken(token_prefix, "TIMESTAMP_ARTWORKINFO");
3510 cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
3512 if (modifiedFileTimestamp(filename_artworkinfo, cache_entry))
3515 checked_free(filename_levelinfo);
3516 checked_free(filename_artworkinfo);
3519 if (!cached && artwork_info != NULL)
3521 freeTreeInfo(artwork_info);
3526 return artwork_info;
3529 static void setArtworkInfoCacheEntry(TreeInfo *artwork_info,
3530 LevelDirTree *level_node, int type)
3532 char *identifier = level_node->subdir;
3533 char *type_string = ARTWORK_DIRECTORY(type);
3534 char *token_prefix = getCacheTokenPrefix(type_string, identifier);
3535 char *token_main = getCacheToken(token_prefix, "CACHED");
3536 boolean set_cache_timestamps = TRUE;
3539 setHashEntry(artworkinfo_cache_new, token_main, "true");
3541 if (set_cache_timestamps)
3543 char *filename_levelinfo = getPath2(getLevelDirFromTreeInfo(level_node),
3544 LEVELINFO_FILENAME);
3545 char *filename_artworkinfo = getPath2(getSetupArtworkDir(artwork_info),
3546 ARTWORKINFO_FILENAME(type));
3547 char *timestamp_levelinfo = getFileTimestampString(filename_levelinfo);
3548 char *timestamp_artworkinfo = getFileTimestampString(filename_artworkinfo);
3550 token_main = getCacheToken(token_prefix, "TIMESTAMP_LEVELINFO");
3551 setHashEntry(artworkinfo_cache_new, token_main, timestamp_levelinfo);
3553 token_main = getCacheToken(token_prefix, "TIMESTAMP_ARTWORKINFO");
3554 setHashEntry(artworkinfo_cache_new, token_main, timestamp_artworkinfo);
3556 checked_free(filename_levelinfo);
3557 checked_free(filename_artworkinfo);
3558 checked_free(timestamp_levelinfo);
3559 checked_free(timestamp_artworkinfo);
3562 ldi = *artwork_info;
3563 for (i = 0; artworkinfo_tokens[i].type != -1; i++)
3565 char *token = getCacheToken(token_prefix, artworkinfo_tokens[i].text);
3566 char *value = getSetupValue(artworkinfo_tokens[i].type,
3567 artworkinfo_tokens[i].value);
3569 setHashEntry(artworkinfo_cache_new, token, value);
3574 // ----------------------------------------------------------------------------
3575 // functions for loading level info and custom artwork info
3576 // ----------------------------------------------------------------------------
3578 int GetZipFileTreeType(char *zip_filename)
3580 static char *top_dir_path = NULL;
3581 static char *top_dir_conf_filename[NUM_BASE_TREE_TYPES] = { NULL };
3582 static char *conf_basename[NUM_BASE_TREE_TYPES] =
3584 GRAPHICSINFO_FILENAME,
3585 SOUNDSINFO_FILENAME,
3591 checked_free(top_dir_path);
3592 top_dir_path = NULL;
3594 for (j = 0; j < NUM_BASE_TREE_TYPES; j++)
3596 checked_free(top_dir_conf_filename[j]);
3597 top_dir_conf_filename[j] = NULL;
3600 char **zip_entries = zip_list(zip_filename);
3602 // check if zip file successfully opened
3603 if (zip_entries == NULL || zip_entries[0] == NULL)
3604 return TREE_TYPE_UNDEFINED;
3606 // first zip file entry is expected to be top level directory
3607 char *top_dir = zip_entries[0];
3609 // check if valid top level directory found in zip file
3610 if (!strSuffix(top_dir, "/"))
3611 return TREE_TYPE_UNDEFINED;
3613 // get filenames of valid configuration files in top level directory
3614 for (j = 0; j < NUM_BASE_TREE_TYPES; j++)
3615 top_dir_conf_filename[j] = getStringCat2(top_dir, conf_basename[j]);
3617 int tree_type = TREE_TYPE_UNDEFINED;
3620 while (zip_entries[e] != NULL)
3622 // check if every zip file entry is below top level directory
3623 if (!strPrefix(zip_entries[e], top_dir))
3624 return TREE_TYPE_UNDEFINED;
3626 // check if this zip file entry is a valid configuration filename
3627 for (j = 0; j < NUM_BASE_TREE_TYPES; j++)
3629 if (strEqual(zip_entries[e], top_dir_conf_filename[j]))
3631 // only exactly one valid configuration file allowed
3632 if (tree_type != TREE_TYPE_UNDEFINED)
3633 return TREE_TYPE_UNDEFINED;
3645 static boolean CheckZipFileForDirectory(char *zip_filename, char *directory,
3648 static char *top_dir_path = NULL;
3649 static char *top_dir_conf_filename = NULL;
3651 checked_free(top_dir_path);
3652 checked_free(top_dir_conf_filename);
3654 top_dir_path = NULL;
3655 top_dir_conf_filename = NULL;
3657 char *conf_basename = (tree_type == TREE_TYPE_LEVEL_DIR ? LEVELINFO_FILENAME :
3658 ARTWORKINFO_FILENAME(tree_type));
3660 // check if valid configuration filename determined
3661 if (conf_basename == NULL || strEqual(conf_basename, ""))
3664 char **zip_entries = zip_list(zip_filename);
3666 // check if zip file successfully opened
3667 if (zip_entries == NULL || zip_entries[0] == NULL)
3670 // first zip file entry is expected to be top level directory
3671 char *top_dir = zip_entries[0];
3673 // check if valid top level directory found in zip file
3674 if (!strSuffix(top_dir, "/"))
3677 // get path of extracted top level directory
3678 top_dir_path = getPath2(directory, top_dir);
3680 // remove trailing directory separator from top level directory path
3681 // (required to be able to check for file and directory in next step)
3682 top_dir_path[strlen(top_dir_path) - 1] = '\0';
3684 // check if zip file's top level directory already exists in target directory
3685 if (fileExists(top_dir_path)) // (checks for file and directory)
3688 // get filename of configuration file in top level directory
3689 top_dir_conf_filename = getStringCat2(top_dir, conf_basename);
3691 boolean found_top_dir_conf_filename = FALSE;
3694 while (zip_entries[i] != NULL)
3696 // check if every zip file entry is below top level directory
3697 if (!strPrefix(zip_entries[i], top_dir))
3700 // check if this zip file entry is the configuration filename
3701 if (strEqual(zip_entries[i], top_dir_conf_filename))
3702 found_top_dir_conf_filename = TRUE;
3707 // check if valid configuration filename was found in zip file
3708 if (!found_top_dir_conf_filename)
3714 char *ExtractZipFileIntoDirectory(char *zip_filename, char *directory,
3717 boolean zip_file_valid = CheckZipFileForDirectory(zip_filename, directory,
3720 if (!zip_file_valid)
3722 Warn("zip file '%s' rejected!", zip_filename);
3727 char **zip_entries = zip_extract(zip_filename, directory);
3729 if (zip_entries == NULL)
3731 Warn("zip file '%s' could not be extracted!", zip_filename);
3736 Info("zip file '%s' successfully extracted!", zip_filename);
3738 // first zip file entry contains top level directory
3739 char *top_dir = zip_entries[0];
3741 // remove trailing directory separator from top level directory
3742 top_dir[strlen(top_dir) - 1] = '\0';
3747 static void ProcessZipFilesInDirectory(char *directory, int tree_type)
3750 DirectoryEntry *dir_entry;
3752 if ((dir = openDirectory(directory)) == NULL)
3754 // display error if directory is main "options.graphics_directory" etc.
3755 if (tree_type == TREE_TYPE_LEVEL_DIR ||
3756 directory == OPTIONS_ARTWORK_DIRECTORY(tree_type))
3757 Warn("cannot read directory '%s'", directory);
3762 while ((dir_entry = readDirectory(dir)) != NULL) // loop all entries
3764 // skip non-zip files (and also directories with zip extension)
3765 if (!strSuffixLower(dir_entry->basename, ".zip") || dir_entry->is_directory)
3768 char *zip_filename = getPath2(directory, dir_entry->basename);
3769 char *zip_filename_extracted = getStringCat2(zip_filename, ".extracted");
3770 char *zip_filename_rejected = getStringCat2(zip_filename, ".rejected");
3772 // check if zip file hasn't already been extracted or rejected
3773 if (!fileExists(zip_filename_extracted) &&
3774 !fileExists(zip_filename_rejected))
3776 char *top_dir = ExtractZipFileIntoDirectory(zip_filename, directory,
3778 char *marker_filename = (top_dir != NULL ? zip_filename_extracted :
3779 zip_filename_rejected);
3782 // create empty file to mark zip file as extracted or rejected
3783 if ((marker_file = fopen(marker_filename, MODE_WRITE)))
3784 fclose(marker_file);
3787 free(zip_filename_extracted);
3788 free(zip_filename_rejected);
3792 closeDirectory(dir);
3795 // forward declaration for recursive call by "LoadLevelInfoFromLevelDir()"
3796 static void LoadLevelInfoFromLevelDir(TreeInfo **, TreeInfo *, char *);
3798 static boolean LoadLevelInfoFromLevelConf(TreeInfo **node_first,
3799 TreeInfo *node_parent,
3800 char *level_directory,
3801 char *directory_name)
3803 char *directory_path = getPath2(level_directory, directory_name);
3804 char *filename = getPath2(directory_path, LEVELINFO_FILENAME);
3805 SetupFileHash *setup_file_hash;
3806 LevelDirTree *leveldir_new = NULL;
3809 // unless debugging, silently ignore directories without "levelinfo.conf"
3810 if (!options.debug && !fileExists(filename))
3812 free(directory_path);
3818 setup_file_hash = loadSetupFileHash(filename);
3820 if (setup_file_hash == NULL)
3822 #if DEBUG_NO_CONFIG_FILE
3823 Debug("setup", "ignoring level directory '%s'", directory_path);
3826 free(directory_path);
3832 leveldir_new = newTreeInfo();
3835 setTreeInfoToDefaultsFromParent(leveldir_new, node_parent);
3837 setTreeInfoToDefaults(leveldir_new, TREE_TYPE_LEVEL_DIR);
3839 leveldir_new->subdir = getStringCopy(directory_name);
3841 // set all structure fields according to the token/value pairs
3842 ldi = *leveldir_new;
3843 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
3844 setSetupInfo(levelinfo_tokens, i,
3845 getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
3846 *leveldir_new = ldi;
3848 if (strEqual(leveldir_new->name, ANONYMOUS_NAME))
3849 setString(&leveldir_new->name, leveldir_new->subdir);
3851 if (leveldir_new->identifier == NULL)
3852 leveldir_new->identifier = getStringCopy(leveldir_new->subdir);
3854 if (leveldir_new->name_sorting == NULL)
3855 leveldir_new->name_sorting = getStringCopy(leveldir_new->name);
3857 if (node_parent == NULL) // top level group
3859 leveldir_new->basepath = getStringCopy(level_directory);
3860 leveldir_new->fullpath = getStringCopy(leveldir_new->subdir);
3862 else // sub level group
3864 leveldir_new->basepath = getStringCopy(node_parent->basepath);
3865 leveldir_new->fullpath = getPath2(node_parent->fullpath, directory_name);
3868 leveldir_new->last_level =
3869 leveldir_new->first_level + leveldir_new->levels - 1;
3871 leveldir_new->in_user_dir =
3872 (!strEqual(leveldir_new->basepath, options.level_directory));
3874 // adjust some settings if user's private level directory was detected
3875 if (leveldir_new->sort_priority == LEVELCLASS_UNDEFINED &&
3876 leveldir_new->in_user_dir &&
3877 (strEqual(leveldir_new->subdir, getLoginName()) ||
3878 strEqual(leveldir_new->name, getLoginName()) ||
3879 strEqual(leveldir_new->author, getRealName())))
3881 leveldir_new->sort_priority = LEVELCLASS_PRIVATE_START;
3882 leveldir_new->readonly = FALSE;
3885 leveldir_new->user_defined =
3886 (leveldir_new->in_user_dir && IS_LEVELCLASS_PRIVATE(leveldir_new));
3888 setString(&leveldir_new->class_desc, getLevelClassDescription(leveldir_new));
3890 leveldir_new->handicap_level = // set handicap to default value
3891 (leveldir_new->user_defined || !leveldir_new->handicap ?
3892 leveldir_new->last_level : leveldir_new->first_level);
3894 DrawInitTextItem(leveldir_new->name);
3896 pushTreeInfo(node_first, leveldir_new);
3898 freeSetupFileHash(setup_file_hash);
3900 if (leveldir_new->level_group)
3902 // create node to link back to current level directory
3903 createParentTreeInfoNode(leveldir_new);
3905 // recursively step into sub-directory and look for more level series
3906 LoadLevelInfoFromLevelDir(&leveldir_new->node_group,
3907 leveldir_new, directory_path);
3910 free(directory_path);
3916 static void LoadLevelInfoFromLevelDir(TreeInfo **node_first,
3917 TreeInfo *node_parent,
3918 char *level_directory)
3920 // ---------- 1st stage: process any level set zip files ----------
3922 ProcessZipFilesInDirectory(level_directory, TREE_TYPE_LEVEL_DIR);
3924 // ---------- 2nd stage: check for level set directories ----------
3927 DirectoryEntry *dir_entry;
3928 boolean valid_entry_found = FALSE;
3930 if ((dir = openDirectory(level_directory)) == NULL)
3932 Warn("cannot read level directory '%s'", level_directory);
3937 while ((dir_entry = readDirectory(dir)) != NULL) // loop all entries
3939 char *directory_name = dir_entry->basename;
3940 char *directory_path = getPath2(level_directory, directory_name);
3942 // skip entries for current and parent directory
3943 if (strEqual(directory_name, ".") ||
3944 strEqual(directory_name, ".."))
3946 free(directory_path);
3951 // find out if directory entry is itself a directory
3952 if (!dir_entry->is_directory) // not a directory
3954 free(directory_path);
3959 free(directory_path);
3961 if (strEqual(directory_name, GRAPHICS_DIRECTORY) ||
3962 strEqual(directory_name, SOUNDS_DIRECTORY) ||
3963 strEqual(directory_name, MUSIC_DIRECTORY))
3966 valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
3971 closeDirectory(dir);
3973 // special case: top level directory may directly contain "levelinfo.conf"
3974 if (node_parent == NULL && !valid_entry_found)
3976 // check if this directory directly contains a file "levelinfo.conf"
3977 valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
3978 level_directory, ".");
3981 boolean valid_entry_expected =
3982 (strEqual(level_directory, options.level_directory) ||
3983 setup.internal.create_user_levelset);
3985 if (valid_entry_expected && !valid_entry_found)
3986 Warn("cannot find any valid level series in directory '%s'",
3990 boolean AdjustGraphicsForEMC(void)
3992 boolean settings_changed = FALSE;
3994 settings_changed |= adjustTreeGraphicsForEMC(leveldir_first_all);
3995 settings_changed |= adjustTreeGraphicsForEMC(leveldir_first);
3997 return settings_changed;
4000 boolean AdjustSoundsForEMC(void)
4002 boolean settings_changed = FALSE;
4004 settings_changed |= adjustTreeSoundsForEMC(leveldir_first_all);
4005 settings_changed |= adjustTreeSoundsForEMC(leveldir_first);
4007 return settings_changed;
4010 void LoadLevelInfo(void)
4012 InitUserLevelDirectory(getLoginName());
4014 DrawInitTextHead("Loading level series");
4016 LoadLevelInfoFromLevelDir(&leveldir_first, NULL, options.level_directory);
4017 LoadLevelInfoFromLevelDir(&leveldir_first, NULL, getUserLevelDir(NULL));
4019 leveldir_first = createTopTreeInfoNode(leveldir_first);
4021 /* after loading all level set information, clone the level directory tree
4022 and remove all level sets without levels (these may still contain artwork
4023 to be offered in the setup menu as "custom artwork", and are therefore
4024 checked for existing artwork in the function "LoadLevelArtworkInfo()") */
4025 leveldir_first_all = leveldir_first;
4026 cloneTree(&leveldir_first, leveldir_first_all, TRUE);
4028 AdjustGraphicsForEMC();
4029 AdjustSoundsForEMC();
4031 // before sorting, the first entries will be from the user directory
4032 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
4034 if (leveldir_first == NULL)
4035 Fail("cannot find any valid level series in any directory");
4037 sortTreeInfo(&leveldir_first);
4039 #if ENABLE_UNUSED_CODE
4040 dumpTreeInfo(leveldir_first, 0);
4044 static boolean LoadArtworkInfoFromArtworkConf(TreeInfo **node_first,
4045 TreeInfo *node_parent,
4046 char *base_directory,
4047 char *directory_name, int type)
4049 char *directory_path = getPath2(base_directory, directory_name);
4050 char *filename = getPath2(directory_path, ARTWORKINFO_FILENAME(type));
4051 SetupFileHash *setup_file_hash = NULL;
4052 TreeInfo *artwork_new = NULL;
4055 if (fileExists(filename))
4056 setup_file_hash = loadSetupFileHash(filename);
4058 if (setup_file_hash == NULL) // no config file -- look for artwork files
4061 DirectoryEntry *dir_entry;
4062 boolean valid_file_found = FALSE;
4064 if ((dir = openDirectory(directory_path)) != NULL)
4066 while ((dir_entry = readDirectory(dir)) != NULL)
4068 if (FileIsArtworkType(dir_entry->filename, type))
4070 valid_file_found = TRUE;
4076 closeDirectory(dir);
4079 if (!valid_file_found)
4081 #if DEBUG_NO_CONFIG_FILE
4082 if (!strEqual(directory_name, "."))
4083 Debug("setup", "ignoring artwork directory '%s'", directory_path);
4086 free(directory_path);
4093 artwork_new = newTreeInfo();
4096 setTreeInfoToDefaultsFromParent(artwork_new, node_parent);
4098 setTreeInfoToDefaults(artwork_new, type);
4100 artwork_new->subdir = getStringCopy(directory_name);
4102 if (setup_file_hash) // (before defining ".color" and ".class_desc")
4104 // set all structure fields according to the token/value pairs
4106 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
4107 setSetupInfo(levelinfo_tokens, i,
4108 getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
4111 if (strEqual(artwork_new->name, ANONYMOUS_NAME))
4112 setString(&artwork_new->name, artwork_new->subdir);
4114 if (artwork_new->identifier == NULL)
4115 artwork_new->identifier = getStringCopy(artwork_new->subdir);
4117 if (artwork_new->name_sorting == NULL)
4118 artwork_new->name_sorting = getStringCopy(artwork_new->name);
4121 if (node_parent == NULL) // top level group
4123 artwork_new->basepath = getStringCopy(base_directory);
4124 artwork_new->fullpath = getStringCopy(artwork_new->subdir);
4126 else // sub level group
4128 artwork_new->basepath = getStringCopy(node_parent->basepath);
4129 artwork_new->fullpath = getPath2(node_parent->fullpath, directory_name);
4132 artwork_new->in_user_dir =
4133 (!strEqual(artwork_new->basepath, OPTIONS_ARTWORK_DIRECTORY(type)));
4135 setString(&artwork_new->class_desc, getLevelClassDescription(artwork_new));
4137 if (setup_file_hash == NULL) // (after determining ".user_defined")
4139 if (strEqual(artwork_new->subdir, "."))
4141 if (artwork_new->user_defined)
4143 setString(&artwork_new->identifier, "private");
4144 artwork_new->sort_priority = ARTWORKCLASS_PRIVATE;
4148 setString(&artwork_new->identifier, "classic");
4149 artwork_new->sort_priority = ARTWORKCLASS_CLASSICS;
4152 setString(&artwork_new->class_desc,
4153 getLevelClassDescription(artwork_new));
4157 setString(&artwork_new->identifier, artwork_new->subdir);
4160 setString(&artwork_new->name, artwork_new->identifier);
4161 setString(&artwork_new->name_sorting, artwork_new->name);
4164 pushTreeInfo(node_first, artwork_new);
4166 freeSetupFileHash(setup_file_hash);
4168 free(directory_path);
4174 static void LoadArtworkInfoFromArtworkDir(TreeInfo **node_first,
4175 TreeInfo *node_parent,
4176 char *base_directory, int type)
4178 // ---------- 1st stage: process any artwork set zip files ----------
4180 ProcessZipFilesInDirectory(base_directory, type);
4182 // ---------- 2nd stage: check for artwork set directories ----------
4185 DirectoryEntry *dir_entry;
4186 boolean valid_entry_found = FALSE;
4188 if ((dir = openDirectory(base_directory)) == NULL)
4190 // display error if directory is main "options.graphics_directory" etc.
4191 if (base_directory == OPTIONS_ARTWORK_DIRECTORY(type))
4192 Warn("cannot read directory '%s'", base_directory);
4197 while ((dir_entry = readDirectory(dir)) != NULL) // loop all entries
4199 char *directory_name = dir_entry->basename;
4200 char *directory_path = getPath2(base_directory, directory_name);
4202 // skip directory entries for current and parent directory
4203 if (strEqual(directory_name, ".") ||
4204 strEqual(directory_name, ".."))
4206 free(directory_path);
4211 // skip directory entries which are not a directory
4212 if (!dir_entry->is_directory) // not a directory
4214 free(directory_path);
4219 free(directory_path);
4221 // check if this directory contains artwork with or without config file
4222 valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
4224 directory_name, type);
4227 closeDirectory(dir);
4229 // check if this directory directly contains artwork itself
4230 valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
4231 base_directory, ".",
4233 if (!valid_entry_found)
4234 Warn("cannot find any valid artwork in directory '%s'", base_directory);
4237 static TreeInfo *getDummyArtworkInfo(int type)
4239 // this is only needed when there is completely no artwork available
4240 TreeInfo *artwork_new = newTreeInfo();
4242 setTreeInfoToDefaults(artwork_new, type);
4244 setString(&artwork_new->subdir, UNDEFINED_FILENAME);
4245 setString(&artwork_new->fullpath, UNDEFINED_FILENAME);
4246 setString(&artwork_new->basepath, UNDEFINED_FILENAME);
4248 setString(&artwork_new->identifier, UNDEFINED_FILENAME);
4249 setString(&artwork_new->name, UNDEFINED_FILENAME);
4250 setString(&artwork_new->name_sorting, UNDEFINED_FILENAME);
4255 void SetCurrentArtwork(int type)
4257 ArtworkDirTree **current_ptr = ARTWORK_CURRENT_PTR(artwork, type);
4258 ArtworkDirTree *first_node = ARTWORK_FIRST_NODE(artwork, type);
4259 char *setup_set = SETUP_ARTWORK_SET(setup, type);
4260 char *default_subdir = ARTWORK_DEFAULT_SUBDIR(type);
4262 // set current artwork to artwork configured in setup menu
4263 *current_ptr = getTreeInfoFromIdentifier(first_node, setup_set);
4265 // if not found, set current artwork to default artwork
4266 if (*current_ptr == NULL)
4267 *current_ptr = getTreeInfoFromIdentifier(first_node, default_subdir);
4269 // if not found, set current artwork to first artwork in tree
4270 if (*current_ptr == NULL)
4271 *current_ptr = getFirstValidTreeInfoEntry(first_node);
4274 void ChangeCurrentArtworkIfNeeded(int type)
4276 char *current_identifier = ARTWORK_CURRENT_IDENTIFIER(artwork, type);
4277 char *setup_set = SETUP_ARTWORK_SET(setup, type);
4279 if (!strEqual(current_identifier, setup_set))
4280 SetCurrentArtwork(type);
4283 void LoadArtworkInfo(void)
4285 LoadArtworkInfoCache();
4287 DrawInitTextHead("Looking for custom artwork");
4289 LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
4290 options.graphics_directory,
4291 TREE_TYPE_GRAPHICS_DIR);
4292 LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
4293 getUserGraphicsDir(),
4294 TREE_TYPE_GRAPHICS_DIR);
4296 LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
4297 options.sounds_directory,
4298 TREE_TYPE_SOUNDS_DIR);
4299 LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
4301 TREE_TYPE_SOUNDS_DIR);
4303 LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
4304 options.music_directory,
4305 TREE_TYPE_MUSIC_DIR);
4306 LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
4308 TREE_TYPE_MUSIC_DIR);
4310 if (artwork.gfx_first == NULL)
4311 artwork.gfx_first = getDummyArtworkInfo(TREE_TYPE_GRAPHICS_DIR);
4312 if (artwork.snd_first == NULL)
4313 artwork.snd_first = getDummyArtworkInfo(TREE_TYPE_SOUNDS_DIR);
4314 if (artwork.mus_first == NULL)
4315 artwork.mus_first = getDummyArtworkInfo(TREE_TYPE_MUSIC_DIR);
4317 // before sorting, the first entries will be from the user directory
4318 SetCurrentArtwork(ARTWORK_TYPE_GRAPHICS);
4319 SetCurrentArtwork(ARTWORK_TYPE_SOUNDS);
4320 SetCurrentArtwork(ARTWORK_TYPE_MUSIC);
4322 artwork.gfx_current_identifier = artwork.gfx_current->identifier;
4323 artwork.snd_current_identifier = artwork.snd_current->identifier;
4324 artwork.mus_current_identifier = artwork.mus_current->identifier;
4326 #if ENABLE_UNUSED_CODE
4327 Debug("setup:LoadArtworkInfo", "graphics set == %s",
4328 artwork.gfx_current_identifier);
4329 Debug("setup:LoadArtworkInfo", "sounds set == %s",
4330 artwork.snd_current_identifier);
4331 Debug("setup:LoadArtworkInfo", "music set == %s",
4332 artwork.mus_current_identifier);
4335 sortTreeInfo(&artwork.gfx_first);
4336 sortTreeInfo(&artwork.snd_first);
4337 sortTreeInfo(&artwork.mus_first);
4339 #if ENABLE_UNUSED_CODE
4340 dumpTreeInfo(artwork.gfx_first, 0);
4341 dumpTreeInfo(artwork.snd_first, 0);
4342 dumpTreeInfo(artwork.mus_first, 0);
4346 static void MoveArtworkInfoIntoSubTree(ArtworkDirTree **artwork_node)
4348 ArtworkDirTree *artwork_new = newTreeInfo();
4349 char *top_node_name = "standalone artwork";
4351 setTreeInfoToDefaults(artwork_new, (*artwork_node)->type);
4353 artwork_new->level_group = TRUE;
4355 setString(&artwork_new->identifier, top_node_name);
4356 setString(&artwork_new->name, top_node_name);
4357 setString(&artwork_new->name_sorting, top_node_name);
4359 // create node to link back to current custom artwork directory
4360 createParentTreeInfoNode(artwork_new);
4362 // move existing custom artwork tree into newly created sub-tree
4363 artwork_new->node_group->next = *artwork_node;
4365 // change custom artwork tree to contain only newly created node
4366 *artwork_node = artwork_new;
4369 static void LoadArtworkInfoFromLevelInfoExt(ArtworkDirTree **artwork_node,
4370 ArtworkDirTree *node_parent,
4371 LevelDirTree *level_node,
4372 boolean empty_level_set_mode)
4374 int type = (*artwork_node)->type;
4376 // recursively check all level directories for artwork sub-directories
4380 boolean empty_level_set = (level_node->levels == 0);
4382 // check all tree entries for artwork, but skip parent link entries
4383 if (!level_node->parent_link && empty_level_set == empty_level_set_mode)
4385 TreeInfo *artwork_new = getArtworkInfoCacheEntry(level_node, type);
4386 boolean cached = (artwork_new != NULL);
4390 pushTreeInfo(artwork_node, artwork_new);
4394 TreeInfo *topnode_last = *artwork_node;
4395 char *path = getPath2(getLevelDirFromTreeInfo(level_node),
4396 ARTWORK_DIRECTORY(type));
4398 LoadArtworkInfoFromArtworkDir(artwork_node, NULL, path, type);
4400 if (topnode_last != *artwork_node) // check for newly added node
4402 artwork_new = *artwork_node;
4404 setString(&artwork_new->identifier, level_node->subdir);
4405 setString(&artwork_new->name, level_node->name);
4406 setString(&artwork_new->name_sorting, level_node->name_sorting);
4408 artwork_new->sort_priority = level_node->sort_priority;
4409 artwork_new->in_user_dir = level_node->in_user_dir;
4411 update_artworkinfo_cache = TRUE;
4417 // insert artwork info (from old cache or filesystem) into new cache
4418 if (artwork_new != NULL)
4419 setArtworkInfoCacheEntry(artwork_new, level_node, type);
4422 DrawInitTextItem(level_node->name);
4424 if (level_node->node_group != NULL)
4426 TreeInfo *artwork_new = newTreeInfo();
4429 setTreeInfoToDefaultsFromParent(artwork_new, node_parent);
4431 setTreeInfoToDefaults(artwork_new, type);
4433 artwork_new->level_group = TRUE;
4435 setString(&artwork_new->identifier, level_node->subdir);
4437 if (node_parent == NULL) // check for top tree node
4439 char *top_node_name = (empty_level_set_mode ?
4440 "artwork for certain level sets" :
4441 "artwork included in level sets");
4443 setString(&artwork_new->name, top_node_name);
4444 setString(&artwork_new->name_sorting, top_node_name);
4448 setString(&artwork_new->name, level_node->name);
4449 setString(&artwork_new->name_sorting, level_node->name_sorting);
4452 pushTreeInfo(artwork_node, artwork_new);
4454 // create node to link back to current custom artwork directory
4455 createParentTreeInfoNode(artwork_new);
4457 // recursively step into sub-directory and look for more custom artwork
4458 LoadArtworkInfoFromLevelInfoExt(&artwork_new->node_group, artwork_new,
4459 level_node->node_group,
4460 empty_level_set_mode);
4462 // if sub-tree has no custom artwork at all, remove it
4463 if (artwork_new->node_group->next == NULL)
4464 removeTreeInfo(artwork_node);
4467 level_node = level_node->next;
4471 static void LoadArtworkInfoFromLevelInfo(ArtworkDirTree **artwork_node)
4473 // move peviously loaded artwork tree into separate sub-tree
4474 MoveArtworkInfoIntoSubTree(artwork_node);
4476 // load artwork from level sets into separate sub-trees
4477 LoadArtworkInfoFromLevelInfoExt(artwork_node, NULL, leveldir_first_all, TRUE);
4478 LoadArtworkInfoFromLevelInfoExt(artwork_node, NULL, leveldir_first_all, FALSE);
4480 // add top tree node over all sub-trees and set parent links
4481 *artwork_node = addTopTreeInfoNode(*artwork_node);
4484 void LoadLevelArtworkInfo(void)
4486 print_timestamp_init("LoadLevelArtworkInfo");
4488 DrawInitTextHead("Looking for custom level artwork");
4490 print_timestamp_time("DrawTimeText");
4492 LoadArtworkInfoFromLevelInfo(&artwork.gfx_first);
4493 print_timestamp_time("LoadArtworkInfoFromLevelInfo (gfx)");
4494 LoadArtworkInfoFromLevelInfo(&artwork.snd_first);
4495 print_timestamp_time("LoadArtworkInfoFromLevelInfo (snd)");
4496 LoadArtworkInfoFromLevelInfo(&artwork.mus_first);
4497 print_timestamp_time("LoadArtworkInfoFromLevelInfo (mus)");
4499 SaveArtworkInfoCache();
4501 print_timestamp_time("SaveArtworkInfoCache");
4503 // needed for reloading level artwork not known at ealier stage
4504 ChangeCurrentArtworkIfNeeded(ARTWORK_TYPE_GRAPHICS);
4505 ChangeCurrentArtworkIfNeeded(ARTWORK_TYPE_SOUNDS);
4506 ChangeCurrentArtworkIfNeeded(ARTWORK_TYPE_MUSIC);
4508 print_timestamp_time("getTreeInfoFromIdentifier");
4510 sortTreeInfo(&artwork.gfx_first);
4511 sortTreeInfo(&artwork.snd_first);
4512 sortTreeInfo(&artwork.mus_first);
4514 print_timestamp_time("sortTreeInfo");
4516 #if ENABLE_UNUSED_CODE
4517 dumpTreeInfo(artwork.gfx_first, 0);
4518 dumpTreeInfo(artwork.snd_first, 0);
4519 dumpTreeInfo(artwork.mus_first, 0);
4522 print_timestamp_done("LoadLevelArtworkInfo");
4525 static boolean AddTreeSetToTreeInfoExt(TreeInfo *tree_node_old, char *tree_dir,
4526 char *tree_subdir_new, int type)
4528 if (tree_node_old == NULL)
4530 if (type == TREE_TYPE_LEVEL_DIR)
4532 // get level info tree node of personal user level set
4533 tree_node_old = getTreeInfoFromIdentifier(leveldir_first, getLoginName());
4535 // this may happen if "setup.internal.create_user_levelset" is FALSE
4536 // or if file "levelinfo.conf" is missing in personal user level set
4537 if (tree_node_old == NULL)
4538 tree_node_old = leveldir_first->node_group;
4542 // get artwork info tree node of first artwork set
4543 tree_node_old = ARTWORK_FIRST_NODE(artwork, type);
4547 if (tree_dir == NULL)
4548 tree_dir = TREE_USERDIR(type);
4550 if (tree_node_old == NULL ||
4552 tree_subdir_new == NULL) // should not happen
4555 int draw_deactivation_mask = GetDrawDeactivationMask();
4557 // override draw deactivation mask (temporarily disable drawing)
4558 SetDrawDeactivationMask(REDRAW_ALL);
4560 if (type == TREE_TYPE_LEVEL_DIR)
4562 // load new level set config and add it next to first user level set
4563 LoadLevelInfoFromLevelConf(&tree_node_old->next,
4564 tree_node_old->node_parent,
4565 tree_dir, tree_subdir_new);
4569 // load new artwork set config and add it next to first artwork set
4570 LoadArtworkInfoFromArtworkConf(&tree_node_old->next,
4571 tree_node_old->node_parent,
4572 tree_dir, tree_subdir_new, type);
4575 // set draw deactivation mask to previous value
4576 SetDrawDeactivationMask(draw_deactivation_mask);
4578 // get first node of level or artwork info tree
4579 TreeInfo **tree_node_first = TREE_FIRST_NODE_PTR(type);
4581 // get tree info node of newly added level or artwork set
4582 TreeInfo *tree_node_new = getTreeInfoFromIdentifier(*tree_node_first,
4585 if (tree_node_new == NULL) // should not happen
4588 // correct top link and parent node link of newly created tree node
4589 tree_node_new->node_top = tree_node_old->node_top;
4590 tree_node_new->node_parent = tree_node_old->node_parent;
4592 // sort tree info to adjust position of newly added tree set
4593 sortTreeInfo(tree_node_first);
4598 void AddTreeSetToTreeInfo(TreeInfo *tree_node, char *tree_dir,
4599 char *tree_subdir_new, int type)
4601 if (!AddTreeSetToTreeInfoExt(tree_node, tree_dir, tree_subdir_new, type))
4602 Fail("internal tree info structure corrupted -- aborting");
4605 void AddUserLevelSetToLevelInfo(char *level_subdir_new)
4607 AddTreeSetToTreeInfo(NULL, NULL, level_subdir_new, TREE_TYPE_LEVEL_DIR);
4610 char *getArtworkIdentifierForUserLevelSet(int type)
4612 char *classic_artwork_set = getClassicArtworkSet(type);
4614 // check for custom artwork configured in "levelinfo.conf"
4615 char *leveldir_artwork_set =
4616 *LEVELDIR_ARTWORK_SET_PTR(leveldir_current, type);
4617 boolean has_leveldir_artwork_set =
4618 (leveldir_artwork_set != NULL && !strEqual(leveldir_artwork_set,
4619 classic_artwork_set));
4621 // check for custom artwork in sub-directory "graphics" etc.
4622 TreeInfo *artwork_first_node = ARTWORK_FIRST_NODE(artwork, type);
4623 char *leveldir_identifier = leveldir_current->identifier;
4624 boolean has_artwork_subdir =
4625 (getTreeInfoFromIdentifier(artwork_first_node,
4626 leveldir_identifier) != NULL);
4628 return (has_leveldir_artwork_set ? leveldir_artwork_set :
4629 has_artwork_subdir ? leveldir_identifier :
4630 classic_artwork_set);
4633 TreeInfo *getArtworkTreeInfoForUserLevelSet(int type)
4635 char *artwork_set = getArtworkIdentifierForUserLevelSet(type);
4636 TreeInfo *artwork_first_node = ARTWORK_FIRST_NODE(artwork, type);
4637 TreeInfo *ti = getTreeInfoFromIdentifier(artwork_first_node, artwork_set);
4641 ti = getTreeInfoFromIdentifier(artwork_first_node,
4642 ARTWORK_DEFAULT_SUBDIR(type));
4644 Fail("cannot find default graphics -- should not happen");
4650 boolean checkIfCustomArtworkExistsForCurrentLevelSet(void)
4652 char *graphics_set =
4653 getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_GRAPHICS);
4655 getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_SOUNDS);
4657 getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_MUSIC);
4659 return (!strEqual(graphics_set, GFX_CLASSIC_SUBDIR) ||
4660 !strEqual(sounds_set, SND_CLASSIC_SUBDIR) ||
4661 !strEqual(music_set, MUS_CLASSIC_SUBDIR));
4664 boolean UpdateUserLevelSet(char *level_subdir, char *level_name,
4665 char *level_author, int num_levels)
4667 char *filename = getPath2(getUserLevelDir(level_subdir), LEVELINFO_FILENAME);
4668 char *filename_tmp = getStringCat2(filename, ".tmp");
4670 FILE *file_tmp = NULL;
4671 char line[MAX_LINE_LEN];
4672 boolean success = FALSE;
4673 LevelDirTree *leveldir = getTreeInfoFromIdentifier(leveldir_first,
4675 // update values in level directory tree
4677 if (level_name != NULL)
4678 setString(&leveldir->name, level_name);
4680 if (level_author != NULL)
4681 setString(&leveldir->author, level_author);
4683 if (num_levels != -1)
4684 leveldir->levels = num_levels;
4686 // update values that depend on other values
4688 setString(&leveldir->name_sorting, leveldir->name);
4690 leveldir->last_level = leveldir->first_level + leveldir->levels - 1;
4692 // sort order of level sets may have changed
4693 sortTreeInfo(&leveldir_first);
4695 if ((file = fopen(filename, MODE_READ)) &&
4696 (file_tmp = fopen(filename_tmp, MODE_WRITE)))
4698 while (fgets(line, MAX_LINE_LEN, file))
4700 if (strPrefix(line, "name:") && level_name != NULL)
4701 fprintf(file_tmp, "%-32s%s\n", "name:", level_name);
4702 else if (strPrefix(line, "author:") && level_author != NULL)
4703 fprintf(file_tmp, "%-32s%s\n", "author:", level_author);
4704 else if (strPrefix(line, "levels:") && num_levels != -1)
4705 fprintf(file_tmp, "%-32s%d\n", "levels:", num_levels);
4707 fputs(line, file_tmp);
4720 success = (rename(filename_tmp, filename) == 0);
4728 boolean CreateUserLevelSet(char *level_subdir, char *level_name,
4729 char *level_author, int num_levels,
4730 boolean use_artwork_set)
4732 LevelDirTree *level_info;
4737 // create user level sub-directory, if needed
4738 createDirectory(getUserLevelDir(level_subdir), "user level");
4740 filename = getPath2(getUserLevelDir(level_subdir), LEVELINFO_FILENAME);
4742 if (!(file = fopen(filename, MODE_WRITE)))
4744 Warn("cannot write level info file '%s'", filename);
4751 level_info = newTreeInfo();
4753 // always start with reliable default values
4754 setTreeInfoToDefaults(level_info, TREE_TYPE_LEVEL_DIR);
4756 setString(&level_info->name, level_name);
4757 setString(&level_info->author, level_author);
4758 level_info->levels = num_levels;
4759 level_info->first_level = 1;
4760 level_info->sort_priority = LEVELCLASS_PRIVATE_START;
4761 level_info->readonly = FALSE;
4763 if (use_artwork_set)
4765 level_info->graphics_set =
4766 getStringCopy(getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_GRAPHICS));
4767 level_info->sounds_set =
4768 getStringCopy(getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_SOUNDS));
4769 level_info->music_set =
4770 getStringCopy(getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_MUSIC));
4773 token_value_position = TOKEN_VALUE_POSITION_SHORT;
4775 fprintFileHeader(file, LEVELINFO_FILENAME);
4778 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
4780 if (i == LEVELINFO_TOKEN_NAME ||
4781 i == LEVELINFO_TOKEN_AUTHOR ||
4782 i == LEVELINFO_TOKEN_LEVELS ||
4783 i == LEVELINFO_TOKEN_FIRST_LEVEL ||
4784 i == LEVELINFO_TOKEN_SORT_PRIORITY ||
4785 i == LEVELINFO_TOKEN_READONLY ||
4786 (use_artwork_set && (i == LEVELINFO_TOKEN_GRAPHICS_SET ||
4787 i == LEVELINFO_TOKEN_SOUNDS_SET ||
4788 i == LEVELINFO_TOKEN_MUSIC_SET)))
4789 fprintf(file, "%s\n", getSetupLine(levelinfo_tokens, "", i));
4791 // just to make things nicer :)
4792 if (i == LEVELINFO_TOKEN_AUTHOR ||
4793 i == LEVELINFO_TOKEN_FIRST_LEVEL ||
4794 (use_artwork_set && i == LEVELINFO_TOKEN_READONLY))
4795 fprintf(file, "\n");
4798 token_value_position = TOKEN_VALUE_POSITION_DEFAULT;
4802 SetFilePermissions(filename, PERMS_PRIVATE);
4804 freeTreeInfo(level_info);
4810 static void SaveUserLevelInfo(void)
4812 CreateUserLevelSet(getLoginName(), getLoginName(), getRealName(), 100, FALSE);
4815 char *getSetupValue(int type, void *value)
4817 static char value_string[MAX_LINE_LEN];
4825 strcpy(value_string, (*(boolean *)value ? "true" : "false"));
4829 strcpy(value_string, (*(boolean *)value ? "on" : "off"));
4833 strcpy(value_string, (*(int *)value == AUTO ? "auto" :
4834 *(int *)value == FALSE ? "off" : "on"));
4838 strcpy(value_string, (*(boolean *)value ? "yes" : "no"));
4841 case TYPE_YES_NO_AUTO:
4842 strcpy(value_string, (*(int *)value == AUTO ? "auto" :
4843 *(int *)value == FALSE ? "no" : "yes"));
4847 strcpy(value_string, (*(boolean *)value ? "AGA" : "ECS"));
4851 strcpy(value_string, getKeyNameFromKey(*(Key *)value));
4855 strcpy(value_string, getX11KeyNameFromKey(*(Key *)value));
4859 sprintf(value_string, "%d", *(int *)value);
4863 if (*(char **)value == NULL)
4866 strcpy(value_string, *(char **)value);
4870 sprintf(value_string, "player_%d", *(int *)value + 1);
4874 value_string[0] = '\0';
4878 if (type & TYPE_GHOSTED)
4879 strcpy(value_string, "n/a");
4881 return value_string;
4884 char *getSetupLine(struct TokenInfo *token_info, char *prefix, int token_nr)
4888 static char token_string[MAX_LINE_LEN];
4889 int token_type = token_info[token_nr].type;
4890 void *setup_value = token_info[token_nr].value;
4891 char *token_text = token_info[token_nr].text;
4892 char *value_string = getSetupValue(token_type, setup_value);
4894 // build complete token string
4895 sprintf(token_string, "%s%s", prefix, token_text);
4897 // build setup entry line
4898 line = getFormattedSetupEntry(token_string, value_string);
4900 if (token_type == TYPE_KEY_X11)
4902 Key key = *(Key *)setup_value;
4903 char *keyname = getKeyNameFromKey(key);
4905 // add comment, if useful
4906 if (!strEqual(keyname, "(undefined)") &&
4907 !strEqual(keyname, "(unknown)"))
4909 // add at least one whitespace
4911 for (i = strlen(line); i < token_comment_position; i++)
4915 strcat(line, keyname);
4922 static void InitLastPlayedLevels_ParentNode(void)
4924 LevelDirTree **leveldir_top = &leveldir_first->node_group->next;
4925 LevelDirTree *leveldir_new = NULL;
4927 // check if parent node for last played levels already exists
4928 if (strEqual((*leveldir_top)->identifier, TOKEN_STR_LAST_LEVEL_SERIES))
4931 leveldir_new = newTreeInfo();
4933 setTreeInfoToDefaultsFromParent(leveldir_new, leveldir_first);
4935 leveldir_new->level_group = TRUE;
4936 leveldir_new->sort_priority = LEVELCLASS_LAST_PLAYED_LEVEL;
4938 setString(&leveldir_new->identifier, TOKEN_STR_LAST_LEVEL_SERIES);
4939 setString(&leveldir_new->name, "<< (last played level sets)");
4940 setString(&leveldir_new->name_sorting, leveldir_new->name);
4942 pushTreeInfo(leveldir_top, leveldir_new);
4944 // create node to link back to current level directory
4945 createParentTreeInfoNode(leveldir_new);
4948 void UpdateLastPlayedLevels_TreeInfo(void)
4950 char **last_level_series = setup.level_setup.last_level_series;
4951 LevelDirTree *leveldir_last;
4952 TreeInfo **node_new = NULL;
4955 if (last_level_series[0] == NULL)
4958 InitLastPlayedLevels_ParentNode();
4960 leveldir_last = getTreeInfoFromIdentifierExt(leveldir_first,
4961 TOKEN_STR_LAST_LEVEL_SERIES,
4962 TREE_NODE_TYPE_GROUP);
4963 if (leveldir_last == NULL)
4966 node_new = &leveldir_last->node_group->next;
4968 freeTreeInfo(*node_new);
4972 for (i = 0; last_level_series[i] != NULL; i++)
4974 LevelDirTree *node_last = getTreeInfoFromIdentifier(leveldir_first,
4975 last_level_series[i]);
4976 if (node_last == NULL)
4979 *node_new = getTreeInfoCopy(node_last); // copy complete node
4981 (*node_new)->node_top = &leveldir_first; // correct top node link
4982 (*node_new)->node_parent = leveldir_last; // correct parent node link
4984 (*node_new)->is_copy = TRUE; // mark entry as node copy
4986 (*node_new)->node_group = NULL;
4987 (*node_new)->next = NULL;
4989 (*node_new)->cl_first = -1; // force setting tree cursor
4991 node_new = &((*node_new)->next);
4995 static void UpdateLastPlayedLevels_List(void)
4997 char **last_level_series = setup.level_setup.last_level_series;
4998 int pos = MAX_LEVELDIR_HISTORY - 1;
5001 // search for potentially already existing entry in list of level sets
5002 for (i = 0; last_level_series[i] != NULL; i++)
5003 if (strEqual(last_level_series[i], leveldir_current->identifier))
5006 // move list of level sets one entry down (using potentially free entry)
5007 for (i = pos; i > 0; i--)
5008 setString(&last_level_series[i], last_level_series[i - 1]);
5010 // put last played level set at top position
5011 setString(&last_level_series[0], leveldir_current->identifier);
5014 #define LAST_PLAYED_MODE_SET 1
5015 #define LAST_PLAYED_MODE_SET_FORCED 2
5016 #define LAST_PLAYED_MODE_GET 3
5018 static TreeInfo *StoreOrRestoreLastPlayedLevels(TreeInfo *node, int mode)
5020 static char *identifier = NULL;
5022 if (mode == LAST_PLAYED_MODE_SET)
5024 setString(&identifier, (node && node->is_copy ? node->identifier : NULL));
5026 else if (mode == LAST_PLAYED_MODE_SET_FORCED)
5028 setString(&identifier, (node ? node->identifier : NULL));
5030 else if (mode == LAST_PLAYED_MODE_GET)
5032 TreeInfo *node_new = getTreeInfoFromIdentifierExt(leveldir_first,
5034 TREE_NODE_TYPE_COPY);
5035 return (node_new != NULL ? node_new : node);
5038 return NULL; // not used
5041 void StoreLastPlayedLevels(TreeInfo *node)
5043 StoreOrRestoreLastPlayedLevels(node, LAST_PLAYED_MODE_SET);
5046 void ForcedStoreLastPlayedLevels(TreeInfo *node)
5048 StoreOrRestoreLastPlayedLevels(node, LAST_PLAYED_MODE_SET_FORCED);
5051 void RestoreLastPlayedLevels(TreeInfo **node)
5053 *node = StoreOrRestoreLastPlayedLevels(*node, LAST_PLAYED_MODE_GET);
5056 boolean CheckLastPlayedLevels(void)
5058 return (StoreOrRestoreLastPlayedLevels(NULL, LAST_PLAYED_MODE_GET) != NULL);
5061 void LoadLevelSetup_LastSeries(void)
5063 // --------------------------------------------------------------------------
5064 // ~/.<program>/levelsetup.conf
5065 // --------------------------------------------------------------------------
5067 char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
5068 SetupFileHash *level_setup_hash = NULL;
5072 // always start with reliable default values
5073 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
5075 // start with empty history of last played level sets
5076 setString(&setup.level_setup.last_level_series[0], NULL);
5078 if (!strEqual(DEFAULT_LEVELSET, UNDEFINED_LEVELSET))
5080 leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
5082 if (leveldir_current == NULL)
5083 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
5086 if ((level_setup_hash = loadSetupFileHash(filename)))
5088 char *last_level_series =
5089 getHashEntry(level_setup_hash, TOKEN_STR_LAST_LEVEL_SERIES);
5091 leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
5093 if (leveldir_current == NULL)
5094 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
5096 char *last_played_menu_used =
5097 getHashEntry(level_setup_hash, TOKEN_STR_LAST_PLAYED_MENU_USED);
5099 // store if last level set was selected from "last played" menu
5100 if (strEqual(last_played_menu_used, "true"))
5101 ForcedStoreLastPlayedLevels(leveldir_current);
5103 for (i = 0; i < MAX_LEVELDIR_HISTORY; i++)
5105 char token[strlen(TOKEN_STR_LAST_LEVEL_SERIES) + 10];
5106 LevelDirTree *leveldir_last;
5108 sprintf(token, "%s.%03d", TOKEN_STR_LAST_LEVEL_SERIES, i);
5110 last_level_series = getHashEntry(level_setup_hash, token);
5112 leveldir_last = getTreeInfoFromIdentifier(leveldir_first,
5114 if (leveldir_last != NULL)
5115 setString(&setup.level_setup.last_level_series[pos++],
5119 setString(&setup.level_setup.last_level_series[pos], NULL);
5121 freeSetupFileHash(level_setup_hash);
5125 Debug("setup", "using default setup values");
5131 static void SaveLevelSetup_LastSeries_Ext(boolean deactivate_last_level_series)
5133 // --------------------------------------------------------------------------
5134 // ~/.<program>/levelsetup.conf
5135 // --------------------------------------------------------------------------
5137 // check if the current level directory structure is available at this point
5138 if (leveldir_current == NULL)
5141 char **last_level_series = setup.level_setup.last_level_series;
5142 char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
5146 InitUserDataDirectory();
5148 UpdateLastPlayedLevels_List();
5150 if (!(file = fopen(filename, MODE_WRITE)))
5152 Warn("cannot write setup file '%s'", filename);
5159 fprintFileHeader(file, LEVELSETUP_FILENAME);
5161 if (deactivate_last_level_series)
5162 fprintf(file, "# %s\n# ", "the following level set may have caused a problem and was deactivated");
5164 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_LAST_LEVEL_SERIES,
5165 leveldir_current->identifier));
5167 // store if last level set was selected from "last played" menu
5168 boolean last_played_menu_used = CheckLastPlayedLevels();
5169 char *setup_value = getSetupValue(TYPE_BOOLEAN, &last_played_menu_used);
5171 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_LAST_PLAYED_MENU_USED,
5174 for (i = 0; last_level_series[i] != NULL; i++)
5176 char token[strlen(TOKEN_STR_LAST_LEVEL_SERIES) + 1 + 10 + 1];
5178 sprintf(token, "%s.%03d", TOKEN_STR_LAST_LEVEL_SERIES, i);
5180 fprintf(file, "%s\n", getFormattedSetupEntry(token, last_level_series[i]));
5185 SetFilePermissions(filename, PERMS_PRIVATE);
5190 void SaveLevelSetup_LastSeries(void)
5192 SaveLevelSetup_LastSeries_Ext(FALSE);
5195 void SaveLevelSetup_LastSeries_Deactivate(void)
5197 SaveLevelSetup_LastSeries_Ext(TRUE);
5200 static void checkSeriesInfo(void)
5202 static char *level_directory = NULL;
5205 DirectoryEntry *dir_entry;
5208 checked_free(level_directory);
5210 // check for more levels besides the 'levels' field of 'levelinfo.conf'
5212 level_directory = getPath2((leveldir_current->in_user_dir ?
5213 getUserLevelDir(NULL) :
5214 options.level_directory),
5215 leveldir_current->fullpath);
5217 if ((dir = openDirectory(level_directory)) == NULL)
5219 Warn("cannot read level directory '%s'", level_directory);
5225 while ((dir_entry = readDirectory(dir)) != NULL) // loop all entries
5227 if (strlen(dir_entry->basename) > 4 &&
5228 dir_entry->basename[3] == '.' &&
5229 strEqual(&dir_entry->basename[4], LEVELFILE_EXTENSION))
5231 char levelnum_str[4];
5234 strncpy(levelnum_str, dir_entry->basename, 3);
5235 levelnum_str[3] = '\0';
5237 levelnum_value = atoi(levelnum_str);
5239 if (levelnum_value < leveldir_current->first_level)
5241 Warn("additional level %d found", levelnum_value);
5243 leveldir_current->first_level = levelnum_value;
5245 else if (levelnum_value > leveldir_current->last_level)
5247 Warn("additional level %d found", levelnum_value);
5249 leveldir_current->last_level = levelnum_value;
5255 closeDirectory(dir);
5258 void LoadLevelSetup_SeriesInfo(void)
5261 SetupFileHash *level_setup_hash = NULL;
5262 char *level_subdir = leveldir_current->subdir;
5265 // always start with reliable default values
5266 level_nr = leveldir_current->first_level;
5268 for (i = 0; i < MAX_LEVELS; i++)
5270 LevelStats_setPlayed(i, 0);
5271 LevelStats_setSolved(i, 0);
5276 // --------------------------------------------------------------------------
5277 // ~/.<program>/levelsetup/<level series>/levelsetup.conf
5278 // --------------------------------------------------------------------------
5280 level_subdir = leveldir_current->subdir;
5282 filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
5284 if ((level_setup_hash = loadSetupFileHash(filename)))
5288 // get last played level in this level set
5290 token_value = getHashEntry(level_setup_hash, TOKEN_STR_LAST_PLAYED_LEVEL);
5294 level_nr = atoi(token_value);
5296 if (level_nr < leveldir_current->first_level)
5297 level_nr = leveldir_current->first_level;
5298 if (level_nr > leveldir_current->last_level)
5299 level_nr = leveldir_current->last_level;
5302 // get handicap level in this level set
5304 token_value = getHashEntry(level_setup_hash, TOKEN_STR_HANDICAP_LEVEL);
5308 int level_nr = atoi(token_value);
5310 if (level_nr < leveldir_current->first_level)
5311 level_nr = leveldir_current->first_level;
5312 if (level_nr > leveldir_current->last_level + 1)
5313 level_nr = leveldir_current->last_level;
5315 if (leveldir_current->user_defined || !leveldir_current->handicap)
5316 level_nr = leveldir_current->last_level;
5318 leveldir_current->handicap_level = level_nr;
5321 // get number of played and solved levels in this level set
5323 BEGIN_HASH_ITERATION(level_setup_hash, itr)
5325 char *token = HASH_ITERATION_TOKEN(itr);
5326 char *value = HASH_ITERATION_VALUE(itr);
5328 if (strlen(token) == 3 &&
5329 token[0] >= '0' && token[0] <= '9' &&
5330 token[1] >= '0' && token[1] <= '9' &&
5331 token[2] >= '0' && token[2] <= '9')
5333 int level_nr = atoi(token);
5336 LevelStats_setPlayed(level_nr, atoi(value)); // read 1st column
5338 value = strchr(value, ' ');
5341 LevelStats_setSolved(level_nr, atoi(value)); // read 2nd column
5344 END_HASH_ITERATION(hash, itr)
5346 freeSetupFileHash(level_setup_hash);
5350 Debug("setup", "using default setup values");
5356 void SaveLevelSetup_SeriesInfo(void)
5359 char *level_subdir = leveldir_current->subdir;
5360 char *level_nr_str = int2str(level_nr, 0);
5361 char *handicap_level_str = int2str(leveldir_current->handicap_level, 0);
5365 // --------------------------------------------------------------------------
5366 // ~/.<program>/levelsetup/<level series>/levelsetup.conf
5367 // --------------------------------------------------------------------------
5369 InitLevelSetupDirectory(level_subdir);
5371 filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
5373 if (!(file = fopen(filename, MODE_WRITE)))
5375 Warn("cannot write setup file '%s'", filename);
5382 fprintFileHeader(file, LEVELSETUP_FILENAME);
5384 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_PLAYED_LEVEL,
5386 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_HANDICAP_LEVEL,
5387 handicap_level_str));
5389 for (i = leveldir_current->first_level; i <= leveldir_current->last_level;
5392 if (LevelStats_getPlayed(i) > 0 ||
5393 LevelStats_getSolved(i) > 0)
5398 sprintf(token, "%03d", i);
5399 sprintf(value, "%d %d", LevelStats_getPlayed(i), LevelStats_getSolved(i));
5401 fprintf(file, "%s\n", getFormattedSetupEntry(token, value));
5407 SetFilePermissions(filename, PERMS_PRIVATE);
5412 int LevelStats_getPlayed(int nr)
5414 return (nr >= 0 && nr < MAX_LEVELS ? level_stats[nr].played : 0);
5417 int LevelStats_getSolved(int nr)
5419 return (nr >= 0 && nr < MAX_LEVELS ? level_stats[nr].solved : 0);
5422 void LevelStats_setPlayed(int nr, int value)
5424 if (nr >= 0 && nr < MAX_LEVELS)
5425 level_stats[nr].played = value;
5428 void LevelStats_setSolved(int nr, int value)
5430 if (nr >= 0 && nr < MAX_LEVELS)
5431 level_stats[nr].solved = value;
5434 void LevelStats_incPlayed(int nr)
5436 if (nr >= 0 && nr < MAX_LEVELS)
5437 level_stats[nr].played++;
5440 void LevelStats_incSolved(int nr)
5442 if (nr >= 0 && nr < MAX_LEVELS)
5443 level_stats[nr].solved++;
5446 void LoadUserSetup(void)
5448 // --------------------------------------------------------------------------
5449 // ~/.<program>/usersetup.conf
5450 // --------------------------------------------------------------------------
5452 char *filename = getPath2(getMainUserGameDataDir(), USERSETUP_FILENAME);
5453 SetupFileHash *user_setup_hash = NULL;
5455 // always start with reliable default values
5458 if ((user_setup_hash = loadSetupFileHash(filename)))
5462 // get last selected user number
5463 token_value = getHashEntry(user_setup_hash, TOKEN_STR_LAST_USER);
5466 user.nr = MIN(MAX(0, atoi(token_value)), MAX_PLAYER_NAMES - 1);
5468 freeSetupFileHash(user_setup_hash);
5472 Debug("setup", "using default setup values");
5478 void SaveUserSetup(void)
5480 // --------------------------------------------------------------------------
5481 // ~/.<program>/usersetup.conf
5482 // --------------------------------------------------------------------------
5484 char *filename = getPath2(getMainUserGameDataDir(), USERSETUP_FILENAME);
5487 InitMainUserDataDirectory();
5489 if (!(file = fopen(filename, MODE_WRITE)))
5491 Warn("cannot write setup file '%s'", filename);
5498 fprintFileHeader(file, USERSETUP_FILENAME);
5500 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_USER,
5504 SetFilePermissions(filename, PERMS_PRIVATE);