1 // ============================================================================
2 // Artsoft Retro-Game Library
3 // ----------------------------------------------------------------------------
4 // (c) 1995-2014 by Artsoft Entertainment
7 // https://www.artsoft.org/
8 // ----------------------------------------------------------------------------
10 // ============================================================================
12 #include <sys/types.h>
26 #include "zip/miniunz.h"
29 #define ENABLE_UNUSED_CODE FALSE // for currently unused functions
30 #define DEBUG_NO_CONFIG_FILE FALSE // for extra-verbose debug output
32 #define NUM_LEVELCLASS_DESC 8
34 static char *levelclass_desc[NUM_LEVELCLASS_DESC] =
46 #define TOKEN_VALUE_POSITION_SHORT 32
47 #define TOKEN_VALUE_POSITION_DEFAULT 40
48 #define TOKEN_COMMENT_POSITION_DEFAULT 60
50 #define TREE_NODE_TYPE_DEFAULT 0
51 #define TREE_NODE_TYPE_PARENT 1
52 #define TREE_NODE_TYPE_GROUP 2
53 #define TREE_NODE_TYPE_COPY 3
55 #define TREE_NODE_TYPE(ti) (ti->node_group ? TREE_NODE_TYPE_GROUP : \
56 ti->parent_link ? TREE_NODE_TYPE_PARENT : \
57 ti->is_copy ? TREE_NODE_TYPE_COPY : \
58 TREE_NODE_TYPE_DEFAULT)
61 static void setTreeInfoToDefaults(TreeInfo *, int);
62 static TreeInfo *getTreeInfoCopy(TreeInfo *ti);
63 static int compareTreeInfoEntries(const void *, const void *);
65 static int token_value_position = TOKEN_VALUE_POSITION_DEFAULT;
66 static int token_comment_position = TOKEN_COMMENT_POSITION_DEFAULT;
68 static SetupFileHash *artworkinfo_cache_old = NULL;
69 static SetupFileHash *artworkinfo_cache_new = NULL;
70 static SetupFileHash *optional_tokens_hash = NULL;
71 static SetupFileHash *missing_file_hash = NULL;
72 static boolean use_artworkinfo_cache = TRUE;
73 static boolean update_artworkinfo_cache = FALSE;
76 // ----------------------------------------------------------------------------
78 // ----------------------------------------------------------------------------
80 static void WarnUsingFallback(char *filename)
82 if (getHashEntry(missing_file_hash, filename) == NULL)
84 setHashEntry(missing_file_hash, filename, "");
86 Debug("setup", "cannot find artwork file '%s' (using fallback)", filename);
90 static char *getLevelClassDescription(TreeInfo *ti)
92 int position = ti->sort_priority / 100;
94 if (position >= 0 && position < NUM_LEVELCLASS_DESC)
95 return levelclass_desc[position];
97 return "Unknown Level Class";
100 static char *getCacheDir(void)
102 static char *cache_dir = NULL;
104 if (cache_dir == NULL)
105 cache_dir = getPath2(getMainUserGameDataDir(), CACHE_DIRECTORY);
110 static char *getScoreDir(char *level_subdir)
112 static char *score_dir = NULL;
113 static char *score_level_dir = NULL;
114 char *score_subdir = SCORES_DIRECTORY;
116 if (score_dir == NULL)
117 score_dir = getPath2(getMainUserGameDataDir(), score_subdir);
119 if (level_subdir != NULL)
121 checked_free(score_level_dir);
123 score_level_dir = getPath2(score_dir, level_subdir);
125 return score_level_dir;
131 static char *getScoreCacheDir(char *level_subdir)
133 static char *score_dir = NULL;
134 static char *score_level_dir = NULL;
135 char *score_subdir = SCORES_DIRECTORY;
137 if (score_dir == NULL)
138 score_dir = getPath2(getCacheDir(), score_subdir);
140 if (level_subdir != NULL)
142 checked_free(score_level_dir);
144 score_level_dir = getPath2(score_dir, level_subdir);
146 return score_level_dir;
152 static char *getScoreTapeDir(char *level_subdir, int nr)
154 static char *score_tape_dir = NULL;
155 char tape_subdir[MAX_FILENAME_LEN];
157 checked_free(score_tape_dir);
159 sprintf(tape_subdir, "%03d", nr);
160 score_tape_dir = getPath2(getScoreDir(level_subdir), tape_subdir);
162 return score_tape_dir;
165 static char *getScoreCacheTapeDir(char *level_subdir, int nr)
167 static char *score_cache_tape_dir = NULL;
168 char tape_subdir[MAX_FILENAME_LEN];
170 checked_free(score_cache_tape_dir);
172 sprintf(tape_subdir, "%03d", nr);
173 score_cache_tape_dir = getPath2(getScoreCacheDir(level_subdir), tape_subdir);
175 return score_cache_tape_dir;
178 static char *getUserSubdir(int nr)
180 static char user_subdir[16] = { 0 };
182 sprintf(user_subdir, "%03d", nr);
187 static char *getUserDir(int nr)
189 static char *user_dir = NULL;
190 char *main_data_dir = getMainUserGameDataDir();
191 char *users_subdir = USERS_DIRECTORY;
192 char *user_subdir = getUserSubdir(nr);
194 checked_free(user_dir);
197 user_dir = getPath3(main_data_dir, users_subdir, user_subdir);
199 user_dir = getPath2(main_data_dir, users_subdir);
204 static char *getLevelSetupDir(char *level_subdir)
206 static char *levelsetup_dir = NULL;
207 char *data_dir = getUserGameDataDir();
208 char *levelsetup_subdir = LEVELSETUP_DIRECTORY;
210 checked_free(levelsetup_dir);
212 if (level_subdir != NULL)
213 levelsetup_dir = getPath3(data_dir, levelsetup_subdir, level_subdir);
215 levelsetup_dir = getPath2(data_dir, levelsetup_subdir);
217 return levelsetup_dir;
220 static char *getNetworkDir(void)
222 static char *network_dir = NULL;
224 if (network_dir == NULL)
225 network_dir = getPath2(getMainUserGameDataDir(), NETWORK_DIRECTORY);
230 char *getLevelDirFromTreeInfo(TreeInfo *node)
232 static char *level_dir = NULL;
235 return options.level_directory;
237 checked_free(level_dir);
239 level_dir = getPath2((node->in_user_dir ? getUserLevelDir(NULL) :
240 options.level_directory), node->fullpath);
245 char *getUserLevelDir(char *level_subdir)
247 static char *userlevel_dir = NULL;
248 char *data_dir = getMainUserGameDataDir();
249 char *userlevel_subdir = LEVELS_DIRECTORY;
251 checked_free(userlevel_dir);
253 if (level_subdir != NULL)
254 userlevel_dir = getPath3(data_dir, userlevel_subdir, level_subdir);
256 userlevel_dir = getPath2(data_dir, userlevel_subdir);
258 return userlevel_dir;
261 char *getNetworkLevelDir(char *level_subdir)
263 static char *network_level_dir = NULL;
264 char *data_dir = getNetworkDir();
265 char *networklevel_subdir = LEVELS_DIRECTORY;
267 checked_free(network_level_dir);
269 if (level_subdir != NULL)
270 network_level_dir = getPath3(data_dir, networklevel_subdir, level_subdir);
272 network_level_dir = getPath2(data_dir, networklevel_subdir);
274 return network_level_dir;
277 char *getCurrentLevelDir(void)
279 return getLevelDirFromTreeInfo(leveldir_current);
282 char *getNewUserLevelSubdir(void)
284 static char *new_level_subdir = NULL;
285 char *subdir_prefix = getLoginName();
286 char subdir_suffix[10];
287 int max_suffix_number = 1000;
290 while (++i < max_suffix_number)
292 sprintf(subdir_suffix, "_%d", i);
294 checked_free(new_level_subdir);
295 new_level_subdir = getStringCat2(subdir_prefix, subdir_suffix);
297 if (!directoryExists(getUserLevelDir(new_level_subdir)))
301 return new_level_subdir;
304 char *getTapeDir(char *level_subdir)
306 static char *tape_dir = NULL;
307 char *data_dir = getUserGameDataDir();
308 char *tape_subdir = TAPES_DIRECTORY;
310 checked_free(tape_dir);
312 if (level_subdir != NULL)
313 tape_dir = getPath3(data_dir, tape_subdir, level_subdir);
315 tape_dir = getPath2(data_dir, tape_subdir);
320 static char *getSolutionTapeDir(void)
322 static char *tape_dir = NULL;
323 char *data_dir = getCurrentLevelDir();
324 char *tape_subdir = TAPES_DIRECTORY;
326 checked_free(tape_dir);
328 tape_dir = getPath2(data_dir, tape_subdir);
333 static char *getDefaultGraphicsDir(char *graphics_subdir)
335 static char *graphics_dir = NULL;
337 if (graphics_subdir == NULL)
338 return options.graphics_directory;
340 checked_free(graphics_dir);
342 graphics_dir = getPath2(options.graphics_directory, graphics_subdir);
347 static char *getDefaultSoundsDir(char *sounds_subdir)
349 static char *sounds_dir = NULL;
351 if (sounds_subdir == NULL)
352 return options.sounds_directory;
354 checked_free(sounds_dir);
356 sounds_dir = getPath2(options.sounds_directory, sounds_subdir);
361 static char *getDefaultMusicDir(char *music_subdir)
363 static char *music_dir = NULL;
365 if (music_subdir == NULL)
366 return options.music_directory;
368 checked_free(music_dir);
370 music_dir = getPath2(options.music_directory, music_subdir);
375 static char *getClassicArtworkSet(int type)
377 return (type == TREE_TYPE_GRAPHICS_DIR ? GFX_CLASSIC_SUBDIR :
378 type == TREE_TYPE_SOUNDS_DIR ? SND_CLASSIC_SUBDIR :
379 type == TREE_TYPE_MUSIC_DIR ? MUS_CLASSIC_SUBDIR : "");
382 static char *getClassicArtworkDir(int type)
384 return (type == TREE_TYPE_GRAPHICS_DIR ?
385 getDefaultGraphicsDir(GFX_CLASSIC_SUBDIR) :
386 type == TREE_TYPE_SOUNDS_DIR ?
387 getDefaultSoundsDir(SND_CLASSIC_SUBDIR) :
388 type == TREE_TYPE_MUSIC_DIR ?
389 getDefaultMusicDir(MUS_CLASSIC_SUBDIR) : "");
392 char *getUserGraphicsDir(void)
394 static char *usergraphics_dir = NULL;
396 if (usergraphics_dir == NULL)
397 usergraphics_dir = getPath2(getMainUserGameDataDir(), GRAPHICS_DIRECTORY);
399 return usergraphics_dir;
402 char *getUserSoundsDir(void)
404 static char *usersounds_dir = NULL;
406 if (usersounds_dir == NULL)
407 usersounds_dir = getPath2(getMainUserGameDataDir(), SOUNDS_DIRECTORY);
409 return usersounds_dir;
412 char *getUserMusicDir(void)
414 static char *usermusic_dir = NULL;
416 if (usermusic_dir == NULL)
417 usermusic_dir = getPath2(getMainUserGameDataDir(), MUSIC_DIRECTORY);
419 return usermusic_dir;
422 static char *getSetupArtworkDir(TreeInfo *ti)
424 static char *artwork_dir = NULL;
429 checked_free(artwork_dir);
431 artwork_dir = getPath2(ti->basepath, ti->fullpath);
436 char *setLevelArtworkDir(TreeInfo *ti)
438 char **artwork_path_ptr, **artwork_set_ptr;
439 TreeInfo *level_artwork;
441 if (ti == NULL || leveldir_current == NULL)
444 artwork_path_ptr = LEVELDIR_ARTWORK_PATH_PTR(leveldir_current, ti->type);
445 artwork_set_ptr = LEVELDIR_ARTWORK_SET_PTR( leveldir_current, ti->type);
447 checked_free(*artwork_path_ptr);
449 if ((level_artwork = getTreeInfoFromIdentifier(ti, *artwork_set_ptr)))
451 *artwork_path_ptr = getStringCopy(getSetupArtworkDir(level_artwork));
456 No (or non-existing) artwork configured in "levelinfo.conf". This would
457 normally result in using the artwork configured in the setup menu. But
458 if an artwork subdirectory exists (which might contain custom artwork
459 or an artwork configuration file), this level artwork must be treated
460 as relative to the default "classic" artwork, not to the artwork that
461 is currently configured in the setup menu.
463 Update: For "special" versions of R'n'D (like "R'n'D jue"), do not use
464 the "default" artwork (which would be "jue0" for "R'n'D jue"), but use
465 the real "classic" artwork from the original R'n'D (like "gfx_classic").
468 char *dir = getPath2(getCurrentLevelDir(), ARTWORK_DIRECTORY(ti->type));
470 checked_free(*artwork_set_ptr);
472 if (directoryExists(dir))
474 *artwork_path_ptr = getStringCopy(getClassicArtworkDir(ti->type));
475 *artwork_set_ptr = getStringCopy(getClassicArtworkSet(ti->type));
479 *artwork_path_ptr = getStringCopy(UNDEFINED_FILENAME);
480 *artwork_set_ptr = NULL;
486 return *artwork_set_ptr;
489 static char *getLevelArtworkSet(int type)
491 if (leveldir_current == NULL)
494 return LEVELDIR_ARTWORK_SET(leveldir_current, type);
497 static char *getLevelArtworkDir(int type)
499 if (leveldir_current == NULL)
500 return UNDEFINED_FILENAME;
502 return LEVELDIR_ARTWORK_PATH(leveldir_current, type);
505 char *getProgramMainDataPath(char *command_filename, char *base_path)
507 // check if the program's main data base directory is configured
508 if (!strEqual(base_path, "."))
509 return getStringCopy(base_path);
511 /* if the program is configured to start from current directory (default),
512 determine program package directory from program binary (some versions
513 of KDE/Konqueror and Mac OS X (especially "Mavericks") apparently do not
514 set the current working directory to the program package directory) */
515 char *main_data_path = getBasePath(command_filename);
517 #if defined(PLATFORM_MAC)
518 if (strSuffix(main_data_path, MAC_APP_BINARY_SUBDIR))
520 char *main_data_path_old = main_data_path;
522 // cut relative path to Mac OS X application binary directory from path
523 main_data_path[strlen(main_data_path) -
524 strlen(MAC_APP_BINARY_SUBDIR)] = '\0';
526 // cut trailing path separator from path (but not if path is root directory)
527 if (strSuffix(main_data_path, "/") && !strEqual(main_data_path, "/"))
528 main_data_path[strlen(main_data_path) - 1] = '\0';
530 // replace empty path with current directory
531 if (strEqual(main_data_path, ""))
532 main_data_path = ".";
534 // add relative path to Mac OS X application resources directory to path
535 main_data_path = getPath2(main_data_path, MAC_APP_FILES_SUBDIR);
537 free(main_data_path_old);
541 return main_data_path;
544 char *getProgramConfigFilename(char *command_filename)
546 static char *config_filename_1 = NULL;
547 static char *config_filename_2 = NULL;
548 static char *config_filename_3 = NULL;
549 static boolean initialized = FALSE;
553 char *command_filename_1 = getStringCopy(command_filename);
555 // strip trailing executable suffix from command filename
556 if (strSuffix(command_filename_1, ".exe"))
557 command_filename_1[strlen(command_filename_1) - 4] = '\0';
559 char *base_path = getProgramMainDataPath(command_filename, BASE_PATH);
560 char *conf_directory = getPath2(base_path, CONF_DIRECTORY);
562 char *command_basepath = getBasePath(command_filename);
563 char *command_basename = getBaseNameNoSuffix(command_filename);
564 char *command_filename_2 = getPath2(command_basepath, command_basename);
566 config_filename_1 = getStringCat2(command_filename_1, ".conf");
567 config_filename_2 = getStringCat2(command_filename_2, ".conf");
568 config_filename_3 = getPath2(conf_directory, SETUP_FILENAME);
570 checked_free(base_path);
571 checked_free(conf_directory);
573 checked_free(command_basepath);
574 checked_free(command_basename);
576 checked_free(command_filename_1);
577 checked_free(command_filename_2);
582 // 1st try: look for config file that exactly matches the binary filename
583 if (fileExists(config_filename_1))
584 return config_filename_1;
586 // 2nd try: look for config file that matches binary filename without suffix
587 if (fileExists(config_filename_2))
588 return config_filename_2;
590 // 3rd try: return setup config filename in global program config directory
591 return config_filename_3;
594 static char *getPlatformConfigFilename(char *config_filename)
596 static char *platform_config_filename = NULL;
597 static boolean initialized = FALSE;
601 char *config_basepath = getBasePath(config_filename);
602 char *config_basename = getBaseNameNoSuffix(config_filename);
603 char *config_filename_prefix = getPath2(config_basepath, config_basename);
604 char *platform_string_lower = getStringToLower(PLATFORM_STRING);
605 char *platform_suffix = getStringCat2("-", platform_string_lower);
607 platform_config_filename = getStringCat3(config_filename_prefix,
608 platform_suffix, ".conf");
610 checked_free(config_basepath);
611 checked_free(config_basename);
612 checked_free(config_filename_prefix);
613 checked_free(platform_string_lower);
614 checked_free(platform_suffix);
619 return platform_config_filename;
622 char *getTapeFilename(int nr)
624 static char *filename = NULL;
625 char basename[MAX_FILENAME_LEN];
627 checked_free(filename);
629 sprintf(basename, "%03d.%s", nr, TAPEFILE_EXTENSION);
630 filename = getPath2(getTapeDir(leveldir_current->subdir), basename);
635 char *getTemporaryTapeFilename(void)
637 static char *filename = NULL;
638 char basename[MAX_FILENAME_LEN];
640 checked_free(filename);
642 sprintf(basename, "tmp.%s", TAPEFILE_EXTENSION);
643 filename = getPath2(getTapeDir(NULL), basename);
648 char *getDefaultSolutionTapeFilename(int nr)
650 static char *filename = NULL;
651 char basename[MAX_FILENAME_LEN];
653 checked_free(filename);
655 sprintf(basename, "%03d.%s", nr, TAPEFILE_EXTENSION);
656 filename = getPath2(getSolutionTapeDir(), basename);
661 char *getSokobanSolutionTapeFilename(int nr)
663 static char *filename = NULL;
664 char basename[MAX_FILENAME_LEN];
666 checked_free(filename);
668 sprintf(basename, "%03d.sln", nr);
669 filename = getPath2(getSolutionTapeDir(), basename);
674 char *getSolutionTapeFilename(int nr)
676 char *filename = getDefaultSolutionTapeFilename(nr);
678 if (!fileExists(filename))
680 char *filename2 = getSokobanSolutionTapeFilename(nr);
682 if (fileExists(filename2))
689 char *getScoreFilename(int nr)
691 static char *filename = NULL;
692 char basename[MAX_FILENAME_LEN];
694 checked_free(filename);
696 sprintf(basename, "%03d.%s", nr, SCOREFILE_EXTENSION);
698 // used instead of "leveldir_current->subdir" (for network games)
699 filename = getPath2(getScoreDir(levelset.identifier), basename);
704 char *getScoreCacheFilename(int nr)
706 static char *filename = NULL;
707 char basename[MAX_FILENAME_LEN];
709 checked_free(filename);
711 sprintf(basename, "%03d.%s", nr, SCOREFILE_EXTENSION);
713 // used instead of "leveldir_current->subdir" (for network games)
714 filename = getPath2(getScoreCacheDir(levelset.identifier), basename);
719 char *getScoreTapeBasename(char *name)
721 static char basename[MAX_FILENAME_LEN];
722 char basename_raw[MAX_FILENAME_LEN];
725 sprintf(timestamp, "%s", getCurrentTimestamp());
726 sprintf(basename_raw, "%s-%s", timestamp, name);
727 sprintf(basename, "%s-%08x", timestamp, get_hash_from_key(basename_raw));
732 char *getScoreTapeFilename(char *basename_no_ext, int nr)
734 static char *filename = NULL;
735 char basename[MAX_FILENAME_LEN];
737 checked_free(filename);
739 sprintf(basename, "%s.%s", basename_no_ext, TAPEFILE_EXTENSION);
741 // used instead of "leveldir_current->subdir" (for network games)
742 filename = getPath2(getScoreTapeDir(levelset.identifier, nr), basename);
747 char *getScoreCacheTapeFilename(char *basename_no_ext, int nr)
749 static char *filename = NULL;
750 char basename[MAX_FILENAME_LEN];
752 checked_free(filename);
754 sprintf(basename, "%s.%s", basename_no_ext, TAPEFILE_EXTENSION);
756 // used instead of "leveldir_current->subdir" (for network games)
757 filename = getPath2(getScoreCacheTapeDir(levelset.identifier, nr), basename);
762 char *getSetupFilename(void)
764 static char *filename = NULL;
766 checked_free(filename);
768 filename = getPath2(getSetupDir(), SETUP_FILENAME);
773 char *getDefaultSetupFilename(void)
775 return program.config_filename;
778 char *getPlatformSetupFilename(void)
780 return getPlatformConfigFilename(program.config_filename);
783 char *getEditorSetupFilename(void)
785 static char *filename = NULL;
787 checked_free(filename);
788 filename = getPath2(getCurrentLevelDir(), EDITORSETUP_FILENAME);
790 if (fileExists(filename))
793 checked_free(filename);
794 filename = getPath2(getSetupDir(), EDITORSETUP_FILENAME);
799 char *getHelpAnimFilename(void)
801 static char *filename = NULL;
803 checked_free(filename);
805 filename = getPath2(getCurrentLevelDir(), HELPANIM_FILENAME);
810 char *getHelpTextFilename(void)
812 static char *filename = NULL;
814 checked_free(filename);
816 filename = getPath2(getCurrentLevelDir(), HELPTEXT_FILENAME);
821 static char *getLevelSetInfoBasename(int nr)
823 static char basename[32];
825 sprintf(basename, "levelset_%d.txt", nr + 1);
830 char *getLevelSetInfoFilename(int nr)
832 char *basename = getLevelSetInfoBasename(nr);
833 static char *info_subdir = NULL;
834 static char *filename = NULL;
836 if (info_subdir == NULL)
837 info_subdir = getPath2(DOCS_DIRECTORY, LEVELSET_INFO_DIRECTORY);
839 checked_free(filename);
841 // look for level set info file the current level set directory
842 filename = getPath3(getCurrentLevelDir(), info_subdir, basename);
843 if (fileExists(filename))
863 for (i = 0; basenames[i] != NULL; i++)
865 checked_free(filename);
866 filename = getPath2(getCurrentLevelDir(), basenames[i]);
868 if (fileExists(filename))
875 static char *getLevelSetTitleMessageBasename(int nr, boolean initial)
877 static char basename[32];
879 sprintf(basename, "%s_%d.txt",
880 (initial ? "titlemessage_initial" : "titlemessage"), nr + 1);
885 char *getLevelSetTitleMessageFilename(int nr, boolean initial)
887 static char *filename = NULL;
889 boolean skip_setup_artwork = FALSE;
891 checked_free(filename);
893 basename = getLevelSetTitleMessageBasename(nr, initial);
895 if (!gfx.override_level_graphics)
897 // 1st try: look for special artwork in current level series directory
898 filename = getPath3(getCurrentLevelDir(), GRAPHICS_DIRECTORY, basename);
899 if (fileExists(filename))
904 // 2nd try: look for message file in current level set directory
905 filename = getPath2(getCurrentLevelDir(), basename);
906 if (fileExists(filename))
911 // check if there is special artwork configured in level series config
912 if (getLevelArtworkSet(ARTWORK_TYPE_GRAPHICS) != NULL)
914 // 3rd try: look for special artwork configured in level series config
915 filename = getPath2(getLevelArtworkDir(ARTWORK_TYPE_GRAPHICS), basename);
916 if (fileExists(filename))
921 // take missing artwork configured in level set config from default
922 skip_setup_artwork = TRUE;
926 if (!skip_setup_artwork)
928 // 4th try: look for special artwork in configured artwork directory
929 filename = getPath2(getSetupArtworkDir(artwork.gfx_current), basename);
930 if (fileExists(filename))
936 // 5th try: look for default artwork in new default artwork directory
937 filename = getPath2(getDefaultGraphicsDir(GFX_DEFAULT_SUBDIR), basename);
938 if (fileExists(filename))
943 // 6th try: look for default artwork in old default artwork directory
944 filename = getPath2(options.graphics_directory, basename);
945 if (fileExists(filename))
948 return NULL; // cannot find specified artwork file anywhere
951 static char *getCreditsBasename(int nr)
953 static char basename[32];
955 sprintf(basename, "credits_%d.txt", nr + 1);
960 char *getCreditsFilename(int nr, boolean global)
962 char *basename = getCreditsBasename(nr);
963 char *basepath = NULL;
964 static char *credits_subdir = NULL;
965 static char *filename = NULL;
967 if (credits_subdir == NULL)
968 credits_subdir = getPath2(DOCS_DIRECTORY, CREDITS_DIRECTORY);
970 checked_free(filename);
972 // look for credits file in the game's base or current level set directory
973 basepath = (global ? options.base_directory : getCurrentLevelDir());
975 filename = getPath3(basepath, credits_subdir, basename);
976 if (fileExists(filename))
979 return NULL; // cannot find credits file
982 static char *getProgramInfoBasename(int nr)
984 static char basename[32];
986 sprintf(basename, "program_%d.txt", nr + 1);
991 char *getProgramInfoFilename(int nr)
993 char *basename = getProgramInfoBasename(nr);
994 static char *info_subdir = NULL;
995 static char *filename = NULL;
997 if (info_subdir == NULL)
998 info_subdir = getPath2(DOCS_DIRECTORY, PROGRAM_INFO_DIRECTORY);
1000 checked_free(filename);
1002 // look for program info file in the game's base directory
1003 filename = getPath3(options.base_directory, info_subdir, basename);
1004 if (fileExists(filename))
1007 return NULL; // cannot find program info file
1010 static char *getCorrectedArtworkBasename(char *basename)
1015 char *getCustomImageFilename(char *basename)
1017 static char *filename = NULL;
1018 boolean skip_setup_artwork = FALSE;
1020 checked_free(filename);
1022 basename = getCorrectedArtworkBasename(basename);
1024 if (!gfx.override_level_graphics)
1026 // 1st try: look for special artwork in current level series directory
1027 filename = getImg3(getCurrentLevelDir(), GRAPHICS_DIRECTORY, basename);
1028 if (fileExists(filename))
1033 // check if there is special artwork configured in level series config
1034 if (getLevelArtworkSet(ARTWORK_TYPE_GRAPHICS) != NULL)
1036 // 2nd try: look for special artwork configured in level series config
1037 filename = getImg2(getLevelArtworkDir(ARTWORK_TYPE_GRAPHICS), basename);
1038 if (fileExists(filename))
1043 // take missing artwork configured in level set config from default
1044 skip_setup_artwork = TRUE;
1048 if (!skip_setup_artwork)
1050 // 3rd try: look for special artwork in configured artwork directory
1051 filename = getImg2(getSetupArtworkDir(artwork.gfx_current), basename);
1052 if (fileExists(filename))
1058 // 4th try: look for default artwork in new default artwork directory
1059 filename = getImg2(getDefaultGraphicsDir(GFX_DEFAULT_SUBDIR), basename);
1060 if (fileExists(filename))
1065 // 5th try: look for default artwork in old default artwork directory
1066 filename = getImg2(options.graphics_directory, basename);
1067 if (fileExists(filename))
1070 if (!strEqual(GFX_FALLBACK_FILENAME, UNDEFINED_FILENAME))
1074 WarnUsingFallback(basename);
1076 // 6th try: look for fallback artwork in old default artwork directory
1077 // (needed to prevent errors when trying to access unused artwork files)
1078 filename = getImg2(options.graphics_directory, GFX_FALLBACK_FILENAME);
1079 if (fileExists(filename))
1083 return NULL; // cannot find specified artwork file anywhere
1086 char *getCustomSoundFilename(char *basename)
1088 static char *filename = NULL;
1089 boolean skip_setup_artwork = FALSE;
1091 checked_free(filename);
1093 basename = getCorrectedArtworkBasename(basename);
1095 if (!gfx.override_level_sounds)
1097 // 1st try: look for special artwork in current level series directory
1098 filename = getPath3(getCurrentLevelDir(), SOUNDS_DIRECTORY, basename);
1099 if (fileExists(filename))
1104 // check if there is special artwork configured in level series config
1105 if (getLevelArtworkSet(ARTWORK_TYPE_SOUNDS) != NULL)
1107 // 2nd try: look for special artwork configured in level series config
1108 filename = getPath2(getLevelArtworkDir(TREE_TYPE_SOUNDS_DIR), basename);
1109 if (fileExists(filename))
1114 // take missing artwork configured in level set config from default
1115 skip_setup_artwork = TRUE;
1119 if (!skip_setup_artwork)
1121 // 3rd try: look for special artwork in configured artwork directory
1122 filename = getPath2(getSetupArtworkDir(artwork.snd_current), basename);
1123 if (fileExists(filename))
1129 // 4th try: look for default artwork in new default artwork directory
1130 filename = getPath2(getDefaultSoundsDir(SND_DEFAULT_SUBDIR), basename);
1131 if (fileExists(filename))
1136 // 5th try: look for default artwork in old default artwork directory
1137 filename = getPath2(options.sounds_directory, basename);
1138 if (fileExists(filename))
1141 if (!strEqual(SND_FALLBACK_FILENAME, UNDEFINED_FILENAME))
1145 WarnUsingFallback(basename);
1147 // 6th try: look for fallback artwork in old default artwork directory
1148 // (needed to prevent errors when trying to access unused artwork files)
1149 filename = getPath2(options.sounds_directory, SND_FALLBACK_FILENAME);
1150 if (fileExists(filename))
1154 return NULL; // cannot find specified artwork file anywhere
1157 char *getCustomMusicFilename(char *basename)
1159 static char *filename = NULL;
1160 boolean skip_setup_artwork = FALSE;
1162 checked_free(filename);
1164 basename = getCorrectedArtworkBasename(basename);
1166 if (!gfx.override_level_music)
1168 // 1st try: look for special artwork in current level series directory
1169 filename = getPath3(getCurrentLevelDir(), MUSIC_DIRECTORY, basename);
1170 if (fileExists(filename))
1175 // check if there is special artwork configured in level series config
1176 if (getLevelArtworkSet(ARTWORK_TYPE_MUSIC) != NULL)
1178 // 2nd try: look for special artwork configured in level series config
1179 filename = getPath2(getLevelArtworkDir(TREE_TYPE_MUSIC_DIR), basename);
1180 if (fileExists(filename))
1185 // take missing artwork configured in level set config from default
1186 skip_setup_artwork = TRUE;
1190 if (!skip_setup_artwork)
1192 // 3rd try: look for special artwork in configured artwork directory
1193 filename = getPath2(getSetupArtworkDir(artwork.mus_current), basename);
1194 if (fileExists(filename))
1200 // 4th try: look for default artwork in new default artwork directory
1201 filename = getPath2(getDefaultMusicDir(MUS_DEFAULT_SUBDIR), basename);
1202 if (fileExists(filename))
1207 // 5th try: look for default artwork in old default artwork directory
1208 filename = getPath2(options.music_directory, basename);
1209 if (fileExists(filename))
1212 if (!strEqual(MUS_FALLBACK_FILENAME, UNDEFINED_FILENAME))
1216 WarnUsingFallback(basename);
1218 // 6th try: look for fallback artwork in old default artwork directory
1219 // (needed to prevent errors when trying to access unused artwork files)
1220 filename = getPath2(options.music_directory, MUS_FALLBACK_FILENAME);
1221 if (fileExists(filename))
1225 return NULL; // cannot find specified artwork file anywhere
1228 char *getCustomArtworkFilename(char *basename, int type)
1230 if (type == ARTWORK_TYPE_GRAPHICS)
1231 return getCustomImageFilename(basename);
1232 else if (type == ARTWORK_TYPE_SOUNDS)
1233 return getCustomSoundFilename(basename);
1234 else if (type == ARTWORK_TYPE_MUSIC)
1235 return getCustomMusicFilename(basename);
1237 return UNDEFINED_FILENAME;
1240 char *getCustomArtworkConfigFilename(int type)
1242 return getCustomArtworkFilename(ARTWORKINFO_FILENAME(type), type);
1245 char *getCustomArtworkLevelConfigFilename(int type)
1247 static char *filename = NULL;
1249 checked_free(filename);
1251 filename = getPath2(getLevelArtworkDir(type), ARTWORKINFO_FILENAME(type));
1256 char *getCustomMusicDirectory(void)
1258 static char *directory = NULL;
1259 boolean skip_setup_artwork = FALSE;
1261 checked_free(directory);
1263 if (!gfx.override_level_music)
1265 // 1st try: look for special artwork in current level series directory
1266 directory = getPath2(getCurrentLevelDir(), MUSIC_DIRECTORY);
1267 if (directoryExists(directory))
1272 // check if there is special artwork configured in level series config
1273 if (getLevelArtworkSet(ARTWORK_TYPE_MUSIC) != NULL)
1275 // 2nd try: look for special artwork configured in level series config
1276 directory = getStringCopy(getLevelArtworkDir(TREE_TYPE_MUSIC_DIR));
1277 if (directoryExists(directory))
1282 // take missing artwork configured in level set config from default
1283 skip_setup_artwork = TRUE;
1287 if (!skip_setup_artwork)
1289 // 3rd try: look for special artwork in configured artwork directory
1290 directory = getStringCopy(getSetupArtworkDir(artwork.mus_current));
1291 if (directoryExists(directory))
1297 // 4th try: look for default artwork in new default artwork directory
1298 directory = getStringCopy(getDefaultMusicDir(MUS_DEFAULT_SUBDIR));
1299 if (directoryExists(directory))
1304 // 5th try: look for default artwork in old default artwork directory
1305 directory = getStringCopy(options.music_directory);
1306 if (directoryExists(directory))
1309 return NULL; // cannot find specified artwork file anywhere
1312 void MarkTapeDirectoryUploadsAsComplete(char *level_subdir)
1314 char *filename = getPath2(getTapeDir(level_subdir), UPLOADED_FILENAME);
1316 touchFile(filename);
1318 checked_free(filename);
1321 void MarkTapeDirectoryUploadsAsIncomplete(char *level_subdir)
1323 char *filename = getPath2(getTapeDir(level_subdir), UPLOADED_FILENAME);
1327 checked_free(filename);
1330 boolean CheckTapeDirectoryUploadsComplete(char *level_subdir)
1332 char *filename = getPath2(getTapeDir(level_subdir), UPLOADED_FILENAME);
1333 boolean success = fileExists(filename);
1335 checked_free(filename);
1340 void InitMissingFileHash(void)
1342 if (missing_file_hash == NULL)
1343 freeSetupFileHash(missing_file_hash);
1345 missing_file_hash = newSetupFileHash();
1348 void InitTapeDirectory(char *level_subdir)
1350 boolean new_tape_dir = !directoryExists(getTapeDir(level_subdir));
1352 createDirectory(getUserGameDataDir(), "user data");
1353 createDirectory(getTapeDir(NULL), "main tape");
1354 createDirectory(getTapeDir(level_subdir), "level tape");
1357 MarkTapeDirectoryUploadsAsComplete(level_subdir);
1360 void InitScoreDirectory(char *level_subdir)
1362 createDirectory(getMainUserGameDataDir(), "main user data");
1363 createDirectory(getScoreDir(NULL), "main score");
1364 createDirectory(getScoreDir(level_subdir), "level score");
1367 void InitScoreCacheDirectory(char *level_subdir)
1369 createDirectory(getMainUserGameDataDir(), "main user data");
1370 createDirectory(getCacheDir(), "cache data");
1371 createDirectory(getScoreCacheDir(NULL), "main score");
1372 createDirectory(getScoreCacheDir(level_subdir), "level score");
1375 void InitScoreTapeDirectory(char *level_subdir, int nr)
1377 InitScoreDirectory(level_subdir);
1379 createDirectory(getScoreTapeDir(level_subdir, nr), "score tape");
1382 void InitScoreCacheTapeDirectory(char *level_subdir, int nr)
1384 InitScoreCacheDirectory(level_subdir);
1386 createDirectory(getScoreCacheTapeDir(level_subdir, nr), "score tape");
1389 static void SaveUserLevelInfo(void);
1391 void InitUserLevelDirectory(char *level_subdir)
1393 if (!directoryExists(getUserLevelDir(level_subdir)))
1395 createDirectory(getMainUserGameDataDir(), "main user data");
1396 createDirectory(getUserLevelDir(NULL), "main user level");
1398 if (setup.internal.create_user_levelset)
1400 createDirectory(getUserLevelDir(level_subdir), "user level");
1402 SaveUserLevelInfo();
1407 void InitNetworkLevelDirectory(char *level_subdir)
1409 if (!directoryExists(getNetworkLevelDir(level_subdir)))
1411 createDirectory(getMainUserGameDataDir(), "main user data");
1412 createDirectory(getNetworkDir(), "network data");
1413 createDirectory(getNetworkLevelDir(NULL), "main network level");
1414 createDirectory(getNetworkLevelDir(level_subdir), "network level");
1418 void InitLevelSetupDirectory(char *level_subdir)
1420 createDirectory(getUserGameDataDir(), "user data");
1421 createDirectory(getLevelSetupDir(NULL), "main level setup");
1422 createDirectory(getLevelSetupDir(level_subdir), "level setup");
1425 static void InitCacheDirectory(void)
1427 createDirectory(getMainUserGameDataDir(), "main user data");
1428 createDirectory(getCacheDir(), "cache data");
1432 // ----------------------------------------------------------------------------
1433 // some functions to handle lists of level and artwork directories
1434 // ----------------------------------------------------------------------------
1436 TreeInfo *newTreeInfo(void)
1438 return checked_calloc(sizeof(TreeInfo));
1441 TreeInfo *newTreeInfo_setDefaults(int type)
1443 TreeInfo *ti = newTreeInfo();
1445 setTreeInfoToDefaults(ti, type);
1450 void pushTreeInfo(TreeInfo **node_first, TreeInfo *node_new)
1452 node_new->next = *node_first;
1453 *node_first = node_new;
1456 void removeTreeInfo(TreeInfo **node_first)
1458 TreeInfo *node_old = *node_first;
1460 *node_first = node_old->next;
1461 node_old->next = NULL;
1463 freeTreeInfo(node_old);
1466 int numTreeInfo(TreeInfo *node)
1479 boolean validLevelSeries(TreeInfo *node)
1481 // in a number of cases, tree node is no valid level set
1482 if (node == NULL || node->node_group || node->parent_link || node->is_copy)
1488 TreeInfo *getValidLevelSeries(TreeInfo *node, TreeInfo *default_node)
1490 if (validLevelSeries(node))
1492 else if (node->is_copy)
1493 return getTreeInfoFromIdentifier(leveldir_first, node->identifier);
1495 return getFirstValidTreeInfoEntry(default_node);
1498 static TreeInfo *getValidTreeInfoEntryExt(TreeInfo *node, boolean get_next_node)
1503 if (node->node_group) // enter node group (step down into tree)
1504 return getFirstValidTreeInfoEntry(node->node_group);
1506 if (node->parent_link) // skip first node (back link) of node group
1507 get_next_node = TRUE;
1509 if (!get_next_node) // get current regular tree node
1512 // get next regular tree node, or step up until one is found
1513 while (node->next == NULL && node->node_parent != NULL)
1514 node = node->node_parent;
1516 return getFirstValidTreeInfoEntry(node->next);
1519 TreeInfo *getFirstValidTreeInfoEntry(TreeInfo *node)
1521 return getValidTreeInfoEntryExt(node, FALSE);
1524 TreeInfo *getNextValidTreeInfoEntry(TreeInfo *node)
1526 return getValidTreeInfoEntryExt(node, TRUE);
1529 TreeInfo *getTreeInfoFirstGroupEntry(TreeInfo *node)
1534 if (node->node_parent == NULL) // top level group
1535 return *node->node_top;
1536 else // sub level group
1537 return node->node_parent->node_group;
1540 int numTreeInfoInGroup(TreeInfo *node)
1542 return numTreeInfo(getTreeInfoFirstGroupEntry(node));
1545 int getPosFromTreeInfo(TreeInfo *node)
1547 TreeInfo *node_cmp = getTreeInfoFirstGroupEntry(node);
1552 if (node_cmp == node)
1556 node_cmp = node_cmp->next;
1562 TreeInfo *getTreeInfoFromPos(TreeInfo *node, int pos)
1564 TreeInfo *node_default = node;
1576 return node_default;
1579 static TreeInfo *getTreeInfoFromIdentifierExt(TreeInfo *node, char *identifier,
1580 int node_type_wanted)
1582 if (identifier == NULL)
1587 if (TREE_NODE_TYPE(node) == node_type_wanted &&
1588 strEqual(identifier, node->identifier))
1591 if (node->node_group)
1593 TreeInfo *node_group = getTreeInfoFromIdentifierExt(node->node_group,
1606 TreeInfo *getTreeInfoFromIdentifier(TreeInfo *node, char *identifier)
1608 return getTreeInfoFromIdentifierExt(node, identifier, TREE_NODE_TYPE_DEFAULT);
1611 static TreeInfo *cloneTreeNode(TreeInfo **node_top, TreeInfo *node_parent,
1612 TreeInfo *node, boolean skip_sets_without_levels)
1619 if (!node->parent_link && !node->level_group &&
1620 skip_sets_without_levels && node->levels == 0)
1621 return cloneTreeNode(node_top, node_parent, node->next,
1622 skip_sets_without_levels);
1624 node_new = getTreeInfoCopy(node); // copy complete node
1626 node_new->node_top = node_top; // correct top node link
1627 node_new->node_parent = node_parent; // correct parent node link
1629 if (node->level_group)
1630 node_new->node_group = cloneTreeNode(node_top, node_new, node->node_group,
1631 skip_sets_without_levels);
1633 node_new->next = cloneTreeNode(node_top, node_parent, node->next,
1634 skip_sets_without_levels);
1639 static void cloneTree(TreeInfo **ti_new, TreeInfo *ti, boolean skip_empty_sets)
1641 TreeInfo *ti_cloned = cloneTreeNode(ti_new, NULL, ti, skip_empty_sets);
1643 *ti_new = ti_cloned;
1646 static boolean adjustTreeGraphicsForEMC(TreeInfo *node)
1648 boolean settings_changed = FALSE;
1652 boolean want_ecs = (setup.prefer_aga_graphics == FALSE);
1653 boolean want_aga = (setup.prefer_aga_graphics == TRUE);
1654 boolean has_only_ecs = (!node->graphics_set && !node->graphics_set_aga);
1655 boolean has_only_aga = (!node->graphics_set && !node->graphics_set_ecs);
1656 char *graphics_set = NULL;
1658 if (node->graphics_set_ecs && (want_ecs || has_only_ecs))
1659 graphics_set = node->graphics_set_ecs;
1661 if (node->graphics_set_aga && (want_aga || has_only_aga))
1662 graphics_set = node->graphics_set_aga;
1664 if (graphics_set && !strEqual(node->graphics_set, graphics_set))
1666 setString(&node->graphics_set, graphics_set);
1667 settings_changed = TRUE;
1670 if (node->node_group != NULL)
1671 settings_changed |= adjustTreeGraphicsForEMC(node->node_group);
1676 return settings_changed;
1679 static boolean adjustTreeSoundsForEMC(TreeInfo *node)
1681 boolean settings_changed = FALSE;
1685 boolean want_default = (setup.prefer_lowpass_sounds == FALSE);
1686 boolean want_lowpass = (setup.prefer_lowpass_sounds == TRUE);
1687 boolean has_only_default = (!node->sounds_set && !node->sounds_set_lowpass);
1688 boolean has_only_lowpass = (!node->sounds_set && !node->sounds_set_default);
1689 char *sounds_set = NULL;
1691 if (node->sounds_set_default && (want_default || has_only_default))
1692 sounds_set = node->sounds_set_default;
1694 if (node->sounds_set_lowpass && (want_lowpass || has_only_lowpass))
1695 sounds_set = node->sounds_set_lowpass;
1697 if (sounds_set && !strEqual(node->sounds_set, sounds_set))
1699 setString(&node->sounds_set, sounds_set);
1700 settings_changed = TRUE;
1703 if (node->node_group != NULL)
1704 settings_changed |= adjustTreeSoundsForEMC(node->node_group);
1709 return settings_changed;
1712 int dumpTreeInfo(TreeInfo *node, int depth)
1714 char bullet_list[] = { '-', '*', 'o' };
1715 int num_leaf_nodes = 0;
1719 Debug("tree", "Dumping TreeInfo:");
1723 char bullet = bullet_list[depth % ARRAY_SIZE(bullet_list)];
1725 for (i = 0; i < depth * 2; i++)
1726 DebugContinued("", " ");
1728 DebugContinued("tree", "%c '%s' ['%s] [PARENT: '%s'] %s\n",
1729 bullet, node->name, node->identifier,
1730 (node->node_parent ? node->node_parent->identifier : "-"),
1731 (node->node_group ? "[GROUP]" :
1732 node->is_copy ? "[COPY]" : ""));
1734 if (!node->node_group && !node->parent_link)
1738 // use for dumping artwork info tree
1739 Debug("tree", "subdir == '%s' ['%s', '%s'] [%d])",
1740 node->subdir, node->fullpath, node->basepath, node->in_user_dir);
1743 if (node->node_group != NULL)
1744 num_leaf_nodes += dumpTreeInfo(node->node_group, depth + 1);
1750 Debug("tree", "Summary: %d leaf nodes found", num_leaf_nodes);
1752 return num_leaf_nodes;
1755 void sortTreeInfoBySortFunction(TreeInfo **node_first,
1756 int (*compare_function)(const void *,
1759 int num_nodes = numTreeInfo(*node_first);
1760 TreeInfo **sort_array;
1761 TreeInfo *node = *node_first;
1767 // allocate array for sorting structure pointers
1768 sort_array = checked_calloc(num_nodes * sizeof(TreeInfo *));
1770 // writing structure pointers to sorting array
1771 while (i < num_nodes && node) // double boundary check...
1773 sort_array[i] = node;
1779 // sorting the structure pointers in the sorting array
1780 qsort(sort_array, num_nodes, sizeof(TreeInfo *),
1783 // update the linkage of list elements with the sorted node array
1784 for (i = 0; i < num_nodes - 1; i++)
1785 sort_array[i]->next = sort_array[i + 1];
1786 sort_array[num_nodes - 1]->next = NULL;
1788 // update the linkage of the main list anchor pointer
1789 *node_first = sort_array[0];
1793 // now recursively sort the level group structures
1797 if (node->node_group != NULL)
1798 sortTreeInfoBySortFunction(&node->node_group, compare_function);
1804 void sortTreeInfo(TreeInfo **node_first)
1806 sortTreeInfoBySortFunction(node_first, compareTreeInfoEntries);
1810 // ============================================================================
1811 // some stuff from "files.c"
1812 // ============================================================================
1814 #if defined(PLATFORM_WINDOWS)
1816 #define S_IRGRP S_IRUSR
1819 #define S_IROTH S_IRUSR
1822 #define S_IWGRP S_IWUSR
1825 #define S_IWOTH S_IWUSR
1828 #define S_IXGRP S_IXUSR
1831 #define S_IXOTH S_IXUSR
1834 #define S_IRWXG (S_IRGRP | S_IWGRP | S_IXGRP)
1839 #endif // PLATFORM_WINDOWS
1841 // file permissions for newly written files
1842 #define MODE_R_ALL (S_IRUSR | S_IRGRP | S_IROTH)
1843 #define MODE_W_ALL (S_IWUSR | S_IWGRP | S_IWOTH)
1844 #define MODE_X_ALL (S_IXUSR | S_IXGRP | S_IXOTH)
1846 #define MODE_W_PRIVATE (S_IWUSR)
1847 #define MODE_W_PUBLIC_FILE (S_IWUSR | S_IWGRP)
1848 #define MODE_W_PUBLIC_DIR (S_IWUSR | S_IWGRP | S_ISGID)
1850 #define DIR_PERMS_PRIVATE (MODE_R_ALL | MODE_X_ALL | MODE_W_PRIVATE)
1851 #define DIR_PERMS_PUBLIC (MODE_R_ALL | MODE_X_ALL | MODE_W_PUBLIC_DIR)
1852 #define DIR_PERMS_PUBLIC_ALL (MODE_R_ALL | MODE_X_ALL | MODE_W_ALL)
1854 #define FILE_PERMS_PRIVATE (MODE_R_ALL | MODE_W_PRIVATE)
1855 #define FILE_PERMS_PUBLIC (MODE_R_ALL | MODE_W_PUBLIC_FILE)
1856 #define FILE_PERMS_PUBLIC_ALL (MODE_R_ALL | MODE_W_ALL)
1859 char *getHomeDir(void)
1861 static char *dir = NULL;
1863 #if defined(PLATFORM_WINDOWS)
1866 dir = checked_malloc(MAX_PATH + 1);
1868 if (!SUCCEEDED(SHGetFolderPath(NULL, CSIDL_PERSONAL, NULL, 0, dir)))
1871 #elif defined(PLATFORM_EMSCRIPTEN)
1872 dir = PERSISTENT_DIRECTORY;
1873 #elif defined(PLATFORM_UNIX)
1876 if ((dir = getenv("HOME")) == NULL)
1878 dir = getUnixHomeDir();
1881 dir = getStringCopy(dir);
1893 char *getPersonalDataDir(void)
1895 static char *personal_data_dir = NULL;
1897 #if defined(PLATFORM_MAC)
1898 if (personal_data_dir == NULL)
1899 personal_data_dir = getPath2(getHomeDir(), "Documents");
1901 if (personal_data_dir == NULL)
1902 personal_data_dir = getHomeDir();
1905 return personal_data_dir;
1908 char *getMainUserGameDataDir(void)
1910 static char *main_user_data_dir = NULL;
1912 #if defined(PLATFORM_ANDROID)
1913 if (main_user_data_dir == NULL)
1914 main_user_data_dir = (char *)(SDL_AndroidGetExternalStorageState() &
1915 SDL_ANDROID_EXTERNAL_STORAGE_WRITE ?
1916 SDL_AndroidGetExternalStoragePath() :
1917 SDL_AndroidGetInternalStoragePath());
1919 if (main_user_data_dir == NULL)
1920 main_user_data_dir = getPath2(getPersonalDataDir(),
1921 program.userdata_subdir);
1924 return main_user_data_dir;
1927 char *getUserGameDataDir(void)
1930 return getMainUserGameDataDir();
1932 return getUserDir(user.nr);
1935 char *getSetupDir(void)
1937 return getUserGameDataDir();
1940 static mode_t posix_umask(mode_t mask)
1942 #if defined(PLATFORM_UNIX)
1949 static int posix_mkdir(const char *pathname, mode_t mode)
1951 #if defined(PLATFORM_WINDOWS)
1952 return mkdir(pathname);
1954 return mkdir(pathname, mode);
1958 static boolean posix_process_running_setgid(void)
1960 #if defined(PLATFORM_UNIX)
1961 return (getgid() != getegid());
1967 void createDirectory(char *dir, char *text)
1969 if (directoryExists(dir))
1972 // leave "other" permissions in umask untouched, but ensure group parts
1973 // of USERDATA_DIR_MODE are not masked
1974 int permission_class = PERMS_PRIVATE;
1975 mode_t dir_mode = (permission_class == PERMS_PRIVATE ?
1976 DIR_PERMS_PRIVATE : DIR_PERMS_PUBLIC);
1977 mode_t last_umask = posix_umask(0);
1978 mode_t group_umask = ~(dir_mode & S_IRWXG);
1979 int running_setgid = posix_process_running_setgid();
1981 if (permission_class == PERMS_PUBLIC)
1983 // if we're setgid, protect files against "other"
1984 // else keep umask(0) to make the dir world-writable
1987 posix_umask(last_umask & group_umask);
1989 dir_mode = DIR_PERMS_PUBLIC_ALL;
1992 if (posix_mkdir(dir, dir_mode) != 0)
1993 Warn("cannot create %s directory '%s': %s", text, dir, strerror(errno));
1995 if (permission_class == PERMS_PUBLIC && !running_setgid)
1996 chmod(dir, dir_mode);
1998 posix_umask(last_umask); // restore previous umask
2001 void InitMainUserDataDirectory(void)
2003 createDirectory(getMainUserGameDataDir(), "main user data");
2006 void InitUserDataDirectory(void)
2008 createDirectory(getMainUserGameDataDir(), "main user data");
2012 createDirectory(getUserDir(-1), "users");
2013 createDirectory(getUserDir(user.nr), "user data");
2017 void SetFilePermissions(char *filename, int permission_class)
2019 int running_setgid = posix_process_running_setgid();
2020 int perms = (permission_class == PERMS_PRIVATE ?
2021 FILE_PERMS_PRIVATE : FILE_PERMS_PUBLIC);
2023 if (permission_class == PERMS_PUBLIC && !running_setgid)
2024 perms = FILE_PERMS_PUBLIC_ALL;
2026 chmod(filename, perms);
2029 void fprintFileHeader(FILE *file, char *basename)
2031 char *prefix = "# ";
2034 fprintf_line_with_prefix(file, prefix, sep1, 77);
2035 fprintf(file, "%s%s\n", prefix, basename);
2036 fprintf_line_with_prefix(file, prefix, sep1, 77);
2037 fprintf(file, "\n");
2040 int getFileVersionFromCookieString(const char *cookie)
2042 const char *ptr_cookie1, *ptr_cookie2;
2043 const char *pattern1 = "_FILE_VERSION_";
2044 const char *pattern2 = "?.?";
2045 const int len_cookie = strlen(cookie);
2046 const int len_pattern1 = strlen(pattern1);
2047 const int len_pattern2 = strlen(pattern2);
2048 const int len_pattern = len_pattern1 + len_pattern2;
2049 int version_super, version_major;
2051 if (len_cookie <= len_pattern)
2054 ptr_cookie1 = &cookie[len_cookie - len_pattern];
2055 ptr_cookie2 = &cookie[len_cookie - len_pattern2];
2057 if (strncmp(ptr_cookie1, pattern1, len_pattern1) != 0)
2060 if (ptr_cookie2[0] < '0' || ptr_cookie2[0] > '9' ||
2061 ptr_cookie2[1] != '.' ||
2062 ptr_cookie2[2] < '0' || ptr_cookie2[2] > '9')
2065 version_super = ptr_cookie2[0] - '0';
2066 version_major = ptr_cookie2[2] - '0';
2068 return VERSION_IDENT(version_super, version_major, 0, 0);
2071 boolean checkCookieString(const char *cookie, const char *template)
2073 const char *pattern = "_FILE_VERSION_?.?";
2074 const int len_cookie = strlen(cookie);
2075 const int len_template = strlen(template);
2076 const int len_pattern = strlen(pattern);
2078 if (len_cookie != len_template)
2081 if (strncmp(cookie, template, len_cookie - len_pattern) != 0)
2088 // ----------------------------------------------------------------------------
2089 // setup file list and hash handling functions
2090 // ----------------------------------------------------------------------------
2092 char *getFormattedSetupEntry(char *token, char *value)
2095 static char entry[MAX_LINE_LEN];
2097 // if value is an empty string, just return token without value
2101 // start with the token and some spaces to format output line
2102 sprintf(entry, "%s:", token);
2103 for (i = strlen(entry); i < token_value_position; i++)
2106 // continue with the token's value
2107 strcat(entry, value);
2112 SetupFileList *newSetupFileList(char *token, char *value)
2114 SetupFileList *new = checked_malloc(sizeof(SetupFileList));
2116 new->token = getStringCopy(token);
2117 new->value = getStringCopy(value);
2124 void freeSetupFileList(SetupFileList *list)
2129 checked_free(list->token);
2130 checked_free(list->value);
2133 freeSetupFileList(list->next);
2138 char *getListEntry(SetupFileList *list, char *token)
2143 if (strEqual(list->token, token))
2146 return getListEntry(list->next, token);
2149 SetupFileList *setListEntry(SetupFileList *list, char *token, char *value)
2154 if (strEqual(list->token, token))
2156 checked_free(list->value);
2158 list->value = getStringCopy(value);
2162 else if (list->next == NULL)
2163 return (list->next = newSetupFileList(token, value));
2165 return setListEntry(list->next, token, value);
2168 SetupFileList *addListEntry(SetupFileList *list, char *token, char *value)
2173 if (list->next == NULL)
2174 return (list->next = newSetupFileList(token, value));
2176 return addListEntry(list->next, token, value);
2179 #if ENABLE_UNUSED_CODE
2181 static void printSetupFileList(SetupFileList *list)
2186 Debug("setup:printSetupFileList", "token: '%s'", list->token);
2187 Debug("setup:printSetupFileList", "value: '%s'", list->value);
2189 printSetupFileList(list->next);
2195 DEFINE_HASHTABLE_INSERT(insert_hash_entry, char, char);
2196 DEFINE_HASHTABLE_SEARCH(search_hash_entry, char, char);
2197 DEFINE_HASHTABLE_CHANGE(change_hash_entry, char, char);
2198 DEFINE_HASHTABLE_REMOVE(remove_hash_entry, char, char);
2200 #define insert_hash_entry hashtable_insert
2201 #define search_hash_entry hashtable_search
2202 #define change_hash_entry hashtable_change
2203 #define remove_hash_entry hashtable_remove
2206 unsigned int get_hash_from_key(void *key)
2211 This algorithm (k=33) was first reported by Dan Bernstein many years ago in
2212 'comp.lang.c'. Another version of this algorithm (now favored by Bernstein)
2213 uses XOR: hash(i) = hash(i - 1) * 33 ^ str[i]; the magic of number 33 (why
2214 it works better than many other constants, prime or not) has never been
2215 adequately explained.
2217 If you just want to have a good hash function, and cannot wait, djb2
2218 is one of the best string hash functions i know. It has excellent
2219 distribution and speed on many different sets of keys and table sizes.
2220 You are not likely to do better with one of the "well known" functions
2221 such as PJW, K&R, etc.
2223 Ozan (oz) Yigit [http://www.cs.yorku.ca/~oz/hash.html]
2226 char *str = (char *)key;
2227 unsigned int hash = 5381;
2230 while ((c = *str++))
2231 hash = ((hash << 5) + hash) + c; // hash * 33 + c
2236 int hash_keys_are_equal(void *key1, void *key2)
2238 return (strEqual((char *)key1, (char *)key2));
2241 SetupFileHash *newSetupFileHash(void)
2243 SetupFileHash *new_hash =
2244 create_hashtable(16, 0.75, get_hash_from_key, hash_keys_are_equal);
2246 if (new_hash == NULL)
2247 Fail("create_hashtable() failed -- out of memory");
2252 void freeSetupFileHash(SetupFileHash *hash)
2257 hashtable_destroy(hash, 1); // 1 == also free values stored in hash
2260 char *getHashEntry(SetupFileHash *hash, char *token)
2265 return search_hash_entry(hash, token);
2268 void setHashEntry(SetupFileHash *hash, char *token, char *value)
2275 value_copy = getStringCopy(value);
2277 // change value; if it does not exist, insert it as new
2278 if (!change_hash_entry(hash, token, value_copy))
2279 if (!insert_hash_entry(hash, getStringCopy(token), value_copy))
2280 Fail("cannot insert into hash -- aborting");
2283 char *removeHashEntry(SetupFileHash *hash, char *token)
2288 return remove_hash_entry(hash, token);
2291 #if ENABLE_UNUSED_CODE
2293 static void printSetupFileHash(SetupFileHash *hash)
2295 BEGIN_HASH_ITERATION(hash, itr)
2297 Debug("setup:printSetupFileHash", "token: '%s'", HASH_ITERATION_TOKEN(itr));
2298 Debug("setup:printSetupFileHash", "value: '%s'", HASH_ITERATION_VALUE(itr));
2300 END_HASH_ITERATION(hash, itr)
2305 #define ALLOW_TOKEN_VALUE_SEPARATOR_BEING_WHITESPACE 1
2306 #define CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING 0
2307 #define CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH 0
2309 static boolean token_value_separator_found = FALSE;
2310 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2311 static boolean token_value_separator_warning = FALSE;
2313 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2314 static boolean token_already_exists_warning = FALSE;
2317 static boolean getTokenValueFromSetupLineExt(char *line,
2318 char **token_ptr, char **value_ptr,
2319 char *filename, char *line_raw,
2321 boolean separator_required)
2323 static char line_copy[MAX_LINE_LEN + 1], line_raw_copy[MAX_LINE_LEN + 1];
2324 char *token, *value, *line_ptr;
2326 // when externally invoked via ReadTokenValueFromLine(), copy line buffers
2327 if (line_raw == NULL)
2329 strncpy(line_copy, line, MAX_LINE_LEN);
2330 line_copy[MAX_LINE_LEN] = '\0';
2333 strcpy(line_raw_copy, line_copy);
2334 line_raw = line_raw_copy;
2337 // cut trailing comment from input line
2338 for (line_ptr = line; *line_ptr; line_ptr++)
2340 if (*line_ptr == '#')
2347 // cut trailing whitespaces from input line
2348 for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
2349 if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
2352 // ignore empty lines
2356 // cut leading whitespaces from token
2357 for (token = line; *token; token++)
2358 if (*token != ' ' && *token != '\t')
2361 // start with empty value as reliable default
2364 token_value_separator_found = FALSE;
2366 // find end of token to determine start of value
2367 for (line_ptr = token; *line_ptr; line_ptr++)
2369 // first look for an explicit token/value separator, like ':' or '='
2370 if (*line_ptr == ':' || *line_ptr == '=')
2372 *line_ptr = '\0'; // terminate token string
2373 value = line_ptr + 1; // set beginning of value
2375 token_value_separator_found = TRUE;
2381 #if ALLOW_TOKEN_VALUE_SEPARATOR_BEING_WHITESPACE
2382 // fallback: if no token/value separator found, also allow whitespaces
2383 if (!token_value_separator_found && !separator_required)
2385 for (line_ptr = token; *line_ptr; line_ptr++)
2387 if (*line_ptr == ' ' || *line_ptr == '\t')
2389 *line_ptr = '\0'; // terminate token string
2390 value = line_ptr + 1; // set beginning of value
2392 token_value_separator_found = TRUE;
2398 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2399 if (token_value_separator_found)
2401 if (!token_value_separator_warning)
2403 Debug("setup", "---");
2405 if (filename != NULL)
2407 Debug("setup", "missing token/value separator(s) in config file:");
2408 Debug("setup", "- config file: '%s'", filename);
2412 Debug("setup", "missing token/value separator(s):");
2415 token_value_separator_warning = TRUE;
2418 if (filename != NULL)
2419 Debug("setup", "- line %d: '%s'", line_nr, line_raw);
2421 Debug("setup", "- line: '%s'", line_raw);
2427 // cut trailing whitespaces from token
2428 for (line_ptr = &token[strlen(token)]; line_ptr >= token; line_ptr--)
2429 if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
2432 // cut leading whitespaces from value
2433 for (; *value; value++)
2434 if (*value != ' ' && *value != '\t')
2443 boolean getTokenValueFromSetupLine(char *line, char **token, char **value)
2445 // while the internal (old) interface does not require a token/value
2446 // separator (for downwards compatibility with existing files which
2447 // don't use them), it is mandatory for the external (new) interface
2449 return getTokenValueFromSetupLineExt(line, token, value, NULL, NULL, 0, TRUE);
2452 static boolean loadSetupFileData(void *setup_file_data, char *filename,
2453 boolean top_recursion_level, boolean is_hash)
2455 static SetupFileHash *include_filename_hash = NULL;
2456 char line[MAX_LINE_LEN], line_raw[MAX_LINE_LEN], previous_line[MAX_LINE_LEN];
2457 char *token, *value, *line_ptr;
2458 void *insert_ptr = NULL;
2459 boolean read_continued_line = FALSE;
2461 int line_nr = 0, token_count = 0, include_count = 0;
2463 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2464 token_value_separator_warning = FALSE;
2467 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2468 token_already_exists_warning = FALSE;
2471 if (!(file = openFile(filename, MODE_READ)))
2473 #if DEBUG_NO_CONFIG_FILE
2474 Debug("setup", "cannot open configuration file '%s'", filename);
2480 // use "insert pointer" to store list end for constant insertion complexity
2482 insert_ptr = setup_file_data;
2484 // on top invocation, create hash to mark included files (to prevent loops)
2485 if (top_recursion_level)
2486 include_filename_hash = newSetupFileHash();
2488 // mark this file as already included (to prevent including it again)
2489 setHashEntry(include_filename_hash, getBaseNamePtr(filename), "true");
2491 while (!checkEndOfFile(file))
2493 // read next line of input file
2494 if (!getStringFromFile(file, line, MAX_LINE_LEN))
2497 // check if line was completely read and is terminated by line break
2498 if (strlen(line) > 0 && line[strlen(line) - 1] == '\n')
2501 // cut trailing line break (this can be newline and/or carriage return)
2502 for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
2503 if ((*line_ptr == '\n' || *line_ptr == '\r') && *(line_ptr + 1) == '\0')
2506 // copy raw input line for later use (mainly debugging output)
2507 strcpy(line_raw, line);
2509 if (read_continued_line)
2511 // append new line to existing line, if there is enough space
2512 if (strlen(previous_line) + strlen(line_ptr) < MAX_LINE_LEN)
2513 strcat(previous_line, line_ptr);
2515 strcpy(line, previous_line); // copy storage buffer to line
2517 read_continued_line = FALSE;
2520 // if the last character is '\', continue at next line
2521 if (strlen(line) > 0 && line[strlen(line) - 1] == '\\')
2523 line[strlen(line) - 1] = '\0'; // cut off trailing backslash
2524 strcpy(previous_line, line); // copy line to storage buffer
2526 read_continued_line = TRUE;
2531 if (!getTokenValueFromSetupLineExt(line, &token, &value, filename,
2532 line_raw, line_nr, FALSE))
2537 if (strEqual(token, "include"))
2539 if (getHashEntry(include_filename_hash, value) == NULL)
2541 char *basepath = getBasePath(filename);
2542 char *basename = getBaseName(value);
2543 char *filename_include = getPath2(basepath, basename);
2545 loadSetupFileData(setup_file_data, filename_include, FALSE, is_hash);
2549 free(filename_include);
2555 Warn("ignoring already processed file '%s'", value);
2562 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2564 getHashEntry((SetupFileHash *)setup_file_data, token);
2566 if (old_value != NULL)
2568 if (!token_already_exists_warning)
2570 Debug("setup", "---");
2571 Debug("setup", "duplicate token(s) found in config file:");
2572 Debug("setup", "- config file: '%s'", filename);
2574 token_already_exists_warning = TRUE;
2577 Debug("setup", "- token: '%s' (in line %d)", token, line_nr);
2578 Debug("setup", " old value: '%s'", old_value);
2579 Debug("setup", " new value: '%s'", value);
2583 setHashEntry((SetupFileHash *)setup_file_data, token, value);
2587 insert_ptr = addListEntry((SetupFileList *)insert_ptr, token, value);
2597 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2598 if (token_value_separator_warning)
2599 Debug("setup", "---");
2602 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2603 if (token_already_exists_warning)
2604 Debug("setup", "---");
2607 if (token_count == 0 && include_count == 0)
2608 Warn("configuration file '%s' is empty", filename);
2610 if (top_recursion_level)
2611 freeSetupFileHash(include_filename_hash);
2616 static int compareSetupFileData(const void *object1, const void *object2)
2618 const struct ConfigInfo *entry1 = (struct ConfigInfo *)object1;
2619 const struct ConfigInfo *entry2 = (struct ConfigInfo *)object2;
2621 return strcmp(entry1->token, entry2->token);
2624 static void saveSetupFileHash(SetupFileHash *hash, char *filename)
2626 int item_count = hashtable_count(hash);
2627 int item_size = sizeof(struct ConfigInfo);
2628 struct ConfigInfo *sort_array = checked_malloc(item_count * item_size);
2632 // copy string pointers from hash to array
2633 BEGIN_HASH_ITERATION(hash, itr)
2635 sort_array[i].token = HASH_ITERATION_TOKEN(itr);
2636 sort_array[i].value = HASH_ITERATION_VALUE(itr);
2640 if (i > item_count) // should never happen
2643 END_HASH_ITERATION(hash, itr)
2645 // sort string pointers from hash in array
2646 qsort(sort_array, item_count, item_size, compareSetupFileData);
2648 if (!(file = fopen(filename, MODE_WRITE)))
2650 Warn("cannot write configuration file '%s'", filename);
2655 fprintf(file, "%s\n\n", getFormattedSetupEntry("program.version",
2656 program.version_string));
2657 for (i = 0; i < item_count; i++)
2658 fprintf(file, "%s\n", getFormattedSetupEntry(sort_array[i].token,
2659 sort_array[i].value));
2662 checked_free(sort_array);
2665 SetupFileList *loadSetupFileList(char *filename)
2667 SetupFileList *setup_file_list = newSetupFileList("", "");
2668 SetupFileList *first_valid_list_entry;
2670 if (!loadSetupFileData(setup_file_list, filename, TRUE, FALSE))
2672 freeSetupFileList(setup_file_list);
2677 first_valid_list_entry = setup_file_list->next;
2679 // free empty list header
2680 setup_file_list->next = NULL;
2681 freeSetupFileList(setup_file_list);
2683 return first_valid_list_entry;
2686 SetupFileHash *loadSetupFileHash(char *filename)
2688 SetupFileHash *setup_file_hash = newSetupFileHash();
2690 if (!loadSetupFileData(setup_file_hash, filename, TRUE, TRUE))
2692 freeSetupFileHash(setup_file_hash);
2697 return setup_file_hash;
2701 // ============================================================================
2703 // ============================================================================
2705 #define TOKEN_STR_LAST_LEVEL_SERIES "last_level_series"
2706 #define TOKEN_STR_LAST_PLAYED_LEVEL "last_played_level"
2707 #define TOKEN_STR_HANDICAP_LEVEL "handicap_level"
2708 #define TOKEN_STR_LAST_USER "last_user"
2710 // level directory info
2711 #define LEVELINFO_TOKEN_IDENTIFIER 0
2712 #define LEVELINFO_TOKEN_NAME 1
2713 #define LEVELINFO_TOKEN_NAME_SORTING 2
2714 #define LEVELINFO_TOKEN_AUTHOR 3
2715 #define LEVELINFO_TOKEN_YEAR 4
2716 #define LEVELINFO_TOKEN_PROGRAM_TITLE 5
2717 #define LEVELINFO_TOKEN_PROGRAM_COPYRIGHT 6
2718 #define LEVELINFO_TOKEN_PROGRAM_COMPANY 7
2719 #define LEVELINFO_TOKEN_IMPORTED_FROM 8
2720 #define LEVELINFO_TOKEN_IMPORTED_BY 9
2721 #define LEVELINFO_TOKEN_TESTED_BY 10
2722 #define LEVELINFO_TOKEN_LEVELS 11
2723 #define LEVELINFO_TOKEN_FIRST_LEVEL 12
2724 #define LEVELINFO_TOKEN_SORT_PRIORITY 13
2725 #define LEVELINFO_TOKEN_LATEST_ENGINE 14
2726 #define LEVELINFO_TOKEN_LEVEL_GROUP 15
2727 #define LEVELINFO_TOKEN_READONLY 16
2728 #define LEVELINFO_TOKEN_GRAPHICS_SET_ECS 17
2729 #define LEVELINFO_TOKEN_GRAPHICS_SET_AGA 18
2730 #define LEVELINFO_TOKEN_GRAPHICS_SET 19
2731 #define LEVELINFO_TOKEN_SOUNDS_SET_DEFAULT 20
2732 #define LEVELINFO_TOKEN_SOUNDS_SET_LOWPASS 21
2733 #define LEVELINFO_TOKEN_SOUNDS_SET 22
2734 #define LEVELINFO_TOKEN_MUSIC_SET 23
2735 #define LEVELINFO_TOKEN_FILENAME 24
2736 #define LEVELINFO_TOKEN_FILETYPE 25
2737 #define LEVELINFO_TOKEN_SPECIAL_FLAGS 26
2738 #define LEVELINFO_TOKEN_EMPTY_LEVEL_NAME 27
2739 #define LEVELINFO_TOKEN_FORCE_LEVEL_NAME 28
2740 #define LEVELINFO_TOKEN_HANDICAP 29
2741 #define LEVELINFO_TOKEN_TIME_LIMIT 30
2742 #define LEVELINFO_TOKEN_SKIP_LEVELS 31
2743 #define LEVELINFO_TOKEN_USE_EMC_TILES 32
2744 #define LEVELINFO_TOKEN_INFO_SCREENS_FROM_MAIN 33
2746 #define NUM_LEVELINFO_TOKENS 34
2748 static LevelDirTree ldi;
2750 static struct TokenInfo levelinfo_tokens[] =
2752 // level directory info
2753 { TYPE_STRING, &ldi.identifier, "identifier" },
2754 { TYPE_STRING, &ldi.name, "name" },
2755 { TYPE_STRING, &ldi.name_sorting, "name_sorting" },
2756 { TYPE_STRING, &ldi.author, "author" },
2757 { TYPE_STRING, &ldi.year, "year" },
2758 { TYPE_STRING, &ldi.program_title, "program_title" },
2759 { TYPE_STRING, &ldi.program_copyright, "program_copyright" },
2760 { TYPE_STRING, &ldi.program_company, "program_company" },
2761 { TYPE_STRING, &ldi.imported_from, "imported_from" },
2762 { TYPE_STRING, &ldi.imported_by, "imported_by" },
2763 { TYPE_STRING, &ldi.tested_by, "tested_by" },
2764 { TYPE_INTEGER, &ldi.levels, "levels" },
2765 { TYPE_INTEGER, &ldi.first_level, "first_level" },
2766 { TYPE_INTEGER, &ldi.sort_priority, "sort_priority" },
2767 { TYPE_BOOLEAN, &ldi.latest_engine, "latest_engine" },
2768 { TYPE_BOOLEAN, &ldi.level_group, "level_group" },
2769 { TYPE_BOOLEAN, &ldi.readonly, "readonly" },
2770 { TYPE_STRING, &ldi.graphics_set_ecs, "graphics_set.ecs" },
2771 { TYPE_STRING, &ldi.graphics_set_aga, "graphics_set.aga" },
2772 { TYPE_STRING, &ldi.graphics_set, "graphics_set" },
2773 { TYPE_STRING, &ldi.sounds_set_default,"sounds_set.default" },
2774 { TYPE_STRING, &ldi.sounds_set_lowpass,"sounds_set.lowpass" },
2775 { TYPE_STRING, &ldi.sounds_set, "sounds_set" },
2776 { TYPE_STRING, &ldi.music_set, "music_set" },
2777 { TYPE_STRING, &ldi.level_filename, "filename" },
2778 { TYPE_STRING, &ldi.level_filetype, "filetype" },
2779 { TYPE_STRING, &ldi.special_flags, "special_flags" },
2780 { TYPE_STRING, &ldi.empty_level_name, "empty_level_name" },
2781 { TYPE_BOOLEAN, &ldi.force_level_name, "force_level_name" },
2782 { TYPE_BOOLEAN, &ldi.handicap, "handicap" },
2783 { TYPE_BOOLEAN, &ldi.time_limit, "time_limit" },
2784 { TYPE_BOOLEAN, &ldi.skip_levels, "skip_levels" },
2785 { TYPE_BOOLEAN, &ldi.use_emc_tiles, "use_emc_tiles" },
2786 { TYPE_BOOLEAN, &ldi.info_screens_from_main, "info_screens_from_main" }
2789 static struct TokenInfo artworkinfo_tokens[] =
2791 // artwork directory info
2792 { TYPE_STRING, &ldi.identifier, "identifier" },
2793 { TYPE_STRING, &ldi.subdir, "subdir" },
2794 { TYPE_STRING, &ldi.name, "name" },
2795 { TYPE_STRING, &ldi.name_sorting, "name_sorting" },
2796 { TYPE_STRING, &ldi.author, "author" },
2797 { TYPE_STRING, &ldi.program_title, "program_title" },
2798 { TYPE_STRING, &ldi.program_copyright, "program_copyright" },
2799 { TYPE_STRING, &ldi.program_company, "program_company" },
2800 { TYPE_INTEGER, &ldi.sort_priority, "sort_priority" },
2801 { TYPE_STRING, &ldi.basepath, "basepath" },
2802 { TYPE_STRING, &ldi.fullpath, "fullpath" },
2803 { TYPE_BOOLEAN, &ldi.in_user_dir, "in_user_dir" },
2804 { TYPE_STRING, &ldi.class_desc, "class_desc" },
2809 static char *optional_tokens[] =
2812 "program_copyright",
2818 static void setTreeInfoToDefaults(TreeInfo *ti, int type)
2822 ti->node_top = (ti->type == TREE_TYPE_LEVEL_DIR ? &leveldir_first :
2823 ti->type == TREE_TYPE_GRAPHICS_DIR ? &artwork.gfx_first :
2824 ti->type == TREE_TYPE_SOUNDS_DIR ? &artwork.snd_first :
2825 ti->type == TREE_TYPE_MUSIC_DIR ? &artwork.mus_first :
2828 ti->node_parent = NULL;
2829 ti->node_group = NULL;
2836 ti->fullpath = NULL;
2837 ti->basepath = NULL;
2838 ti->identifier = NULL;
2839 ti->name = getStringCopy(ANONYMOUS_NAME);
2840 ti->name_sorting = NULL;
2841 ti->author = getStringCopy(ANONYMOUS_NAME);
2844 ti->program_title = NULL;
2845 ti->program_copyright = NULL;
2846 ti->program_company = NULL;
2848 ti->sort_priority = LEVELCLASS_UNDEFINED; // default: least priority
2849 ti->latest_engine = FALSE; // default: get from level
2850 ti->parent_link = FALSE;
2851 ti->is_copy = FALSE;
2852 ti->in_user_dir = FALSE;
2853 ti->user_defined = FALSE;
2855 ti->class_desc = NULL;
2857 ti->infotext = getStringCopy(TREE_INFOTEXT(ti->type));
2859 if (ti->type == TREE_TYPE_LEVEL_DIR)
2861 ti->imported_from = NULL;
2862 ti->imported_by = NULL;
2863 ti->tested_by = NULL;
2865 ti->graphics_set_ecs = NULL;
2866 ti->graphics_set_aga = NULL;
2867 ti->graphics_set = NULL;
2868 ti->sounds_set_default = NULL;
2869 ti->sounds_set_lowpass = NULL;
2870 ti->sounds_set = NULL;
2871 ti->music_set = NULL;
2872 ti->graphics_path = getStringCopy(UNDEFINED_FILENAME);
2873 ti->sounds_path = getStringCopy(UNDEFINED_FILENAME);
2874 ti->music_path = getStringCopy(UNDEFINED_FILENAME);
2876 ti->level_filename = NULL;
2877 ti->level_filetype = NULL;
2879 ti->special_flags = NULL;
2881 ti->empty_level_name = NULL;
2882 ti->force_level_name = FALSE;
2885 ti->first_level = 0;
2887 ti->level_group = FALSE;
2888 ti->handicap_level = 0;
2889 ti->readonly = TRUE;
2890 ti->handicap = TRUE;
2891 ti->time_limit = TRUE;
2892 ti->skip_levels = FALSE;
2894 ti->use_emc_tiles = FALSE;
2895 ti->info_screens_from_main = FALSE;
2899 static void setTreeInfoToDefaultsFromParent(TreeInfo *ti, TreeInfo *parent)
2903 Warn("setTreeInfoToDefaultsFromParent(): parent == NULL");
2905 setTreeInfoToDefaults(ti, TREE_TYPE_UNDEFINED);
2910 // copy all values from the parent structure
2912 ti->type = parent->type;
2914 ti->node_top = parent->node_top;
2915 ti->node_parent = parent;
2916 ti->node_group = NULL;
2923 ti->fullpath = NULL;
2924 ti->basepath = NULL;
2925 ti->identifier = NULL;
2926 ti->name = getStringCopy(ANONYMOUS_NAME);
2927 ti->name_sorting = NULL;
2928 ti->author = getStringCopy(parent->author);
2929 ti->year = getStringCopy(parent->year);
2931 ti->program_title = getStringCopy(parent->program_title);
2932 ti->program_copyright = getStringCopy(parent->program_copyright);
2933 ti->program_company = getStringCopy(parent->program_company);
2935 ti->sort_priority = parent->sort_priority;
2936 ti->latest_engine = parent->latest_engine;
2937 ti->parent_link = FALSE;
2938 ti->is_copy = FALSE;
2939 ti->in_user_dir = parent->in_user_dir;
2940 ti->user_defined = parent->user_defined;
2941 ti->color = parent->color;
2942 ti->class_desc = getStringCopy(parent->class_desc);
2944 ti->infotext = getStringCopy(parent->infotext);
2946 if (ti->type == TREE_TYPE_LEVEL_DIR)
2948 ti->imported_from = getStringCopy(parent->imported_from);
2949 ti->imported_by = getStringCopy(parent->imported_by);
2950 ti->tested_by = getStringCopy(parent->tested_by);
2952 ti->graphics_set_ecs = getStringCopy(parent->graphics_set_ecs);
2953 ti->graphics_set_aga = getStringCopy(parent->graphics_set_aga);
2954 ti->graphics_set = getStringCopy(parent->graphics_set);
2955 ti->sounds_set_default = getStringCopy(parent->sounds_set_default);
2956 ti->sounds_set_lowpass = getStringCopy(parent->sounds_set_lowpass);
2957 ti->sounds_set = getStringCopy(parent->sounds_set);
2958 ti->music_set = getStringCopy(parent->music_set);
2959 ti->graphics_path = getStringCopy(UNDEFINED_FILENAME);
2960 ti->sounds_path = getStringCopy(UNDEFINED_FILENAME);
2961 ti->music_path = getStringCopy(UNDEFINED_FILENAME);
2963 ti->level_filename = getStringCopy(parent->level_filename);
2964 ti->level_filetype = getStringCopy(parent->level_filetype);
2966 ti->special_flags = getStringCopy(parent->special_flags);
2968 ti->empty_level_name = getStringCopy(parent->empty_level_name);
2969 ti->force_level_name = parent->force_level_name;
2971 ti->levels = parent->levels;
2972 ti->first_level = parent->first_level;
2973 ti->last_level = parent->last_level;
2974 ti->level_group = FALSE;
2975 ti->handicap_level = parent->handicap_level;
2976 ti->readonly = parent->readonly;
2977 ti->handicap = parent->handicap;
2978 ti->time_limit = parent->time_limit;
2979 ti->skip_levels = parent->skip_levels;
2981 ti->use_emc_tiles = parent->use_emc_tiles;
2982 ti->info_screens_from_main = parent->info_screens_from_main;
2986 static TreeInfo *getTreeInfoCopy(TreeInfo *ti)
2988 TreeInfo *ti_copy = newTreeInfo();
2990 // copy all values from the original structure
2992 ti_copy->type = ti->type;
2994 ti_copy->node_top = ti->node_top;
2995 ti_copy->node_parent = ti->node_parent;
2996 ti_copy->node_group = ti->node_group;
2997 ti_copy->next = ti->next;
2999 ti_copy->cl_first = ti->cl_first;
3000 ti_copy->cl_cursor = ti->cl_cursor;
3002 ti_copy->subdir = getStringCopy(ti->subdir);
3003 ti_copy->fullpath = getStringCopy(ti->fullpath);
3004 ti_copy->basepath = getStringCopy(ti->basepath);
3005 ti_copy->identifier = getStringCopy(ti->identifier);
3006 ti_copy->name = getStringCopy(ti->name);
3007 ti_copy->name_sorting = getStringCopy(ti->name_sorting);
3008 ti_copy->author = getStringCopy(ti->author);
3009 ti_copy->year = getStringCopy(ti->year);
3011 ti_copy->program_title = getStringCopy(ti->program_title);
3012 ti_copy->program_copyright = getStringCopy(ti->program_copyright);
3013 ti_copy->program_company = getStringCopy(ti->program_company);
3015 ti_copy->imported_from = getStringCopy(ti->imported_from);
3016 ti_copy->imported_by = getStringCopy(ti->imported_by);
3017 ti_copy->tested_by = getStringCopy(ti->tested_by);
3019 ti_copy->graphics_set_ecs = getStringCopy(ti->graphics_set_ecs);
3020 ti_copy->graphics_set_aga = getStringCopy(ti->graphics_set_aga);
3021 ti_copy->graphics_set = getStringCopy(ti->graphics_set);
3022 ti_copy->sounds_set_default = getStringCopy(ti->sounds_set_default);
3023 ti_copy->sounds_set_lowpass = getStringCopy(ti->sounds_set_lowpass);
3024 ti_copy->sounds_set = getStringCopy(ti->sounds_set);
3025 ti_copy->music_set = getStringCopy(ti->music_set);
3026 ti_copy->graphics_path = getStringCopy(ti->graphics_path);
3027 ti_copy->sounds_path = getStringCopy(ti->sounds_path);
3028 ti_copy->music_path = getStringCopy(ti->music_path);
3030 ti_copy->level_filename = getStringCopy(ti->level_filename);
3031 ti_copy->level_filetype = getStringCopy(ti->level_filetype);
3033 ti_copy->special_flags = getStringCopy(ti->special_flags);
3035 ti_copy->empty_level_name = getStringCopy(ti->empty_level_name);
3036 ti_copy->force_level_name = ti->force_level_name;
3038 ti_copy->levels = ti->levels;
3039 ti_copy->first_level = ti->first_level;
3040 ti_copy->last_level = ti->last_level;
3041 ti_copy->sort_priority = ti->sort_priority;
3043 ti_copy->latest_engine = ti->latest_engine;
3045 ti_copy->level_group = ti->level_group;
3046 ti_copy->parent_link = ti->parent_link;
3047 ti_copy->is_copy = ti->is_copy;
3048 ti_copy->in_user_dir = ti->in_user_dir;
3049 ti_copy->user_defined = ti->user_defined;
3050 ti_copy->readonly = ti->readonly;
3051 ti_copy->handicap = ti->handicap;
3052 ti_copy->time_limit = ti->time_limit;
3053 ti_copy->skip_levels = ti->skip_levels;
3055 ti_copy->use_emc_tiles = ti->use_emc_tiles;
3056 ti_copy->info_screens_from_main = ti->info_screens_from_main;
3058 ti_copy->color = ti->color;
3059 ti_copy->class_desc = getStringCopy(ti->class_desc);
3060 ti_copy->handicap_level = ti->handicap_level;
3062 ti_copy->infotext = getStringCopy(ti->infotext);
3067 void freeTreeInfo(TreeInfo *ti)
3072 checked_free(ti->subdir);
3073 checked_free(ti->fullpath);
3074 checked_free(ti->basepath);
3075 checked_free(ti->identifier);
3077 checked_free(ti->name);
3078 checked_free(ti->name_sorting);
3079 checked_free(ti->author);
3080 checked_free(ti->year);
3082 checked_free(ti->program_title);
3083 checked_free(ti->program_copyright);
3084 checked_free(ti->program_company);
3086 checked_free(ti->class_desc);
3088 checked_free(ti->infotext);
3090 if (ti->type == TREE_TYPE_LEVEL_DIR)
3092 checked_free(ti->imported_from);
3093 checked_free(ti->imported_by);
3094 checked_free(ti->tested_by);
3096 checked_free(ti->graphics_set_ecs);
3097 checked_free(ti->graphics_set_aga);
3098 checked_free(ti->graphics_set);
3099 checked_free(ti->sounds_set_default);
3100 checked_free(ti->sounds_set_lowpass);
3101 checked_free(ti->sounds_set);
3102 checked_free(ti->music_set);
3104 checked_free(ti->graphics_path);
3105 checked_free(ti->sounds_path);
3106 checked_free(ti->music_path);
3108 checked_free(ti->level_filename);
3109 checked_free(ti->level_filetype);
3111 checked_free(ti->special_flags);
3114 // recursively free child node
3116 freeTreeInfo(ti->node_group);
3118 // recursively free next node
3120 freeTreeInfo(ti->next);
3125 void setSetupInfo(struct TokenInfo *token_info,
3126 int token_nr, char *token_value)
3128 int token_type = token_info[token_nr].type;
3129 void *setup_value = token_info[token_nr].value;
3131 if (token_value == NULL)
3134 // set setup field to corresponding token value
3139 *(boolean *)setup_value = get_boolean_from_string(token_value);
3143 *(int *)setup_value = get_switch3_from_string(token_value);
3147 *(Key *)setup_value = getKeyFromKeyName(token_value);
3151 *(Key *)setup_value = getKeyFromX11KeyName(token_value);
3155 *(int *)setup_value = get_integer_from_string(token_value);
3159 checked_free(*(char **)setup_value);
3160 *(char **)setup_value = getStringCopy(token_value);
3164 *(int *)setup_value = get_player_nr_from_string(token_value);
3172 static int compareTreeInfoEntries(const void *object1, const void *object2)
3174 const TreeInfo *entry1 = *((TreeInfo **)object1);
3175 const TreeInfo *entry2 = *((TreeInfo **)object2);
3176 int tree_sorting1 = TREE_SORTING(entry1);
3177 int tree_sorting2 = TREE_SORTING(entry2);
3179 if (tree_sorting1 != tree_sorting2)
3180 return (tree_sorting1 - tree_sorting2);
3182 return strcasecmp(entry1->name_sorting, entry2->name_sorting);
3185 static TreeInfo *createParentTreeInfoNode(TreeInfo *node_parent)
3189 if (node_parent == NULL)
3192 ti_new = newTreeInfo();
3193 setTreeInfoToDefaults(ti_new, node_parent->type);
3195 ti_new->node_parent = node_parent;
3196 ti_new->parent_link = TRUE;
3198 setString(&ti_new->identifier, node_parent->identifier);
3199 setString(&ti_new->name, BACKLINK_TEXT_PARENT);
3200 setString(&ti_new->name_sorting, ti_new->name);
3202 setString(&ti_new->subdir, STRING_PARENT_DIRECTORY);
3203 setString(&ti_new->fullpath, node_parent->fullpath);
3205 ti_new->sort_priority = LEVELCLASS_PARENT;
3206 ti_new->latest_engine = node_parent->latest_engine;
3208 setString(&ti_new->class_desc, getLevelClassDescription(ti_new));
3210 pushTreeInfo(&node_parent->node_group, ti_new);
3215 static TreeInfo *createTopTreeInfoNode(TreeInfo *node_first)
3217 if (node_first == NULL)
3220 TreeInfo *ti_new = newTreeInfo();
3221 int type = node_first->type;
3223 setTreeInfoToDefaults(ti_new, type);
3225 ti_new->node_parent = NULL;
3226 ti_new->parent_link = FALSE;
3228 setString(&ti_new->identifier, "top_tree_node");
3229 setString(&ti_new->name, TREE_INFOTEXT(type));
3230 setString(&ti_new->name_sorting, ti_new->name);
3232 setString(&ti_new->subdir, STRING_TOP_DIRECTORY);
3233 setString(&ti_new->fullpath, ".");
3235 ti_new->sort_priority = LEVELCLASS_TOP;
3236 ti_new->latest_engine = node_first->latest_engine;
3238 setString(&ti_new->class_desc, TREE_INFOTEXT(type));
3240 ti_new->node_group = node_first;
3241 ti_new->level_group = TRUE;
3243 TreeInfo *ti_new2 = createParentTreeInfoNode(ti_new);
3245 setString(&ti_new2->name, TREE_BACKLINK_TEXT(type));
3246 setString(&ti_new2->name_sorting, ti_new2->name);
3251 static void setTreeInfoParentNodes(TreeInfo *node, TreeInfo *node_parent)
3255 if (node->node_group)
3256 setTreeInfoParentNodes(node->node_group, node);
3258 node->node_parent = node_parent;
3264 TreeInfo *addTopTreeInfoNode(TreeInfo *node_first)
3266 // add top tree node with back link node in previous tree
3267 node_first = createTopTreeInfoNode(node_first);
3269 // set all parent links (back links) in complete tree
3270 setTreeInfoParentNodes(node_first, NULL);
3276 // ----------------------------------------------------------------------------
3277 // functions for handling level and custom artwork info cache
3278 // ----------------------------------------------------------------------------
3280 static void LoadArtworkInfoCache(void)
3282 InitCacheDirectory();
3284 if (artworkinfo_cache_old == NULL)
3286 char *filename = getPath2(getCacheDir(), ARTWORKINFO_CACHE_FILE);
3288 // try to load artwork info hash from already existing cache file
3289 artworkinfo_cache_old = loadSetupFileHash(filename);
3291 // try to get program version that artwork info cache was written with
3292 char *version = getHashEntry(artworkinfo_cache_old, "program.version");
3294 // check program version of artwork info cache against current version
3295 if (!strEqual(version, program.version_string))
3297 freeSetupFileHash(artworkinfo_cache_old);
3299 artworkinfo_cache_old = NULL;
3302 // if no artwork info cache file was found, start with empty hash
3303 if (artworkinfo_cache_old == NULL)
3304 artworkinfo_cache_old = newSetupFileHash();
3309 if (artworkinfo_cache_new == NULL)
3310 artworkinfo_cache_new = newSetupFileHash();
3312 update_artworkinfo_cache = FALSE;
3315 static void SaveArtworkInfoCache(void)
3317 if (!update_artworkinfo_cache)
3320 char *filename = getPath2(getCacheDir(), ARTWORKINFO_CACHE_FILE);
3322 InitCacheDirectory();
3324 saveSetupFileHash(artworkinfo_cache_new, filename);
3329 static char *getCacheTokenPrefix(char *prefix1, char *prefix2)
3331 static char *prefix = NULL;
3333 checked_free(prefix);
3335 prefix = getStringCat2WithSeparator(prefix1, prefix2, ".");
3340 // (identical to above function, but separate string buffer needed -- nasty)
3341 static char *getCacheToken(char *prefix, char *suffix)
3343 static char *token = NULL;
3345 checked_free(token);
3347 token = getStringCat2WithSeparator(prefix, suffix, ".");
3352 static char *getFileTimestampString(char *filename)
3354 return getStringCopy(i_to_a(getFileTimestampEpochSeconds(filename)));
3357 static boolean modifiedFileTimestamp(char *filename, char *timestamp_string)
3359 struct stat file_status;
3361 if (timestamp_string == NULL)
3364 if (!fileExists(filename)) // file does not exist
3365 return (atoi(timestamp_string) != 0);
3367 if (stat(filename, &file_status) != 0) // cannot stat file
3370 return (file_status.st_mtime != atoi(timestamp_string));
3373 static TreeInfo *getArtworkInfoCacheEntry(LevelDirTree *level_node, int type)
3375 char *identifier = level_node->subdir;
3376 char *type_string = ARTWORK_DIRECTORY(type);
3377 char *token_prefix = getCacheTokenPrefix(type_string, identifier);
3378 char *token_main = getCacheToken(token_prefix, "CACHED");
3379 char *cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
3380 boolean cached = (cache_entry != NULL && strEqual(cache_entry, "true"));
3381 TreeInfo *artwork_info = NULL;
3383 if (!use_artworkinfo_cache)
3386 if (optional_tokens_hash == NULL)
3390 // create hash from list of optional tokens (for quick access)
3391 optional_tokens_hash = newSetupFileHash();
3392 for (i = 0; optional_tokens[i] != NULL; i++)
3393 setHashEntry(optional_tokens_hash, optional_tokens[i], "");
3400 artwork_info = newTreeInfo();
3401 setTreeInfoToDefaults(artwork_info, type);
3403 // set all structure fields according to the token/value pairs
3404 ldi = *artwork_info;
3405 for (i = 0; artworkinfo_tokens[i].type != -1; i++)
3407 char *token_suffix = artworkinfo_tokens[i].text;
3408 char *token = getCacheToken(token_prefix, token_suffix);
3409 char *value = getHashEntry(artworkinfo_cache_old, token);
3411 (getHashEntry(optional_tokens_hash, token_suffix) != NULL);
3413 setSetupInfo(artworkinfo_tokens, i, value);
3415 // check if cache entry for this item is mandatory, but missing
3416 if (value == NULL && !optional)
3418 Warn("missing cache entry '%s'", token);
3424 *artwork_info = ldi;
3429 char *filename_levelinfo = getPath2(getLevelDirFromTreeInfo(level_node),
3430 LEVELINFO_FILENAME);
3431 char *filename_artworkinfo = getPath2(getSetupArtworkDir(artwork_info),
3432 ARTWORKINFO_FILENAME(type));
3434 // check if corresponding "levelinfo.conf" file has changed
3435 token_main = getCacheToken(token_prefix, "TIMESTAMP_LEVELINFO");
3436 cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
3438 if (modifiedFileTimestamp(filename_levelinfo, cache_entry))
3441 // check if corresponding "<artworkinfo>.conf" file has changed
3442 token_main = getCacheToken(token_prefix, "TIMESTAMP_ARTWORKINFO");
3443 cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
3445 if (modifiedFileTimestamp(filename_artworkinfo, cache_entry))
3448 checked_free(filename_levelinfo);
3449 checked_free(filename_artworkinfo);
3452 if (!cached && artwork_info != NULL)
3454 freeTreeInfo(artwork_info);
3459 return artwork_info;
3462 static void setArtworkInfoCacheEntry(TreeInfo *artwork_info,
3463 LevelDirTree *level_node, int type)
3465 char *identifier = level_node->subdir;
3466 char *type_string = ARTWORK_DIRECTORY(type);
3467 char *token_prefix = getCacheTokenPrefix(type_string, identifier);
3468 char *token_main = getCacheToken(token_prefix, "CACHED");
3469 boolean set_cache_timestamps = TRUE;
3472 setHashEntry(artworkinfo_cache_new, token_main, "true");
3474 if (set_cache_timestamps)
3476 char *filename_levelinfo = getPath2(getLevelDirFromTreeInfo(level_node),
3477 LEVELINFO_FILENAME);
3478 char *filename_artworkinfo = getPath2(getSetupArtworkDir(artwork_info),
3479 ARTWORKINFO_FILENAME(type));
3480 char *timestamp_levelinfo = getFileTimestampString(filename_levelinfo);
3481 char *timestamp_artworkinfo = getFileTimestampString(filename_artworkinfo);
3483 token_main = getCacheToken(token_prefix, "TIMESTAMP_LEVELINFO");
3484 setHashEntry(artworkinfo_cache_new, token_main, timestamp_levelinfo);
3486 token_main = getCacheToken(token_prefix, "TIMESTAMP_ARTWORKINFO");
3487 setHashEntry(artworkinfo_cache_new, token_main, timestamp_artworkinfo);
3489 checked_free(filename_levelinfo);
3490 checked_free(filename_artworkinfo);
3491 checked_free(timestamp_levelinfo);
3492 checked_free(timestamp_artworkinfo);
3495 ldi = *artwork_info;
3496 for (i = 0; artworkinfo_tokens[i].type != -1; i++)
3498 char *token = getCacheToken(token_prefix, artworkinfo_tokens[i].text);
3499 char *value = getSetupValue(artworkinfo_tokens[i].type,
3500 artworkinfo_tokens[i].value);
3502 setHashEntry(artworkinfo_cache_new, token, value);
3507 // ----------------------------------------------------------------------------
3508 // functions for loading level info and custom artwork info
3509 // ----------------------------------------------------------------------------
3511 int GetZipFileTreeType(char *zip_filename)
3513 static char *top_dir_path = NULL;
3514 static char *top_dir_conf_filename[NUM_BASE_TREE_TYPES] = { NULL };
3515 static char *conf_basename[NUM_BASE_TREE_TYPES] =
3517 GRAPHICSINFO_FILENAME,
3518 SOUNDSINFO_FILENAME,
3524 checked_free(top_dir_path);
3525 top_dir_path = NULL;
3527 for (j = 0; j < NUM_BASE_TREE_TYPES; j++)
3529 checked_free(top_dir_conf_filename[j]);
3530 top_dir_conf_filename[j] = NULL;
3533 char **zip_entries = zip_list(zip_filename);
3535 // check if zip file successfully opened
3536 if (zip_entries == NULL || zip_entries[0] == NULL)
3537 return TREE_TYPE_UNDEFINED;
3539 // first zip file entry is expected to be top level directory
3540 char *top_dir = zip_entries[0];
3542 // check if valid top level directory found in zip file
3543 if (!strSuffix(top_dir, "/"))
3544 return TREE_TYPE_UNDEFINED;
3546 // get filenames of valid configuration files in top level directory
3547 for (j = 0; j < NUM_BASE_TREE_TYPES; j++)
3548 top_dir_conf_filename[j] = getStringCat2(top_dir, conf_basename[j]);
3550 int tree_type = TREE_TYPE_UNDEFINED;
3553 while (zip_entries[e] != NULL)
3555 // check if every zip file entry is below top level directory
3556 if (!strPrefix(zip_entries[e], top_dir))
3557 return TREE_TYPE_UNDEFINED;
3559 // check if this zip file entry is a valid configuration filename
3560 for (j = 0; j < NUM_BASE_TREE_TYPES; j++)
3562 if (strEqual(zip_entries[e], top_dir_conf_filename[j]))
3564 // only exactly one valid configuration file allowed
3565 if (tree_type != TREE_TYPE_UNDEFINED)
3566 return TREE_TYPE_UNDEFINED;
3578 static boolean CheckZipFileForDirectory(char *zip_filename, char *directory,
3581 static char *top_dir_path = NULL;
3582 static char *top_dir_conf_filename = NULL;
3584 checked_free(top_dir_path);
3585 checked_free(top_dir_conf_filename);
3587 top_dir_path = NULL;
3588 top_dir_conf_filename = NULL;
3590 char *conf_basename = (tree_type == TREE_TYPE_LEVEL_DIR ? LEVELINFO_FILENAME :
3591 ARTWORKINFO_FILENAME(tree_type));
3593 // check if valid configuration filename determined
3594 if (conf_basename == NULL || strEqual(conf_basename, ""))
3597 char **zip_entries = zip_list(zip_filename);
3599 // check if zip file successfully opened
3600 if (zip_entries == NULL || zip_entries[0] == NULL)
3603 // first zip file entry is expected to be top level directory
3604 char *top_dir = zip_entries[0];
3606 // check if valid top level directory found in zip file
3607 if (!strSuffix(top_dir, "/"))
3610 // get path of extracted top level directory
3611 top_dir_path = getPath2(directory, top_dir);
3613 // remove trailing directory separator from top level directory path
3614 // (required to be able to check for file and directory in next step)
3615 top_dir_path[strlen(top_dir_path) - 1] = '\0';
3617 // check if zip file's top level directory already exists in target directory
3618 if (fileExists(top_dir_path)) // (checks for file and directory)
3621 // get filename of configuration file in top level directory
3622 top_dir_conf_filename = getStringCat2(top_dir, conf_basename);
3624 boolean found_top_dir_conf_filename = FALSE;
3627 while (zip_entries[i] != NULL)
3629 // check if every zip file entry is below top level directory
3630 if (!strPrefix(zip_entries[i], top_dir))
3633 // check if this zip file entry is the configuration filename
3634 if (strEqual(zip_entries[i], top_dir_conf_filename))
3635 found_top_dir_conf_filename = TRUE;
3640 // check if valid configuration filename was found in zip file
3641 if (!found_top_dir_conf_filename)
3647 char *ExtractZipFileIntoDirectory(char *zip_filename, char *directory,
3650 boolean zip_file_valid = CheckZipFileForDirectory(zip_filename, directory,
3653 if (!zip_file_valid)
3655 Warn("zip file '%s' rejected!", zip_filename);
3660 char **zip_entries = zip_extract(zip_filename, directory);
3662 if (zip_entries == NULL)
3664 Warn("zip file '%s' could not be extracted!", zip_filename);
3669 Info("zip file '%s' successfully extracted!", zip_filename);
3671 // first zip file entry contains top level directory
3672 char *top_dir = zip_entries[0];
3674 // remove trailing directory separator from top level directory
3675 top_dir[strlen(top_dir) - 1] = '\0';
3680 static void ProcessZipFilesInDirectory(char *directory, int tree_type)
3683 DirectoryEntry *dir_entry;
3685 if ((dir = openDirectory(directory)) == NULL)
3687 // display error if directory is main "options.graphics_directory" etc.
3688 if (tree_type == TREE_TYPE_LEVEL_DIR ||
3689 directory == OPTIONS_ARTWORK_DIRECTORY(tree_type))
3690 Warn("cannot read directory '%s'", directory);
3695 while ((dir_entry = readDirectory(dir)) != NULL) // loop all entries
3697 // skip non-zip files (and also directories with zip extension)
3698 if (!strSuffixLower(dir_entry->basename, ".zip") || dir_entry->is_directory)
3701 char *zip_filename = getPath2(directory, dir_entry->basename);
3702 char *zip_filename_extracted = getStringCat2(zip_filename, ".extracted");
3703 char *zip_filename_rejected = getStringCat2(zip_filename, ".rejected");
3705 // check if zip file hasn't already been extracted or rejected
3706 if (!fileExists(zip_filename_extracted) &&
3707 !fileExists(zip_filename_rejected))
3709 char *top_dir = ExtractZipFileIntoDirectory(zip_filename, directory,
3711 char *marker_filename = (top_dir != NULL ? zip_filename_extracted :
3712 zip_filename_rejected);
3715 // create empty file to mark zip file as extracted or rejected
3716 if ((marker_file = fopen(marker_filename, MODE_WRITE)))
3717 fclose(marker_file);
3720 free(zip_filename_extracted);
3721 free(zip_filename_rejected);
3725 closeDirectory(dir);
3728 // forward declaration for recursive call by "LoadLevelInfoFromLevelDir()"
3729 static void LoadLevelInfoFromLevelDir(TreeInfo **, TreeInfo *, char *);
3731 static boolean LoadLevelInfoFromLevelConf(TreeInfo **node_first,
3732 TreeInfo *node_parent,
3733 char *level_directory,
3734 char *directory_name)
3736 char *directory_path = getPath2(level_directory, directory_name);
3737 char *filename = getPath2(directory_path, LEVELINFO_FILENAME);
3738 SetupFileHash *setup_file_hash;
3739 LevelDirTree *leveldir_new = NULL;
3742 // unless debugging, silently ignore directories without "levelinfo.conf"
3743 if (!options.debug && !fileExists(filename))
3745 free(directory_path);
3751 setup_file_hash = loadSetupFileHash(filename);
3753 if (setup_file_hash == NULL)
3755 #if DEBUG_NO_CONFIG_FILE
3756 Debug("setup", "ignoring level directory '%s'", directory_path);
3759 free(directory_path);
3765 leveldir_new = newTreeInfo();
3768 setTreeInfoToDefaultsFromParent(leveldir_new, node_parent);
3770 setTreeInfoToDefaults(leveldir_new, TREE_TYPE_LEVEL_DIR);
3772 leveldir_new->subdir = getStringCopy(directory_name);
3774 // set all structure fields according to the token/value pairs
3775 ldi = *leveldir_new;
3776 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
3777 setSetupInfo(levelinfo_tokens, i,
3778 getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
3779 *leveldir_new = ldi;
3781 if (strEqual(leveldir_new->name, ANONYMOUS_NAME))
3782 setString(&leveldir_new->name, leveldir_new->subdir);
3784 if (leveldir_new->identifier == NULL)
3785 leveldir_new->identifier = getStringCopy(leveldir_new->subdir);
3787 if (leveldir_new->name_sorting == NULL)
3788 leveldir_new->name_sorting = getStringCopy(leveldir_new->name);
3790 if (node_parent == NULL) // top level group
3792 leveldir_new->basepath = getStringCopy(level_directory);
3793 leveldir_new->fullpath = getStringCopy(leveldir_new->subdir);
3795 else // sub level group
3797 leveldir_new->basepath = getStringCopy(node_parent->basepath);
3798 leveldir_new->fullpath = getPath2(node_parent->fullpath, directory_name);
3801 leveldir_new->last_level =
3802 leveldir_new->first_level + leveldir_new->levels - 1;
3804 leveldir_new->in_user_dir =
3805 (!strEqual(leveldir_new->basepath, options.level_directory));
3807 // adjust some settings if user's private level directory was detected
3808 if (leveldir_new->sort_priority == LEVELCLASS_UNDEFINED &&
3809 leveldir_new->in_user_dir &&
3810 (strEqual(leveldir_new->subdir, getLoginName()) ||
3811 strEqual(leveldir_new->name, getLoginName()) ||
3812 strEqual(leveldir_new->author, getRealName())))
3814 leveldir_new->sort_priority = LEVELCLASS_PRIVATE_START;
3815 leveldir_new->readonly = FALSE;
3818 leveldir_new->user_defined =
3819 (leveldir_new->in_user_dir && IS_LEVELCLASS_PRIVATE(leveldir_new));
3821 setString(&leveldir_new->class_desc, getLevelClassDescription(leveldir_new));
3823 leveldir_new->handicap_level = // set handicap to default value
3824 (leveldir_new->user_defined || !leveldir_new->handicap ?
3825 leveldir_new->last_level : leveldir_new->first_level);
3827 DrawInitTextItem(leveldir_new->name);
3829 pushTreeInfo(node_first, leveldir_new);
3831 freeSetupFileHash(setup_file_hash);
3833 if (leveldir_new->level_group)
3835 // create node to link back to current level directory
3836 createParentTreeInfoNode(leveldir_new);
3838 // recursively step into sub-directory and look for more level series
3839 LoadLevelInfoFromLevelDir(&leveldir_new->node_group,
3840 leveldir_new, directory_path);
3843 free(directory_path);
3849 static void LoadLevelInfoFromLevelDir(TreeInfo **node_first,
3850 TreeInfo *node_parent,
3851 char *level_directory)
3853 // ---------- 1st stage: process any level set zip files ----------
3855 ProcessZipFilesInDirectory(level_directory, TREE_TYPE_LEVEL_DIR);
3857 // ---------- 2nd stage: check for level set directories ----------
3860 DirectoryEntry *dir_entry;
3861 boolean valid_entry_found = FALSE;
3863 if ((dir = openDirectory(level_directory)) == NULL)
3865 Warn("cannot read level directory '%s'", level_directory);
3870 while ((dir_entry = readDirectory(dir)) != NULL) // loop all entries
3872 char *directory_name = dir_entry->basename;
3873 char *directory_path = getPath2(level_directory, directory_name);
3875 // skip entries for current and parent directory
3876 if (strEqual(directory_name, ".") ||
3877 strEqual(directory_name, ".."))
3879 free(directory_path);
3884 // find out if directory entry is itself a directory
3885 if (!dir_entry->is_directory) // not a directory
3887 free(directory_path);
3892 free(directory_path);
3894 if (strEqual(directory_name, GRAPHICS_DIRECTORY) ||
3895 strEqual(directory_name, SOUNDS_DIRECTORY) ||
3896 strEqual(directory_name, MUSIC_DIRECTORY))
3899 valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
3904 closeDirectory(dir);
3906 // special case: top level directory may directly contain "levelinfo.conf"
3907 if (node_parent == NULL && !valid_entry_found)
3909 // check if this directory directly contains a file "levelinfo.conf"
3910 valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
3911 level_directory, ".");
3914 boolean valid_entry_expected =
3915 (strEqual(level_directory, options.level_directory) ||
3916 setup.internal.create_user_levelset);
3918 if (valid_entry_expected && !valid_entry_found)
3919 Warn("cannot find any valid level series in directory '%s'",
3923 boolean AdjustGraphicsForEMC(void)
3925 boolean settings_changed = FALSE;
3927 settings_changed |= adjustTreeGraphicsForEMC(leveldir_first_all);
3928 settings_changed |= adjustTreeGraphicsForEMC(leveldir_first);
3930 return settings_changed;
3933 boolean AdjustSoundsForEMC(void)
3935 boolean settings_changed = FALSE;
3937 settings_changed |= adjustTreeSoundsForEMC(leveldir_first_all);
3938 settings_changed |= adjustTreeSoundsForEMC(leveldir_first);
3940 return settings_changed;
3943 void LoadLevelInfo(void)
3945 InitUserLevelDirectory(getLoginName());
3947 DrawInitTextHead("Loading level series");
3949 LoadLevelInfoFromLevelDir(&leveldir_first, NULL, options.level_directory);
3950 LoadLevelInfoFromLevelDir(&leveldir_first, NULL, getUserLevelDir(NULL));
3952 leveldir_first = createTopTreeInfoNode(leveldir_first);
3954 /* after loading all level set information, clone the level directory tree
3955 and remove all level sets without levels (these may still contain artwork
3956 to be offered in the setup menu as "custom artwork", and are therefore
3957 checked for existing artwork in the function "LoadLevelArtworkInfo()") */
3958 leveldir_first_all = leveldir_first;
3959 cloneTree(&leveldir_first, leveldir_first_all, TRUE);
3961 AdjustGraphicsForEMC();
3962 AdjustSoundsForEMC();
3964 // before sorting, the first entries will be from the user directory
3965 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
3967 if (leveldir_first == NULL)
3968 Fail("cannot find any valid level series in any directory");
3970 sortTreeInfo(&leveldir_first);
3972 #if ENABLE_UNUSED_CODE
3973 dumpTreeInfo(leveldir_first, 0);
3977 static boolean LoadArtworkInfoFromArtworkConf(TreeInfo **node_first,
3978 TreeInfo *node_parent,
3979 char *base_directory,
3980 char *directory_name, int type)
3982 char *directory_path = getPath2(base_directory, directory_name);
3983 char *filename = getPath2(directory_path, ARTWORKINFO_FILENAME(type));
3984 SetupFileHash *setup_file_hash = NULL;
3985 TreeInfo *artwork_new = NULL;
3988 if (fileExists(filename))
3989 setup_file_hash = loadSetupFileHash(filename);
3991 if (setup_file_hash == NULL) // no config file -- look for artwork files
3994 DirectoryEntry *dir_entry;
3995 boolean valid_file_found = FALSE;
3997 if ((dir = openDirectory(directory_path)) != NULL)
3999 while ((dir_entry = readDirectory(dir)) != NULL)
4001 if (FileIsArtworkType(dir_entry->filename, type))
4003 valid_file_found = TRUE;
4009 closeDirectory(dir);
4012 if (!valid_file_found)
4014 #if DEBUG_NO_CONFIG_FILE
4015 if (!strEqual(directory_name, "."))
4016 Debug("setup", "ignoring artwork directory '%s'", directory_path);
4019 free(directory_path);
4026 artwork_new = newTreeInfo();
4029 setTreeInfoToDefaultsFromParent(artwork_new, node_parent);
4031 setTreeInfoToDefaults(artwork_new, type);
4033 artwork_new->subdir = getStringCopy(directory_name);
4035 if (setup_file_hash) // (before defining ".color" and ".class_desc")
4037 // set all structure fields according to the token/value pairs
4039 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
4040 setSetupInfo(levelinfo_tokens, i,
4041 getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
4044 if (strEqual(artwork_new->name, ANONYMOUS_NAME))
4045 setString(&artwork_new->name, artwork_new->subdir);
4047 if (artwork_new->identifier == NULL)
4048 artwork_new->identifier = getStringCopy(artwork_new->subdir);
4050 if (artwork_new->name_sorting == NULL)
4051 artwork_new->name_sorting = getStringCopy(artwork_new->name);
4054 if (node_parent == NULL) // top level group
4056 artwork_new->basepath = getStringCopy(base_directory);
4057 artwork_new->fullpath = getStringCopy(artwork_new->subdir);
4059 else // sub level group
4061 artwork_new->basepath = getStringCopy(node_parent->basepath);
4062 artwork_new->fullpath = getPath2(node_parent->fullpath, directory_name);
4065 artwork_new->in_user_dir =
4066 (!strEqual(artwork_new->basepath, OPTIONS_ARTWORK_DIRECTORY(type)));
4068 setString(&artwork_new->class_desc, getLevelClassDescription(artwork_new));
4070 if (setup_file_hash == NULL) // (after determining ".user_defined")
4072 if (strEqual(artwork_new->subdir, "."))
4074 if (artwork_new->user_defined)
4076 setString(&artwork_new->identifier, "private");
4077 artwork_new->sort_priority = ARTWORKCLASS_PRIVATE;
4081 setString(&artwork_new->identifier, "classic");
4082 artwork_new->sort_priority = ARTWORKCLASS_CLASSICS;
4085 setString(&artwork_new->class_desc,
4086 getLevelClassDescription(artwork_new));
4090 setString(&artwork_new->identifier, artwork_new->subdir);
4093 setString(&artwork_new->name, artwork_new->identifier);
4094 setString(&artwork_new->name_sorting, artwork_new->name);
4097 pushTreeInfo(node_first, artwork_new);
4099 freeSetupFileHash(setup_file_hash);
4101 free(directory_path);
4107 static void LoadArtworkInfoFromArtworkDir(TreeInfo **node_first,
4108 TreeInfo *node_parent,
4109 char *base_directory, int type)
4111 // ---------- 1st stage: process any artwork set zip files ----------
4113 ProcessZipFilesInDirectory(base_directory, type);
4115 // ---------- 2nd stage: check for artwork set directories ----------
4118 DirectoryEntry *dir_entry;
4119 boolean valid_entry_found = FALSE;
4121 if ((dir = openDirectory(base_directory)) == NULL)
4123 // display error if directory is main "options.graphics_directory" etc.
4124 if (base_directory == OPTIONS_ARTWORK_DIRECTORY(type))
4125 Warn("cannot read directory '%s'", base_directory);
4130 while ((dir_entry = readDirectory(dir)) != NULL) // loop all entries
4132 char *directory_name = dir_entry->basename;
4133 char *directory_path = getPath2(base_directory, directory_name);
4135 // skip directory entries for current and parent directory
4136 if (strEqual(directory_name, ".") ||
4137 strEqual(directory_name, ".."))
4139 free(directory_path);
4144 // skip directory entries which are not a directory
4145 if (!dir_entry->is_directory) // not a directory
4147 free(directory_path);
4152 free(directory_path);
4154 // check if this directory contains artwork with or without config file
4155 valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
4157 directory_name, type);
4160 closeDirectory(dir);
4162 // check if this directory directly contains artwork itself
4163 valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
4164 base_directory, ".",
4166 if (!valid_entry_found)
4167 Warn("cannot find any valid artwork in directory '%s'", base_directory);
4170 static TreeInfo *getDummyArtworkInfo(int type)
4172 // this is only needed when there is completely no artwork available
4173 TreeInfo *artwork_new = newTreeInfo();
4175 setTreeInfoToDefaults(artwork_new, type);
4177 setString(&artwork_new->subdir, UNDEFINED_FILENAME);
4178 setString(&artwork_new->fullpath, UNDEFINED_FILENAME);
4179 setString(&artwork_new->basepath, UNDEFINED_FILENAME);
4181 setString(&artwork_new->identifier, UNDEFINED_FILENAME);
4182 setString(&artwork_new->name, UNDEFINED_FILENAME);
4183 setString(&artwork_new->name_sorting, UNDEFINED_FILENAME);
4188 void SetCurrentArtwork(int type)
4190 ArtworkDirTree **current_ptr = ARTWORK_CURRENT_PTR(artwork, type);
4191 ArtworkDirTree *first_node = ARTWORK_FIRST_NODE(artwork, type);
4192 char *setup_set = SETUP_ARTWORK_SET(setup, type);
4193 char *default_subdir = ARTWORK_DEFAULT_SUBDIR(type);
4195 // set current artwork to artwork configured in setup menu
4196 *current_ptr = getTreeInfoFromIdentifier(first_node, setup_set);
4198 // if not found, set current artwork to default artwork
4199 if (*current_ptr == NULL)
4200 *current_ptr = getTreeInfoFromIdentifier(first_node, default_subdir);
4202 // if not found, set current artwork to first artwork in tree
4203 if (*current_ptr == NULL)
4204 *current_ptr = getFirstValidTreeInfoEntry(first_node);
4207 void ChangeCurrentArtworkIfNeeded(int type)
4209 char *current_identifier = ARTWORK_CURRENT_IDENTIFIER(artwork, type);
4210 char *setup_set = SETUP_ARTWORK_SET(setup, type);
4212 if (!strEqual(current_identifier, setup_set))
4213 SetCurrentArtwork(type);
4216 void LoadArtworkInfo(void)
4218 LoadArtworkInfoCache();
4220 DrawInitTextHead("Looking for custom artwork");
4222 LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
4223 options.graphics_directory,
4224 TREE_TYPE_GRAPHICS_DIR);
4225 LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
4226 getUserGraphicsDir(),
4227 TREE_TYPE_GRAPHICS_DIR);
4229 LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
4230 options.sounds_directory,
4231 TREE_TYPE_SOUNDS_DIR);
4232 LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
4234 TREE_TYPE_SOUNDS_DIR);
4236 LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
4237 options.music_directory,
4238 TREE_TYPE_MUSIC_DIR);
4239 LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
4241 TREE_TYPE_MUSIC_DIR);
4243 if (artwork.gfx_first == NULL)
4244 artwork.gfx_first = getDummyArtworkInfo(TREE_TYPE_GRAPHICS_DIR);
4245 if (artwork.snd_first == NULL)
4246 artwork.snd_first = getDummyArtworkInfo(TREE_TYPE_SOUNDS_DIR);
4247 if (artwork.mus_first == NULL)
4248 artwork.mus_first = getDummyArtworkInfo(TREE_TYPE_MUSIC_DIR);
4250 // before sorting, the first entries will be from the user directory
4251 SetCurrentArtwork(ARTWORK_TYPE_GRAPHICS);
4252 SetCurrentArtwork(ARTWORK_TYPE_SOUNDS);
4253 SetCurrentArtwork(ARTWORK_TYPE_MUSIC);
4255 artwork.gfx_current_identifier = artwork.gfx_current->identifier;
4256 artwork.snd_current_identifier = artwork.snd_current->identifier;
4257 artwork.mus_current_identifier = artwork.mus_current->identifier;
4259 #if ENABLE_UNUSED_CODE
4260 Debug("setup:LoadArtworkInfo", "graphics set == %s",
4261 artwork.gfx_current_identifier);
4262 Debug("setup:LoadArtworkInfo", "sounds set == %s",
4263 artwork.snd_current_identifier);
4264 Debug("setup:LoadArtworkInfo", "music set == %s",
4265 artwork.mus_current_identifier);
4268 sortTreeInfo(&artwork.gfx_first);
4269 sortTreeInfo(&artwork.snd_first);
4270 sortTreeInfo(&artwork.mus_first);
4272 #if ENABLE_UNUSED_CODE
4273 dumpTreeInfo(artwork.gfx_first, 0);
4274 dumpTreeInfo(artwork.snd_first, 0);
4275 dumpTreeInfo(artwork.mus_first, 0);
4279 static void MoveArtworkInfoIntoSubTree(ArtworkDirTree **artwork_node)
4281 ArtworkDirTree *artwork_new = newTreeInfo();
4282 char *top_node_name = "standalone artwork";
4284 setTreeInfoToDefaults(artwork_new, (*artwork_node)->type);
4286 artwork_new->level_group = TRUE;
4288 setString(&artwork_new->identifier, top_node_name);
4289 setString(&artwork_new->name, top_node_name);
4290 setString(&artwork_new->name_sorting, top_node_name);
4292 // create node to link back to current custom artwork directory
4293 createParentTreeInfoNode(artwork_new);
4295 // move existing custom artwork tree into newly created sub-tree
4296 artwork_new->node_group->next = *artwork_node;
4298 // change custom artwork tree to contain only newly created node
4299 *artwork_node = artwork_new;
4302 static void LoadArtworkInfoFromLevelInfoExt(ArtworkDirTree **artwork_node,
4303 ArtworkDirTree *node_parent,
4304 LevelDirTree *level_node,
4305 boolean empty_level_set_mode)
4307 int type = (*artwork_node)->type;
4309 // recursively check all level directories for artwork sub-directories
4313 boolean empty_level_set = (level_node->levels == 0);
4315 // check all tree entries for artwork, but skip parent link entries
4316 if (!level_node->parent_link && empty_level_set == empty_level_set_mode)
4318 TreeInfo *artwork_new = getArtworkInfoCacheEntry(level_node, type);
4319 boolean cached = (artwork_new != NULL);
4323 pushTreeInfo(artwork_node, artwork_new);
4327 TreeInfo *topnode_last = *artwork_node;
4328 char *path = getPath2(getLevelDirFromTreeInfo(level_node),
4329 ARTWORK_DIRECTORY(type));
4331 LoadArtworkInfoFromArtworkDir(artwork_node, NULL, path, type);
4333 if (topnode_last != *artwork_node) // check for newly added node
4335 artwork_new = *artwork_node;
4337 setString(&artwork_new->identifier, level_node->subdir);
4338 setString(&artwork_new->name, level_node->name);
4339 setString(&artwork_new->name_sorting, level_node->name_sorting);
4341 artwork_new->sort_priority = level_node->sort_priority;
4342 artwork_new->in_user_dir = level_node->in_user_dir;
4344 update_artworkinfo_cache = TRUE;
4350 // insert artwork info (from old cache or filesystem) into new cache
4351 if (artwork_new != NULL)
4352 setArtworkInfoCacheEntry(artwork_new, level_node, type);
4355 DrawInitTextItem(level_node->name);
4357 if (level_node->node_group != NULL)
4359 TreeInfo *artwork_new = newTreeInfo();
4362 setTreeInfoToDefaultsFromParent(artwork_new, node_parent);
4364 setTreeInfoToDefaults(artwork_new, type);
4366 artwork_new->level_group = TRUE;
4368 setString(&artwork_new->identifier, level_node->subdir);
4370 if (node_parent == NULL) // check for top tree node
4372 char *top_node_name = (empty_level_set_mode ?
4373 "artwork for certain level sets" :
4374 "artwork included in level sets");
4376 setString(&artwork_new->name, top_node_name);
4377 setString(&artwork_new->name_sorting, top_node_name);
4381 setString(&artwork_new->name, level_node->name);
4382 setString(&artwork_new->name_sorting, level_node->name_sorting);
4385 pushTreeInfo(artwork_node, artwork_new);
4387 // create node to link back to current custom artwork directory
4388 createParentTreeInfoNode(artwork_new);
4390 // recursively step into sub-directory and look for more custom artwork
4391 LoadArtworkInfoFromLevelInfoExt(&artwork_new->node_group, artwork_new,
4392 level_node->node_group,
4393 empty_level_set_mode);
4395 // if sub-tree has no custom artwork at all, remove it
4396 if (artwork_new->node_group->next == NULL)
4397 removeTreeInfo(artwork_node);
4400 level_node = level_node->next;
4404 static void LoadArtworkInfoFromLevelInfo(ArtworkDirTree **artwork_node)
4406 // move peviously loaded artwork tree into separate sub-tree
4407 MoveArtworkInfoIntoSubTree(artwork_node);
4409 // load artwork from level sets into separate sub-trees
4410 LoadArtworkInfoFromLevelInfoExt(artwork_node, NULL, leveldir_first_all, TRUE);
4411 LoadArtworkInfoFromLevelInfoExt(artwork_node, NULL, leveldir_first_all, FALSE);
4413 // add top tree node over all sub-trees and set parent links
4414 *artwork_node = addTopTreeInfoNode(*artwork_node);
4417 void LoadLevelArtworkInfo(void)
4419 print_timestamp_init("LoadLevelArtworkInfo");
4421 DrawInitTextHead("Looking for custom level artwork");
4423 print_timestamp_time("DrawTimeText");
4425 LoadArtworkInfoFromLevelInfo(&artwork.gfx_first);
4426 print_timestamp_time("LoadArtworkInfoFromLevelInfo (gfx)");
4427 LoadArtworkInfoFromLevelInfo(&artwork.snd_first);
4428 print_timestamp_time("LoadArtworkInfoFromLevelInfo (snd)");
4429 LoadArtworkInfoFromLevelInfo(&artwork.mus_first);
4430 print_timestamp_time("LoadArtworkInfoFromLevelInfo (mus)");
4432 SaveArtworkInfoCache();
4434 print_timestamp_time("SaveArtworkInfoCache");
4436 // needed for reloading level artwork not known at ealier stage
4437 ChangeCurrentArtworkIfNeeded(ARTWORK_TYPE_GRAPHICS);
4438 ChangeCurrentArtworkIfNeeded(ARTWORK_TYPE_SOUNDS);
4439 ChangeCurrentArtworkIfNeeded(ARTWORK_TYPE_MUSIC);
4441 print_timestamp_time("getTreeInfoFromIdentifier");
4443 sortTreeInfo(&artwork.gfx_first);
4444 sortTreeInfo(&artwork.snd_first);
4445 sortTreeInfo(&artwork.mus_first);
4447 print_timestamp_time("sortTreeInfo");
4449 #if ENABLE_UNUSED_CODE
4450 dumpTreeInfo(artwork.gfx_first, 0);
4451 dumpTreeInfo(artwork.snd_first, 0);
4452 dumpTreeInfo(artwork.mus_first, 0);
4455 print_timestamp_done("LoadLevelArtworkInfo");
4458 static boolean AddTreeSetToTreeInfoExt(TreeInfo *tree_node_old, char *tree_dir,
4459 char *tree_subdir_new, int type)
4461 if (tree_node_old == NULL)
4463 if (type == TREE_TYPE_LEVEL_DIR)
4465 // get level info tree node of personal user level set
4466 tree_node_old = getTreeInfoFromIdentifier(leveldir_first, getLoginName());
4468 // this may happen if "setup.internal.create_user_levelset" is FALSE
4469 // or if file "levelinfo.conf" is missing in personal user level set
4470 if (tree_node_old == NULL)
4471 tree_node_old = leveldir_first->node_group;
4475 // get artwork info tree node of first artwork set
4476 tree_node_old = ARTWORK_FIRST_NODE(artwork, type);
4480 if (tree_dir == NULL)
4481 tree_dir = TREE_USERDIR(type);
4483 if (tree_node_old == NULL ||
4485 tree_subdir_new == NULL) // should not happen
4488 int draw_deactivation_mask = GetDrawDeactivationMask();
4490 // override draw deactivation mask (temporarily disable drawing)
4491 SetDrawDeactivationMask(REDRAW_ALL);
4493 if (type == TREE_TYPE_LEVEL_DIR)
4495 // load new level set config and add it next to first user level set
4496 LoadLevelInfoFromLevelConf(&tree_node_old->next,
4497 tree_node_old->node_parent,
4498 tree_dir, tree_subdir_new);
4502 // load new artwork set config and add it next to first artwork set
4503 LoadArtworkInfoFromArtworkConf(&tree_node_old->next,
4504 tree_node_old->node_parent,
4505 tree_dir, tree_subdir_new, type);
4508 // set draw deactivation mask to previous value
4509 SetDrawDeactivationMask(draw_deactivation_mask);
4511 // get first node of level or artwork info tree
4512 TreeInfo **tree_node_first = TREE_FIRST_NODE_PTR(type);
4514 // get tree info node of newly added level or artwork set
4515 TreeInfo *tree_node_new = getTreeInfoFromIdentifier(*tree_node_first,
4518 if (tree_node_new == NULL) // should not happen
4521 // correct top link and parent node link of newly created tree node
4522 tree_node_new->node_top = tree_node_old->node_top;
4523 tree_node_new->node_parent = tree_node_old->node_parent;
4525 // sort tree info to adjust position of newly added tree set
4526 sortTreeInfo(tree_node_first);
4531 void AddTreeSetToTreeInfo(TreeInfo *tree_node, char *tree_dir,
4532 char *tree_subdir_new, int type)
4534 if (!AddTreeSetToTreeInfoExt(tree_node, tree_dir, tree_subdir_new, type))
4535 Fail("internal tree info structure corrupted -- aborting");
4538 void AddUserLevelSetToLevelInfo(char *level_subdir_new)
4540 AddTreeSetToTreeInfo(NULL, NULL, level_subdir_new, TREE_TYPE_LEVEL_DIR);
4543 char *getArtworkIdentifierForUserLevelSet(int type)
4545 char *classic_artwork_set = getClassicArtworkSet(type);
4547 // check for custom artwork configured in "levelinfo.conf"
4548 char *leveldir_artwork_set =
4549 *LEVELDIR_ARTWORK_SET_PTR(leveldir_current, type);
4550 boolean has_leveldir_artwork_set =
4551 (leveldir_artwork_set != NULL && !strEqual(leveldir_artwork_set,
4552 classic_artwork_set));
4554 // check for custom artwork in sub-directory "graphics" etc.
4555 TreeInfo *artwork_first_node = ARTWORK_FIRST_NODE(artwork, type);
4556 char *leveldir_identifier = leveldir_current->identifier;
4557 boolean has_artwork_subdir =
4558 (getTreeInfoFromIdentifier(artwork_first_node,
4559 leveldir_identifier) != NULL);
4561 return (has_leveldir_artwork_set ? leveldir_artwork_set :
4562 has_artwork_subdir ? leveldir_identifier :
4563 classic_artwork_set);
4566 TreeInfo *getArtworkTreeInfoForUserLevelSet(int type)
4568 char *artwork_set = getArtworkIdentifierForUserLevelSet(type);
4569 TreeInfo *artwork_first_node = ARTWORK_FIRST_NODE(artwork, type);
4570 TreeInfo *ti = getTreeInfoFromIdentifier(artwork_first_node, artwork_set);
4574 ti = getTreeInfoFromIdentifier(artwork_first_node,
4575 ARTWORK_DEFAULT_SUBDIR(type));
4577 Fail("cannot find default graphics -- should not happen");
4583 boolean checkIfCustomArtworkExistsForCurrentLevelSet(void)
4585 char *graphics_set =
4586 getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_GRAPHICS);
4588 getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_SOUNDS);
4590 getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_MUSIC);
4592 return (!strEqual(graphics_set, GFX_CLASSIC_SUBDIR) ||
4593 !strEqual(sounds_set, SND_CLASSIC_SUBDIR) ||
4594 !strEqual(music_set, MUS_CLASSIC_SUBDIR));
4597 boolean UpdateUserLevelSet(char *level_subdir, char *level_name,
4598 char *level_author, int num_levels)
4600 char *filename = getPath2(getUserLevelDir(level_subdir), LEVELINFO_FILENAME);
4601 char *filename_tmp = getStringCat2(filename, ".tmp");
4603 FILE *file_tmp = NULL;
4604 char line[MAX_LINE_LEN];
4605 boolean success = FALSE;
4606 LevelDirTree *leveldir = getTreeInfoFromIdentifier(leveldir_first,
4608 // update values in level directory tree
4610 if (level_name != NULL)
4611 setString(&leveldir->name, level_name);
4613 if (level_author != NULL)
4614 setString(&leveldir->author, level_author);
4616 if (num_levels != -1)
4617 leveldir->levels = num_levels;
4619 // update values that depend on other values
4621 setString(&leveldir->name_sorting, leveldir->name);
4623 leveldir->last_level = leveldir->first_level + leveldir->levels - 1;
4625 // sort order of level sets may have changed
4626 sortTreeInfo(&leveldir_first);
4628 if ((file = fopen(filename, MODE_READ)) &&
4629 (file_tmp = fopen(filename_tmp, MODE_WRITE)))
4631 while (fgets(line, MAX_LINE_LEN, file))
4633 if (strPrefix(line, "name:") && level_name != NULL)
4634 fprintf(file_tmp, "%-32s%s\n", "name:", level_name);
4635 else if (strPrefix(line, "author:") && level_author != NULL)
4636 fprintf(file_tmp, "%-32s%s\n", "author:", level_author);
4637 else if (strPrefix(line, "levels:") && num_levels != -1)
4638 fprintf(file_tmp, "%-32s%d\n", "levels:", num_levels);
4640 fputs(line, file_tmp);
4653 success = (rename(filename_tmp, filename) == 0);
4661 boolean CreateUserLevelSet(char *level_subdir, char *level_name,
4662 char *level_author, int num_levels,
4663 boolean use_artwork_set)
4665 LevelDirTree *level_info;
4670 // create user level sub-directory, if needed
4671 createDirectory(getUserLevelDir(level_subdir), "user level");
4673 filename = getPath2(getUserLevelDir(level_subdir), LEVELINFO_FILENAME);
4675 if (!(file = fopen(filename, MODE_WRITE)))
4677 Warn("cannot write level info file '%s'", filename);
4684 level_info = newTreeInfo();
4686 // always start with reliable default values
4687 setTreeInfoToDefaults(level_info, TREE_TYPE_LEVEL_DIR);
4689 setString(&level_info->name, level_name);
4690 setString(&level_info->author, level_author);
4691 level_info->levels = num_levels;
4692 level_info->first_level = 1;
4693 level_info->sort_priority = LEVELCLASS_PRIVATE_START;
4694 level_info->readonly = FALSE;
4696 if (use_artwork_set)
4698 level_info->graphics_set =
4699 getStringCopy(getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_GRAPHICS));
4700 level_info->sounds_set =
4701 getStringCopy(getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_SOUNDS));
4702 level_info->music_set =
4703 getStringCopy(getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_MUSIC));
4706 token_value_position = TOKEN_VALUE_POSITION_SHORT;
4708 fprintFileHeader(file, LEVELINFO_FILENAME);
4711 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
4713 if (i == LEVELINFO_TOKEN_NAME ||
4714 i == LEVELINFO_TOKEN_AUTHOR ||
4715 i == LEVELINFO_TOKEN_LEVELS ||
4716 i == LEVELINFO_TOKEN_FIRST_LEVEL ||
4717 i == LEVELINFO_TOKEN_SORT_PRIORITY ||
4718 i == LEVELINFO_TOKEN_READONLY ||
4719 (use_artwork_set && (i == LEVELINFO_TOKEN_GRAPHICS_SET ||
4720 i == LEVELINFO_TOKEN_SOUNDS_SET ||
4721 i == LEVELINFO_TOKEN_MUSIC_SET)))
4722 fprintf(file, "%s\n", getSetupLine(levelinfo_tokens, "", i));
4724 // just to make things nicer :)
4725 if (i == LEVELINFO_TOKEN_AUTHOR ||
4726 i == LEVELINFO_TOKEN_FIRST_LEVEL ||
4727 (use_artwork_set && i == LEVELINFO_TOKEN_READONLY))
4728 fprintf(file, "\n");
4731 token_value_position = TOKEN_VALUE_POSITION_DEFAULT;
4735 SetFilePermissions(filename, PERMS_PRIVATE);
4737 freeTreeInfo(level_info);
4743 static void SaveUserLevelInfo(void)
4745 CreateUserLevelSet(getLoginName(), getLoginName(), getRealName(), 100, FALSE);
4748 char *getSetupValue(int type, void *value)
4750 static char value_string[MAX_LINE_LEN];
4758 strcpy(value_string, (*(boolean *)value ? "true" : "false"));
4762 strcpy(value_string, (*(boolean *)value ? "on" : "off"));
4766 strcpy(value_string, (*(int *)value == AUTO ? "auto" :
4767 *(int *)value == FALSE ? "off" : "on"));
4771 strcpy(value_string, (*(boolean *)value ? "yes" : "no"));
4774 case TYPE_YES_NO_AUTO:
4775 strcpy(value_string, (*(int *)value == AUTO ? "auto" :
4776 *(int *)value == FALSE ? "no" : "yes"));
4780 strcpy(value_string, (*(boolean *)value ? "AGA" : "ECS"));
4784 strcpy(value_string, getKeyNameFromKey(*(Key *)value));
4788 strcpy(value_string, getX11KeyNameFromKey(*(Key *)value));
4792 sprintf(value_string, "%d", *(int *)value);
4796 if (*(char **)value == NULL)
4799 strcpy(value_string, *(char **)value);
4803 sprintf(value_string, "player_%d", *(int *)value + 1);
4807 value_string[0] = '\0';
4811 if (type & TYPE_GHOSTED)
4812 strcpy(value_string, "n/a");
4814 return value_string;
4817 char *getSetupLine(struct TokenInfo *token_info, char *prefix, int token_nr)
4821 static char token_string[MAX_LINE_LEN];
4822 int token_type = token_info[token_nr].type;
4823 void *setup_value = token_info[token_nr].value;
4824 char *token_text = token_info[token_nr].text;
4825 char *value_string = getSetupValue(token_type, setup_value);
4827 // build complete token string
4828 sprintf(token_string, "%s%s", prefix, token_text);
4830 // build setup entry line
4831 line = getFormattedSetupEntry(token_string, value_string);
4833 if (token_type == TYPE_KEY_X11)
4835 Key key = *(Key *)setup_value;
4836 char *keyname = getKeyNameFromKey(key);
4838 // add comment, if useful
4839 if (!strEqual(keyname, "(undefined)") &&
4840 !strEqual(keyname, "(unknown)"))
4842 // add at least one whitespace
4844 for (i = strlen(line); i < token_comment_position; i++)
4848 strcat(line, keyname);
4855 static void InitLastPlayedLevels_ParentNode(void)
4857 LevelDirTree **leveldir_top = &leveldir_first->node_group->next;
4858 LevelDirTree *leveldir_new = NULL;
4860 // check if parent node for last played levels already exists
4861 if (strEqual((*leveldir_top)->identifier, TOKEN_STR_LAST_LEVEL_SERIES))
4864 leveldir_new = newTreeInfo();
4866 setTreeInfoToDefaultsFromParent(leveldir_new, leveldir_first);
4868 leveldir_new->level_group = TRUE;
4869 leveldir_new->sort_priority = LEVELCLASS_LAST_PLAYED_LEVEL;
4871 setString(&leveldir_new->identifier, TOKEN_STR_LAST_LEVEL_SERIES);
4872 setString(&leveldir_new->name, "<< (last played level sets)");
4873 setString(&leveldir_new->name_sorting, leveldir_new->name);
4875 pushTreeInfo(leveldir_top, leveldir_new);
4877 // create node to link back to current level directory
4878 createParentTreeInfoNode(leveldir_new);
4881 void UpdateLastPlayedLevels_TreeInfo(void)
4883 char **last_level_series = setup.level_setup.last_level_series;
4884 LevelDirTree *leveldir_last;
4885 TreeInfo **node_new = NULL;
4888 if (last_level_series[0] == NULL)
4891 InitLastPlayedLevels_ParentNode();
4893 leveldir_last = getTreeInfoFromIdentifierExt(leveldir_first,
4894 TOKEN_STR_LAST_LEVEL_SERIES,
4895 TREE_NODE_TYPE_GROUP);
4896 if (leveldir_last == NULL)
4899 node_new = &leveldir_last->node_group->next;
4901 freeTreeInfo(*node_new);
4905 for (i = 0; last_level_series[i] != NULL; i++)
4907 LevelDirTree *node_last = getTreeInfoFromIdentifier(leveldir_first,
4908 last_level_series[i]);
4909 if (node_last == NULL)
4912 *node_new = getTreeInfoCopy(node_last); // copy complete node
4914 (*node_new)->node_top = &leveldir_first; // correct top node link
4915 (*node_new)->node_parent = leveldir_last; // correct parent node link
4917 (*node_new)->is_copy = TRUE; // mark entry as node copy
4919 (*node_new)->node_group = NULL;
4920 (*node_new)->next = NULL;
4922 (*node_new)->cl_first = -1; // force setting tree cursor
4924 node_new = &((*node_new)->next);
4928 static void UpdateLastPlayedLevels_List(void)
4930 char **last_level_series = setup.level_setup.last_level_series;
4931 int pos = MAX_LEVELDIR_HISTORY - 1;
4934 // search for potentially already existing entry in list of level sets
4935 for (i = 0; last_level_series[i] != NULL; i++)
4936 if (strEqual(last_level_series[i], leveldir_current->identifier))
4939 // move list of level sets one entry down (using potentially free entry)
4940 for (i = pos; i > 0; i--)
4941 setString(&last_level_series[i], last_level_series[i - 1]);
4943 // put last played level set at top position
4944 setString(&last_level_series[0], leveldir_current->identifier);
4947 static TreeInfo *StoreOrRestoreLastPlayedLevels(TreeInfo *node, boolean store)
4949 static char *identifier = NULL;
4953 setString(&identifier, (node && node->is_copy ? node->identifier : NULL));
4955 return NULL; // not used
4959 TreeInfo *node_new = getTreeInfoFromIdentifierExt(leveldir_first,
4961 TREE_NODE_TYPE_COPY);
4962 return (node_new != NULL ? node_new : node);
4966 void StoreLastPlayedLevels(TreeInfo *node)
4968 StoreOrRestoreLastPlayedLevels(node, TRUE);
4971 void RestoreLastPlayedLevels(TreeInfo **node)
4973 *node = StoreOrRestoreLastPlayedLevels(*node, FALSE);
4976 void LoadLevelSetup_LastSeries(void)
4978 // --------------------------------------------------------------------------
4979 // ~/.<program>/levelsetup.conf
4980 // --------------------------------------------------------------------------
4982 char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
4983 SetupFileHash *level_setup_hash = NULL;
4987 // always start with reliable default values
4988 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
4990 // start with empty history of last played level sets
4991 setString(&setup.level_setup.last_level_series[0], NULL);
4993 if (!strEqual(DEFAULT_LEVELSET, UNDEFINED_LEVELSET))
4995 leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
4997 if (leveldir_current == NULL)
4998 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
5001 if ((level_setup_hash = loadSetupFileHash(filename)))
5003 char *last_level_series =
5004 getHashEntry(level_setup_hash, TOKEN_STR_LAST_LEVEL_SERIES);
5006 leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
5008 if (leveldir_current == NULL)
5009 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
5011 for (i = 0; i < MAX_LEVELDIR_HISTORY; i++)
5013 char token[strlen(TOKEN_STR_LAST_LEVEL_SERIES) + 10];
5014 LevelDirTree *leveldir_last;
5016 sprintf(token, "%s.%03d", TOKEN_STR_LAST_LEVEL_SERIES, i);
5018 last_level_series = getHashEntry(level_setup_hash, token);
5020 leveldir_last = getTreeInfoFromIdentifier(leveldir_first,
5022 if (leveldir_last != NULL)
5023 setString(&setup.level_setup.last_level_series[pos++],
5027 setString(&setup.level_setup.last_level_series[pos], NULL);
5029 freeSetupFileHash(level_setup_hash);
5033 Debug("setup", "using default setup values");
5039 static void SaveLevelSetup_LastSeries_Ext(boolean deactivate_last_level_series)
5041 // --------------------------------------------------------------------------
5042 // ~/.<program>/levelsetup.conf
5043 // --------------------------------------------------------------------------
5045 // check if the current level directory structure is available at this point
5046 if (leveldir_current == NULL)
5049 char **last_level_series = setup.level_setup.last_level_series;
5050 char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
5054 InitUserDataDirectory();
5056 UpdateLastPlayedLevels_List();
5058 if (!(file = fopen(filename, MODE_WRITE)))
5060 Warn("cannot write setup file '%s'", filename);
5067 fprintFileHeader(file, LEVELSETUP_FILENAME);
5069 if (deactivate_last_level_series)
5070 fprintf(file, "# %s\n# ", "the following level set may have caused a problem and was deactivated");
5072 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_LAST_LEVEL_SERIES,
5073 leveldir_current->identifier));
5075 for (i = 0; last_level_series[i] != NULL; i++)
5077 char token[strlen(TOKEN_STR_LAST_LEVEL_SERIES) + 1 + 10 + 1];
5079 sprintf(token, "%s.%03d", TOKEN_STR_LAST_LEVEL_SERIES, i);
5081 fprintf(file, "%s\n", getFormattedSetupEntry(token, last_level_series[i]));
5086 SetFilePermissions(filename, PERMS_PRIVATE);
5091 void SaveLevelSetup_LastSeries(void)
5093 SaveLevelSetup_LastSeries_Ext(FALSE);
5096 void SaveLevelSetup_LastSeries_Deactivate(void)
5098 SaveLevelSetup_LastSeries_Ext(TRUE);
5101 static void checkSeriesInfo(void)
5103 static char *level_directory = NULL;
5106 DirectoryEntry *dir_entry;
5109 checked_free(level_directory);
5111 // check for more levels besides the 'levels' field of 'levelinfo.conf'
5113 level_directory = getPath2((leveldir_current->in_user_dir ?
5114 getUserLevelDir(NULL) :
5115 options.level_directory),
5116 leveldir_current->fullpath);
5118 if ((dir = openDirectory(level_directory)) == NULL)
5120 Warn("cannot read level directory '%s'", level_directory);
5126 while ((dir_entry = readDirectory(dir)) != NULL) // loop all entries
5128 if (strlen(dir_entry->basename) > 4 &&
5129 dir_entry->basename[3] == '.' &&
5130 strEqual(&dir_entry->basename[4], LEVELFILE_EXTENSION))
5132 char levelnum_str[4];
5135 strncpy(levelnum_str, dir_entry->basename, 3);
5136 levelnum_str[3] = '\0';
5138 levelnum_value = atoi(levelnum_str);
5140 if (levelnum_value < leveldir_current->first_level)
5142 Warn("additional level %d found", levelnum_value);
5144 leveldir_current->first_level = levelnum_value;
5146 else if (levelnum_value > leveldir_current->last_level)
5148 Warn("additional level %d found", levelnum_value);
5150 leveldir_current->last_level = levelnum_value;
5156 closeDirectory(dir);
5159 void LoadLevelSetup_SeriesInfo(void)
5162 SetupFileHash *level_setup_hash = NULL;
5163 char *level_subdir = leveldir_current->subdir;
5166 // always start with reliable default values
5167 level_nr = leveldir_current->first_level;
5169 for (i = 0; i < MAX_LEVELS; i++)
5171 LevelStats_setPlayed(i, 0);
5172 LevelStats_setSolved(i, 0);
5177 // --------------------------------------------------------------------------
5178 // ~/.<program>/levelsetup/<level series>/levelsetup.conf
5179 // --------------------------------------------------------------------------
5181 level_subdir = leveldir_current->subdir;
5183 filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
5185 if ((level_setup_hash = loadSetupFileHash(filename)))
5189 // get last played level in this level set
5191 token_value = getHashEntry(level_setup_hash, TOKEN_STR_LAST_PLAYED_LEVEL);
5195 level_nr = atoi(token_value);
5197 if (level_nr < leveldir_current->first_level)
5198 level_nr = leveldir_current->first_level;
5199 if (level_nr > leveldir_current->last_level)
5200 level_nr = leveldir_current->last_level;
5203 // get handicap level in this level set
5205 token_value = getHashEntry(level_setup_hash, TOKEN_STR_HANDICAP_LEVEL);
5209 int level_nr = atoi(token_value);
5211 if (level_nr < leveldir_current->first_level)
5212 level_nr = leveldir_current->first_level;
5213 if (level_nr > leveldir_current->last_level + 1)
5214 level_nr = leveldir_current->last_level;
5216 if (leveldir_current->user_defined || !leveldir_current->handicap)
5217 level_nr = leveldir_current->last_level;
5219 leveldir_current->handicap_level = level_nr;
5222 // get number of played and solved levels in this level set
5224 BEGIN_HASH_ITERATION(level_setup_hash, itr)
5226 char *token = HASH_ITERATION_TOKEN(itr);
5227 char *value = HASH_ITERATION_VALUE(itr);
5229 if (strlen(token) == 3 &&
5230 token[0] >= '0' && token[0] <= '9' &&
5231 token[1] >= '0' && token[1] <= '9' &&
5232 token[2] >= '0' && token[2] <= '9')
5234 int level_nr = atoi(token);
5237 LevelStats_setPlayed(level_nr, atoi(value)); // read 1st column
5239 value = strchr(value, ' ');
5242 LevelStats_setSolved(level_nr, atoi(value)); // read 2nd column
5245 END_HASH_ITERATION(hash, itr)
5247 freeSetupFileHash(level_setup_hash);
5251 Debug("setup", "using default setup values");
5257 void SaveLevelSetup_SeriesInfo(void)
5260 char *level_subdir = leveldir_current->subdir;
5261 char *level_nr_str = int2str(level_nr, 0);
5262 char *handicap_level_str = int2str(leveldir_current->handicap_level, 0);
5266 // --------------------------------------------------------------------------
5267 // ~/.<program>/levelsetup/<level series>/levelsetup.conf
5268 // --------------------------------------------------------------------------
5270 InitLevelSetupDirectory(level_subdir);
5272 filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
5274 if (!(file = fopen(filename, MODE_WRITE)))
5276 Warn("cannot write setup file '%s'", filename);
5283 fprintFileHeader(file, LEVELSETUP_FILENAME);
5285 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_PLAYED_LEVEL,
5287 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_HANDICAP_LEVEL,
5288 handicap_level_str));
5290 for (i = leveldir_current->first_level; i <= leveldir_current->last_level;
5293 if (LevelStats_getPlayed(i) > 0 ||
5294 LevelStats_getSolved(i) > 0)
5299 sprintf(token, "%03d", i);
5300 sprintf(value, "%d %d", LevelStats_getPlayed(i), LevelStats_getSolved(i));
5302 fprintf(file, "%s\n", getFormattedSetupEntry(token, value));
5308 SetFilePermissions(filename, PERMS_PRIVATE);
5313 int LevelStats_getPlayed(int nr)
5315 return (nr >= 0 && nr < MAX_LEVELS ? level_stats[nr].played : 0);
5318 int LevelStats_getSolved(int nr)
5320 return (nr >= 0 && nr < MAX_LEVELS ? level_stats[nr].solved : 0);
5323 void LevelStats_setPlayed(int nr, int value)
5325 if (nr >= 0 && nr < MAX_LEVELS)
5326 level_stats[nr].played = value;
5329 void LevelStats_setSolved(int nr, int value)
5331 if (nr >= 0 && nr < MAX_LEVELS)
5332 level_stats[nr].solved = value;
5335 void LevelStats_incPlayed(int nr)
5337 if (nr >= 0 && nr < MAX_LEVELS)
5338 level_stats[nr].played++;
5341 void LevelStats_incSolved(int nr)
5343 if (nr >= 0 && nr < MAX_LEVELS)
5344 level_stats[nr].solved++;
5347 void LoadUserSetup(void)
5349 // --------------------------------------------------------------------------
5350 // ~/.<program>/usersetup.conf
5351 // --------------------------------------------------------------------------
5353 char *filename = getPath2(getMainUserGameDataDir(), USERSETUP_FILENAME);
5354 SetupFileHash *user_setup_hash = NULL;
5356 // always start with reliable default values
5359 if ((user_setup_hash = loadSetupFileHash(filename)))
5363 // get last selected user number
5364 token_value = getHashEntry(user_setup_hash, TOKEN_STR_LAST_USER);
5367 user.nr = MIN(MAX(0, atoi(token_value)), MAX_PLAYER_NAMES - 1);
5369 freeSetupFileHash(user_setup_hash);
5373 Debug("setup", "using default setup values");
5379 void SaveUserSetup(void)
5381 // --------------------------------------------------------------------------
5382 // ~/.<program>/usersetup.conf
5383 // --------------------------------------------------------------------------
5385 char *filename = getPath2(getMainUserGameDataDir(), USERSETUP_FILENAME);
5388 InitMainUserDataDirectory();
5390 if (!(file = fopen(filename, MODE_WRITE)))
5392 Warn("cannot write setup file '%s'", filename);
5399 fprintFileHeader(file, USERSETUP_FILENAME);
5401 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_USER,
5405 SetFilePermissions(filename, PERMS_PRIVATE);