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 MAX_COOKIE_LEN 256
52 #define TREE_NODE_TYPE_DEFAULT 0
53 #define TREE_NODE_TYPE_PARENT 1
54 #define TREE_NODE_TYPE_GROUP 2
55 #define TREE_NODE_TYPE_COPY 3
57 #define TREE_NODE_TYPE(ti) (ti->node_group ? TREE_NODE_TYPE_GROUP : \
58 ti->parent_link ? TREE_NODE_TYPE_PARENT : \
59 ti->is_copy ? TREE_NODE_TYPE_COPY : \
60 TREE_NODE_TYPE_DEFAULT)
63 static void setTreeInfoToDefaults(TreeInfo *, int);
64 static TreeInfo *getTreeInfoCopy(TreeInfo *ti);
65 static int compareTreeInfoEntries(const void *, const void *);
67 static int token_value_position = TOKEN_VALUE_POSITION_DEFAULT;
68 static int token_comment_position = TOKEN_COMMENT_POSITION_DEFAULT;
70 static SetupFileHash *artworkinfo_cache_old = NULL;
71 static SetupFileHash *artworkinfo_cache_new = NULL;
72 static SetupFileHash *optional_tokens_hash = NULL;
73 static boolean use_artworkinfo_cache = TRUE;
74 static boolean update_artworkinfo_cache = FALSE;
77 // ----------------------------------------------------------------------------
79 // ----------------------------------------------------------------------------
81 static char *getLevelClassDescription(TreeInfo *ti)
83 int position = ti->sort_priority / 100;
85 if (position >= 0 && position < NUM_LEVELCLASS_DESC)
86 return levelclass_desc[position];
88 return "Unknown Level Class";
91 static char *getCacheDir(void)
93 static char *cache_dir = NULL;
95 if (cache_dir == NULL)
96 cache_dir = getPath2(getMainUserGameDataDir(), CACHE_DIRECTORY);
101 static char *getScoreDir(char *level_subdir)
103 static char *score_dir = NULL;
104 static char *score_level_dir = NULL;
105 char *score_subdir = SCORES_DIRECTORY;
107 if (score_dir == NULL)
108 score_dir = getPath2(getMainUserGameDataDir(), score_subdir);
110 if (level_subdir != NULL)
112 checked_free(score_level_dir);
114 score_level_dir = getPath2(score_dir, level_subdir);
116 return score_level_dir;
122 static char *getScoreCacheDir(char *level_subdir)
124 static char *score_dir = NULL;
125 static char *score_level_dir = NULL;
126 char *score_subdir = SCORES_DIRECTORY;
128 if (score_dir == NULL)
129 score_dir = getPath2(getCacheDir(), score_subdir);
131 if (level_subdir != NULL)
133 checked_free(score_level_dir);
135 score_level_dir = getPath2(score_dir, level_subdir);
137 return score_level_dir;
143 static char *getScoreTapeDir(char *level_subdir, int nr)
145 static char *score_tape_dir = NULL;
146 char tape_subdir[MAX_FILENAME_LEN];
148 checked_free(score_tape_dir);
150 sprintf(tape_subdir, "%03d", nr);
151 score_tape_dir = getPath2(getScoreDir(level_subdir), tape_subdir);
153 return score_tape_dir;
156 static char *getUserSubdir(int nr)
158 static char user_subdir[16] = { 0 };
160 sprintf(user_subdir, "%03d", nr);
165 static char *getUserDir(int nr)
167 static char *user_dir = NULL;
168 char *main_data_dir = getMainUserGameDataDir();
169 char *users_subdir = USERS_DIRECTORY;
170 char *user_subdir = getUserSubdir(nr);
172 checked_free(user_dir);
175 user_dir = getPath3(main_data_dir, users_subdir, user_subdir);
177 user_dir = getPath2(main_data_dir, users_subdir);
182 static char *getLevelSetupDir(char *level_subdir)
184 static char *levelsetup_dir = NULL;
185 char *data_dir = getUserGameDataDir();
186 char *levelsetup_subdir = LEVELSETUP_DIRECTORY;
188 checked_free(levelsetup_dir);
190 if (level_subdir != NULL)
191 levelsetup_dir = getPath3(data_dir, levelsetup_subdir, level_subdir);
193 levelsetup_dir = getPath2(data_dir, levelsetup_subdir);
195 return levelsetup_dir;
198 static char *getNetworkDir(void)
200 static char *network_dir = NULL;
202 if (network_dir == NULL)
203 network_dir = getPath2(getMainUserGameDataDir(), NETWORK_DIRECTORY);
208 char *getLevelDirFromTreeInfo(TreeInfo *node)
210 static char *level_dir = NULL;
213 return options.level_directory;
215 checked_free(level_dir);
217 level_dir = getPath2((node->in_user_dir ? getUserLevelDir(NULL) :
218 options.level_directory), node->fullpath);
223 char *getUserLevelDir(char *level_subdir)
225 static char *userlevel_dir = NULL;
226 char *data_dir = getMainUserGameDataDir();
227 char *userlevel_subdir = LEVELS_DIRECTORY;
229 checked_free(userlevel_dir);
231 if (level_subdir != NULL)
232 userlevel_dir = getPath3(data_dir, userlevel_subdir, level_subdir);
234 userlevel_dir = getPath2(data_dir, userlevel_subdir);
236 return userlevel_dir;
239 char *getNetworkLevelDir(char *level_subdir)
241 static char *network_level_dir = NULL;
242 char *data_dir = getNetworkDir();
243 char *networklevel_subdir = LEVELS_DIRECTORY;
245 checked_free(network_level_dir);
247 if (level_subdir != NULL)
248 network_level_dir = getPath3(data_dir, networklevel_subdir, level_subdir);
250 network_level_dir = getPath2(data_dir, networklevel_subdir);
252 return network_level_dir;
255 char *getCurrentLevelDir(void)
257 return getLevelDirFromTreeInfo(leveldir_current);
260 char *getNewUserLevelSubdir(void)
262 static char *new_level_subdir = NULL;
263 char *subdir_prefix = getLoginName();
264 char subdir_suffix[10];
265 int max_suffix_number = 1000;
268 while (++i < max_suffix_number)
270 sprintf(subdir_suffix, "_%d", i);
272 checked_free(new_level_subdir);
273 new_level_subdir = getStringCat2(subdir_prefix, subdir_suffix);
275 if (!directoryExists(getUserLevelDir(new_level_subdir)))
279 return new_level_subdir;
282 static char *getTapeDir(char *level_subdir)
284 static char *tape_dir = NULL;
285 char *data_dir = getUserGameDataDir();
286 char *tape_subdir = TAPES_DIRECTORY;
288 checked_free(tape_dir);
290 if (level_subdir != NULL)
291 tape_dir = getPath3(data_dir, tape_subdir, level_subdir);
293 tape_dir = getPath2(data_dir, tape_subdir);
298 static char *getSolutionTapeDir(void)
300 static char *tape_dir = NULL;
301 char *data_dir = getCurrentLevelDir();
302 char *tape_subdir = TAPES_DIRECTORY;
304 checked_free(tape_dir);
306 tape_dir = getPath2(data_dir, tape_subdir);
311 static char *getDefaultGraphicsDir(char *graphics_subdir)
313 static char *graphics_dir = NULL;
315 if (graphics_subdir == NULL)
316 return options.graphics_directory;
318 checked_free(graphics_dir);
320 graphics_dir = getPath2(options.graphics_directory, graphics_subdir);
325 static char *getDefaultSoundsDir(char *sounds_subdir)
327 static char *sounds_dir = NULL;
329 if (sounds_subdir == NULL)
330 return options.sounds_directory;
332 checked_free(sounds_dir);
334 sounds_dir = getPath2(options.sounds_directory, sounds_subdir);
339 static char *getDefaultMusicDir(char *music_subdir)
341 static char *music_dir = NULL;
343 if (music_subdir == NULL)
344 return options.music_directory;
346 checked_free(music_dir);
348 music_dir = getPath2(options.music_directory, music_subdir);
353 static char *getClassicArtworkSet(int type)
355 return (type == TREE_TYPE_GRAPHICS_DIR ? GFX_CLASSIC_SUBDIR :
356 type == TREE_TYPE_SOUNDS_DIR ? SND_CLASSIC_SUBDIR :
357 type == TREE_TYPE_MUSIC_DIR ? MUS_CLASSIC_SUBDIR : "");
360 static char *getClassicArtworkDir(int type)
362 return (type == TREE_TYPE_GRAPHICS_DIR ?
363 getDefaultGraphicsDir(GFX_CLASSIC_SUBDIR) :
364 type == TREE_TYPE_SOUNDS_DIR ?
365 getDefaultSoundsDir(SND_CLASSIC_SUBDIR) :
366 type == TREE_TYPE_MUSIC_DIR ?
367 getDefaultMusicDir(MUS_CLASSIC_SUBDIR) : "");
370 char *getUserGraphicsDir(void)
372 static char *usergraphics_dir = NULL;
374 if (usergraphics_dir == NULL)
375 usergraphics_dir = getPath2(getMainUserGameDataDir(), GRAPHICS_DIRECTORY);
377 return usergraphics_dir;
380 char *getUserSoundsDir(void)
382 static char *usersounds_dir = NULL;
384 if (usersounds_dir == NULL)
385 usersounds_dir = getPath2(getMainUserGameDataDir(), SOUNDS_DIRECTORY);
387 return usersounds_dir;
390 char *getUserMusicDir(void)
392 static char *usermusic_dir = NULL;
394 if (usermusic_dir == NULL)
395 usermusic_dir = getPath2(getMainUserGameDataDir(), MUSIC_DIRECTORY);
397 return usermusic_dir;
400 static char *getSetupArtworkDir(TreeInfo *ti)
402 static char *artwork_dir = NULL;
407 checked_free(artwork_dir);
409 artwork_dir = getPath2(ti->basepath, ti->fullpath);
414 char *setLevelArtworkDir(TreeInfo *ti)
416 char **artwork_path_ptr, **artwork_set_ptr;
417 TreeInfo *level_artwork;
419 if (ti == NULL || leveldir_current == NULL)
422 artwork_path_ptr = LEVELDIR_ARTWORK_PATH_PTR(leveldir_current, ti->type);
423 artwork_set_ptr = LEVELDIR_ARTWORK_SET_PTR( leveldir_current, ti->type);
425 checked_free(*artwork_path_ptr);
427 if ((level_artwork = getTreeInfoFromIdentifier(ti, *artwork_set_ptr)))
429 *artwork_path_ptr = getStringCopy(getSetupArtworkDir(level_artwork));
434 No (or non-existing) artwork configured in "levelinfo.conf". This would
435 normally result in using the artwork configured in the setup menu. But
436 if an artwork subdirectory exists (which might contain custom artwork
437 or an artwork configuration file), this level artwork must be treated
438 as relative to the default "classic" artwork, not to the artwork that
439 is currently configured in the setup menu.
441 Update: For "special" versions of R'n'D (like "R'n'D jue"), do not use
442 the "default" artwork (which would be "jue0" for "R'n'D jue"), but use
443 the real "classic" artwork from the original R'n'D (like "gfx_classic").
446 char *dir = getPath2(getCurrentLevelDir(), ARTWORK_DIRECTORY(ti->type));
448 checked_free(*artwork_set_ptr);
450 if (directoryExists(dir))
452 *artwork_path_ptr = getStringCopy(getClassicArtworkDir(ti->type));
453 *artwork_set_ptr = getStringCopy(getClassicArtworkSet(ti->type));
457 *artwork_path_ptr = getStringCopy(UNDEFINED_FILENAME);
458 *artwork_set_ptr = NULL;
464 return *artwork_set_ptr;
467 static char *getLevelArtworkSet(int type)
469 if (leveldir_current == NULL)
472 return LEVELDIR_ARTWORK_SET(leveldir_current, type);
475 static char *getLevelArtworkDir(int type)
477 if (leveldir_current == NULL)
478 return UNDEFINED_FILENAME;
480 return LEVELDIR_ARTWORK_PATH(leveldir_current, type);
483 char *getProgramMainDataPath(char *command_filename, char *base_path)
485 // check if the program's main data base directory is configured
486 if (!strEqual(base_path, "."))
487 return getStringCopy(base_path);
489 /* if the program is configured to start from current directory (default),
490 determine program package directory from program binary (some versions
491 of KDE/Konqueror and Mac OS X (especially "Mavericks") apparently do not
492 set the current working directory to the program package directory) */
493 char *main_data_path = getBasePath(command_filename);
495 #if defined(PLATFORM_MACOSX)
496 if (strSuffix(main_data_path, MAC_APP_BINARY_SUBDIR))
498 char *main_data_path_old = main_data_path;
500 // cut relative path to Mac OS X application binary directory from path
501 main_data_path[strlen(main_data_path) -
502 strlen(MAC_APP_BINARY_SUBDIR)] = '\0';
504 // cut trailing path separator from path (but not if path is root directory)
505 if (strSuffix(main_data_path, "/") && !strEqual(main_data_path, "/"))
506 main_data_path[strlen(main_data_path) - 1] = '\0';
508 // replace empty path with current directory
509 if (strEqual(main_data_path, ""))
510 main_data_path = ".";
512 // add relative path to Mac OS X application resources directory to path
513 main_data_path = getPath2(main_data_path, MAC_APP_FILES_SUBDIR);
515 free(main_data_path_old);
519 return main_data_path;
522 char *getProgramConfigFilename(char *command_filename)
524 static char *config_filename_1 = NULL;
525 static char *config_filename_2 = NULL;
526 static char *config_filename_3 = NULL;
527 static boolean initialized = FALSE;
531 char *command_filename_1 = getStringCopy(command_filename);
533 // strip trailing executable suffix from command filename
534 if (strSuffix(command_filename_1, ".exe"))
535 command_filename_1[strlen(command_filename_1) - 4] = '\0';
537 char *base_path = getProgramMainDataPath(command_filename, BASE_PATH);
538 char *conf_directory = getPath2(base_path, CONF_DIRECTORY);
540 char *command_basepath = getBasePath(command_filename);
541 char *command_basename = getBaseNameNoSuffix(command_filename);
542 char *command_filename_2 = getPath2(command_basepath, command_basename);
544 config_filename_1 = getStringCat2(command_filename_1, ".conf");
545 config_filename_2 = getStringCat2(command_filename_2, ".conf");
546 config_filename_3 = getPath2(conf_directory, SETUP_FILENAME);
548 checked_free(base_path);
549 checked_free(conf_directory);
551 checked_free(command_basepath);
552 checked_free(command_basename);
554 checked_free(command_filename_1);
555 checked_free(command_filename_2);
560 // 1st try: look for config file that exactly matches the binary filename
561 if (fileExists(config_filename_1))
562 return config_filename_1;
564 // 2nd try: look for config file that matches binary filename without suffix
565 if (fileExists(config_filename_2))
566 return config_filename_2;
568 // 3rd try: return setup config filename in global program config directory
569 return config_filename_3;
572 char *getTapeFilename(int nr)
574 static char *filename = NULL;
575 char basename[MAX_FILENAME_LEN];
577 checked_free(filename);
579 sprintf(basename, "%03d.%s", nr, TAPEFILE_EXTENSION);
580 filename = getPath2(getTapeDir(leveldir_current->subdir), basename);
585 char *getSolutionTapeFilename(int nr)
587 static char *filename = NULL;
588 char basename[MAX_FILENAME_LEN];
590 checked_free(filename);
592 sprintf(basename, "%03d.%s", nr, TAPEFILE_EXTENSION);
593 filename = getPath2(getSolutionTapeDir(), basename);
595 if (!fileExists(filename))
597 static char *filename_sln = NULL;
599 checked_free(filename_sln);
601 sprintf(basename, "%03d.sln", nr);
602 filename_sln = getPath2(getSolutionTapeDir(), basename);
604 if (fileExists(filename_sln))
611 char *getScoreFilename(int nr)
613 static char *filename = NULL;
614 char basename[MAX_FILENAME_LEN];
616 checked_free(filename);
618 sprintf(basename, "%03d.%s", nr, SCOREFILE_EXTENSION);
620 // used instead of "leveldir_current->subdir" (for network games)
621 filename = getPath2(getScoreDir(levelset.identifier), basename);
626 char *getScoreCacheFilename(int nr)
628 static char *filename = NULL;
629 char basename[MAX_FILENAME_LEN];
631 checked_free(filename);
633 sprintf(basename, "%03d.%s", nr, SCOREFILE_EXTENSION);
635 // used instead of "leveldir_current->subdir" (for network games)
636 filename = getPath2(getScoreCacheDir(levelset.identifier), basename);
641 char *getScoreTapeBasename(char *name)
643 static char basename[MAX_FILENAME_LEN];
644 char basename_raw[MAX_FILENAME_LEN];
647 sprintf(timestamp, "%s", getCurrentTimestamp());
648 sprintf(basename_raw, "%s-%s", timestamp, name);
649 sprintf(basename, "%s-%08x", timestamp, get_hash_from_key(basename_raw));
654 char *getScoreTapeFilename(char *basename_no_ext, int nr)
656 static char *filename = NULL;
657 char basename[MAX_FILENAME_LEN];
659 checked_free(filename);
661 sprintf(basename, "%s.%s", basename_no_ext, TAPEFILE_EXTENSION);
663 // used instead of "leveldir_current->subdir" (for network games)
664 filename = getPath2(getScoreTapeDir(levelset.identifier, nr), basename);
669 char *getSetupFilename(void)
671 static char *filename = NULL;
673 checked_free(filename);
675 filename = getPath2(getSetupDir(), SETUP_FILENAME);
680 char *getDefaultSetupFilename(void)
682 return program.config_filename;
685 char *getEditorSetupFilename(void)
687 static char *filename = NULL;
689 checked_free(filename);
690 filename = getPath2(getCurrentLevelDir(), EDITORSETUP_FILENAME);
692 if (fileExists(filename))
695 checked_free(filename);
696 filename = getPath2(getSetupDir(), EDITORSETUP_FILENAME);
701 char *getHelpAnimFilename(void)
703 static char *filename = NULL;
705 checked_free(filename);
707 filename = getPath2(getCurrentLevelDir(), HELPANIM_FILENAME);
712 char *getHelpTextFilename(void)
714 static char *filename = NULL;
716 checked_free(filename);
718 filename = getPath2(getCurrentLevelDir(), HELPTEXT_FILENAME);
723 char *getLevelSetInfoFilename(void)
725 static char *filename = NULL;
740 for (i = 0; basenames[i] != NULL; i++)
742 checked_free(filename);
743 filename = getPath2(getCurrentLevelDir(), basenames[i]);
745 if (fileExists(filename))
752 static char *getLevelSetTitleMessageBasename(int nr, boolean initial)
754 static char basename[32];
756 sprintf(basename, "%s_%d.txt",
757 (initial ? "titlemessage_initial" : "titlemessage"), nr + 1);
762 char *getLevelSetTitleMessageFilename(int nr, boolean initial)
764 static char *filename = NULL;
766 boolean skip_setup_artwork = FALSE;
768 checked_free(filename);
770 basename = getLevelSetTitleMessageBasename(nr, initial);
772 if (!gfx.override_level_graphics)
774 // 1st try: look for special artwork in current level series directory
775 filename = getPath3(getCurrentLevelDir(), GRAPHICS_DIRECTORY, basename);
776 if (fileExists(filename))
781 // 2nd try: look for message file in current level set directory
782 filename = getPath2(getCurrentLevelDir(), basename);
783 if (fileExists(filename))
788 // check if there is special artwork configured in level series config
789 if (getLevelArtworkSet(ARTWORK_TYPE_GRAPHICS) != NULL)
791 // 3rd try: look for special artwork configured in level series config
792 filename = getPath2(getLevelArtworkDir(ARTWORK_TYPE_GRAPHICS), basename);
793 if (fileExists(filename))
798 // take missing artwork configured in level set config from default
799 skip_setup_artwork = TRUE;
803 if (!skip_setup_artwork)
805 // 4th try: look for special artwork in configured artwork directory
806 filename = getPath2(getSetupArtworkDir(artwork.gfx_current), basename);
807 if (fileExists(filename))
813 // 5th try: look for default artwork in new default artwork directory
814 filename = getPath2(getDefaultGraphicsDir(GFX_DEFAULT_SUBDIR), basename);
815 if (fileExists(filename))
820 // 6th try: look for default artwork in old default artwork directory
821 filename = getPath2(options.graphics_directory, basename);
822 if (fileExists(filename))
825 return NULL; // cannot find specified artwork file anywhere
828 static char *getCorrectedArtworkBasename(char *basename)
833 char *getCustomImageFilename(char *basename)
835 static char *filename = NULL;
836 boolean skip_setup_artwork = FALSE;
838 checked_free(filename);
840 basename = getCorrectedArtworkBasename(basename);
842 if (!gfx.override_level_graphics)
844 // 1st try: look for special artwork in current level series directory
845 filename = getImg3(getCurrentLevelDir(), GRAPHICS_DIRECTORY, basename);
846 if (fileExists(filename))
851 // check if there is special artwork configured in level series config
852 if (getLevelArtworkSet(ARTWORK_TYPE_GRAPHICS) != NULL)
854 // 2nd try: look for special artwork configured in level series config
855 filename = getImg2(getLevelArtworkDir(ARTWORK_TYPE_GRAPHICS), basename);
856 if (fileExists(filename))
861 // take missing artwork configured in level set config from default
862 skip_setup_artwork = TRUE;
866 if (!skip_setup_artwork)
868 // 3rd try: look for special artwork in configured artwork directory
869 filename = getImg2(getSetupArtworkDir(artwork.gfx_current), basename);
870 if (fileExists(filename))
876 // 4th try: look for default artwork in new default artwork directory
877 filename = getImg2(getDefaultGraphicsDir(GFX_DEFAULT_SUBDIR), basename);
878 if (fileExists(filename))
883 // 5th try: look for default artwork in old default artwork directory
884 filename = getImg2(options.graphics_directory, basename);
885 if (fileExists(filename))
888 if (!strEqual(GFX_FALLBACK_FILENAME, UNDEFINED_FILENAME))
892 Warn("cannot find artwork file '%s' (using fallback)", basename);
894 // 6th try: look for fallback artwork in old default artwork directory
895 // (needed to prevent errors when trying to access unused artwork files)
896 filename = getImg2(options.graphics_directory, GFX_FALLBACK_FILENAME);
897 if (fileExists(filename))
901 return NULL; // cannot find specified artwork file anywhere
904 char *getCustomSoundFilename(char *basename)
906 static char *filename = NULL;
907 boolean skip_setup_artwork = FALSE;
909 checked_free(filename);
911 basename = getCorrectedArtworkBasename(basename);
913 if (!gfx.override_level_sounds)
915 // 1st try: look for special artwork in current level series directory
916 filename = getPath3(getCurrentLevelDir(), SOUNDS_DIRECTORY, basename);
917 if (fileExists(filename))
922 // check if there is special artwork configured in level series config
923 if (getLevelArtworkSet(ARTWORK_TYPE_SOUNDS) != NULL)
925 // 2nd try: look for special artwork configured in level series config
926 filename = getPath2(getLevelArtworkDir(TREE_TYPE_SOUNDS_DIR), basename);
927 if (fileExists(filename))
932 // take missing artwork configured in level set config from default
933 skip_setup_artwork = TRUE;
937 if (!skip_setup_artwork)
939 // 3rd try: look for special artwork in configured artwork directory
940 filename = getPath2(getSetupArtworkDir(artwork.snd_current), basename);
941 if (fileExists(filename))
947 // 4th try: look for default artwork in new default artwork directory
948 filename = getPath2(getDefaultSoundsDir(SND_DEFAULT_SUBDIR), basename);
949 if (fileExists(filename))
954 // 5th try: look for default artwork in old default artwork directory
955 filename = getPath2(options.sounds_directory, basename);
956 if (fileExists(filename))
959 if (!strEqual(SND_FALLBACK_FILENAME, UNDEFINED_FILENAME))
963 Warn("cannot find artwork file '%s' (using fallback)", basename);
965 // 6th try: look for fallback artwork in old default artwork directory
966 // (needed to prevent errors when trying to access unused artwork files)
967 filename = getPath2(options.sounds_directory, SND_FALLBACK_FILENAME);
968 if (fileExists(filename))
972 return NULL; // cannot find specified artwork file anywhere
975 char *getCustomMusicFilename(char *basename)
977 static char *filename = NULL;
978 boolean skip_setup_artwork = FALSE;
980 checked_free(filename);
982 basename = getCorrectedArtworkBasename(basename);
984 if (!gfx.override_level_music)
986 // 1st try: look for special artwork in current level series directory
987 filename = getPath3(getCurrentLevelDir(), MUSIC_DIRECTORY, basename);
988 if (fileExists(filename))
993 // check if there is special artwork configured in level series config
994 if (getLevelArtworkSet(ARTWORK_TYPE_MUSIC) != NULL)
996 // 2nd try: look for special artwork configured in level series config
997 filename = getPath2(getLevelArtworkDir(TREE_TYPE_MUSIC_DIR), basename);
998 if (fileExists(filename))
1003 // take missing artwork configured in level set config from default
1004 skip_setup_artwork = TRUE;
1008 if (!skip_setup_artwork)
1010 // 3rd try: look for special artwork in configured artwork directory
1011 filename = getPath2(getSetupArtworkDir(artwork.mus_current), basename);
1012 if (fileExists(filename))
1018 // 4th try: look for default artwork in new default artwork directory
1019 filename = getPath2(getDefaultMusicDir(MUS_DEFAULT_SUBDIR), basename);
1020 if (fileExists(filename))
1025 // 5th try: look for default artwork in old default artwork directory
1026 filename = getPath2(options.music_directory, basename);
1027 if (fileExists(filename))
1030 if (!strEqual(MUS_FALLBACK_FILENAME, UNDEFINED_FILENAME))
1034 Warn("cannot find artwork file '%s' (using fallback)", basename);
1036 // 6th try: look for fallback artwork in old default artwork directory
1037 // (needed to prevent errors when trying to access unused artwork files)
1038 filename = getPath2(options.music_directory, MUS_FALLBACK_FILENAME);
1039 if (fileExists(filename))
1043 return NULL; // cannot find specified artwork file anywhere
1046 char *getCustomArtworkFilename(char *basename, int type)
1048 if (type == ARTWORK_TYPE_GRAPHICS)
1049 return getCustomImageFilename(basename);
1050 else if (type == ARTWORK_TYPE_SOUNDS)
1051 return getCustomSoundFilename(basename);
1052 else if (type == ARTWORK_TYPE_MUSIC)
1053 return getCustomMusicFilename(basename);
1055 return UNDEFINED_FILENAME;
1058 char *getCustomArtworkConfigFilename(int type)
1060 return getCustomArtworkFilename(ARTWORKINFO_FILENAME(type), type);
1063 char *getCustomArtworkLevelConfigFilename(int type)
1065 static char *filename = NULL;
1067 checked_free(filename);
1069 filename = getPath2(getLevelArtworkDir(type), ARTWORKINFO_FILENAME(type));
1074 char *getCustomMusicDirectory(void)
1076 static char *directory = NULL;
1077 boolean skip_setup_artwork = FALSE;
1079 checked_free(directory);
1081 if (!gfx.override_level_music)
1083 // 1st try: look for special artwork in current level series directory
1084 directory = getPath2(getCurrentLevelDir(), MUSIC_DIRECTORY);
1085 if (directoryExists(directory))
1090 // check if there is special artwork configured in level series config
1091 if (getLevelArtworkSet(ARTWORK_TYPE_MUSIC) != NULL)
1093 // 2nd try: look for special artwork configured in level series config
1094 directory = getStringCopy(getLevelArtworkDir(TREE_TYPE_MUSIC_DIR));
1095 if (directoryExists(directory))
1100 // take missing artwork configured in level set config from default
1101 skip_setup_artwork = TRUE;
1105 if (!skip_setup_artwork)
1107 // 3rd try: look for special artwork in configured artwork directory
1108 directory = getStringCopy(getSetupArtworkDir(artwork.mus_current));
1109 if (directoryExists(directory))
1115 // 4th try: look for default artwork in new default artwork directory
1116 directory = getStringCopy(getDefaultMusicDir(MUS_DEFAULT_SUBDIR));
1117 if (directoryExists(directory))
1122 // 5th try: look for default artwork in old default artwork directory
1123 directory = getStringCopy(options.music_directory);
1124 if (directoryExists(directory))
1127 return NULL; // cannot find specified artwork file anywhere
1130 void InitTapeDirectory(char *level_subdir)
1132 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
1133 createDirectory(getTapeDir(NULL), "main tape", PERMS_PRIVATE);
1134 createDirectory(getTapeDir(level_subdir), "level tape", PERMS_PRIVATE);
1137 void InitScoreDirectory(char *level_subdir)
1139 createDirectory(getMainUserGameDataDir(), "main user data", PERMS_PRIVATE);
1140 createDirectory(getScoreDir(NULL), "main score", PERMS_PRIVATE);
1141 createDirectory(getScoreDir(level_subdir), "level score", PERMS_PRIVATE);
1144 void InitScoreCacheDirectory(char *level_subdir)
1146 createDirectory(getMainUserGameDataDir(), "main user data", PERMS_PRIVATE);
1147 createDirectory(getCacheDir(), "cache data", PERMS_PRIVATE);
1148 createDirectory(getScoreCacheDir(NULL), "main score", PERMS_PRIVATE);
1149 createDirectory(getScoreCacheDir(level_subdir), "level score", PERMS_PRIVATE);
1152 void InitScoreTapeDirectory(char *level_subdir, int nr)
1154 InitScoreDirectory(level_subdir);
1156 createDirectory(getScoreTapeDir(level_subdir, nr), "score tape", PERMS_PRIVATE);
1159 static void SaveUserLevelInfo(void);
1161 void InitUserLevelDirectory(char *level_subdir)
1163 if (!directoryExists(getUserLevelDir(level_subdir)))
1165 createDirectory(getMainUserGameDataDir(), "main user data", PERMS_PRIVATE);
1166 createDirectory(getUserLevelDir(NULL), "main user level", PERMS_PRIVATE);
1167 createDirectory(getUserLevelDir(level_subdir), "user level", PERMS_PRIVATE);
1169 if (setup.internal.create_user_levelset)
1170 SaveUserLevelInfo();
1174 void InitNetworkLevelDirectory(char *level_subdir)
1176 if (!directoryExists(getNetworkLevelDir(level_subdir)))
1178 createDirectory(getMainUserGameDataDir(), "main user data", PERMS_PRIVATE);
1179 createDirectory(getNetworkDir(), "network data", PERMS_PRIVATE);
1180 createDirectory(getNetworkLevelDir(NULL), "main network level", PERMS_PRIVATE);
1181 createDirectory(getNetworkLevelDir(level_subdir), "network level", PERMS_PRIVATE);
1185 void InitLevelSetupDirectory(char *level_subdir)
1187 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
1188 createDirectory(getLevelSetupDir(NULL), "main level setup", PERMS_PRIVATE);
1189 createDirectory(getLevelSetupDir(level_subdir), "level setup", PERMS_PRIVATE);
1192 static void InitCacheDirectory(void)
1194 createDirectory(getMainUserGameDataDir(), "main user data", PERMS_PRIVATE);
1195 createDirectory(getCacheDir(), "cache data", PERMS_PRIVATE);
1199 // ----------------------------------------------------------------------------
1200 // some functions to handle lists of level and artwork directories
1201 // ----------------------------------------------------------------------------
1203 TreeInfo *newTreeInfo(void)
1205 return checked_calloc(sizeof(TreeInfo));
1208 TreeInfo *newTreeInfo_setDefaults(int type)
1210 TreeInfo *ti = newTreeInfo();
1212 setTreeInfoToDefaults(ti, type);
1217 void pushTreeInfo(TreeInfo **node_first, TreeInfo *node_new)
1219 node_new->next = *node_first;
1220 *node_first = node_new;
1223 void removeTreeInfo(TreeInfo **node_first)
1225 TreeInfo *node_old = *node_first;
1227 *node_first = node_old->next;
1228 node_old->next = NULL;
1230 freeTreeInfo(node_old);
1233 int numTreeInfo(TreeInfo *node)
1246 boolean validLevelSeries(TreeInfo *node)
1248 // in a number of cases, tree node is no valid level set
1249 if (node == NULL || node->node_group || node->parent_link || node->is_copy)
1255 TreeInfo *getValidLevelSeries(TreeInfo *node, TreeInfo *default_node)
1257 if (validLevelSeries(node))
1259 else if (node->is_copy)
1260 return getTreeInfoFromIdentifier(leveldir_first, node->identifier);
1262 return getFirstValidTreeInfoEntry(default_node);
1265 TreeInfo *getFirstValidTreeInfoEntry(TreeInfo *node)
1270 if (node->node_group) // enter level group (step down into tree)
1271 return getFirstValidTreeInfoEntry(node->node_group);
1272 else if (node->parent_link) // skip start entry of level group
1274 if (node->next) // get first real level series entry
1275 return getFirstValidTreeInfoEntry(node->next);
1276 else // leave empty level group and go on
1277 return getFirstValidTreeInfoEntry(node->node_parent->next);
1279 else // this seems to be a regular level series
1283 TreeInfo *getTreeInfoFirstGroupEntry(TreeInfo *node)
1288 if (node->node_parent == NULL) // top level group
1289 return *node->node_top;
1290 else // sub level group
1291 return node->node_parent->node_group;
1294 int numTreeInfoInGroup(TreeInfo *node)
1296 return numTreeInfo(getTreeInfoFirstGroupEntry(node));
1299 int getPosFromTreeInfo(TreeInfo *node)
1301 TreeInfo *node_cmp = getTreeInfoFirstGroupEntry(node);
1306 if (node_cmp == node)
1310 node_cmp = node_cmp->next;
1316 TreeInfo *getTreeInfoFromPos(TreeInfo *node, int pos)
1318 TreeInfo *node_default = node;
1330 return node_default;
1333 static TreeInfo *getTreeInfoFromIdentifierExt(TreeInfo *node, char *identifier,
1334 int node_type_wanted)
1336 if (identifier == NULL)
1341 if (TREE_NODE_TYPE(node) == node_type_wanted &&
1342 strEqual(identifier, node->identifier))
1345 if (node->node_group)
1347 TreeInfo *node_group = getTreeInfoFromIdentifierExt(node->node_group,
1360 TreeInfo *getTreeInfoFromIdentifier(TreeInfo *node, char *identifier)
1362 return getTreeInfoFromIdentifierExt(node, identifier, TREE_NODE_TYPE_DEFAULT);
1365 static TreeInfo *cloneTreeNode(TreeInfo **node_top, TreeInfo *node_parent,
1366 TreeInfo *node, boolean skip_sets_without_levels)
1373 if (!node->parent_link && !node->level_group &&
1374 skip_sets_without_levels && node->levels == 0)
1375 return cloneTreeNode(node_top, node_parent, node->next,
1376 skip_sets_without_levels);
1378 node_new = getTreeInfoCopy(node); // copy complete node
1380 node_new->node_top = node_top; // correct top node link
1381 node_new->node_parent = node_parent; // correct parent node link
1383 if (node->level_group)
1384 node_new->node_group = cloneTreeNode(node_top, node_new, node->node_group,
1385 skip_sets_without_levels);
1387 node_new->next = cloneTreeNode(node_top, node_parent, node->next,
1388 skip_sets_without_levels);
1393 static void cloneTree(TreeInfo **ti_new, TreeInfo *ti, boolean skip_empty_sets)
1395 TreeInfo *ti_cloned = cloneTreeNode(ti_new, NULL, ti, skip_empty_sets);
1397 *ti_new = ti_cloned;
1400 static boolean adjustTreeGraphicsForEMC(TreeInfo *node)
1402 boolean settings_changed = FALSE;
1406 boolean want_ecs = (setup.prefer_aga_graphics == FALSE);
1407 boolean want_aga = (setup.prefer_aga_graphics == TRUE);
1408 boolean has_only_ecs = (!node->graphics_set && !node->graphics_set_aga);
1409 boolean has_only_aga = (!node->graphics_set && !node->graphics_set_ecs);
1410 char *graphics_set = NULL;
1412 if (node->graphics_set_ecs && (want_ecs || has_only_ecs))
1413 graphics_set = node->graphics_set_ecs;
1415 if (node->graphics_set_aga && (want_aga || has_only_aga))
1416 graphics_set = node->graphics_set_aga;
1418 if (graphics_set && !strEqual(node->graphics_set, graphics_set))
1420 setString(&node->graphics_set, graphics_set);
1421 settings_changed = TRUE;
1424 if (node->node_group != NULL)
1425 settings_changed |= adjustTreeGraphicsForEMC(node->node_group);
1430 return settings_changed;
1433 static boolean adjustTreeSoundsForEMC(TreeInfo *node)
1435 boolean settings_changed = FALSE;
1439 boolean want_default = (setup.prefer_lowpass_sounds == FALSE);
1440 boolean want_lowpass = (setup.prefer_lowpass_sounds == TRUE);
1441 boolean has_only_default = (!node->sounds_set && !node->sounds_set_lowpass);
1442 boolean has_only_lowpass = (!node->sounds_set && !node->sounds_set_default);
1443 char *sounds_set = NULL;
1445 if (node->sounds_set_default && (want_default || has_only_default))
1446 sounds_set = node->sounds_set_default;
1448 if (node->sounds_set_lowpass && (want_lowpass || has_only_lowpass))
1449 sounds_set = node->sounds_set_lowpass;
1451 if (sounds_set && !strEqual(node->sounds_set, sounds_set))
1453 setString(&node->sounds_set, sounds_set);
1454 settings_changed = TRUE;
1457 if (node->node_group != NULL)
1458 settings_changed |= adjustTreeSoundsForEMC(node->node_group);
1463 return settings_changed;
1466 void dumpTreeInfo(TreeInfo *node, int depth)
1468 char bullet_list[] = { '-', '*', 'o' };
1472 Debug("tree", "Dumping TreeInfo:");
1476 char bullet = bullet_list[depth % ARRAY_SIZE(bullet_list)];
1478 for (i = 0; i < depth * 2; i++)
1479 DebugContinued("", " ");
1481 DebugContinued("tree", "%c '%s' ['%s] [PARENT: '%s'] %s\n",
1482 bullet, node->name, node->identifier,
1483 (node->node_parent ? node->node_parent->identifier : "-"),
1484 (node->node_group ? "[GROUP]" : ""));
1487 // use for dumping artwork info tree
1488 Debug("tree", "subdir == '%s' ['%s', '%s'] [%d])",
1489 node->subdir, node->fullpath, node->basepath, node->in_user_dir);
1492 if (node->node_group != NULL)
1493 dumpTreeInfo(node->node_group, depth + 1);
1499 void sortTreeInfoBySortFunction(TreeInfo **node_first,
1500 int (*compare_function)(const void *,
1503 int num_nodes = numTreeInfo(*node_first);
1504 TreeInfo **sort_array;
1505 TreeInfo *node = *node_first;
1511 // allocate array for sorting structure pointers
1512 sort_array = checked_calloc(num_nodes * sizeof(TreeInfo *));
1514 // writing structure pointers to sorting array
1515 while (i < num_nodes && node) // double boundary check...
1517 sort_array[i] = node;
1523 // sorting the structure pointers in the sorting array
1524 qsort(sort_array, num_nodes, sizeof(TreeInfo *),
1527 // update the linkage of list elements with the sorted node array
1528 for (i = 0; i < num_nodes - 1; i++)
1529 sort_array[i]->next = sort_array[i + 1];
1530 sort_array[num_nodes - 1]->next = NULL;
1532 // update the linkage of the main list anchor pointer
1533 *node_first = sort_array[0];
1537 // now recursively sort the level group structures
1541 if (node->node_group != NULL)
1542 sortTreeInfoBySortFunction(&node->node_group, compare_function);
1548 void sortTreeInfo(TreeInfo **node_first)
1550 sortTreeInfoBySortFunction(node_first, compareTreeInfoEntries);
1554 // ============================================================================
1555 // some stuff from "files.c"
1556 // ============================================================================
1558 #if defined(PLATFORM_WIN32)
1560 #define S_IRGRP S_IRUSR
1563 #define S_IROTH S_IRUSR
1566 #define S_IWGRP S_IWUSR
1569 #define S_IWOTH S_IWUSR
1572 #define S_IXGRP S_IXUSR
1575 #define S_IXOTH S_IXUSR
1578 #define S_IRWXG (S_IRGRP | S_IWGRP | S_IXGRP)
1583 #endif // PLATFORM_WIN32
1585 // file permissions for newly written files
1586 #define MODE_R_ALL (S_IRUSR | S_IRGRP | S_IROTH)
1587 #define MODE_W_ALL (S_IWUSR | S_IWGRP | S_IWOTH)
1588 #define MODE_X_ALL (S_IXUSR | S_IXGRP | S_IXOTH)
1590 #define MODE_W_PRIVATE (S_IWUSR)
1591 #define MODE_W_PUBLIC_FILE (S_IWUSR | S_IWGRP)
1592 #define MODE_W_PUBLIC_DIR (S_IWUSR | S_IWGRP | S_ISGID)
1594 #define DIR_PERMS_PRIVATE (MODE_R_ALL | MODE_X_ALL | MODE_W_PRIVATE)
1595 #define DIR_PERMS_PUBLIC (MODE_R_ALL | MODE_X_ALL | MODE_W_PUBLIC_DIR)
1596 #define DIR_PERMS_PUBLIC_ALL (MODE_R_ALL | MODE_X_ALL | MODE_W_ALL)
1598 #define FILE_PERMS_PRIVATE (MODE_R_ALL | MODE_W_PRIVATE)
1599 #define FILE_PERMS_PUBLIC (MODE_R_ALL | MODE_W_PUBLIC_FILE)
1600 #define FILE_PERMS_PUBLIC_ALL (MODE_R_ALL | MODE_W_ALL)
1603 char *getHomeDir(void)
1605 static char *dir = NULL;
1607 #if defined(PLATFORM_WIN32)
1610 dir = checked_malloc(MAX_PATH + 1);
1612 if (!SUCCEEDED(SHGetFolderPath(NULL, CSIDL_PERSONAL, NULL, 0, dir)))
1615 #elif defined(PLATFORM_EMSCRIPTEN)
1616 dir = "/persistent";
1617 #elif defined(PLATFORM_UNIX)
1620 if ((dir = getenv("HOME")) == NULL)
1622 dir = getUnixHomeDir();
1625 dir = getStringCopy(dir);
1637 char *getPersonalDataDir(void)
1639 static char *personal_data_dir = NULL;
1641 #if defined(PLATFORM_MACOSX)
1642 if (personal_data_dir == NULL)
1643 personal_data_dir = getPath2(getHomeDir(), "Documents");
1645 if (personal_data_dir == NULL)
1646 personal_data_dir = getHomeDir();
1649 return personal_data_dir;
1652 char *getMainUserGameDataDir(void)
1654 static char *main_user_data_dir = NULL;
1656 #if defined(PLATFORM_ANDROID)
1657 if (main_user_data_dir == NULL)
1658 main_user_data_dir = (char *)(SDL_AndroidGetExternalStorageState() &
1659 SDL_ANDROID_EXTERNAL_STORAGE_WRITE ?
1660 SDL_AndroidGetExternalStoragePath() :
1661 SDL_AndroidGetInternalStoragePath());
1663 if (main_user_data_dir == NULL)
1664 main_user_data_dir = getPath2(getPersonalDataDir(),
1665 program.userdata_subdir);
1668 return main_user_data_dir;
1671 char *getUserGameDataDir(void)
1674 return getMainUserGameDataDir();
1676 return getUserDir(user.nr);
1679 char *getSetupDir(void)
1681 return getUserGameDataDir();
1684 static mode_t posix_umask(mode_t mask)
1686 #if defined(PLATFORM_UNIX)
1693 static int posix_mkdir(const char *pathname, mode_t mode)
1695 #if defined(PLATFORM_WIN32)
1696 return mkdir(pathname);
1698 return mkdir(pathname, mode);
1702 static boolean posix_process_running_setgid(void)
1704 #if defined(PLATFORM_UNIX)
1705 return (getgid() != getegid());
1711 void createDirectory(char *dir, char *text, int permission_class)
1713 if (directoryExists(dir))
1716 // leave "other" permissions in umask untouched, but ensure group parts
1717 // of USERDATA_DIR_MODE are not masked
1718 mode_t dir_mode = (permission_class == PERMS_PRIVATE ?
1719 DIR_PERMS_PRIVATE : DIR_PERMS_PUBLIC);
1720 mode_t last_umask = posix_umask(0);
1721 mode_t group_umask = ~(dir_mode & S_IRWXG);
1722 int running_setgid = posix_process_running_setgid();
1724 if (permission_class == PERMS_PUBLIC)
1726 // if we're setgid, protect files against "other"
1727 // else keep umask(0) to make the dir world-writable
1730 posix_umask(last_umask & group_umask);
1732 dir_mode = DIR_PERMS_PUBLIC_ALL;
1735 if (posix_mkdir(dir, dir_mode) != 0)
1736 Warn("cannot create %s directory '%s': %s", text, dir, strerror(errno));
1738 if (permission_class == PERMS_PUBLIC && !running_setgid)
1739 chmod(dir, dir_mode);
1741 posix_umask(last_umask); // restore previous umask
1744 void InitMainUserDataDirectory(void)
1746 createDirectory(getMainUserGameDataDir(), "main user data", PERMS_PRIVATE);
1749 void InitUserDataDirectory(void)
1751 createDirectory(getMainUserGameDataDir(), "main user data", PERMS_PRIVATE);
1755 createDirectory(getUserDir(-1), "users", PERMS_PRIVATE);
1756 createDirectory(getUserDir(user.nr), "user data", PERMS_PRIVATE);
1760 void SetFilePermissions(char *filename, int permission_class)
1762 int running_setgid = posix_process_running_setgid();
1763 int perms = (permission_class == PERMS_PRIVATE ?
1764 FILE_PERMS_PRIVATE : FILE_PERMS_PUBLIC);
1766 if (permission_class == PERMS_PUBLIC && !running_setgid)
1767 perms = FILE_PERMS_PUBLIC_ALL;
1769 chmod(filename, perms);
1772 char *getCookie(char *file_type)
1774 static char cookie[MAX_COOKIE_LEN + 1];
1776 if (strlen(program.cookie_prefix) + 1 +
1777 strlen(file_type) + strlen("_FILE_VERSION_x.x") > MAX_COOKIE_LEN)
1778 return "[COOKIE ERROR]"; // should never happen
1780 sprintf(cookie, "%s_%s_FILE_VERSION_%d.%d",
1781 program.cookie_prefix, file_type,
1782 program.version_super, program.version_major);
1787 void fprintFileHeader(FILE *file, char *basename)
1789 char *prefix = "# ";
1792 fprintf_line_with_prefix(file, prefix, sep1, 77);
1793 fprintf(file, "%s%s\n", prefix, basename);
1794 fprintf_line_with_prefix(file, prefix, sep1, 77);
1795 fprintf(file, "\n");
1798 int getFileVersionFromCookieString(const char *cookie)
1800 const char *ptr_cookie1, *ptr_cookie2;
1801 const char *pattern1 = "_FILE_VERSION_";
1802 const char *pattern2 = "?.?";
1803 const int len_cookie = strlen(cookie);
1804 const int len_pattern1 = strlen(pattern1);
1805 const int len_pattern2 = strlen(pattern2);
1806 const int len_pattern = len_pattern1 + len_pattern2;
1807 int version_super, version_major;
1809 if (len_cookie <= len_pattern)
1812 ptr_cookie1 = &cookie[len_cookie - len_pattern];
1813 ptr_cookie2 = &cookie[len_cookie - len_pattern2];
1815 if (strncmp(ptr_cookie1, pattern1, len_pattern1) != 0)
1818 if (ptr_cookie2[0] < '0' || ptr_cookie2[0] > '9' ||
1819 ptr_cookie2[1] != '.' ||
1820 ptr_cookie2[2] < '0' || ptr_cookie2[2] > '9')
1823 version_super = ptr_cookie2[0] - '0';
1824 version_major = ptr_cookie2[2] - '0';
1826 return VERSION_IDENT(version_super, version_major, 0, 0);
1829 boolean checkCookieString(const char *cookie, const char *template)
1831 const char *pattern = "_FILE_VERSION_?.?";
1832 const int len_cookie = strlen(cookie);
1833 const int len_template = strlen(template);
1834 const int len_pattern = strlen(pattern);
1836 if (len_cookie != len_template)
1839 if (strncmp(cookie, template, len_cookie - len_pattern) != 0)
1846 // ----------------------------------------------------------------------------
1847 // setup file list and hash handling functions
1848 // ----------------------------------------------------------------------------
1850 char *getFormattedSetupEntry(char *token, char *value)
1853 static char entry[MAX_LINE_LEN];
1855 // if value is an empty string, just return token without value
1859 // start with the token and some spaces to format output line
1860 sprintf(entry, "%s:", token);
1861 for (i = strlen(entry); i < token_value_position; i++)
1864 // continue with the token's value
1865 strcat(entry, value);
1870 SetupFileList *newSetupFileList(char *token, char *value)
1872 SetupFileList *new = checked_malloc(sizeof(SetupFileList));
1874 new->token = getStringCopy(token);
1875 new->value = getStringCopy(value);
1882 void freeSetupFileList(SetupFileList *list)
1887 checked_free(list->token);
1888 checked_free(list->value);
1891 freeSetupFileList(list->next);
1896 char *getListEntry(SetupFileList *list, char *token)
1901 if (strEqual(list->token, token))
1904 return getListEntry(list->next, token);
1907 SetupFileList *setListEntry(SetupFileList *list, char *token, char *value)
1912 if (strEqual(list->token, token))
1914 checked_free(list->value);
1916 list->value = getStringCopy(value);
1920 else if (list->next == NULL)
1921 return (list->next = newSetupFileList(token, value));
1923 return setListEntry(list->next, token, value);
1926 SetupFileList *addListEntry(SetupFileList *list, char *token, char *value)
1931 if (list->next == NULL)
1932 return (list->next = newSetupFileList(token, value));
1934 return addListEntry(list->next, token, value);
1937 #if ENABLE_UNUSED_CODE
1939 static void printSetupFileList(SetupFileList *list)
1944 Debug("setup:printSetupFileList", "token: '%s'", list->token);
1945 Debug("setup:printSetupFileList", "value: '%s'", list->value);
1947 printSetupFileList(list->next);
1953 DEFINE_HASHTABLE_INSERT(insert_hash_entry, char, char);
1954 DEFINE_HASHTABLE_SEARCH(search_hash_entry, char, char);
1955 DEFINE_HASHTABLE_CHANGE(change_hash_entry, char, char);
1956 DEFINE_HASHTABLE_REMOVE(remove_hash_entry, char, char);
1958 #define insert_hash_entry hashtable_insert
1959 #define search_hash_entry hashtable_search
1960 #define change_hash_entry hashtable_change
1961 #define remove_hash_entry hashtable_remove
1964 unsigned int get_hash_from_key(void *key)
1969 This algorithm (k=33) was first reported by Dan Bernstein many years ago in
1970 'comp.lang.c'. Another version of this algorithm (now favored by Bernstein)
1971 uses XOR: hash(i) = hash(i - 1) * 33 ^ str[i]; the magic of number 33 (why
1972 it works better than many other constants, prime or not) has never been
1973 adequately explained.
1975 If you just want to have a good hash function, and cannot wait, djb2
1976 is one of the best string hash functions i know. It has excellent
1977 distribution and speed on many different sets of keys and table sizes.
1978 You are not likely to do better with one of the "well known" functions
1979 such as PJW, K&R, etc.
1981 Ozan (oz) Yigit [http://www.cs.yorku.ca/~oz/hash.html]
1984 char *str = (char *)key;
1985 unsigned int hash = 5381;
1988 while ((c = *str++))
1989 hash = ((hash << 5) + hash) + c; // hash * 33 + c
1994 static int keys_are_equal(void *key1, void *key2)
1996 return (strEqual((char *)key1, (char *)key2));
1999 SetupFileHash *newSetupFileHash(void)
2001 SetupFileHash *new_hash =
2002 create_hashtable(16, 0.75, get_hash_from_key, keys_are_equal);
2004 if (new_hash == NULL)
2005 Fail("create_hashtable() failed -- out of memory");
2010 void freeSetupFileHash(SetupFileHash *hash)
2015 hashtable_destroy(hash, 1); // 1 == also free values stored in hash
2018 char *getHashEntry(SetupFileHash *hash, char *token)
2023 return search_hash_entry(hash, token);
2026 void setHashEntry(SetupFileHash *hash, char *token, char *value)
2033 value_copy = getStringCopy(value);
2035 // change value; if it does not exist, insert it as new
2036 if (!change_hash_entry(hash, token, value_copy))
2037 if (!insert_hash_entry(hash, getStringCopy(token), value_copy))
2038 Fail("cannot insert into hash -- aborting");
2041 char *removeHashEntry(SetupFileHash *hash, char *token)
2046 return remove_hash_entry(hash, token);
2049 #if ENABLE_UNUSED_CODE
2051 static void printSetupFileHash(SetupFileHash *hash)
2053 BEGIN_HASH_ITERATION(hash, itr)
2055 Debug("setup:printSetupFileHash", "token: '%s'", HASH_ITERATION_TOKEN(itr));
2056 Debug("setup:printSetupFileHash", "value: '%s'", HASH_ITERATION_VALUE(itr));
2058 END_HASH_ITERATION(hash, itr)
2063 #define ALLOW_TOKEN_VALUE_SEPARATOR_BEING_WHITESPACE 1
2064 #define CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING 0
2065 #define CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH 0
2067 static boolean token_value_separator_found = FALSE;
2068 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2069 static boolean token_value_separator_warning = FALSE;
2071 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2072 static boolean token_already_exists_warning = FALSE;
2075 static boolean getTokenValueFromSetupLineExt(char *line,
2076 char **token_ptr, char **value_ptr,
2077 char *filename, char *line_raw,
2079 boolean separator_required)
2081 static char line_copy[MAX_LINE_LEN + 1], line_raw_copy[MAX_LINE_LEN + 1];
2082 char *token, *value, *line_ptr;
2084 // when externally invoked via ReadTokenValueFromLine(), copy line buffers
2085 if (line_raw == NULL)
2087 strncpy(line_copy, line, MAX_LINE_LEN);
2088 line_copy[MAX_LINE_LEN] = '\0';
2091 strcpy(line_raw_copy, line_copy);
2092 line_raw = line_raw_copy;
2095 // cut trailing comment from input line
2096 for (line_ptr = line; *line_ptr; line_ptr++)
2098 if (*line_ptr == '#')
2105 // cut trailing whitespaces from input line
2106 for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
2107 if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
2110 // ignore empty lines
2114 // cut leading whitespaces from token
2115 for (token = line; *token; token++)
2116 if (*token != ' ' && *token != '\t')
2119 // start with empty value as reliable default
2122 token_value_separator_found = FALSE;
2124 // find end of token to determine start of value
2125 for (line_ptr = token; *line_ptr; line_ptr++)
2127 // first look for an explicit token/value separator, like ':' or '='
2128 if (*line_ptr == ':' || *line_ptr == '=')
2130 *line_ptr = '\0'; // terminate token string
2131 value = line_ptr + 1; // set beginning of value
2133 token_value_separator_found = TRUE;
2139 #if ALLOW_TOKEN_VALUE_SEPARATOR_BEING_WHITESPACE
2140 // fallback: if no token/value separator found, also allow whitespaces
2141 if (!token_value_separator_found && !separator_required)
2143 for (line_ptr = token; *line_ptr; line_ptr++)
2145 if (*line_ptr == ' ' || *line_ptr == '\t')
2147 *line_ptr = '\0'; // terminate token string
2148 value = line_ptr + 1; // set beginning of value
2150 token_value_separator_found = TRUE;
2156 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2157 if (token_value_separator_found)
2159 if (!token_value_separator_warning)
2161 Debug("setup", "---");
2163 if (filename != NULL)
2165 Debug("setup", "missing token/value separator(s) in config file:");
2166 Debug("setup", "- config file: '%s'", filename);
2170 Debug("setup", "missing token/value separator(s):");
2173 token_value_separator_warning = TRUE;
2176 if (filename != NULL)
2177 Debug("setup", "- line %d: '%s'", line_nr, line_raw);
2179 Debug("setup", "- line: '%s'", line_raw);
2185 // cut trailing whitespaces from token
2186 for (line_ptr = &token[strlen(token)]; line_ptr >= token; line_ptr--)
2187 if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
2190 // cut leading whitespaces from value
2191 for (; *value; value++)
2192 if (*value != ' ' && *value != '\t')
2201 boolean getTokenValueFromSetupLine(char *line, char **token, char **value)
2203 // while the internal (old) interface does not require a token/value
2204 // separator (for downwards compatibility with existing files which
2205 // don't use them), it is mandatory for the external (new) interface
2207 return getTokenValueFromSetupLineExt(line, token, value, NULL, NULL, 0, TRUE);
2210 static boolean loadSetupFileData(void *setup_file_data, char *filename,
2211 boolean top_recursion_level, boolean is_hash)
2213 static SetupFileHash *include_filename_hash = NULL;
2214 char line[MAX_LINE_LEN], line_raw[MAX_LINE_LEN], previous_line[MAX_LINE_LEN];
2215 char *token, *value, *line_ptr;
2216 void *insert_ptr = NULL;
2217 boolean read_continued_line = FALSE;
2219 int line_nr = 0, token_count = 0, include_count = 0;
2221 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2222 token_value_separator_warning = FALSE;
2225 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2226 token_already_exists_warning = FALSE;
2229 if (!(file = openFile(filename, MODE_READ)))
2231 #if DEBUG_NO_CONFIG_FILE
2232 Debug("setup", "cannot open configuration file '%s'", filename);
2238 // use "insert pointer" to store list end for constant insertion complexity
2240 insert_ptr = setup_file_data;
2242 // on top invocation, create hash to mark included files (to prevent loops)
2243 if (top_recursion_level)
2244 include_filename_hash = newSetupFileHash();
2246 // mark this file as already included (to prevent including it again)
2247 setHashEntry(include_filename_hash, getBaseNamePtr(filename), "true");
2249 while (!checkEndOfFile(file))
2251 // read next line of input file
2252 if (!getStringFromFile(file, line, MAX_LINE_LEN))
2255 // check if line was completely read and is terminated by line break
2256 if (strlen(line) > 0 && line[strlen(line) - 1] == '\n')
2259 // cut trailing line break (this can be newline and/or carriage return)
2260 for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
2261 if ((*line_ptr == '\n' || *line_ptr == '\r') && *(line_ptr + 1) == '\0')
2264 // copy raw input line for later use (mainly debugging output)
2265 strcpy(line_raw, line);
2267 if (read_continued_line)
2269 // append new line to existing line, if there is enough space
2270 if (strlen(previous_line) + strlen(line_ptr) < MAX_LINE_LEN)
2271 strcat(previous_line, line_ptr);
2273 strcpy(line, previous_line); // copy storage buffer to line
2275 read_continued_line = FALSE;
2278 // if the last character is '\', continue at next line
2279 if (strlen(line) > 0 && line[strlen(line) - 1] == '\\')
2281 line[strlen(line) - 1] = '\0'; // cut off trailing backslash
2282 strcpy(previous_line, line); // copy line to storage buffer
2284 read_continued_line = TRUE;
2289 if (!getTokenValueFromSetupLineExt(line, &token, &value, filename,
2290 line_raw, line_nr, FALSE))
2295 if (strEqual(token, "include"))
2297 if (getHashEntry(include_filename_hash, value) == NULL)
2299 char *basepath = getBasePath(filename);
2300 char *basename = getBaseName(value);
2301 char *filename_include = getPath2(basepath, basename);
2303 loadSetupFileData(setup_file_data, filename_include, FALSE, is_hash);
2307 free(filename_include);
2313 Warn("ignoring already processed file '%s'", value);
2320 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2322 getHashEntry((SetupFileHash *)setup_file_data, token);
2324 if (old_value != NULL)
2326 if (!token_already_exists_warning)
2328 Debug("setup", "---");
2329 Debug("setup", "duplicate token(s) found in config file:");
2330 Debug("setup", "- config file: '%s'", filename);
2332 token_already_exists_warning = TRUE;
2335 Debug("setup", "- token: '%s' (in line %d)", token, line_nr);
2336 Debug("setup", " old value: '%s'", old_value);
2337 Debug("setup", " new value: '%s'", value);
2341 setHashEntry((SetupFileHash *)setup_file_data, token, value);
2345 insert_ptr = addListEntry((SetupFileList *)insert_ptr, token, value);
2355 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2356 if (token_value_separator_warning)
2357 Debug("setup", "---");
2360 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2361 if (token_already_exists_warning)
2362 Debug("setup", "---");
2365 if (token_count == 0 && include_count == 0)
2366 Warn("configuration file '%s' is empty", filename);
2368 if (top_recursion_level)
2369 freeSetupFileHash(include_filename_hash);
2374 static int compareSetupFileData(const void *object1, const void *object2)
2376 const struct ConfigInfo *entry1 = (struct ConfigInfo *)object1;
2377 const struct ConfigInfo *entry2 = (struct ConfigInfo *)object2;
2379 return strcmp(entry1->token, entry2->token);
2382 static void saveSetupFileHash(SetupFileHash *hash, char *filename)
2384 int item_count = hashtable_count(hash);
2385 int item_size = sizeof(struct ConfigInfo);
2386 struct ConfigInfo *sort_array = checked_malloc(item_count * item_size);
2390 // copy string pointers from hash to array
2391 BEGIN_HASH_ITERATION(hash, itr)
2393 sort_array[i].token = HASH_ITERATION_TOKEN(itr);
2394 sort_array[i].value = HASH_ITERATION_VALUE(itr);
2398 if (i > item_count) // should never happen
2401 END_HASH_ITERATION(hash, itr)
2403 // sort string pointers from hash in array
2404 qsort(sort_array, item_count, item_size, compareSetupFileData);
2406 if (!(file = fopen(filename, MODE_WRITE)))
2408 Warn("cannot write configuration file '%s'", filename);
2413 fprintf(file, "%s\n\n", getFormattedSetupEntry("program.version",
2414 program.version_string));
2415 for (i = 0; i < item_count; i++)
2416 fprintf(file, "%s\n", getFormattedSetupEntry(sort_array[i].token,
2417 sort_array[i].value));
2420 checked_free(sort_array);
2423 SetupFileList *loadSetupFileList(char *filename)
2425 SetupFileList *setup_file_list = newSetupFileList("", "");
2426 SetupFileList *first_valid_list_entry;
2428 if (!loadSetupFileData(setup_file_list, filename, TRUE, FALSE))
2430 freeSetupFileList(setup_file_list);
2435 first_valid_list_entry = setup_file_list->next;
2437 // free empty list header
2438 setup_file_list->next = NULL;
2439 freeSetupFileList(setup_file_list);
2441 return first_valid_list_entry;
2444 SetupFileHash *loadSetupFileHash(char *filename)
2446 SetupFileHash *setup_file_hash = newSetupFileHash();
2448 if (!loadSetupFileData(setup_file_hash, filename, TRUE, TRUE))
2450 freeSetupFileHash(setup_file_hash);
2455 return setup_file_hash;
2459 // ============================================================================
2461 // ============================================================================
2463 #define TOKEN_STR_LAST_LEVEL_SERIES "last_level_series"
2464 #define TOKEN_STR_LAST_PLAYED_LEVEL "last_played_level"
2465 #define TOKEN_STR_HANDICAP_LEVEL "handicap_level"
2466 #define TOKEN_STR_LAST_USER "last_user"
2468 // level directory info
2469 #define LEVELINFO_TOKEN_IDENTIFIER 0
2470 #define LEVELINFO_TOKEN_NAME 1
2471 #define LEVELINFO_TOKEN_NAME_SORTING 2
2472 #define LEVELINFO_TOKEN_AUTHOR 3
2473 #define LEVELINFO_TOKEN_YEAR 4
2474 #define LEVELINFO_TOKEN_PROGRAM_TITLE 5
2475 #define LEVELINFO_TOKEN_PROGRAM_COPYRIGHT 6
2476 #define LEVELINFO_TOKEN_PROGRAM_COMPANY 7
2477 #define LEVELINFO_TOKEN_IMPORTED_FROM 8
2478 #define LEVELINFO_TOKEN_IMPORTED_BY 9
2479 #define LEVELINFO_TOKEN_TESTED_BY 10
2480 #define LEVELINFO_TOKEN_LEVELS 11
2481 #define LEVELINFO_TOKEN_FIRST_LEVEL 12
2482 #define LEVELINFO_TOKEN_SORT_PRIORITY 13
2483 #define LEVELINFO_TOKEN_LATEST_ENGINE 14
2484 #define LEVELINFO_TOKEN_LEVEL_GROUP 15
2485 #define LEVELINFO_TOKEN_READONLY 16
2486 #define LEVELINFO_TOKEN_GRAPHICS_SET_ECS 17
2487 #define LEVELINFO_TOKEN_GRAPHICS_SET_AGA 18
2488 #define LEVELINFO_TOKEN_GRAPHICS_SET 19
2489 #define LEVELINFO_TOKEN_SOUNDS_SET_DEFAULT 20
2490 #define LEVELINFO_TOKEN_SOUNDS_SET_LOWPASS 21
2491 #define LEVELINFO_TOKEN_SOUNDS_SET 22
2492 #define LEVELINFO_TOKEN_MUSIC_SET 23
2493 #define LEVELINFO_TOKEN_FILENAME 24
2494 #define LEVELINFO_TOKEN_FILETYPE 25
2495 #define LEVELINFO_TOKEN_SPECIAL_FLAGS 26
2496 #define LEVELINFO_TOKEN_HANDICAP 27
2497 #define LEVELINFO_TOKEN_SKIP_LEVELS 28
2498 #define LEVELINFO_TOKEN_USE_EMC_TILES 29
2500 #define NUM_LEVELINFO_TOKENS 30
2502 static LevelDirTree ldi;
2504 static struct TokenInfo levelinfo_tokens[] =
2506 // level directory info
2507 { TYPE_STRING, &ldi.identifier, "identifier" },
2508 { TYPE_STRING, &ldi.name, "name" },
2509 { TYPE_STRING, &ldi.name_sorting, "name_sorting" },
2510 { TYPE_STRING, &ldi.author, "author" },
2511 { TYPE_STRING, &ldi.year, "year" },
2512 { TYPE_STRING, &ldi.program_title, "program_title" },
2513 { TYPE_STRING, &ldi.program_copyright, "program_copyright" },
2514 { TYPE_STRING, &ldi.program_company, "program_company" },
2515 { TYPE_STRING, &ldi.imported_from, "imported_from" },
2516 { TYPE_STRING, &ldi.imported_by, "imported_by" },
2517 { TYPE_STRING, &ldi.tested_by, "tested_by" },
2518 { TYPE_INTEGER, &ldi.levels, "levels" },
2519 { TYPE_INTEGER, &ldi.first_level, "first_level" },
2520 { TYPE_INTEGER, &ldi.sort_priority, "sort_priority" },
2521 { TYPE_BOOLEAN, &ldi.latest_engine, "latest_engine" },
2522 { TYPE_BOOLEAN, &ldi.level_group, "level_group" },
2523 { TYPE_BOOLEAN, &ldi.readonly, "readonly" },
2524 { TYPE_STRING, &ldi.graphics_set_ecs, "graphics_set.ecs" },
2525 { TYPE_STRING, &ldi.graphics_set_aga, "graphics_set.aga" },
2526 { TYPE_STRING, &ldi.graphics_set, "graphics_set" },
2527 { TYPE_STRING, &ldi.sounds_set_default,"sounds_set.default" },
2528 { TYPE_STRING, &ldi.sounds_set_lowpass,"sounds_set.lowpass" },
2529 { TYPE_STRING, &ldi.sounds_set, "sounds_set" },
2530 { TYPE_STRING, &ldi.music_set, "music_set" },
2531 { TYPE_STRING, &ldi.level_filename, "filename" },
2532 { TYPE_STRING, &ldi.level_filetype, "filetype" },
2533 { TYPE_STRING, &ldi.special_flags, "special_flags" },
2534 { TYPE_BOOLEAN, &ldi.handicap, "handicap" },
2535 { TYPE_BOOLEAN, &ldi.skip_levels, "skip_levels" },
2536 { TYPE_BOOLEAN, &ldi.use_emc_tiles, "use_emc_tiles" }
2539 static struct TokenInfo artworkinfo_tokens[] =
2541 // artwork directory info
2542 { TYPE_STRING, &ldi.identifier, "identifier" },
2543 { TYPE_STRING, &ldi.subdir, "subdir" },
2544 { TYPE_STRING, &ldi.name, "name" },
2545 { TYPE_STRING, &ldi.name_sorting, "name_sorting" },
2546 { TYPE_STRING, &ldi.author, "author" },
2547 { TYPE_STRING, &ldi.program_title, "program_title" },
2548 { TYPE_STRING, &ldi.program_copyright, "program_copyright" },
2549 { TYPE_STRING, &ldi.program_company, "program_company" },
2550 { TYPE_INTEGER, &ldi.sort_priority, "sort_priority" },
2551 { TYPE_STRING, &ldi.basepath, "basepath" },
2552 { TYPE_STRING, &ldi.fullpath, "fullpath" },
2553 { TYPE_BOOLEAN, &ldi.in_user_dir, "in_user_dir" },
2554 { TYPE_STRING, &ldi.class_desc, "class_desc" },
2559 static char *optional_tokens[] =
2562 "program_copyright",
2568 static void setTreeInfoToDefaults(TreeInfo *ti, int type)
2572 ti->node_top = (ti->type == TREE_TYPE_LEVEL_DIR ? &leveldir_first :
2573 ti->type == TREE_TYPE_GRAPHICS_DIR ? &artwork.gfx_first :
2574 ti->type == TREE_TYPE_SOUNDS_DIR ? &artwork.snd_first :
2575 ti->type == TREE_TYPE_MUSIC_DIR ? &artwork.mus_first :
2578 ti->node_parent = NULL;
2579 ti->node_group = NULL;
2586 ti->fullpath = NULL;
2587 ti->basepath = NULL;
2588 ti->identifier = NULL;
2589 ti->name = getStringCopy(ANONYMOUS_NAME);
2590 ti->name_sorting = NULL;
2591 ti->author = getStringCopy(ANONYMOUS_NAME);
2594 ti->program_title = NULL;
2595 ti->program_copyright = NULL;
2596 ti->program_company = NULL;
2598 ti->sort_priority = LEVELCLASS_UNDEFINED; // default: least priority
2599 ti->latest_engine = FALSE; // default: get from level
2600 ti->parent_link = FALSE;
2601 ti->is_copy = FALSE;
2602 ti->in_user_dir = FALSE;
2603 ti->user_defined = FALSE;
2605 ti->class_desc = NULL;
2607 ti->infotext = getStringCopy(TREE_INFOTEXT(ti->type));
2609 if (ti->type == TREE_TYPE_LEVEL_DIR)
2611 ti->imported_from = NULL;
2612 ti->imported_by = NULL;
2613 ti->tested_by = NULL;
2615 ti->graphics_set_ecs = NULL;
2616 ti->graphics_set_aga = NULL;
2617 ti->graphics_set = NULL;
2618 ti->sounds_set_default = NULL;
2619 ti->sounds_set_lowpass = NULL;
2620 ti->sounds_set = NULL;
2621 ti->music_set = NULL;
2622 ti->graphics_path = getStringCopy(UNDEFINED_FILENAME);
2623 ti->sounds_path = getStringCopy(UNDEFINED_FILENAME);
2624 ti->music_path = getStringCopy(UNDEFINED_FILENAME);
2626 ti->level_filename = NULL;
2627 ti->level_filetype = NULL;
2629 ti->special_flags = NULL;
2632 ti->first_level = 0;
2634 ti->level_group = FALSE;
2635 ti->handicap_level = 0;
2636 ti->readonly = TRUE;
2637 ti->handicap = TRUE;
2638 ti->skip_levels = FALSE;
2640 ti->use_emc_tiles = FALSE;
2644 static void setTreeInfoToDefaultsFromParent(TreeInfo *ti, TreeInfo *parent)
2648 Warn("setTreeInfoToDefaultsFromParent(): parent == NULL");
2650 setTreeInfoToDefaults(ti, TREE_TYPE_UNDEFINED);
2655 // copy all values from the parent structure
2657 ti->type = parent->type;
2659 ti->node_top = parent->node_top;
2660 ti->node_parent = parent;
2661 ti->node_group = NULL;
2668 ti->fullpath = NULL;
2669 ti->basepath = NULL;
2670 ti->identifier = NULL;
2671 ti->name = getStringCopy(ANONYMOUS_NAME);
2672 ti->name_sorting = NULL;
2673 ti->author = getStringCopy(parent->author);
2674 ti->year = getStringCopy(parent->year);
2676 ti->program_title = getStringCopy(parent->program_title);
2677 ti->program_copyright = getStringCopy(parent->program_copyright);
2678 ti->program_company = getStringCopy(parent->program_company);
2680 ti->sort_priority = parent->sort_priority;
2681 ti->latest_engine = parent->latest_engine;
2682 ti->parent_link = FALSE;
2683 ti->is_copy = FALSE;
2684 ti->in_user_dir = parent->in_user_dir;
2685 ti->user_defined = parent->user_defined;
2686 ti->color = parent->color;
2687 ti->class_desc = getStringCopy(parent->class_desc);
2689 ti->infotext = getStringCopy(parent->infotext);
2691 if (ti->type == TREE_TYPE_LEVEL_DIR)
2693 ti->imported_from = getStringCopy(parent->imported_from);
2694 ti->imported_by = getStringCopy(parent->imported_by);
2695 ti->tested_by = getStringCopy(parent->tested_by);
2697 ti->graphics_set_ecs = getStringCopy(parent->graphics_set_ecs);
2698 ti->graphics_set_aga = getStringCopy(parent->graphics_set_aga);
2699 ti->graphics_set = getStringCopy(parent->graphics_set);
2700 ti->sounds_set_default = getStringCopy(parent->sounds_set_default);
2701 ti->sounds_set_lowpass = getStringCopy(parent->sounds_set_lowpass);
2702 ti->sounds_set = getStringCopy(parent->sounds_set);
2703 ti->music_set = getStringCopy(parent->music_set);
2704 ti->graphics_path = getStringCopy(UNDEFINED_FILENAME);
2705 ti->sounds_path = getStringCopy(UNDEFINED_FILENAME);
2706 ti->music_path = getStringCopy(UNDEFINED_FILENAME);
2708 ti->level_filename = getStringCopy(parent->level_filename);
2709 ti->level_filetype = getStringCopy(parent->level_filetype);
2711 ti->special_flags = getStringCopy(parent->special_flags);
2713 ti->levels = parent->levels;
2714 ti->first_level = parent->first_level;
2715 ti->last_level = parent->last_level;
2716 ti->level_group = FALSE;
2717 ti->handicap_level = parent->handicap_level;
2718 ti->readonly = parent->readonly;
2719 ti->handicap = parent->handicap;
2720 ti->skip_levels = parent->skip_levels;
2722 ti->use_emc_tiles = parent->use_emc_tiles;
2726 static TreeInfo *getTreeInfoCopy(TreeInfo *ti)
2728 TreeInfo *ti_copy = newTreeInfo();
2730 // copy all values from the original structure
2732 ti_copy->type = ti->type;
2734 ti_copy->node_top = ti->node_top;
2735 ti_copy->node_parent = ti->node_parent;
2736 ti_copy->node_group = ti->node_group;
2737 ti_copy->next = ti->next;
2739 ti_copy->cl_first = ti->cl_first;
2740 ti_copy->cl_cursor = ti->cl_cursor;
2742 ti_copy->subdir = getStringCopy(ti->subdir);
2743 ti_copy->fullpath = getStringCopy(ti->fullpath);
2744 ti_copy->basepath = getStringCopy(ti->basepath);
2745 ti_copy->identifier = getStringCopy(ti->identifier);
2746 ti_copy->name = getStringCopy(ti->name);
2747 ti_copy->name_sorting = getStringCopy(ti->name_sorting);
2748 ti_copy->author = getStringCopy(ti->author);
2749 ti_copy->year = getStringCopy(ti->year);
2751 ti_copy->program_title = getStringCopy(ti->program_title);
2752 ti_copy->program_copyright = getStringCopy(ti->program_copyright);
2753 ti_copy->program_company = getStringCopy(ti->program_company);
2755 ti_copy->imported_from = getStringCopy(ti->imported_from);
2756 ti_copy->imported_by = getStringCopy(ti->imported_by);
2757 ti_copy->tested_by = getStringCopy(ti->tested_by);
2759 ti_copy->graphics_set_ecs = getStringCopy(ti->graphics_set_ecs);
2760 ti_copy->graphics_set_aga = getStringCopy(ti->graphics_set_aga);
2761 ti_copy->graphics_set = getStringCopy(ti->graphics_set);
2762 ti_copy->sounds_set_default = getStringCopy(ti->sounds_set_default);
2763 ti_copy->sounds_set_lowpass = getStringCopy(ti->sounds_set_lowpass);
2764 ti_copy->sounds_set = getStringCopy(ti->sounds_set);
2765 ti_copy->music_set = getStringCopy(ti->music_set);
2766 ti_copy->graphics_path = getStringCopy(ti->graphics_path);
2767 ti_copy->sounds_path = getStringCopy(ti->sounds_path);
2768 ti_copy->music_path = getStringCopy(ti->music_path);
2770 ti_copy->level_filename = getStringCopy(ti->level_filename);
2771 ti_copy->level_filetype = getStringCopy(ti->level_filetype);
2773 ti_copy->special_flags = getStringCopy(ti->special_flags);
2775 ti_copy->levels = ti->levels;
2776 ti_copy->first_level = ti->first_level;
2777 ti_copy->last_level = ti->last_level;
2778 ti_copy->sort_priority = ti->sort_priority;
2780 ti_copy->latest_engine = ti->latest_engine;
2782 ti_copy->level_group = ti->level_group;
2783 ti_copy->parent_link = ti->parent_link;
2784 ti_copy->is_copy = ti->is_copy;
2785 ti_copy->in_user_dir = ti->in_user_dir;
2786 ti_copy->user_defined = ti->user_defined;
2787 ti_copy->readonly = ti->readonly;
2788 ti_copy->handicap = ti->handicap;
2789 ti_copy->skip_levels = ti->skip_levels;
2791 ti_copy->use_emc_tiles = ti->use_emc_tiles;
2793 ti_copy->color = ti->color;
2794 ti_copy->class_desc = getStringCopy(ti->class_desc);
2795 ti_copy->handicap_level = ti->handicap_level;
2797 ti_copy->infotext = getStringCopy(ti->infotext);
2802 void freeTreeInfo(TreeInfo *ti)
2807 checked_free(ti->subdir);
2808 checked_free(ti->fullpath);
2809 checked_free(ti->basepath);
2810 checked_free(ti->identifier);
2812 checked_free(ti->name);
2813 checked_free(ti->name_sorting);
2814 checked_free(ti->author);
2815 checked_free(ti->year);
2817 checked_free(ti->program_title);
2818 checked_free(ti->program_copyright);
2819 checked_free(ti->program_company);
2821 checked_free(ti->class_desc);
2823 checked_free(ti->infotext);
2825 if (ti->type == TREE_TYPE_LEVEL_DIR)
2827 checked_free(ti->imported_from);
2828 checked_free(ti->imported_by);
2829 checked_free(ti->tested_by);
2831 checked_free(ti->graphics_set_ecs);
2832 checked_free(ti->graphics_set_aga);
2833 checked_free(ti->graphics_set);
2834 checked_free(ti->sounds_set_default);
2835 checked_free(ti->sounds_set_lowpass);
2836 checked_free(ti->sounds_set);
2837 checked_free(ti->music_set);
2839 checked_free(ti->graphics_path);
2840 checked_free(ti->sounds_path);
2841 checked_free(ti->music_path);
2843 checked_free(ti->level_filename);
2844 checked_free(ti->level_filetype);
2846 checked_free(ti->special_flags);
2849 // recursively free child node
2851 freeTreeInfo(ti->node_group);
2853 // recursively free next node
2855 freeTreeInfo(ti->next);
2860 void setSetupInfo(struct TokenInfo *token_info,
2861 int token_nr, char *token_value)
2863 int token_type = token_info[token_nr].type;
2864 void *setup_value = token_info[token_nr].value;
2866 if (token_value == NULL)
2869 // set setup field to corresponding token value
2874 *(boolean *)setup_value = get_boolean_from_string(token_value);
2878 *(int *)setup_value = get_switch3_from_string(token_value);
2882 *(Key *)setup_value = getKeyFromKeyName(token_value);
2886 *(Key *)setup_value = getKeyFromX11KeyName(token_value);
2890 *(int *)setup_value = get_integer_from_string(token_value);
2894 checked_free(*(char **)setup_value);
2895 *(char **)setup_value = getStringCopy(token_value);
2899 *(int *)setup_value = get_player_nr_from_string(token_value);
2907 static int compareTreeInfoEntries(const void *object1, const void *object2)
2909 const TreeInfo *entry1 = *((TreeInfo **)object1);
2910 const TreeInfo *entry2 = *((TreeInfo **)object2);
2911 int tree_sorting1 = TREE_SORTING(entry1);
2912 int tree_sorting2 = TREE_SORTING(entry2);
2914 if (tree_sorting1 != tree_sorting2)
2915 return (tree_sorting1 - tree_sorting2);
2917 return strcasecmp(entry1->name_sorting, entry2->name_sorting);
2920 static TreeInfo *createParentTreeInfoNode(TreeInfo *node_parent)
2924 if (node_parent == NULL)
2927 ti_new = newTreeInfo();
2928 setTreeInfoToDefaults(ti_new, node_parent->type);
2930 ti_new->node_parent = node_parent;
2931 ti_new->parent_link = TRUE;
2933 setString(&ti_new->identifier, node_parent->identifier);
2934 setString(&ti_new->name, BACKLINK_TEXT_PARENT);
2935 setString(&ti_new->name_sorting, ti_new->name);
2937 setString(&ti_new->subdir, STRING_PARENT_DIRECTORY);
2938 setString(&ti_new->fullpath, node_parent->fullpath);
2940 ti_new->sort_priority = LEVELCLASS_PARENT;
2941 ti_new->latest_engine = node_parent->latest_engine;
2943 setString(&ti_new->class_desc, getLevelClassDescription(ti_new));
2945 pushTreeInfo(&node_parent->node_group, ti_new);
2950 static TreeInfo *createTopTreeInfoNode(TreeInfo *node_first)
2952 if (node_first == NULL)
2955 TreeInfo *ti_new = newTreeInfo();
2956 int type = node_first->type;
2958 setTreeInfoToDefaults(ti_new, type);
2960 ti_new->node_parent = NULL;
2961 ti_new->parent_link = FALSE;
2963 setString(&ti_new->identifier, "top_tree_node");
2964 setString(&ti_new->name, TREE_INFOTEXT(type));
2965 setString(&ti_new->name_sorting, ti_new->name);
2967 setString(&ti_new->subdir, STRING_TOP_DIRECTORY);
2968 setString(&ti_new->fullpath, ".");
2970 ti_new->sort_priority = LEVELCLASS_TOP;
2971 ti_new->latest_engine = node_first->latest_engine;
2973 setString(&ti_new->class_desc, TREE_INFOTEXT(type));
2975 ti_new->node_group = node_first;
2976 ti_new->level_group = TRUE;
2978 TreeInfo *ti_new2 = createParentTreeInfoNode(ti_new);
2980 setString(&ti_new2->name, TREE_BACKLINK_TEXT(type));
2981 setString(&ti_new2->name_sorting, ti_new2->name);
2986 static void setTreeInfoParentNodes(TreeInfo *node, TreeInfo *node_parent)
2990 if (node->node_group)
2991 setTreeInfoParentNodes(node->node_group, node);
2993 node->node_parent = node_parent;
3000 // ----------------------------------------------------------------------------
3001 // functions for handling level and custom artwork info cache
3002 // ----------------------------------------------------------------------------
3004 static void LoadArtworkInfoCache(void)
3006 InitCacheDirectory();
3008 if (artworkinfo_cache_old == NULL)
3010 char *filename = getPath2(getCacheDir(), ARTWORKINFO_CACHE_FILE);
3012 // try to load artwork info hash from already existing cache file
3013 artworkinfo_cache_old = loadSetupFileHash(filename);
3015 // try to get program version that artwork info cache was written with
3016 char *version = getHashEntry(artworkinfo_cache_old, "program.version");
3018 // check program version of artwork info cache against current version
3019 if (!strEqual(version, program.version_string))
3021 freeSetupFileHash(artworkinfo_cache_old);
3023 artworkinfo_cache_old = NULL;
3026 // if no artwork info cache file was found, start with empty hash
3027 if (artworkinfo_cache_old == NULL)
3028 artworkinfo_cache_old = newSetupFileHash();
3033 if (artworkinfo_cache_new == NULL)
3034 artworkinfo_cache_new = newSetupFileHash();
3036 update_artworkinfo_cache = FALSE;
3039 static void SaveArtworkInfoCache(void)
3041 if (!update_artworkinfo_cache)
3044 char *filename = getPath2(getCacheDir(), ARTWORKINFO_CACHE_FILE);
3046 InitCacheDirectory();
3048 saveSetupFileHash(artworkinfo_cache_new, filename);
3053 static char *getCacheTokenPrefix(char *prefix1, char *prefix2)
3055 static char *prefix = NULL;
3057 checked_free(prefix);
3059 prefix = getStringCat2WithSeparator(prefix1, prefix2, ".");
3064 // (identical to above function, but separate string buffer needed -- nasty)
3065 static char *getCacheToken(char *prefix, char *suffix)
3067 static char *token = NULL;
3069 checked_free(token);
3071 token = getStringCat2WithSeparator(prefix, suffix, ".");
3076 static char *getFileTimestampString(char *filename)
3078 return getStringCopy(i_to_a(getFileTimestampEpochSeconds(filename)));
3081 static boolean modifiedFileTimestamp(char *filename, char *timestamp_string)
3083 struct stat file_status;
3085 if (timestamp_string == NULL)
3088 if (!fileExists(filename)) // file does not exist
3089 return (atoi(timestamp_string) != 0);
3091 if (stat(filename, &file_status) != 0) // cannot stat file
3094 return (file_status.st_mtime != atoi(timestamp_string));
3097 static TreeInfo *getArtworkInfoCacheEntry(LevelDirTree *level_node, int type)
3099 char *identifier = level_node->subdir;
3100 char *type_string = ARTWORK_DIRECTORY(type);
3101 char *token_prefix = getCacheTokenPrefix(type_string, identifier);
3102 char *token_main = getCacheToken(token_prefix, "CACHED");
3103 char *cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
3104 boolean cached = (cache_entry != NULL && strEqual(cache_entry, "true"));
3105 TreeInfo *artwork_info = NULL;
3107 if (!use_artworkinfo_cache)
3110 if (optional_tokens_hash == NULL)
3114 // create hash from list of optional tokens (for quick access)
3115 optional_tokens_hash = newSetupFileHash();
3116 for (i = 0; optional_tokens[i] != NULL; i++)
3117 setHashEntry(optional_tokens_hash, optional_tokens[i], "");
3124 artwork_info = newTreeInfo();
3125 setTreeInfoToDefaults(artwork_info, type);
3127 // set all structure fields according to the token/value pairs
3128 ldi = *artwork_info;
3129 for (i = 0; artworkinfo_tokens[i].type != -1; i++)
3131 char *token_suffix = artworkinfo_tokens[i].text;
3132 char *token = getCacheToken(token_prefix, token_suffix);
3133 char *value = getHashEntry(artworkinfo_cache_old, token);
3135 (getHashEntry(optional_tokens_hash, token_suffix) != NULL);
3137 setSetupInfo(artworkinfo_tokens, i, value);
3139 // check if cache entry for this item is mandatory, but missing
3140 if (value == NULL && !optional)
3142 Warn("missing cache entry '%s'", token);
3148 *artwork_info = ldi;
3153 char *filename_levelinfo = getPath2(getLevelDirFromTreeInfo(level_node),
3154 LEVELINFO_FILENAME);
3155 char *filename_artworkinfo = getPath2(getSetupArtworkDir(artwork_info),
3156 ARTWORKINFO_FILENAME(type));
3158 // check if corresponding "levelinfo.conf" file has changed
3159 token_main = getCacheToken(token_prefix, "TIMESTAMP_LEVELINFO");
3160 cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
3162 if (modifiedFileTimestamp(filename_levelinfo, cache_entry))
3165 // check if corresponding "<artworkinfo>.conf" file has changed
3166 token_main = getCacheToken(token_prefix, "TIMESTAMP_ARTWORKINFO");
3167 cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
3169 if (modifiedFileTimestamp(filename_artworkinfo, cache_entry))
3172 checked_free(filename_levelinfo);
3173 checked_free(filename_artworkinfo);
3176 if (!cached && artwork_info != NULL)
3178 freeTreeInfo(artwork_info);
3183 return artwork_info;
3186 static void setArtworkInfoCacheEntry(TreeInfo *artwork_info,
3187 LevelDirTree *level_node, int type)
3189 char *identifier = level_node->subdir;
3190 char *type_string = ARTWORK_DIRECTORY(type);
3191 char *token_prefix = getCacheTokenPrefix(type_string, identifier);
3192 char *token_main = getCacheToken(token_prefix, "CACHED");
3193 boolean set_cache_timestamps = TRUE;
3196 setHashEntry(artworkinfo_cache_new, token_main, "true");
3198 if (set_cache_timestamps)
3200 char *filename_levelinfo = getPath2(getLevelDirFromTreeInfo(level_node),
3201 LEVELINFO_FILENAME);
3202 char *filename_artworkinfo = getPath2(getSetupArtworkDir(artwork_info),
3203 ARTWORKINFO_FILENAME(type));
3204 char *timestamp_levelinfo = getFileTimestampString(filename_levelinfo);
3205 char *timestamp_artworkinfo = getFileTimestampString(filename_artworkinfo);
3207 token_main = getCacheToken(token_prefix, "TIMESTAMP_LEVELINFO");
3208 setHashEntry(artworkinfo_cache_new, token_main, timestamp_levelinfo);
3210 token_main = getCacheToken(token_prefix, "TIMESTAMP_ARTWORKINFO");
3211 setHashEntry(artworkinfo_cache_new, token_main, timestamp_artworkinfo);
3213 checked_free(filename_levelinfo);
3214 checked_free(filename_artworkinfo);
3215 checked_free(timestamp_levelinfo);
3216 checked_free(timestamp_artworkinfo);
3219 ldi = *artwork_info;
3220 for (i = 0; artworkinfo_tokens[i].type != -1; i++)
3222 char *token = getCacheToken(token_prefix, artworkinfo_tokens[i].text);
3223 char *value = getSetupValue(artworkinfo_tokens[i].type,
3224 artworkinfo_tokens[i].value);
3226 setHashEntry(artworkinfo_cache_new, token, value);
3231 // ----------------------------------------------------------------------------
3232 // functions for loading level info and custom artwork info
3233 // ----------------------------------------------------------------------------
3235 int GetZipFileTreeType(char *zip_filename)
3237 static char *top_dir_path = NULL;
3238 static char *top_dir_conf_filename[NUM_BASE_TREE_TYPES] = { NULL };
3239 static char *conf_basename[NUM_BASE_TREE_TYPES] =
3241 GRAPHICSINFO_FILENAME,
3242 SOUNDSINFO_FILENAME,
3248 checked_free(top_dir_path);
3249 top_dir_path = NULL;
3251 for (j = 0; j < NUM_BASE_TREE_TYPES; j++)
3253 checked_free(top_dir_conf_filename[j]);
3254 top_dir_conf_filename[j] = NULL;
3257 char **zip_entries = zip_list(zip_filename);
3259 // check if zip file successfully opened
3260 if (zip_entries == NULL || zip_entries[0] == NULL)
3261 return TREE_TYPE_UNDEFINED;
3263 // first zip file entry is expected to be top level directory
3264 char *top_dir = zip_entries[0];
3266 // check if valid top level directory found in zip file
3267 if (!strSuffix(top_dir, "/"))
3268 return TREE_TYPE_UNDEFINED;
3270 // get filenames of valid configuration files in top level directory
3271 for (j = 0; j < NUM_BASE_TREE_TYPES; j++)
3272 top_dir_conf_filename[j] = getStringCat2(top_dir, conf_basename[j]);
3274 int tree_type = TREE_TYPE_UNDEFINED;
3277 while (zip_entries[e] != NULL)
3279 // check if every zip file entry is below top level directory
3280 if (!strPrefix(zip_entries[e], top_dir))
3281 return TREE_TYPE_UNDEFINED;
3283 // check if this zip file entry is a valid configuration filename
3284 for (j = 0; j < NUM_BASE_TREE_TYPES; j++)
3286 if (strEqual(zip_entries[e], top_dir_conf_filename[j]))
3288 // only exactly one valid configuration file allowed
3289 if (tree_type != TREE_TYPE_UNDEFINED)
3290 return TREE_TYPE_UNDEFINED;
3302 static boolean CheckZipFileForDirectory(char *zip_filename, char *directory,
3305 static char *top_dir_path = NULL;
3306 static char *top_dir_conf_filename = NULL;
3308 checked_free(top_dir_path);
3309 checked_free(top_dir_conf_filename);
3311 top_dir_path = NULL;
3312 top_dir_conf_filename = NULL;
3314 char *conf_basename = (tree_type == TREE_TYPE_LEVEL_DIR ? LEVELINFO_FILENAME :
3315 ARTWORKINFO_FILENAME(tree_type));
3317 // check if valid configuration filename determined
3318 if (conf_basename == NULL || strEqual(conf_basename, ""))
3321 char **zip_entries = zip_list(zip_filename);
3323 // check if zip file successfully opened
3324 if (zip_entries == NULL || zip_entries[0] == NULL)
3327 // first zip file entry is expected to be top level directory
3328 char *top_dir = zip_entries[0];
3330 // check if valid top level directory found in zip file
3331 if (!strSuffix(top_dir, "/"))
3334 // get path of extracted top level directory
3335 top_dir_path = getPath2(directory, top_dir);
3337 // remove trailing directory separator from top level directory path
3338 // (required to be able to check for file and directory in next step)
3339 top_dir_path[strlen(top_dir_path) - 1] = '\0';
3341 // check if zip file's top level directory already exists in target directory
3342 if (fileExists(top_dir_path)) // (checks for file and directory)
3345 // get filename of configuration file in top level directory
3346 top_dir_conf_filename = getStringCat2(top_dir, conf_basename);
3348 boolean found_top_dir_conf_filename = FALSE;
3351 while (zip_entries[i] != NULL)
3353 // check if every zip file entry is below top level directory
3354 if (!strPrefix(zip_entries[i], top_dir))
3357 // check if this zip file entry is the configuration filename
3358 if (strEqual(zip_entries[i], top_dir_conf_filename))
3359 found_top_dir_conf_filename = TRUE;
3364 // check if valid configuration filename was found in zip file
3365 if (!found_top_dir_conf_filename)
3371 char *ExtractZipFileIntoDirectory(char *zip_filename, char *directory,
3374 boolean zip_file_valid = CheckZipFileForDirectory(zip_filename, directory,
3377 if (!zip_file_valid)
3379 Warn("zip file '%s' rejected!", zip_filename);
3384 char **zip_entries = zip_extract(zip_filename, directory);
3386 if (zip_entries == NULL)
3388 Warn("zip file '%s' could not be extracted!", zip_filename);
3393 Info("zip file '%s' successfully extracted!", zip_filename);
3395 // first zip file entry contains top level directory
3396 char *top_dir = zip_entries[0];
3398 // remove trailing directory separator from top level directory
3399 top_dir[strlen(top_dir) - 1] = '\0';
3404 static void ProcessZipFilesInDirectory(char *directory, int tree_type)
3407 DirectoryEntry *dir_entry;
3409 if ((dir = openDirectory(directory)) == NULL)
3411 // display error if directory is main "options.graphics_directory" etc.
3412 if (tree_type == TREE_TYPE_LEVEL_DIR ||
3413 directory == OPTIONS_ARTWORK_DIRECTORY(tree_type))
3414 Warn("cannot read directory '%s'", directory);
3419 while ((dir_entry = readDirectory(dir)) != NULL) // loop all entries
3421 // skip non-zip files (and also directories with zip extension)
3422 if (!strSuffixLower(dir_entry->basename, ".zip") || dir_entry->is_directory)
3425 char *zip_filename = getPath2(directory, dir_entry->basename);
3426 char *zip_filename_extracted = getStringCat2(zip_filename, ".extracted");
3427 char *zip_filename_rejected = getStringCat2(zip_filename, ".rejected");
3429 // check if zip file hasn't already been extracted or rejected
3430 if (!fileExists(zip_filename_extracted) &&
3431 !fileExists(zip_filename_rejected))
3433 char *top_dir = ExtractZipFileIntoDirectory(zip_filename, directory,
3435 char *marker_filename = (top_dir != NULL ? zip_filename_extracted :
3436 zip_filename_rejected);
3439 // create empty file to mark zip file as extracted or rejected
3440 if ((marker_file = fopen(marker_filename, MODE_WRITE)))
3441 fclose(marker_file);
3444 free(zip_filename_extracted);
3445 free(zip_filename_rejected);
3449 closeDirectory(dir);
3452 // forward declaration for recursive call by "LoadLevelInfoFromLevelDir()"
3453 static void LoadLevelInfoFromLevelDir(TreeInfo **, TreeInfo *, char *);
3455 static boolean LoadLevelInfoFromLevelConf(TreeInfo **node_first,
3456 TreeInfo *node_parent,
3457 char *level_directory,
3458 char *directory_name)
3460 char *directory_path = getPath2(level_directory, directory_name);
3461 char *filename = getPath2(directory_path, LEVELINFO_FILENAME);
3462 SetupFileHash *setup_file_hash;
3463 LevelDirTree *leveldir_new = NULL;
3466 // unless debugging, silently ignore directories without "levelinfo.conf"
3467 if (!options.debug && !fileExists(filename))
3469 free(directory_path);
3475 setup_file_hash = loadSetupFileHash(filename);
3477 if (setup_file_hash == NULL)
3479 #if DEBUG_NO_CONFIG_FILE
3480 Debug("setup", "ignoring level directory '%s'", directory_path);
3483 free(directory_path);
3489 leveldir_new = newTreeInfo();
3492 setTreeInfoToDefaultsFromParent(leveldir_new, node_parent);
3494 setTreeInfoToDefaults(leveldir_new, TREE_TYPE_LEVEL_DIR);
3496 leveldir_new->subdir = getStringCopy(directory_name);
3498 // set all structure fields according to the token/value pairs
3499 ldi = *leveldir_new;
3500 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
3501 setSetupInfo(levelinfo_tokens, i,
3502 getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
3503 *leveldir_new = ldi;
3505 if (strEqual(leveldir_new->name, ANONYMOUS_NAME))
3506 setString(&leveldir_new->name, leveldir_new->subdir);
3508 if (leveldir_new->identifier == NULL)
3509 leveldir_new->identifier = getStringCopy(leveldir_new->subdir);
3511 if (leveldir_new->name_sorting == NULL)
3512 leveldir_new->name_sorting = getStringCopy(leveldir_new->name);
3514 if (node_parent == NULL) // top level group
3516 leveldir_new->basepath = getStringCopy(level_directory);
3517 leveldir_new->fullpath = getStringCopy(leveldir_new->subdir);
3519 else // sub level group
3521 leveldir_new->basepath = getStringCopy(node_parent->basepath);
3522 leveldir_new->fullpath = getPath2(node_parent->fullpath, directory_name);
3525 leveldir_new->last_level =
3526 leveldir_new->first_level + leveldir_new->levels - 1;
3528 leveldir_new->in_user_dir =
3529 (!strEqual(leveldir_new->basepath, options.level_directory));
3531 // adjust some settings if user's private level directory was detected
3532 if (leveldir_new->sort_priority == LEVELCLASS_UNDEFINED &&
3533 leveldir_new->in_user_dir &&
3534 (strEqual(leveldir_new->subdir, getLoginName()) ||
3535 strEqual(leveldir_new->name, getLoginName()) ||
3536 strEqual(leveldir_new->author, getRealName())))
3538 leveldir_new->sort_priority = LEVELCLASS_PRIVATE_START;
3539 leveldir_new->readonly = FALSE;
3542 leveldir_new->user_defined =
3543 (leveldir_new->in_user_dir && IS_LEVELCLASS_PRIVATE(leveldir_new));
3545 setString(&leveldir_new->class_desc, getLevelClassDescription(leveldir_new));
3547 leveldir_new->handicap_level = // set handicap to default value
3548 (leveldir_new->user_defined || !leveldir_new->handicap ?
3549 leveldir_new->last_level : leveldir_new->first_level);
3551 DrawInitText(leveldir_new->name, 150, FC_YELLOW);
3553 pushTreeInfo(node_first, leveldir_new);
3555 freeSetupFileHash(setup_file_hash);
3557 if (leveldir_new->level_group)
3559 // create node to link back to current level directory
3560 createParentTreeInfoNode(leveldir_new);
3562 // recursively step into sub-directory and look for more level series
3563 LoadLevelInfoFromLevelDir(&leveldir_new->node_group,
3564 leveldir_new, directory_path);
3567 free(directory_path);
3573 static void LoadLevelInfoFromLevelDir(TreeInfo **node_first,
3574 TreeInfo *node_parent,
3575 char *level_directory)
3577 // ---------- 1st stage: process any level set zip files ----------
3579 ProcessZipFilesInDirectory(level_directory, TREE_TYPE_LEVEL_DIR);
3581 // ---------- 2nd stage: check for level set directories ----------
3584 DirectoryEntry *dir_entry;
3585 boolean valid_entry_found = FALSE;
3587 if ((dir = openDirectory(level_directory)) == NULL)
3589 Warn("cannot read level directory '%s'", level_directory);
3594 while ((dir_entry = readDirectory(dir)) != NULL) // loop all entries
3596 char *directory_name = dir_entry->basename;
3597 char *directory_path = getPath2(level_directory, directory_name);
3599 // skip entries for current and parent directory
3600 if (strEqual(directory_name, ".") ||
3601 strEqual(directory_name, ".."))
3603 free(directory_path);
3608 // find out if directory entry is itself a directory
3609 if (!dir_entry->is_directory) // not a directory
3611 free(directory_path);
3616 free(directory_path);
3618 if (strEqual(directory_name, GRAPHICS_DIRECTORY) ||
3619 strEqual(directory_name, SOUNDS_DIRECTORY) ||
3620 strEqual(directory_name, MUSIC_DIRECTORY))
3623 valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
3628 closeDirectory(dir);
3630 // special case: top level directory may directly contain "levelinfo.conf"
3631 if (node_parent == NULL && !valid_entry_found)
3633 // check if this directory directly contains a file "levelinfo.conf"
3634 valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
3635 level_directory, ".");
3638 if (!valid_entry_found)
3639 Warn("cannot find any valid level series in directory '%s'",
3643 boolean AdjustGraphicsForEMC(void)
3645 boolean settings_changed = FALSE;
3647 settings_changed |= adjustTreeGraphicsForEMC(leveldir_first_all);
3648 settings_changed |= adjustTreeGraphicsForEMC(leveldir_first);
3650 return settings_changed;
3653 boolean AdjustSoundsForEMC(void)
3655 boolean settings_changed = FALSE;
3657 settings_changed |= adjustTreeSoundsForEMC(leveldir_first_all);
3658 settings_changed |= adjustTreeSoundsForEMC(leveldir_first);
3660 return settings_changed;
3663 void LoadLevelInfo(void)
3665 InitUserLevelDirectory(getLoginName());
3667 DrawInitText("Loading level series", 120, FC_GREEN);
3669 LoadLevelInfoFromLevelDir(&leveldir_first, NULL, options.level_directory);
3670 LoadLevelInfoFromLevelDir(&leveldir_first, NULL, getUserLevelDir(NULL));
3672 leveldir_first = createTopTreeInfoNode(leveldir_first);
3674 /* after loading all level set information, clone the level directory tree
3675 and remove all level sets without levels (these may still contain artwork
3676 to be offered in the setup menu as "custom artwork", and are therefore
3677 checked for existing artwork in the function "LoadLevelArtworkInfo()") */
3678 leveldir_first_all = leveldir_first;
3679 cloneTree(&leveldir_first, leveldir_first_all, TRUE);
3681 AdjustGraphicsForEMC();
3682 AdjustSoundsForEMC();
3684 // before sorting, the first entries will be from the user directory
3685 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
3687 if (leveldir_first == NULL)
3688 Fail("cannot find any valid level series in any directory");
3690 sortTreeInfo(&leveldir_first);
3692 #if ENABLE_UNUSED_CODE
3693 dumpTreeInfo(leveldir_first, 0);
3697 static boolean LoadArtworkInfoFromArtworkConf(TreeInfo **node_first,
3698 TreeInfo *node_parent,
3699 char *base_directory,
3700 char *directory_name, int type)
3702 char *directory_path = getPath2(base_directory, directory_name);
3703 char *filename = getPath2(directory_path, ARTWORKINFO_FILENAME(type));
3704 SetupFileHash *setup_file_hash = NULL;
3705 TreeInfo *artwork_new = NULL;
3708 if (fileExists(filename))
3709 setup_file_hash = loadSetupFileHash(filename);
3711 if (setup_file_hash == NULL) // no config file -- look for artwork files
3714 DirectoryEntry *dir_entry;
3715 boolean valid_file_found = FALSE;
3717 if ((dir = openDirectory(directory_path)) != NULL)
3719 while ((dir_entry = readDirectory(dir)) != NULL)
3721 if (FileIsArtworkType(dir_entry->filename, type))
3723 valid_file_found = TRUE;
3729 closeDirectory(dir);
3732 if (!valid_file_found)
3734 #if DEBUG_NO_CONFIG_FILE
3735 if (!strEqual(directory_name, "."))
3736 Debug("setup", "ignoring artwork directory '%s'", directory_path);
3739 free(directory_path);
3746 artwork_new = newTreeInfo();
3749 setTreeInfoToDefaultsFromParent(artwork_new, node_parent);
3751 setTreeInfoToDefaults(artwork_new, type);
3753 artwork_new->subdir = getStringCopy(directory_name);
3755 if (setup_file_hash) // (before defining ".color" and ".class_desc")
3757 // set all structure fields according to the token/value pairs
3759 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
3760 setSetupInfo(levelinfo_tokens, i,
3761 getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
3764 if (strEqual(artwork_new->name, ANONYMOUS_NAME))
3765 setString(&artwork_new->name, artwork_new->subdir);
3767 if (artwork_new->identifier == NULL)
3768 artwork_new->identifier = getStringCopy(artwork_new->subdir);
3770 if (artwork_new->name_sorting == NULL)
3771 artwork_new->name_sorting = getStringCopy(artwork_new->name);
3774 if (node_parent == NULL) // top level group
3776 artwork_new->basepath = getStringCopy(base_directory);
3777 artwork_new->fullpath = getStringCopy(artwork_new->subdir);
3779 else // sub level group
3781 artwork_new->basepath = getStringCopy(node_parent->basepath);
3782 artwork_new->fullpath = getPath2(node_parent->fullpath, directory_name);
3785 artwork_new->in_user_dir =
3786 (!strEqual(artwork_new->basepath, OPTIONS_ARTWORK_DIRECTORY(type)));
3788 setString(&artwork_new->class_desc, getLevelClassDescription(artwork_new));
3790 if (setup_file_hash == NULL) // (after determining ".user_defined")
3792 if (strEqual(artwork_new->subdir, "."))
3794 if (artwork_new->user_defined)
3796 setString(&artwork_new->identifier, "private");
3797 artwork_new->sort_priority = ARTWORKCLASS_PRIVATE;
3801 setString(&artwork_new->identifier, "classic");
3802 artwork_new->sort_priority = ARTWORKCLASS_CLASSICS;
3805 setString(&artwork_new->class_desc,
3806 getLevelClassDescription(artwork_new));
3810 setString(&artwork_new->identifier, artwork_new->subdir);
3813 setString(&artwork_new->name, artwork_new->identifier);
3814 setString(&artwork_new->name_sorting, artwork_new->name);
3817 pushTreeInfo(node_first, artwork_new);
3819 freeSetupFileHash(setup_file_hash);
3821 free(directory_path);
3827 static void LoadArtworkInfoFromArtworkDir(TreeInfo **node_first,
3828 TreeInfo *node_parent,
3829 char *base_directory, int type)
3831 // ---------- 1st stage: process any artwork set zip files ----------
3833 ProcessZipFilesInDirectory(base_directory, type);
3835 // ---------- 2nd stage: check for artwork set directories ----------
3838 DirectoryEntry *dir_entry;
3839 boolean valid_entry_found = FALSE;
3841 if ((dir = openDirectory(base_directory)) == NULL)
3843 // display error if directory is main "options.graphics_directory" etc.
3844 if (base_directory == OPTIONS_ARTWORK_DIRECTORY(type))
3845 Warn("cannot read directory '%s'", base_directory);
3850 while ((dir_entry = readDirectory(dir)) != NULL) // loop all entries
3852 char *directory_name = dir_entry->basename;
3853 char *directory_path = getPath2(base_directory, directory_name);
3855 // skip directory entries for current and parent directory
3856 if (strEqual(directory_name, ".") ||
3857 strEqual(directory_name, ".."))
3859 free(directory_path);
3864 // skip directory entries which are not a directory
3865 if (!dir_entry->is_directory) // not a directory
3867 free(directory_path);
3872 free(directory_path);
3874 // check if this directory contains artwork with or without config file
3875 valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
3877 directory_name, type);
3880 closeDirectory(dir);
3882 // check if this directory directly contains artwork itself
3883 valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
3884 base_directory, ".",
3886 if (!valid_entry_found)
3887 Warn("cannot find any valid artwork in directory '%s'", base_directory);
3890 static TreeInfo *getDummyArtworkInfo(int type)
3892 // this is only needed when there is completely no artwork available
3893 TreeInfo *artwork_new = newTreeInfo();
3895 setTreeInfoToDefaults(artwork_new, type);
3897 setString(&artwork_new->subdir, UNDEFINED_FILENAME);
3898 setString(&artwork_new->fullpath, UNDEFINED_FILENAME);
3899 setString(&artwork_new->basepath, UNDEFINED_FILENAME);
3901 setString(&artwork_new->identifier, UNDEFINED_FILENAME);
3902 setString(&artwork_new->name, UNDEFINED_FILENAME);
3903 setString(&artwork_new->name_sorting, UNDEFINED_FILENAME);
3908 void SetCurrentArtwork(int type)
3910 ArtworkDirTree **current_ptr = ARTWORK_CURRENT_PTR(artwork, type);
3911 ArtworkDirTree *first_node = ARTWORK_FIRST_NODE(artwork, type);
3912 char *setup_set = SETUP_ARTWORK_SET(setup, type);
3913 char *default_subdir = ARTWORK_DEFAULT_SUBDIR(type);
3915 // set current artwork to artwork configured in setup menu
3916 *current_ptr = getTreeInfoFromIdentifier(first_node, setup_set);
3918 // if not found, set current artwork to default artwork
3919 if (*current_ptr == NULL)
3920 *current_ptr = getTreeInfoFromIdentifier(first_node, default_subdir);
3922 // if not found, set current artwork to first artwork in tree
3923 if (*current_ptr == NULL)
3924 *current_ptr = getFirstValidTreeInfoEntry(first_node);
3927 void ChangeCurrentArtworkIfNeeded(int type)
3929 char *current_identifier = ARTWORK_CURRENT_IDENTIFIER(artwork, type);
3930 char *setup_set = SETUP_ARTWORK_SET(setup, type);
3932 if (!strEqual(current_identifier, setup_set))
3933 SetCurrentArtwork(type);
3936 void LoadArtworkInfo(void)
3938 LoadArtworkInfoCache();
3940 DrawInitText("Looking for custom artwork", 120, FC_GREEN);
3942 LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
3943 options.graphics_directory,
3944 TREE_TYPE_GRAPHICS_DIR);
3945 LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
3946 getUserGraphicsDir(),
3947 TREE_TYPE_GRAPHICS_DIR);
3949 LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
3950 options.sounds_directory,
3951 TREE_TYPE_SOUNDS_DIR);
3952 LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
3954 TREE_TYPE_SOUNDS_DIR);
3956 LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
3957 options.music_directory,
3958 TREE_TYPE_MUSIC_DIR);
3959 LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
3961 TREE_TYPE_MUSIC_DIR);
3963 if (artwork.gfx_first == NULL)
3964 artwork.gfx_first = getDummyArtworkInfo(TREE_TYPE_GRAPHICS_DIR);
3965 if (artwork.snd_first == NULL)
3966 artwork.snd_first = getDummyArtworkInfo(TREE_TYPE_SOUNDS_DIR);
3967 if (artwork.mus_first == NULL)
3968 artwork.mus_first = getDummyArtworkInfo(TREE_TYPE_MUSIC_DIR);
3970 // before sorting, the first entries will be from the user directory
3971 SetCurrentArtwork(ARTWORK_TYPE_GRAPHICS);
3972 SetCurrentArtwork(ARTWORK_TYPE_SOUNDS);
3973 SetCurrentArtwork(ARTWORK_TYPE_MUSIC);
3975 artwork.gfx_current_identifier = artwork.gfx_current->identifier;
3976 artwork.snd_current_identifier = artwork.snd_current->identifier;
3977 artwork.mus_current_identifier = artwork.mus_current->identifier;
3979 #if ENABLE_UNUSED_CODE
3980 Debug("setup:LoadArtworkInfo", "graphics set == %s",
3981 artwork.gfx_current_identifier);
3982 Debug("setup:LoadArtworkInfo", "sounds set == %s",
3983 artwork.snd_current_identifier);
3984 Debug("setup:LoadArtworkInfo", "music set == %s",
3985 artwork.mus_current_identifier);
3988 sortTreeInfo(&artwork.gfx_first);
3989 sortTreeInfo(&artwork.snd_first);
3990 sortTreeInfo(&artwork.mus_first);
3992 #if ENABLE_UNUSED_CODE
3993 dumpTreeInfo(artwork.gfx_first, 0);
3994 dumpTreeInfo(artwork.snd_first, 0);
3995 dumpTreeInfo(artwork.mus_first, 0);
3999 static void MoveArtworkInfoIntoSubTree(ArtworkDirTree **artwork_node)
4001 ArtworkDirTree *artwork_new = newTreeInfo();
4002 char *top_node_name = "standalone artwork";
4004 setTreeInfoToDefaults(artwork_new, (*artwork_node)->type);
4006 artwork_new->level_group = TRUE;
4008 setString(&artwork_new->identifier, top_node_name);
4009 setString(&artwork_new->name, top_node_name);
4010 setString(&artwork_new->name_sorting, top_node_name);
4012 // create node to link back to current custom artwork directory
4013 createParentTreeInfoNode(artwork_new);
4015 // move existing custom artwork tree into newly created sub-tree
4016 artwork_new->node_group->next = *artwork_node;
4018 // change custom artwork tree to contain only newly created node
4019 *artwork_node = artwork_new;
4022 static void LoadArtworkInfoFromLevelInfoExt(ArtworkDirTree **artwork_node,
4023 ArtworkDirTree *node_parent,
4024 LevelDirTree *level_node,
4025 boolean empty_level_set_mode)
4027 int type = (*artwork_node)->type;
4029 // recursively check all level directories for artwork sub-directories
4033 boolean empty_level_set = (level_node->levels == 0);
4035 // check all tree entries for artwork, but skip parent link entries
4036 if (!level_node->parent_link && empty_level_set == empty_level_set_mode)
4038 TreeInfo *artwork_new = getArtworkInfoCacheEntry(level_node, type);
4039 boolean cached = (artwork_new != NULL);
4043 pushTreeInfo(artwork_node, artwork_new);
4047 TreeInfo *topnode_last = *artwork_node;
4048 char *path = getPath2(getLevelDirFromTreeInfo(level_node),
4049 ARTWORK_DIRECTORY(type));
4051 LoadArtworkInfoFromArtworkDir(artwork_node, NULL, path, type);
4053 if (topnode_last != *artwork_node) // check for newly added node
4055 artwork_new = *artwork_node;
4057 setString(&artwork_new->identifier, level_node->subdir);
4058 setString(&artwork_new->name, level_node->name);
4059 setString(&artwork_new->name_sorting, level_node->name_sorting);
4061 artwork_new->sort_priority = level_node->sort_priority;
4062 artwork_new->in_user_dir = level_node->in_user_dir;
4064 update_artworkinfo_cache = TRUE;
4070 // insert artwork info (from old cache or filesystem) into new cache
4071 if (artwork_new != NULL)
4072 setArtworkInfoCacheEntry(artwork_new, level_node, type);
4075 DrawInitText(level_node->name, 150, FC_YELLOW);
4077 if (level_node->node_group != NULL)
4079 TreeInfo *artwork_new = newTreeInfo();
4082 setTreeInfoToDefaultsFromParent(artwork_new, node_parent);
4084 setTreeInfoToDefaults(artwork_new, type);
4086 artwork_new->level_group = TRUE;
4088 setString(&artwork_new->identifier, level_node->subdir);
4090 if (node_parent == NULL) // check for top tree node
4092 char *top_node_name = (empty_level_set_mode ?
4093 "artwork for certain level sets" :
4094 "artwork included in level sets");
4096 setString(&artwork_new->name, top_node_name);
4097 setString(&artwork_new->name_sorting, top_node_name);
4101 setString(&artwork_new->name, level_node->name);
4102 setString(&artwork_new->name_sorting, level_node->name_sorting);
4105 pushTreeInfo(artwork_node, artwork_new);
4107 // create node to link back to current custom artwork directory
4108 createParentTreeInfoNode(artwork_new);
4110 // recursively step into sub-directory and look for more custom artwork
4111 LoadArtworkInfoFromLevelInfoExt(&artwork_new->node_group, artwork_new,
4112 level_node->node_group,
4113 empty_level_set_mode);
4115 // if sub-tree has no custom artwork at all, remove it
4116 if (artwork_new->node_group->next == NULL)
4117 removeTreeInfo(artwork_node);
4120 level_node = level_node->next;
4124 static void LoadArtworkInfoFromLevelInfo(ArtworkDirTree **artwork_node)
4126 // move peviously loaded artwork tree into separate sub-tree
4127 MoveArtworkInfoIntoSubTree(artwork_node);
4129 // load artwork from level sets into separate sub-trees
4130 LoadArtworkInfoFromLevelInfoExt(artwork_node, NULL, leveldir_first_all, TRUE);
4131 LoadArtworkInfoFromLevelInfoExt(artwork_node, NULL, leveldir_first_all, FALSE);
4133 // add top tree node over all three separate sub-trees
4134 *artwork_node = createTopTreeInfoNode(*artwork_node);
4136 // set all parent links (back links) in complete artwork tree
4137 setTreeInfoParentNodes(*artwork_node, NULL);
4140 void LoadLevelArtworkInfo(void)
4142 print_timestamp_init("LoadLevelArtworkInfo");
4144 DrawInitText("Looking for custom level artwork", 120, FC_GREEN);
4146 print_timestamp_time("DrawTimeText");
4148 LoadArtworkInfoFromLevelInfo(&artwork.gfx_first);
4149 print_timestamp_time("LoadArtworkInfoFromLevelInfo (gfx)");
4150 LoadArtworkInfoFromLevelInfo(&artwork.snd_first);
4151 print_timestamp_time("LoadArtworkInfoFromLevelInfo (snd)");
4152 LoadArtworkInfoFromLevelInfo(&artwork.mus_first);
4153 print_timestamp_time("LoadArtworkInfoFromLevelInfo (mus)");
4155 SaveArtworkInfoCache();
4157 print_timestamp_time("SaveArtworkInfoCache");
4159 // needed for reloading level artwork not known at ealier stage
4160 ChangeCurrentArtworkIfNeeded(ARTWORK_TYPE_GRAPHICS);
4161 ChangeCurrentArtworkIfNeeded(ARTWORK_TYPE_SOUNDS);
4162 ChangeCurrentArtworkIfNeeded(ARTWORK_TYPE_MUSIC);
4164 print_timestamp_time("getTreeInfoFromIdentifier");
4166 sortTreeInfo(&artwork.gfx_first);
4167 sortTreeInfo(&artwork.snd_first);
4168 sortTreeInfo(&artwork.mus_first);
4170 print_timestamp_time("sortTreeInfo");
4172 #if ENABLE_UNUSED_CODE
4173 dumpTreeInfo(artwork.gfx_first, 0);
4174 dumpTreeInfo(artwork.snd_first, 0);
4175 dumpTreeInfo(artwork.mus_first, 0);
4178 print_timestamp_done("LoadLevelArtworkInfo");
4181 static boolean AddTreeSetToTreeInfoExt(TreeInfo *tree_node_old, char *tree_dir,
4182 char *tree_subdir_new, int type)
4184 if (tree_node_old == NULL)
4186 if (type == TREE_TYPE_LEVEL_DIR)
4188 // get level info tree node of personal user level set
4189 tree_node_old = getTreeInfoFromIdentifier(leveldir_first, getLoginName());
4191 // this may happen if "setup.internal.create_user_levelset" is FALSE
4192 // or if file "levelinfo.conf" is missing in personal user level set
4193 if (tree_node_old == NULL)
4194 tree_node_old = leveldir_first->node_group;
4198 // get artwork info tree node of first artwork set
4199 tree_node_old = ARTWORK_FIRST_NODE(artwork, type);
4203 if (tree_dir == NULL)
4204 tree_dir = TREE_USERDIR(type);
4206 if (tree_node_old == NULL ||
4208 tree_subdir_new == NULL) // should not happen
4211 int draw_deactivation_mask = GetDrawDeactivationMask();
4213 // override draw deactivation mask (temporarily disable drawing)
4214 SetDrawDeactivationMask(REDRAW_ALL);
4216 if (type == TREE_TYPE_LEVEL_DIR)
4218 // load new level set config and add it next to first user level set
4219 LoadLevelInfoFromLevelConf(&tree_node_old->next,
4220 tree_node_old->node_parent,
4221 tree_dir, tree_subdir_new);
4225 // load new artwork set config and add it next to first artwork set
4226 LoadArtworkInfoFromArtworkConf(&tree_node_old->next,
4227 tree_node_old->node_parent,
4228 tree_dir, tree_subdir_new, type);
4231 // set draw deactivation mask to previous value
4232 SetDrawDeactivationMask(draw_deactivation_mask);
4234 // get first node of level or artwork info tree
4235 TreeInfo **tree_node_first = TREE_FIRST_NODE_PTR(type);
4237 // get tree info node of newly added level or artwork set
4238 TreeInfo *tree_node_new = getTreeInfoFromIdentifier(*tree_node_first,
4241 if (tree_node_new == NULL) // should not happen
4244 // correct top link and parent node link of newly created tree node
4245 tree_node_new->node_top = tree_node_old->node_top;
4246 tree_node_new->node_parent = tree_node_old->node_parent;
4248 // sort tree info to adjust position of newly added tree set
4249 sortTreeInfo(tree_node_first);
4254 void AddTreeSetToTreeInfo(TreeInfo *tree_node, char *tree_dir,
4255 char *tree_subdir_new, int type)
4257 if (!AddTreeSetToTreeInfoExt(tree_node, tree_dir, tree_subdir_new, type))
4258 Fail("internal tree info structure corrupted -- aborting");
4261 void AddUserLevelSetToLevelInfo(char *level_subdir_new)
4263 AddTreeSetToTreeInfo(NULL, NULL, level_subdir_new, TREE_TYPE_LEVEL_DIR);
4266 char *getArtworkIdentifierForUserLevelSet(int type)
4268 char *classic_artwork_set = getClassicArtworkSet(type);
4270 // check for custom artwork configured in "levelinfo.conf"
4271 char *leveldir_artwork_set =
4272 *LEVELDIR_ARTWORK_SET_PTR(leveldir_current, type);
4273 boolean has_leveldir_artwork_set =
4274 (leveldir_artwork_set != NULL && !strEqual(leveldir_artwork_set,
4275 classic_artwork_set));
4277 // check for custom artwork in sub-directory "graphics" etc.
4278 TreeInfo *artwork_first_node = ARTWORK_FIRST_NODE(artwork, type);
4279 char *leveldir_identifier = leveldir_current->identifier;
4280 boolean has_artwork_subdir =
4281 (getTreeInfoFromIdentifier(artwork_first_node,
4282 leveldir_identifier) != NULL);
4284 return (has_leveldir_artwork_set ? leveldir_artwork_set :
4285 has_artwork_subdir ? leveldir_identifier :
4286 classic_artwork_set);
4289 TreeInfo *getArtworkTreeInfoForUserLevelSet(int type)
4291 char *artwork_set = getArtworkIdentifierForUserLevelSet(type);
4292 TreeInfo *artwork_first_node = ARTWORK_FIRST_NODE(artwork, type);
4293 TreeInfo *ti = getTreeInfoFromIdentifier(artwork_first_node, artwork_set);
4297 ti = getTreeInfoFromIdentifier(artwork_first_node,
4298 ARTWORK_DEFAULT_SUBDIR(type));
4300 Fail("cannot find default graphics -- should not happen");
4306 boolean checkIfCustomArtworkExistsForCurrentLevelSet(void)
4308 char *graphics_set =
4309 getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_GRAPHICS);
4311 getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_SOUNDS);
4313 getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_MUSIC);
4315 return (!strEqual(graphics_set, GFX_CLASSIC_SUBDIR) ||
4316 !strEqual(sounds_set, SND_CLASSIC_SUBDIR) ||
4317 !strEqual(music_set, MUS_CLASSIC_SUBDIR));
4320 boolean UpdateUserLevelSet(char *level_subdir, char *level_name,
4321 char *level_author, int num_levels)
4323 char *filename = getPath2(getUserLevelDir(level_subdir), LEVELINFO_FILENAME);
4324 char *filename_tmp = getStringCat2(filename, ".tmp");
4326 FILE *file_tmp = NULL;
4327 char line[MAX_LINE_LEN];
4328 boolean success = FALSE;
4329 LevelDirTree *leveldir = getTreeInfoFromIdentifier(leveldir_first,
4331 // update values in level directory tree
4333 if (level_name != NULL)
4334 setString(&leveldir->name, level_name);
4336 if (level_author != NULL)
4337 setString(&leveldir->author, level_author);
4339 if (num_levels != -1)
4340 leveldir->levels = num_levels;
4342 // update values that depend on other values
4344 setString(&leveldir->name_sorting, leveldir->name);
4346 leveldir->last_level = leveldir->first_level + leveldir->levels - 1;
4348 // sort order of level sets may have changed
4349 sortTreeInfo(&leveldir_first);
4351 if ((file = fopen(filename, MODE_READ)) &&
4352 (file_tmp = fopen(filename_tmp, MODE_WRITE)))
4354 while (fgets(line, MAX_LINE_LEN, file))
4356 if (strPrefix(line, "name:") && level_name != NULL)
4357 fprintf(file_tmp, "%-32s%s\n", "name:", level_name);
4358 else if (strPrefix(line, "author:") && level_author != NULL)
4359 fprintf(file_tmp, "%-32s%s\n", "author:", level_author);
4360 else if (strPrefix(line, "levels:") && num_levels != -1)
4361 fprintf(file_tmp, "%-32s%d\n", "levels:", num_levels);
4363 fputs(line, file_tmp);
4376 success = (rename(filename_tmp, filename) == 0);
4384 boolean CreateUserLevelSet(char *level_subdir, char *level_name,
4385 char *level_author, int num_levels,
4386 boolean use_artwork_set)
4388 LevelDirTree *level_info;
4393 // create user level sub-directory, if needed
4394 createDirectory(getUserLevelDir(level_subdir), "user level", PERMS_PRIVATE);
4396 filename = getPath2(getUserLevelDir(level_subdir), LEVELINFO_FILENAME);
4398 if (!(file = fopen(filename, MODE_WRITE)))
4400 Warn("cannot write level info file '%s'", filename);
4407 level_info = newTreeInfo();
4409 // always start with reliable default values
4410 setTreeInfoToDefaults(level_info, TREE_TYPE_LEVEL_DIR);
4412 setString(&level_info->name, level_name);
4413 setString(&level_info->author, level_author);
4414 level_info->levels = num_levels;
4415 level_info->first_level = 1;
4416 level_info->sort_priority = LEVELCLASS_PRIVATE_START;
4417 level_info->readonly = FALSE;
4419 if (use_artwork_set)
4421 level_info->graphics_set =
4422 getStringCopy(getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_GRAPHICS));
4423 level_info->sounds_set =
4424 getStringCopy(getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_SOUNDS));
4425 level_info->music_set =
4426 getStringCopy(getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_MUSIC));
4429 token_value_position = TOKEN_VALUE_POSITION_SHORT;
4431 fprintFileHeader(file, LEVELINFO_FILENAME);
4434 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
4436 if (i == LEVELINFO_TOKEN_NAME ||
4437 i == LEVELINFO_TOKEN_AUTHOR ||
4438 i == LEVELINFO_TOKEN_LEVELS ||
4439 i == LEVELINFO_TOKEN_FIRST_LEVEL ||
4440 i == LEVELINFO_TOKEN_SORT_PRIORITY ||
4441 i == LEVELINFO_TOKEN_READONLY ||
4442 (use_artwork_set && (i == LEVELINFO_TOKEN_GRAPHICS_SET ||
4443 i == LEVELINFO_TOKEN_SOUNDS_SET ||
4444 i == LEVELINFO_TOKEN_MUSIC_SET)))
4445 fprintf(file, "%s\n", getSetupLine(levelinfo_tokens, "", i));
4447 // just to make things nicer :)
4448 if (i == LEVELINFO_TOKEN_AUTHOR ||
4449 i == LEVELINFO_TOKEN_FIRST_LEVEL ||
4450 (use_artwork_set && i == LEVELINFO_TOKEN_READONLY))
4451 fprintf(file, "\n");
4454 token_value_position = TOKEN_VALUE_POSITION_DEFAULT;
4458 SetFilePermissions(filename, PERMS_PRIVATE);
4460 freeTreeInfo(level_info);
4466 static void SaveUserLevelInfo(void)
4468 CreateUserLevelSet(getLoginName(), getLoginName(), getRealName(), 100, FALSE);
4471 char *getSetupValue(int type, void *value)
4473 static char value_string[MAX_LINE_LEN];
4481 strcpy(value_string, (*(boolean *)value ? "true" : "false"));
4485 strcpy(value_string, (*(boolean *)value ? "on" : "off"));
4489 strcpy(value_string, (*(int *)value == AUTO ? "auto" :
4490 *(int *)value == FALSE ? "off" : "on"));
4494 strcpy(value_string, (*(boolean *)value ? "yes" : "no"));
4497 case TYPE_YES_NO_AUTO:
4498 strcpy(value_string, (*(int *)value == AUTO ? "auto" :
4499 *(int *)value == FALSE ? "no" : "yes"));
4503 strcpy(value_string, (*(boolean *)value ? "AGA" : "ECS"));
4507 strcpy(value_string, getKeyNameFromKey(*(Key *)value));
4511 strcpy(value_string, getX11KeyNameFromKey(*(Key *)value));
4515 sprintf(value_string, "%d", *(int *)value);
4519 if (*(char **)value == NULL)
4522 strcpy(value_string, *(char **)value);
4526 sprintf(value_string, "player_%d", *(int *)value + 1);
4530 value_string[0] = '\0';
4534 if (type & TYPE_GHOSTED)
4535 strcpy(value_string, "n/a");
4537 return value_string;
4540 char *getSetupLine(struct TokenInfo *token_info, char *prefix, int token_nr)
4544 static char token_string[MAX_LINE_LEN];
4545 int token_type = token_info[token_nr].type;
4546 void *setup_value = token_info[token_nr].value;
4547 char *token_text = token_info[token_nr].text;
4548 char *value_string = getSetupValue(token_type, setup_value);
4550 // build complete token string
4551 sprintf(token_string, "%s%s", prefix, token_text);
4553 // build setup entry line
4554 line = getFormattedSetupEntry(token_string, value_string);
4556 if (token_type == TYPE_KEY_X11)
4558 Key key = *(Key *)setup_value;
4559 char *keyname = getKeyNameFromKey(key);
4561 // add comment, if useful
4562 if (!strEqual(keyname, "(undefined)") &&
4563 !strEqual(keyname, "(unknown)"))
4565 // add at least one whitespace
4567 for (i = strlen(line); i < token_comment_position; i++)
4571 strcat(line, keyname);
4578 static void InitLastPlayedLevels_ParentNode(void)
4580 LevelDirTree **leveldir_top = &leveldir_first->node_group->next;
4581 LevelDirTree *leveldir_new = NULL;
4583 // check if parent node for last played levels already exists
4584 if (strEqual((*leveldir_top)->identifier, TOKEN_STR_LAST_LEVEL_SERIES))
4587 leveldir_new = newTreeInfo();
4589 setTreeInfoToDefaultsFromParent(leveldir_new, leveldir_first);
4591 leveldir_new->level_group = TRUE;
4592 leveldir_new->sort_priority = LEVELCLASS_LAST_PLAYED_LEVEL;
4594 setString(&leveldir_new->identifier, TOKEN_STR_LAST_LEVEL_SERIES);
4595 setString(&leveldir_new->name, "<< (last played level sets)");
4596 setString(&leveldir_new->name_sorting, leveldir_new->name);
4598 pushTreeInfo(leveldir_top, leveldir_new);
4600 // create node to link back to current level directory
4601 createParentTreeInfoNode(leveldir_new);
4604 void UpdateLastPlayedLevels_TreeInfo(void)
4606 char **last_level_series = setup.level_setup.last_level_series;
4607 LevelDirTree *leveldir_last;
4608 TreeInfo **node_new = NULL;
4611 if (last_level_series[0] == NULL)
4614 InitLastPlayedLevels_ParentNode();
4616 leveldir_last = getTreeInfoFromIdentifierExt(leveldir_first,
4617 TOKEN_STR_LAST_LEVEL_SERIES,
4618 TREE_NODE_TYPE_GROUP);
4619 if (leveldir_last == NULL)
4622 node_new = &leveldir_last->node_group->next;
4624 freeTreeInfo(*node_new);
4628 for (i = 0; last_level_series[i] != NULL; i++)
4630 LevelDirTree *node_last = getTreeInfoFromIdentifier(leveldir_first,
4631 last_level_series[i]);
4632 if (node_last == NULL)
4635 *node_new = getTreeInfoCopy(node_last); // copy complete node
4637 (*node_new)->node_top = &leveldir_first; // correct top node link
4638 (*node_new)->node_parent = leveldir_last; // correct parent node link
4640 (*node_new)->is_copy = TRUE; // mark entry as node copy
4642 (*node_new)->node_group = NULL;
4643 (*node_new)->next = NULL;
4645 (*node_new)->cl_first = -1; // force setting tree cursor
4647 node_new = &((*node_new)->next);
4651 static void UpdateLastPlayedLevels_List(void)
4653 char **last_level_series = setup.level_setup.last_level_series;
4654 int pos = MAX_LEVELDIR_HISTORY - 1;
4657 // search for potentially already existing entry in list of level sets
4658 for (i = 0; last_level_series[i] != NULL; i++)
4659 if (strEqual(last_level_series[i], leveldir_current->identifier))
4662 // move list of level sets one entry down (using potentially free entry)
4663 for (i = pos; i > 0; i--)
4664 setString(&last_level_series[i], last_level_series[i - 1]);
4666 // put last played level set at top position
4667 setString(&last_level_series[0], leveldir_current->identifier);
4670 static TreeInfo *StoreOrRestoreLastPlayedLevels(TreeInfo *node, boolean store)
4672 static char *identifier = NULL;
4676 setString(&identifier, (node && node->is_copy ? node->identifier : NULL));
4678 return NULL; // not used
4682 TreeInfo *node_new = getTreeInfoFromIdentifierExt(leveldir_first,
4684 TREE_NODE_TYPE_COPY);
4685 return (node_new != NULL ? node_new : node);
4689 void StoreLastPlayedLevels(TreeInfo *node)
4691 StoreOrRestoreLastPlayedLevels(node, TRUE);
4694 void RestoreLastPlayedLevels(TreeInfo **node)
4696 *node = StoreOrRestoreLastPlayedLevels(*node, FALSE);
4699 void LoadLevelSetup_LastSeries(void)
4701 // --------------------------------------------------------------------------
4702 // ~/.<program>/levelsetup.conf
4703 // --------------------------------------------------------------------------
4705 char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
4706 SetupFileHash *level_setup_hash = NULL;
4710 // always start with reliable default values
4711 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
4713 // start with empty history of last played level sets
4714 setString(&setup.level_setup.last_level_series[0], NULL);
4716 if (!strEqual(DEFAULT_LEVELSET, UNDEFINED_LEVELSET))
4718 leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
4720 if (leveldir_current == NULL)
4721 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
4724 if ((level_setup_hash = loadSetupFileHash(filename)))
4726 char *last_level_series =
4727 getHashEntry(level_setup_hash, TOKEN_STR_LAST_LEVEL_SERIES);
4729 leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
4731 if (leveldir_current == NULL)
4732 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
4734 for (i = 0; i < MAX_LEVELDIR_HISTORY; i++)
4736 char token[strlen(TOKEN_STR_LAST_LEVEL_SERIES) + 10];
4737 LevelDirTree *leveldir_last;
4739 sprintf(token, "%s.%03d", TOKEN_STR_LAST_LEVEL_SERIES, i);
4741 last_level_series = getHashEntry(level_setup_hash, token);
4743 leveldir_last = getTreeInfoFromIdentifier(leveldir_first,
4745 if (leveldir_last != NULL)
4746 setString(&setup.level_setup.last_level_series[pos++],
4750 setString(&setup.level_setup.last_level_series[pos], NULL);
4752 freeSetupFileHash(level_setup_hash);
4756 Debug("setup", "using default setup values");
4762 static void SaveLevelSetup_LastSeries_Ext(boolean deactivate_last_level_series)
4764 // --------------------------------------------------------------------------
4765 // ~/.<program>/levelsetup.conf
4766 // --------------------------------------------------------------------------
4768 // check if the current level directory structure is available at this point
4769 if (leveldir_current == NULL)
4772 char **last_level_series = setup.level_setup.last_level_series;
4773 char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
4777 InitUserDataDirectory();
4779 UpdateLastPlayedLevels_List();
4781 if (!(file = fopen(filename, MODE_WRITE)))
4783 Warn("cannot write setup file '%s'", filename);
4790 fprintFileHeader(file, LEVELSETUP_FILENAME);
4792 if (deactivate_last_level_series)
4793 fprintf(file, "# %s\n# ", "the following level set may have caused a problem and was deactivated");
4795 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_LAST_LEVEL_SERIES,
4796 leveldir_current->identifier));
4798 for (i = 0; last_level_series[i] != NULL; i++)
4800 char token[strlen(TOKEN_STR_LAST_LEVEL_SERIES) + 10];
4802 sprintf(token, "%s.%03d", TOKEN_STR_LAST_LEVEL_SERIES, i);
4804 fprintf(file, "%s\n", getFormattedSetupEntry(token, last_level_series[i]));
4809 SetFilePermissions(filename, PERMS_PRIVATE);
4814 void SaveLevelSetup_LastSeries(void)
4816 SaveLevelSetup_LastSeries_Ext(FALSE);
4819 void SaveLevelSetup_LastSeries_Deactivate(void)
4821 SaveLevelSetup_LastSeries_Ext(TRUE);
4824 static void checkSeriesInfo(void)
4826 static char *level_directory = NULL;
4829 DirectoryEntry *dir_entry;
4832 checked_free(level_directory);
4834 // check for more levels besides the 'levels' field of 'levelinfo.conf'
4836 level_directory = getPath2((leveldir_current->in_user_dir ?
4837 getUserLevelDir(NULL) :
4838 options.level_directory),
4839 leveldir_current->fullpath);
4841 if ((dir = openDirectory(level_directory)) == NULL)
4843 Warn("cannot read level directory '%s'", level_directory);
4849 while ((dir_entry = readDirectory(dir)) != NULL) // loop all entries
4851 if (strlen(dir_entry->basename) > 4 &&
4852 dir_entry->basename[3] == '.' &&
4853 strEqual(&dir_entry->basename[4], LEVELFILE_EXTENSION))
4855 char levelnum_str[4];
4858 strncpy(levelnum_str, dir_entry->basename, 3);
4859 levelnum_str[3] = '\0';
4861 levelnum_value = atoi(levelnum_str);
4863 if (levelnum_value < leveldir_current->first_level)
4865 Warn("additional level %d found", levelnum_value);
4867 leveldir_current->first_level = levelnum_value;
4869 else if (levelnum_value > leveldir_current->last_level)
4871 Warn("additional level %d found", levelnum_value);
4873 leveldir_current->last_level = levelnum_value;
4879 closeDirectory(dir);
4882 void LoadLevelSetup_SeriesInfo(void)
4885 SetupFileHash *level_setup_hash = NULL;
4886 char *level_subdir = leveldir_current->subdir;
4889 // always start with reliable default values
4890 level_nr = leveldir_current->first_level;
4892 for (i = 0; i < MAX_LEVELS; i++)
4894 LevelStats_setPlayed(i, 0);
4895 LevelStats_setSolved(i, 0);
4900 // --------------------------------------------------------------------------
4901 // ~/.<program>/levelsetup/<level series>/levelsetup.conf
4902 // --------------------------------------------------------------------------
4904 level_subdir = leveldir_current->subdir;
4906 filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
4908 if ((level_setup_hash = loadSetupFileHash(filename)))
4912 // get last played level in this level set
4914 token_value = getHashEntry(level_setup_hash, TOKEN_STR_LAST_PLAYED_LEVEL);
4918 level_nr = atoi(token_value);
4920 if (level_nr < leveldir_current->first_level)
4921 level_nr = leveldir_current->first_level;
4922 if (level_nr > leveldir_current->last_level)
4923 level_nr = leveldir_current->last_level;
4926 // get handicap level in this level set
4928 token_value = getHashEntry(level_setup_hash, TOKEN_STR_HANDICAP_LEVEL);
4932 int level_nr = atoi(token_value);
4934 if (level_nr < leveldir_current->first_level)
4935 level_nr = leveldir_current->first_level;
4936 if (level_nr > leveldir_current->last_level + 1)
4937 level_nr = leveldir_current->last_level;
4939 if (leveldir_current->user_defined || !leveldir_current->handicap)
4940 level_nr = leveldir_current->last_level;
4942 leveldir_current->handicap_level = level_nr;
4945 // get number of played and solved levels in this level set
4947 BEGIN_HASH_ITERATION(level_setup_hash, itr)
4949 char *token = HASH_ITERATION_TOKEN(itr);
4950 char *value = HASH_ITERATION_VALUE(itr);
4952 if (strlen(token) == 3 &&
4953 token[0] >= '0' && token[0] <= '9' &&
4954 token[1] >= '0' && token[1] <= '9' &&
4955 token[2] >= '0' && token[2] <= '9')
4957 int level_nr = atoi(token);
4960 LevelStats_setPlayed(level_nr, atoi(value)); // read 1st column
4962 value = strchr(value, ' ');
4965 LevelStats_setSolved(level_nr, atoi(value)); // read 2nd column
4968 END_HASH_ITERATION(hash, itr)
4970 freeSetupFileHash(level_setup_hash);
4974 Debug("setup", "using default setup values");
4980 void SaveLevelSetup_SeriesInfo(void)
4983 char *level_subdir = leveldir_current->subdir;
4984 char *level_nr_str = int2str(level_nr, 0);
4985 char *handicap_level_str = int2str(leveldir_current->handicap_level, 0);
4989 // --------------------------------------------------------------------------
4990 // ~/.<program>/levelsetup/<level series>/levelsetup.conf
4991 // --------------------------------------------------------------------------
4993 InitLevelSetupDirectory(level_subdir);
4995 filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
4997 if (!(file = fopen(filename, MODE_WRITE)))
4999 Warn("cannot write setup file '%s'", filename);
5006 fprintFileHeader(file, LEVELSETUP_FILENAME);
5008 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_PLAYED_LEVEL,
5010 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_HANDICAP_LEVEL,
5011 handicap_level_str));
5013 for (i = leveldir_current->first_level; i <= leveldir_current->last_level;
5016 if (LevelStats_getPlayed(i) > 0 ||
5017 LevelStats_getSolved(i) > 0)
5022 sprintf(token, "%03d", i);
5023 sprintf(value, "%d %d", LevelStats_getPlayed(i), LevelStats_getSolved(i));
5025 fprintf(file, "%s\n", getFormattedSetupEntry(token, value));
5031 SetFilePermissions(filename, PERMS_PRIVATE);
5036 int LevelStats_getPlayed(int nr)
5038 return (nr >= 0 && nr < MAX_LEVELS ? level_stats[nr].played : 0);
5041 int LevelStats_getSolved(int nr)
5043 return (nr >= 0 && nr < MAX_LEVELS ? level_stats[nr].solved : 0);
5046 void LevelStats_setPlayed(int nr, int value)
5048 if (nr >= 0 && nr < MAX_LEVELS)
5049 level_stats[nr].played = value;
5052 void LevelStats_setSolved(int nr, int value)
5054 if (nr >= 0 && nr < MAX_LEVELS)
5055 level_stats[nr].solved = value;
5058 void LevelStats_incPlayed(int nr)
5060 if (nr >= 0 && nr < MAX_LEVELS)
5061 level_stats[nr].played++;
5064 void LevelStats_incSolved(int nr)
5066 if (nr >= 0 && nr < MAX_LEVELS)
5067 level_stats[nr].solved++;
5070 void LoadUserSetup(void)
5072 // --------------------------------------------------------------------------
5073 // ~/.<program>/usersetup.conf
5074 // --------------------------------------------------------------------------
5076 char *filename = getPath2(getMainUserGameDataDir(), USERSETUP_FILENAME);
5077 SetupFileHash *user_setup_hash = NULL;
5079 // always start with reliable default values
5082 if ((user_setup_hash = loadSetupFileHash(filename)))
5086 // get last selected user number
5087 token_value = getHashEntry(user_setup_hash, TOKEN_STR_LAST_USER);
5090 user.nr = MIN(MAX(0, atoi(token_value)), MAX_PLAYER_NAMES - 1);
5092 freeSetupFileHash(user_setup_hash);
5096 Debug("setup", "using default setup values");
5102 void SaveUserSetup(void)
5104 // --------------------------------------------------------------------------
5105 // ~/.<program>/usersetup.conf
5106 // --------------------------------------------------------------------------
5108 char *filename = getPath2(getMainUserGameDataDir(), USERSETUP_FILENAME);
5111 InitMainUserDataDirectory();
5113 if (!(file = fopen(filename, MODE_WRITE)))
5115 Warn("cannot write setup file '%s'", filename);
5122 fprintFileHeader(file, USERSETUP_FILENAME);
5124 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_USER,
5128 SetFilePermissions(filename, PERMS_PRIVATE);