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 *getDefaultSolutionTapeFilename(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);
598 char *getSokobanSolutionTapeFilename(int nr)
600 static char *filename = NULL;
601 char basename[MAX_FILENAME_LEN];
603 checked_free(filename);
605 sprintf(basename, "%03d.sln", nr);
606 filename = getPath2(getSolutionTapeDir(), basename);
611 char *getSolutionTapeFilename(int nr)
613 char *filename = getDefaultSolutionTapeFilename(nr);
615 if (!fileExists(filename))
617 char *filename2 = getSokobanSolutionTapeFilename(nr);
619 if (fileExists(filename2))
626 char *getScoreFilename(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(getScoreDir(levelset.identifier), basename);
641 char *getScoreCacheFilename(int nr)
643 static char *filename = NULL;
644 char basename[MAX_FILENAME_LEN];
646 checked_free(filename);
648 sprintf(basename, "%03d.%s", nr, SCOREFILE_EXTENSION);
650 // used instead of "leveldir_current->subdir" (for network games)
651 filename = getPath2(getScoreCacheDir(levelset.identifier), basename);
656 char *getScoreTapeBasename(char *name)
658 static char basename[MAX_FILENAME_LEN];
659 char basename_raw[MAX_FILENAME_LEN];
662 sprintf(timestamp, "%s", getCurrentTimestamp());
663 sprintf(basename_raw, "%s-%s", timestamp, name);
664 sprintf(basename, "%s-%08x", timestamp, get_hash_from_key(basename_raw));
669 char *getScoreTapeFilename(char *basename_no_ext, int nr)
671 static char *filename = NULL;
672 char basename[MAX_FILENAME_LEN];
674 checked_free(filename);
676 sprintf(basename, "%s.%s", basename_no_ext, TAPEFILE_EXTENSION);
678 // used instead of "leveldir_current->subdir" (for network games)
679 filename = getPath2(getScoreTapeDir(levelset.identifier, nr), basename);
684 char *getSetupFilename(void)
686 static char *filename = NULL;
688 checked_free(filename);
690 filename = getPath2(getSetupDir(), SETUP_FILENAME);
695 char *getDefaultSetupFilename(void)
697 return program.config_filename;
700 char *getEditorSetupFilename(void)
702 static char *filename = NULL;
704 checked_free(filename);
705 filename = getPath2(getCurrentLevelDir(), EDITORSETUP_FILENAME);
707 if (fileExists(filename))
710 checked_free(filename);
711 filename = getPath2(getSetupDir(), EDITORSETUP_FILENAME);
716 char *getHelpAnimFilename(void)
718 static char *filename = NULL;
720 checked_free(filename);
722 filename = getPath2(getCurrentLevelDir(), HELPANIM_FILENAME);
727 char *getHelpTextFilename(void)
729 static char *filename = NULL;
731 checked_free(filename);
733 filename = getPath2(getCurrentLevelDir(), HELPTEXT_FILENAME);
738 char *getLevelSetInfoFilename(void)
740 static char *filename = NULL;
755 for (i = 0; basenames[i] != NULL; i++)
757 checked_free(filename);
758 filename = getPath2(getCurrentLevelDir(), basenames[i]);
760 if (fileExists(filename))
767 static char *getLevelSetTitleMessageBasename(int nr, boolean initial)
769 static char basename[32];
771 sprintf(basename, "%s_%d.txt",
772 (initial ? "titlemessage_initial" : "titlemessage"), nr + 1);
777 char *getLevelSetTitleMessageFilename(int nr, boolean initial)
779 static char *filename = NULL;
781 boolean skip_setup_artwork = FALSE;
783 checked_free(filename);
785 basename = getLevelSetTitleMessageBasename(nr, initial);
787 if (!gfx.override_level_graphics)
789 // 1st try: look for special artwork in current level series directory
790 filename = getPath3(getCurrentLevelDir(), GRAPHICS_DIRECTORY, basename);
791 if (fileExists(filename))
796 // 2nd try: look for message file in current level set directory
797 filename = getPath2(getCurrentLevelDir(), basename);
798 if (fileExists(filename))
803 // check if there is special artwork configured in level series config
804 if (getLevelArtworkSet(ARTWORK_TYPE_GRAPHICS) != NULL)
806 // 3rd try: look for special artwork configured in level series config
807 filename = getPath2(getLevelArtworkDir(ARTWORK_TYPE_GRAPHICS), basename);
808 if (fileExists(filename))
813 // take missing artwork configured in level set config from default
814 skip_setup_artwork = TRUE;
818 if (!skip_setup_artwork)
820 // 4th try: look for special artwork in configured artwork directory
821 filename = getPath2(getSetupArtworkDir(artwork.gfx_current), basename);
822 if (fileExists(filename))
828 // 5th try: look for default artwork in new default artwork directory
829 filename = getPath2(getDefaultGraphicsDir(GFX_DEFAULT_SUBDIR), basename);
830 if (fileExists(filename))
835 // 6th try: look for default artwork in old default artwork directory
836 filename = getPath2(options.graphics_directory, basename);
837 if (fileExists(filename))
840 return NULL; // cannot find specified artwork file anywhere
843 static char *getCorrectedArtworkBasename(char *basename)
848 char *getCustomImageFilename(char *basename)
850 static char *filename = NULL;
851 boolean skip_setup_artwork = FALSE;
853 checked_free(filename);
855 basename = getCorrectedArtworkBasename(basename);
857 if (!gfx.override_level_graphics)
859 // 1st try: look for special artwork in current level series directory
860 filename = getImg3(getCurrentLevelDir(), GRAPHICS_DIRECTORY, basename);
861 if (fileExists(filename))
866 // check if there is special artwork configured in level series config
867 if (getLevelArtworkSet(ARTWORK_TYPE_GRAPHICS) != NULL)
869 // 2nd try: look for special artwork configured in level series config
870 filename = getImg2(getLevelArtworkDir(ARTWORK_TYPE_GRAPHICS), basename);
871 if (fileExists(filename))
876 // take missing artwork configured in level set config from default
877 skip_setup_artwork = TRUE;
881 if (!skip_setup_artwork)
883 // 3rd try: look for special artwork in configured artwork directory
884 filename = getImg2(getSetupArtworkDir(artwork.gfx_current), basename);
885 if (fileExists(filename))
891 // 4th try: look for default artwork in new default artwork directory
892 filename = getImg2(getDefaultGraphicsDir(GFX_DEFAULT_SUBDIR), basename);
893 if (fileExists(filename))
898 // 5th try: look for default artwork in old default artwork directory
899 filename = getImg2(options.graphics_directory, basename);
900 if (fileExists(filename))
903 if (!strEqual(GFX_FALLBACK_FILENAME, UNDEFINED_FILENAME))
907 Warn("cannot find artwork file '%s' (using fallback)", basename);
909 // 6th try: look for fallback artwork in old default artwork directory
910 // (needed to prevent errors when trying to access unused artwork files)
911 filename = getImg2(options.graphics_directory, GFX_FALLBACK_FILENAME);
912 if (fileExists(filename))
916 return NULL; // cannot find specified artwork file anywhere
919 char *getCustomSoundFilename(char *basename)
921 static char *filename = NULL;
922 boolean skip_setup_artwork = FALSE;
924 checked_free(filename);
926 basename = getCorrectedArtworkBasename(basename);
928 if (!gfx.override_level_sounds)
930 // 1st try: look for special artwork in current level series directory
931 filename = getPath3(getCurrentLevelDir(), SOUNDS_DIRECTORY, basename);
932 if (fileExists(filename))
937 // check if there is special artwork configured in level series config
938 if (getLevelArtworkSet(ARTWORK_TYPE_SOUNDS) != NULL)
940 // 2nd try: look for special artwork configured in level series config
941 filename = getPath2(getLevelArtworkDir(TREE_TYPE_SOUNDS_DIR), basename);
942 if (fileExists(filename))
947 // take missing artwork configured in level set config from default
948 skip_setup_artwork = TRUE;
952 if (!skip_setup_artwork)
954 // 3rd try: look for special artwork in configured artwork directory
955 filename = getPath2(getSetupArtworkDir(artwork.snd_current), basename);
956 if (fileExists(filename))
962 // 4th try: look for default artwork in new default artwork directory
963 filename = getPath2(getDefaultSoundsDir(SND_DEFAULT_SUBDIR), basename);
964 if (fileExists(filename))
969 // 5th try: look for default artwork in old default artwork directory
970 filename = getPath2(options.sounds_directory, basename);
971 if (fileExists(filename))
974 if (!strEqual(SND_FALLBACK_FILENAME, UNDEFINED_FILENAME))
978 Warn("cannot find artwork file '%s' (using fallback)", basename);
980 // 6th try: look for fallback artwork in old default artwork directory
981 // (needed to prevent errors when trying to access unused artwork files)
982 filename = getPath2(options.sounds_directory, SND_FALLBACK_FILENAME);
983 if (fileExists(filename))
987 return NULL; // cannot find specified artwork file anywhere
990 char *getCustomMusicFilename(char *basename)
992 static char *filename = NULL;
993 boolean skip_setup_artwork = FALSE;
995 checked_free(filename);
997 basename = getCorrectedArtworkBasename(basename);
999 if (!gfx.override_level_music)
1001 // 1st try: look for special artwork in current level series directory
1002 filename = getPath3(getCurrentLevelDir(), MUSIC_DIRECTORY, basename);
1003 if (fileExists(filename))
1008 // check if there is special artwork configured in level series config
1009 if (getLevelArtworkSet(ARTWORK_TYPE_MUSIC) != NULL)
1011 // 2nd try: look for special artwork configured in level series config
1012 filename = getPath2(getLevelArtworkDir(TREE_TYPE_MUSIC_DIR), basename);
1013 if (fileExists(filename))
1018 // take missing artwork configured in level set config from default
1019 skip_setup_artwork = TRUE;
1023 if (!skip_setup_artwork)
1025 // 3rd try: look for special artwork in configured artwork directory
1026 filename = getPath2(getSetupArtworkDir(artwork.mus_current), basename);
1027 if (fileExists(filename))
1033 // 4th try: look for default artwork in new default artwork directory
1034 filename = getPath2(getDefaultMusicDir(MUS_DEFAULT_SUBDIR), basename);
1035 if (fileExists(filename))
1040 // 5th try: look for default artwork in old default artwork directory
1041 filename = getPath2(options.music_directory, basename);
1042 if (fileExists(filename))
1045 if (!strEqual(MUS_FALLBACK_FILENAME, UNDEFINED_FILENAME))
1049 Warn("cannot find artwork file '%s' (using fallback)", basename);
1051 // 6th try: look for fallback artwork in old default artwork directory
1052 // (needed to prevent errors when trying to access unused artwork files)
1053 filename = getPath2(options.music_directory, MUS_FALLBACK_FILENAME);
1054 if (fileExists(filename))
1058 return NULL; // cannot find specified artwork file anywhere
1061 char *getCustomArtworkFilename(char *basename, int type)
1063 if (type == ARTWORK_TYPE_GRAPHICS)
1064 return getCustomImageFilename(basename);
1065 else if (type == ARTWORK_TYPE_SOUNDS)
1066 return getCustomSoundFilename(basename);
1067 else if (type == ARTWORK_TYPE_MUSIC)
1068 return getCustomMusicFilename(basename);
1070 return UNDEFINED_FILENAME;
1073 char *getCustomArtworkConfigFilename(int type)
1075 return getCustomArtworkFilename(ARTWORKINFO_FILENAME(type), type);
1078 char *getCustomArtworkLevelConfigFilename(int type)
1080 static char *filename = NULL;
1082 checked_free(filename);
1084 filename = getPath2(getLevelArtworkDir(type), ARTWORKINFO_FILENAME(type));
1089 char *getCustomMusicDirectory(void)
1091 static char *directory = NULL;
1092 boolean skip_setup_artwork = FALSE;
1094 checked_free(directory);
1096 if (!gfx.override_level_music)
1098 // 1st try: look for special artwork in current level series directory
1099 directory = getPath2(getCurrentLevelDir(), MUSIC_DIRECTORY);
1100 if (directoryExists(directory))
1105 // check if there is special artwork configured in level series config
1106 if (getLevelArtworkSet(ARTWORK_TYPE_MUSIC) != NULL)
1108 // 2nd try: look for special artwork configured in level series config
1109 directory = getStringCopy(getLevelArtworkDir(TREE_TYPE_MUSIC_DIR));
1110 if (directoryExists(directory))
1115 // take missing artwork configured in level set config from default
1116 skip_setup_artwork = TRUE;
1120 if (!skip_setup_artwork)
1122 // 3rd try: look for special artwork in configured artwork directory
1123 directory = getStringCopy(getSetupArtworkDir(artwork.mus_current));
1124 if (directoryExists(directory))
1130 // 4th try: look for default artwork in new default artwork directory
1131 directory = getStringCopy(getDefaultMusicDir(MUS_DEFAULT_SUBDIR));
1132 if (directoryExists(directory))
1137 // 5th try: look for default artwork in old default artwork directory
1138 directory = getStringCopy(options.music_directory);
1139 if (directoryExists(directory))
1142 return NULL; // cannot find specified artwork file anywhere
1145 void InitTapeDirectory(char *level_subdir)
1147 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
1148 createDirectory(getTapeDir(NULL), "main tape", PERMS_PRIVATE);
1149 createDirectory(getTapeDir(level_subdir), "level tape", PERMS_PRIVATE);
1152 void InitScoreDirectory(char *level_subdir)
1154 createDirectory(getMainUserGameDataDir(), "main user data", PERMS_PRIVATE);
1155 createDirectory(getScoreDir(NULL), "main score", PERMS_PRIVATE);
1156 createDirectory(getScoreDir(level_subdir), "level score", PERMS_PRIVATE);
1159 void InitScoreCacheDirectory(char *level_subdir)
1161 createDirectory(getMainUserGameDataDir(), "main user data", PERMS_PRIVATE);
1162 createDirectory(getCacheDir(), "cache data", PERMS_PRIVATE);
1163 createDirectory(getScoreCacheDir(NULL), "main score", PERMS_PRIVATE);
1164 createDirectory(getScoreCacheDir(level_subdir), "level score", PERMS_PRIVATE);
1167 void InitScoreTapeDirectory(char *level_subdir, int nr)
1169 InitScoreDirectory(level_subdir);
1171 createDirectory(getScoreTapeDir(level_subdir, nr), "score tape", PERMS_PRIVATE);
1174 static void SaveUserLevelInfo(void);
1176 void InitUserLevelDirectory(char *level_subdir)
1178 if (!directoryExists(getUserLevelDir(level_subdir)))
1180 createDirectory(getMainUserGameDataDir(), "main user data", PERMS_PRIVATE);
1181 createDirectory(getUserLevelDir(NULL), "main user level", PERMS_PRIVATE);
1182 createDirectory(getUserLevelDir(level_subdir), "user level", PERMS_PRIVATE);
1184 if (setup.internal.create_user_levelset)
1185 SaveUserLevelInfo();
1189 void InitNetworkLevelDirectory(char *level_subdir)
1191 if (!directoryExists(getNetworkLevelDir(level_subdir)))
1193 createDirectory(getMainUserGameDataDir(), "main user data", PERMS_PRIVATE);
1194 createDirectory(getNetworkDir(), "network data", PERMS_PRIVATE);
1195 createDirectory(getNetworkLevelDir(NULL), "main network level", PERMS_PRIVATE);
1196 createDirectory(getNetworkLevelDir(level_subdir), "network level", PERMS_PRIVATE);
1200 void InitLevelSetupDirectory(char *level_subdir)
1202 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
1203 createDirectory(getLevelSetupDir(NULL), "main level setup", PERMS_PRIVATE);
1204 createDirectory(getLevelSetupDir(level_subdir), "level setup", PERMS_PRIVATE);
1207 static void InitCacheDirectory(void)
1209 createDirectory(getMainUserGameDataDir(), "main user data", PERMS_PRIVATE);
1210 createDirectory(getCacheDir(), "cache data", PERMS_PRIVATE);
1214 // ----------------------------------------------------------------------------
1215 // some functions to handle lists of level and artwork directories
1216 // ----------------------------------------------------------------------------
1218 TreeInfo *newTreeInfo(void)
1220 return checked_calloc(sizeof(TreeInfo));
1223 TreeInfo *newTreeInfo_setDefaults(int type)
1225 TreeInfo *ti = newTreeInfo();
1227 setTreeInfoToDefaults(ti, type);
1232 void pushTreeInfo(TreeInfo **node_first, TreeInfo *node_new)
1234 node_new->next = *node_first;
1235 *node_first = node_new;
1238 void removeTreeInfo(TreeInfo **node_first)
1240 TreeInfo *node_old = *node_first;
1242 *node_first = node_old->next;
1243 node_old->next = NULL;
1245 freeTreeInfo(node_old);
1248 int numTreeInfo(TreeInfo *node)
1261 boolean validLevelSeries(TreeInfo *node)
1263 // in a number of cases, tree node is no valid level set
1264 if (node == NULL || node->node_group || node->parent_link || node->is_copy)
1270 TreeInfo *getValidLevelSeries(TreeInfo *node, TreeInfo *default_node)
1272 if (validLevelSeries(node))
1274 else if (node->is_copy)
1275 return getTreeInfoFromIdentifier(leveldir_first, node->identifier);
1277 return getFirstValidTreeInfoEntry(default_node);
1280 TreeInfo *getFirstValidTreeInfoEntry(TreeInfo *node)
1285 if (node->node_group) // enter level group (step down into tree)
1286 return getFirstValidTreeInfoEntry(node->node_group);
1287 else if (node->parent_link) // skip start entry of level group
1289 if (node->next) // get first real level series entry
1290 return getFirstValidTreeInfoEntry(node->next);
1291 else // leave empty level group and go on
1292 return getFirstValidTreeInfoEntry(node->node_parent->next);
1294 else // this seems to be a regular level series
1298 TreeInfo *getTreeInfoFirstGroupEntry(TreeInfo *node)
1303 if (node->node_parent == NULL) // top level group
1304 return *node->node_top;
1305 else // sub level group
1306 return node->node_parent->node_group;
1309 int numTreeInfoInGroup(TreeInfo *node)
1311 return numTreeInfo(getTreeInfoFirstGroupEntry(node));
1314 int getPosFromTreeInfo(TreeInfo *node)
1316 TreeInfo *node_cmp = getTreeInfoFirstGroupEntry(node);
1321 if (node_cmp == node)
1325 node_cmp = node_cmp->next;
1331 TreeInfo *getTreeInfoFromPos(TreeInfo *node, int pos)
1333 TreeInfo *node_default = node;
1345 return node_default;
1348 static TreeInfo *getTreeInfoFromIdentifierExt(TreeInfo *node, char *identifier,
1349 int node_type_wanted)
1351 if (identifier == NULL)
1356 if (TREE_NODE_TYPE(node) == node_type_wanted &&
1357 strEqual(identifier, node->identifier))
1360 if (node->node_group)
1362 TreeInfo *node_group = getTreeInfoFromIdentifierExt(node->node_group,
1375 TreeInfo *getTreeInfoFromIdentifier(TreeInfo *node, char *identifier)
1377 return getTreeInfoFromIdentifierExt(node, identifier, TREE_NODE_TYPE_DEFAULT);
1380 static TreeInfo *cloneTreeNode(TreeInfo **node_top, TreeInfo *node_parent,
1381 TreeInfo *node, boolean skip_sets_without_levels)
1388 if (!node->parent_link && !node->level_group &&
1389 skip_sets_without_levels && node->levels == 0)
1390 return cloneTreeNode(node_top, node_parent, node->next,
1391 skip_sets_without_levels);
1393 node_new = getTreeInfoCopy(node); // copy complete node
1395 node_new->node_top = node_top; // correct top node link
1396 node_new->node_parent = node_parent; // correct parent node link
1398 if (node->level_group)
1399 node_new->node_group = cloneTreeNode(node_top, node_new, node->node_group,
1400 skip_sets_without_levels);
1402 node_new->next = cloneTreeNode(node_top, node_parent, node->next,
1403 skip_sets_without_levels);
1408 static void cloneTree(TreeInfo **ti_new, TreeInfo *ti, boolean skip_empty_sets)
1410 TreeInfo *ti_cloned = cloneTreeNode(ti_new, NULL, ti, skip_empty_sets);
1412 *ti_new = ti_cloned;
1415 static boolean adjustTreeGraphicsForEMC(TreeInfo *node)
1417 boolean settings_changed = FALSE;
1421 boolean want_ecs = (setup.prefer_aga_graphics == FALSE);
1422 boolean want_aga = (setup.prefer_aga_graphics == TRUE);
1423 boolean has_only_ecs = (!node->graphics_set && !node->graphics_set_aga);
1424 boolean has_only_aga = (!node->graphics_set && !node->graphics_set_ecs);
1425 char *graphics_set = NULL;
1427 if (node->graphics_set_ecs && (want_ecs || has_only_ecs))
1428 graphics_set = node->graphics_set_ecs;
1430 if (node->graphics_set_aga && (want_aga || has_only_aga))
1431 graphics_set = node->graphics_set_aga;
1433 if (graphics_set && !strEqual(node->graphics_set, graphics_set))
1435 setString(&node->graphics_set, graphics_set);
1436 settings_changed = TRUE;
1439 if (node->node_group != NULL)
1440 settings_changed |= adjustTreeGraphicsForEMC(node->node_group);
1445 return settings_changed;
1448 static boolean adjustTreeSoundsForEMC(TreeInfo *node)
1450 boolean settings_changed = FALSE;
1454 boolean want_default = (setup.prefer_lowpass_sounds == FALSE);
1455 boolean want_lowpass = (setup.prefer_lowpass_sounds == TRUE);
1456 boolean has_only_default = (!node->sounds_set && !node->sounds_set_lowpass);
1457 boolean has_only_lowpass = (!node->sounds_set && !node->sounds_set_default);
1458 char *sounds_set = NULL;
1460 if (node->sounds_set_default && (want_default || has_only_default))
1461 sounds_set = node->sounds_set_default;
1463 if (node->sounds_set_lowpass && (want_lowpass || has_only_lowpass))
1464 sounds_set = node->sounds_set_lowpass;
1466 if (sounds_set && !strEqual(node->sounds_set, sounds_set))
1468 setString(&node->sounds_set, sounds_set);
1469 settings_changed = TRUE;
1472 if (node->node_group != NULL)
1473 settings_changed |= adjustTreeSoundsForEMC(node->node_group);
1478 return settings_changed;
1481 void dumpTreeInfo(TreeInfo *node, int depth)
1483 char bullet_list[] = { '-', '*', 'o' };
1487 Debug("tree", "Dumping TreeInfo:");
1491 char bullet = bullet_list[depth % ARRAY_SIZE(bullet_list)];
1493 for (i = 0; i < depth * 2; i++)
1494 DebugContinued("", " ");
1496 DebugContinued("tree", "%c '%s' ['%s] [PARENT: '%s'] %s\n",
1497 bullet, node->name, node->identifier,
1498 (node->node_parent ? node->node_parent->identifier : "-"),
1499 (node->node_group ? "[GROUP]" : ""));
1502 // use for dumping artwork info tree
1503 Debug("tree", "subdir == '%s' ['%s', '%s'] [%d])",
1504 node->subdir, node->fullpath, node->basepath, node->in_user_dir);
1507 if (node->node_group != NULL)
1508 dumpTreeInfo(node->node_group, depth + 1);
1514 void sortTreeInfoBySortFunction(TreeInfo **node_first,
1515 int (*compare_function)(const void *,
1518 int num_nodes = numTreeInfo(*node_first);
1519 TreeInfo **sort_array;
1520 TreeInfo *node = *node_first;
1526 // allocate array for sorting structure pointers
1527 sort_array = checked_calloc(num_nodes * sizeof(TreeInfo *));
1529 // writing structure pointers to sorting array
1530 while (i < num_nodes && node) // double boundary check...
1532 sort_array[i] = node;
1538 // sorting the structure pointers in the sorting array
1539 qsort(sort_array, num_nodes, sizeof(TreeInfo *),
1542 // update the linkage of list elements with the sorted node array
1543 for (i = 0; i < num_nodes - 1; i++)
1544 sort_array[i]->next = sort_array[i + 1];
1545 sort_array[num_nodes - 1]->next = NULL;
1547 // update the linkage of the main list anchor pointer
1548 *node_first = sort_array[0];
1552 // now recursively sort the level group structures
1556 if (node->node_group != NULL)
1557 sortTreeInfoBySortFunction(&node->node_group, compare_function);
1563 void sortTreeInfo(TreeInfo **node_first)
1565 sortTreeInfoBySortFunction(node_first, compareTreeInfoEntries);
1569 // ============================================================================
1570 // some stuff from "files.c"
1571 // ============================================================================
1573 #if defined(PLATFORM_WIN32)
1575 #define S_IRGRP S_IRUSR
1578 #define S_IROTH S_IRUSR
1581 #define S_IWGRP S_IWUSR
1584 #define S_IWOTH S_IWUSR
1587 #define S_IXGRP S_IXUSR
1590 #define S_IXOTH S_IXUSR
1593 #define S_IRWXG (S_IRGRP | S_IWGRP | S_IXGRP)
1598 #endif // PLATFORM_WIN32
1600 // file permissions for newly written files
1601 #define MODE_R_ALL (S_IRUSR | S_IRGRP | S_IROTH)
1602 #define MODE_W_ALL (S_IWUSR | S_IWGRP | S_IWOTH)
1603 #define MODE_X_ALL (S_IXUSR | S_IXGRP | S_IXOTH)
1605 #define MODE_W_PRIVATE (S_IWUSR)
1606 #define MODE_W_PUBLIC_FILE (S_IWUSR | S_IWGRP)
1607 #define MODE_W_PUBLIC_DIR (S_IWUSR | S_IWGRP | S_ISGID)
1609 #define DIR_PERMS_PRIVATE (MODE_R_ALL | MODE_X_ALL | MODE_W_PRIVATE)
1610 #define DIR_PERMS_PUBLIC (MODE_R_ALL | MODE_X_ALL | MODE_W_PUBLIC_DIR)
1611 #define DIR_PERMS_PUBLIC_ALL (MODE_R_ALL | MODE_X_ALL | MODE_W_ALL)
1613 #define FILE_PERMS_PRIVATE (MODE_R_ALL | MODE_W_PRIVATE)
1614 #define FILE_PERMS_PUBLIC (MODE_R_ALL | MODE_W_PUBLIC_FILE)
1615 #define FILE_PERMS_PUBLIC_ALL (MODE_R_ALL | MODE_W_ALL)
1618 char *getHomeDir(void)
1620 static char *dir = NULL;
1622 #if defined(PLATFORM_WIN32)
1625 dir = checked_malloc(MAX_PATH + 1);
1627 if (!SUCCEEDED(SHGetFolderPath(NULL, CSIDL_PERSONAL, NULL, 0, dir)))
1630 #elif defined(PLATFORM_EMSCRIPTEN)
1631 dir = "/persistent";
1632 #elif defined(PLATFORM_UNIX)
1635 if ((dir = getenv("HOME")) == NULL)
1637 dir = getUnixHomeDir();
1640 dir = getStringCopy(dir);
1652 char *getPersonalDataDir(void)
1654 static char *personal_data_dir = NULL;
1656 #if defined(PLATFORM_MACOSX)
1657 if (personal_data_dir == NULL)
1658 personal_data_dir = getPath2(getHomeDir(), "Documents");
1660 if (personal_data_dir == NULL)
1661 personal_data_dir = getHomeDir();
1664 return personal_data_dir;
1667 char *getMainUserGameDataDir(void)
1669 static char *main_user_data_dir = NULL;
1671 #if defined(PLATFORM_ANDROID)
1672 if (main_user_data_dir == NULL)
1673 main_user_data_dir = (char *)(SDL_AndroidGetExternalStorageState() &
1674 SDL_ANDROID_EXTERNAL_STORAGE_WRITE ?
1675 SDL_AndroidGetExternalStoragePath() :
1676 SDL_AndroidGetInternalStoragePath());
1678 if (main_user_data_dir == NULL)
1679 main_user_data_dir = getPath2(getPersonalDataDir(),
1680 program.userdata_subdir);
1683 return main_user_data_dir;
1686 char *getUserGameDataDir(void)
1689 return getMainUserGameDataDir();
1691 return getUserDir(user.nr);
1694 char *getSetupDir(void)
1696 return getUserGameDataDir();
1699 static mode_t posix_umask(mode_t mask)
1701 #if defined(PLATFORM_UNIX)
1708 static int posix_mkdir(const char *pathname, mode_t mode)
1710 #if defined(PLATFORM_WIN32)
1711 return mkdir(pathname);
1713 return mkdir(pathname, mode);
1717 static boolean posix_process_running_setgid(void)
1719 #if defined(PLATFORM_UNIX)
1720 return (getgid() != getegid());
1726 void createDirectory(char *dir, char *text, int permission_class)
1728 if (directoryExists(dir))
1731 // leave "other" permissions in umask untouched, but ensure group parts
1732 // of USERDATA_DIR_MODE are not masked
1733 mode_t dir_mode = (permission_class == PERMS_PRIVATE ?
1734 DIR_PERMS_PRIVATE : DIR_PERMS_PUBLIC);
1735 mode_t last_umask = posix_umask(0);
1736 mode_t group_umask = ~(dir_mode & S_IRWXG);
1737 int running_setgid = posix_process_running_setgid();
1739 if (permission_class == PERMS_PUBLIC)
1741 // if we're setgid, protect files against "other"
1742 // else keep umask(0) to make the dir world-writable
1745 posix_umask(last_umask & group_umask);
1747 dir_mode = DIR_PERMS_PUBLIC_ALL;
1750 if (posix_mkdir(dir, dir_mode) != 0)
1751 Warn("cannot create %s directory '%s': %s", text, dir, strerror(errno));
1753 if (permission_class == PERMS_PUBLIC && !running_setgid)
1754 chmod(dir, dir_mode);
1756 posix_umask(last_umask); // restore previous umask
1759 void InitMainUserDataDirectory(void)
1761 createDirectory(getMainUserGameDataDir(), "main user data", PERMS_PRIVATE);
1764 void InitUserDataDirectory(void)
1766 createDirectory(getMainUserGameDataDir(), "main user data", PERMS_PRIVATE);
1770 createDirectory(getUserDir(-1), "users", PERMS_PRIVATE);
1771 createDirectory(getUserDir(user.nr), "user data", PERMS_PRIVATE);
1775 void SetFilePermissions(char *filename, int permission_class)
1777 int running_setgid = posix_process_running_setgid();
1778 int perms = (permission_class == PERMS_PRIVATE ?
1779 FILE_PERMS_PRIVATE : FILE_PERMS_PUBLIC);
1781 if (permission_class == PERMS_PUBLIC && !running_setgid)
1782 perms = FILE_PERMS_PUBLIC_ALL;
1784 chmod(filename, perms);
1787 char *getCookie(char *file_type)
1789 static char cookie[MAX_COOKIE_LEN + 1];
1791 if (strlen(program.cookie_prefix) + 1 +
1792 strlen(file_type) + strlen("_FILE_VERSION_x.x") > MAX_COOKIE_LEN)
1793 return "[COOKIE ERROR]"; // should never happen
1795 sprintf(cookie, "%s_%s_FILE_VERSION_%d.%d",
1796 program.cookie_prefix, file_type,
1797 program.version_super, program.version_major);
1802 void fprintFileHeader(FILE *file, char *basename)
1804 char *prefix = "# ";
1807 fprintf_line_with_prefix(file, prefix, sep1, 77);
1808 fprintf(file, "%s%s\n", prefix, basename);
1809 fprintf_line_with_prefix(file, prefix, sep1, 77);
1810 fprintf(file, "\n");
1813 int getFileVersionFromCookieString(const char *cookie)
1815 const char *ptr_cookie1, *ptr_cookie2;
1816 const char *pattern1 = "_FILE_VERSION_";
1817 const char *pattern2 = "?.?";
1818 const int len_cookie = strlen(cookie);
1819 const int len_pattern1 = strlen(pattern1);
1820 const int len_pattern2 = strlen(pattern2);
1821 const int len_pattern = len_pattern1 + len_pattern2;
1822 int version_super, version_major;
1824 if (len_cookie <= len_pattern)
1827 ptr_cookie1 = &cookie[len_cookie - len_pattern];
1828 ptr_cookie2 = &cookie[len_cookie - len_pattern2];
1830 if (strncmp(ptr_cookie1, pattern1, len_pattern1) != 0)
1833 if (ptr_cookie2[0] < '0' || ptr_cookie2[0] > '9' ||
1834 ptr_cookie2[1] != '.' ||
1835 ptr_cookie2[2] < '0' || ptr_cookie2[2] > '9')
1838 version_super = ptr_cookie2[0] - '0';
1839 version_major = ptr_cookie2[2] - '0';
1841 return VERSION_IDENT(version_super, version_major, 0, 0);
1844 boolean checkCookieString(const char *cookie, const char *template)
1846 const char *pattern = "_FILE_VERSION_?.?";
1847 const int len_cookie = strlen(cookie);
1848 const int len_template = strlen(template);
1849 const int len_pattern = strlen(pattern);
1851 if (len_cookie != len_template)
1854 if (strncmp(cookie, template, len_cookie - len_pattern) != 0)
1861 // ----------------------------------------------------------------------------
1862 // setup file list and hash handling functions
1863 // ----------------------------------------------------------------------------
1865 char *getFormattedSetupEntry(char *token, char *value)
1868 static char entry[MAX_LINE_LEN];
1870 // if value is an empty string, just return token without value
1874 // start with the token and some spaces to format output line
1875 sprintf(entry, "%s:", token);
1876 for (i = strlen(entry); i < token_value_position; i++)
1879 // continue with the token's value
1880 strcat(entry, value);
1885 SetupFileList *newSetupFileList(char *token, char *value)
1887 SetupFileList *new = checked_malloc(sizeof(SetupFileList));
1889 new->token = getStringCopy(token);
1890 new->value = getStringCopy(value);
1897 void freeSetupFileList(SetupFileList *list)
1902 checked_free(list->token);
1903 checked_free(list->value);
1906 freeSetupFileList(list->next);
1911 char *getListEntry(SetupFileList *list, char *token)
1916 if (strEqual(list->token, token))
1919 return getListEntry(list->next, token);
1922 SetupFileList *setListEntry(SetupFileList *list, char *token, char *value)
1927 if (strEqual(list->token, token))
1929 checked_free(list->value);
1931 list->value = getStringCopy(value);
1935 else if (list->next == NULL)
1936 return (list->next = newSetupFileList(token, value));
1938 return setListEntry(list->next, token, value);
1941 SetupFileList *addListEntry(SetupFileList *list, char *token, char *value)
1946 if (list->next == NULL)
1947 return (list->next = newSetupFileList(token, value));
1949 return addListEntry(list->next, token, value);
1952 #if ENABLE_UNUSED_CODE
1954 static void printSetupFileList(SetupFileList *list)
1959 Debug("setup:printSetupFileList", "token: '%s'", list->token);
1960 Debug("setup:printSetupFileList", "value: '%s'", list->value);
1962 printSetupFileList(list->next);
1968 DEFINE_HASHTABLE_INSERT(insert_hash_entry, char, char);
1969 DEFINE_HASHTABLE_SEARCH(search_hash_entry, char, char);
1970 DEFINE_HASHTABLE_CHANGE(change_hash_entry, char, char);
1971 DEFINE_HASHTABLE_REMOVE(remove_hash_entry, char, char);
1973 #define insert_hash_entry hashtable_insert
1974 #define search_hash_entry hashtable_search
1975 #define change_hash_entry hashtable_change
1976 #define remove_hash_entry hashtable_remove
1979 unsigned int get_hash_from_key(void *key)
1984 This algorithm (k=33) was first reported by Dan Bernstein many years ago in
1985 'comp.lang.c'. Another version of this algorithm (now favored by Bernstein)
1986 uses XOR: hash(i) = hash(i - 1) * 33 ^ str[i]; the magic of number 33 (why
1987 it works better than many other constants, prime or not) has never been
1988 adequately explained.
1990 If you just want to have a good hash function, and cannot wait, djb2
1991 is one of the best string hash functions i know. It has excellent
1992 distribution and speed on many different sets of keys and table sizes.
1993 You are not likely to do better with one of the "well known" functions
1994 such as PJW, K&R, etc.
1996 Ozan (oz) Yigit [http://www.cs.yorku.ca/~oz/hash.html]
1999 char *str = (char *)key;
2000 unsigned int hash = 5381;
2003 while ((c = *str++))
2004 hash = ((hash << 5) + hash) + c; // hash * 33 + c
2009 static int keys_are_equal(void *key1, void *key2)
2011 return (strEqual((char *)key1, (char *)key2));
2014 SetupFileHash *newSetupFileHash(void)
2016 SetupFileHash *new_hash =
2017 create_hashtable(16, 0.75, get_hash_from_key, keys_are_equal);
2019 if (new_hash == NULL)
2020 Fail("create_hashtable() failed -- out of memory");
2025 void freeSetupFileHash(SetupFileHash *hash)
2030 hashtable_destroy(hash, 1); // 1 == also free values stored in hash
2033 char *getHashEntry(SetupFileHash *hash, char *token)
2038 return search_hash_entry(hash, token);
2041 void setHashEntry(SetupFileHash *hash, char *token, char *value)
2048 value_copy = getStringCopy(value);
2050 // change value; if it does not exist, insert it as new
2051 if (!change_hash_entry(hash, token, value_copy))
2052 if (!insert_hash_entry(hash, getStringCopy(token), value_copy))
2053 Fail("cannot insert into hash -- aborting");
2056 char *removeHashEntry(SetupFileHash *hash, char *token)
2061 return remove_hash_entry(hash, token);
2064 #if ENABLE_UNUSED_CODE
2066 static void printSetupFileHash(SetupFileHash *hash)
2068 BEGIN_HASH_ITERATION(hash, itr)
2070 Debug("setup:printSetupFileHash", "token: '%s'", HASH_ITERATION_TOKEN(itr));
2071 Debug("setup:printSetupFileHash", "value: '%s'", HASH_ITERATION_VALUE(itr));
2073 END_HASH_ITERATION(hash, itr)
2078 #define ALLOW_TOKEN_VALUE_SEPARATOR_BEING_WHITESPACE 1
2079 #define CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING 0
2080 #define CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH 0
2082 static boolean token_value_separator_found = FALSE;
2083 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2084 static boolean token_value_separator_warning = FALSE;
2086 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2087 static boolean token_already_exists_warning = FALSE;
2090 static boolean getTokenValueFromSetupLineExt(char *line,
2091 char **token_ptr, char **value_ptr,
2092 char *filename, char *line_raw,
2094 boolean separator_required)
2096 static char line_copy[MAX_LINE_LEN + 1], line_raw_copy[MAX_LINE_LEN + 1];
2097 char *token, *value, *line_ptr;
2099 // when externally invoked via ReadTokenValueFromLine(), copy line buffers
2100 if (line_raw == NULL)
2102 strncpy(line_copy, line, MAX_LINE_LEN);
2103 line_copy[MAX_LINE_LEN] = '\0';
2106 strcpy(line_raw_copy, line_copy);
2107 line_raw = line_raw_copy;
2110 // cut trailing comment from input line
2111 for (line_ptr = line; *line_ptr; line_ptr++)
2113 if (*line_ptr == '#')
2120 // cut trailing whitespaces from input line
2121 for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
2122 if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
2125 // ignore empty lines
2129 // cut leading whitespaces from token
2130 for (token = line; *token; token++)
2131 if (*token != ' ' && *token != '\t')
2134 // start with empty value as reliable default
2137 token_value_separator_found = FALSE;
2139 // find end of token to determine start of value
2140 for (line_ptr = token; *line_ptr; line_ptr++)
2142 // first look for an explicit token/value separator, like ':' or '='
2143 if (*line_ptr == ':' || *line_ptr == '=')
2145 *line_ptr = '\0'; // terminate token string
2146 value = line_ptr + 1; // set beginning of value
2148 token_value_separator_found = TRUE;
2154 #if ALLOW_TOKEN_VALUE_SEPARATOR_BEING_WHITESPACE
2155 // fallback: if no token/value separator found, also allow whitespaces
2156 if (!token_value_separator_found && !separator_required)
2158 for (line_ptr = token; *line_ptr; line_ptr++)
2160 if (*line_ptr == ' ' || *line_ptr == '\t')
2162 *line_ptr = '\0'; // terminate token string
2163 value = line_ptr + 1; // set beginning of value
2165 token_value_separator_found = TRUE;
2171 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2172 if (token_value_separator_found)
2174 if (!token_value_separator_warning)
2176 Debug("setup", "---");
2178 if (filename != NULL)
2180 Debug("setup", "missing token/value separator(s) in config file:");
2181 Debug("setup", "- config file: '%s'", filename);
2185 Debug("setup", "missing token/value separator(s):");
2188 token_value_separator_warning = TRUE;
2191 if (filename != NULL)
2192 Debug("setup", "- line %d: '%s'", line_nr, line_raw);
2194 Debug("setup", "- line: '%s'", line_raw);
2200 // cut trailing whitespaces from token
2201 for (line_ptr = &token[strlen(token)]; line_ptr >= token; line_ptr--)
2202 if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
2205 // cut leading whitespaces from value
2206 for (; *value; value++)
2207 if (*value != ' ' && *value != '\t')
2216 boolean getTokenValueFromSetupLine(char *line, char **token, char **value)
2218 // while the internal (old) interface does not require a token/value
2219 // separator (for downwards compatibility with existing files which
2220 // don't use them), it is mandatory for the external (new) interface
2222 return getTokenValueFromSetupLineExt(line, token, value, NULL, NULL, 0, TRUE);
2225 static boolean loadSetupFileData(void *setup_file_data, char *filename,
2226 boolean top_recursion_level, boolean is_hash)
2228 static SetupFileHash *include_filename_hash = NULL;
2229 char line[MAX_LINE_LEN], line_raw[MAX_LINE_LEN], previous_line[MAX_LINE_LEN];
2230 char *token, *value, *line_ptr;
2231 void *insert_ptr = NULL;
2232 boolean read_continued_line = FALSE;
2234 int line_nr = 0, token_count = 0, include_count = 0;
2236 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2237 token_value_separator_warning = FALSE;
2240 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2241 token_already_exists_warning = FALSE;
2244 if (!(file = openFile(filename, MODE_READ)))
2246 #if DEBUG_NO_CONFIG_FILE
2247 Debug("setup", "cannot open configuration file '%s'", filename);
2253 // use "insert pointer" to store list end for constant insertion complexity
2255 insert_ptr = setup_file_data;
2257 // on top invocation, create hash to mark included files (to prevent loops)
2258 if (top_recursion_level)
2259 include_filename_hash = newSetupFileHash();
2261 // mark this file as already included (to prevent including it again)
2262 setHashEntry(include_filename_hash, getBaseNamePtr(filename), "true");
2264 while (!checkEndOfFile(file))
2266 // read next line of input file
2267 if (!getStringFromFile(file, line, MAX_LINE_LEN))
2270 // check if line was completely read and is terminated by line break
2271 if (strlen(line) > 0 && line[strlen(line) - 1] == '\n')
2274 // cut trailing line break (this can be newline and/or carriage return)
2275 for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
2276 if ((*line_ptr == '\n' || *line_ptr == '\r') && *(line_ptr + 1) == '\0')
2279 // copy raw input line for later use (mainly debugging output)
2280 strcpy(line_raw, line);
2282 if (read_continued_line)
2284 // append new line to existing line, if there is enough space
2285 if (strlen(previous_line) + strlen(line_ptr) < MAX_LINE_LEN)
2286 strcat(previous_line, line_ptr);
2288 strcpy(line, previous_line); // copy storage buffer to line
2290 read_continued_line = FALSE;
2293 // if the last character is '\', continue at next line
2294 if (strlen(line) > 0 && line[strlen(line) - 1] == '\\')
2296 line[strlen(line) - 1] = '\0'; // cut off trailing backslash
2297 strcpy(previous_line, line); // copy line to storage buffer
2299 read_continued_line = TRUE;
2304 if (!getTokenValueFromSetupLineExt(line, &token, &value, filename,
2305 line_raw, line_nr, FALSE))
2310 if (strEqual(token, "include"))
2312 if (getHashEntry(include_filename_hash, value) == NULL)
2314 char *basepath = getBasePath(filename);
2315 char *basename = getBaseName(value);
2316 char *filename_include = getPath2(basepath, basename);
2318 loadSetupFileData(setup_file_data, filename_include, FALSE, is_hash);
2322 free(filename_include);
2328 Warn("ignoring already processed file '%s'", value);
2335 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2337 getHashEntry((SetupFileHash *)setup_file_data, token);
2339 if (old_value != NULL)
2341 if (!token_already_exists_warning)
2343 Debug("setup", "---");
2344 Debug("setup", "duplicate token(s) found in config file:");
2345 Debug("setup", "- config file: '%s'", filename);
2347 token_already_exists_warning = TRUE;
2350 Debug("setup", "- token: '%s' (in line %d)", token, line_nr);
2351 Debug("setup", " old value: '%s'", old_value);
2352 Debug("setup", " new value: '%s'", value);
2356 setHashEntry((SetupFileHash *)setup_file_data, token, value);
2360 insert_ptr = addListEntry((SetupFileList *)insert_ptr, token, value);
2370 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2371 if (token_value_separator_warning)
2372 Debug("setup", "---");
2375 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2376 if (token_already_exists_warning)
2377 Debug("setup", "---");
2380 if (token_count == 0 && include_count == 0)
2381 Warn("configuration file '%s' is empty", filename);
2383 if (top_recursion_level)
2384 freeSetupFileHash(include_filename_hash);
2389 static int compareSetupFileData(const void *object1, const void *object2)
2391 const struct ConfigInfo *entry1 = (struct ConfigInfo *)object1;
2392 const struct ConfigInfo *entry2 = (struct ConfigInfo *)object2;
2394 return strcmp(entry1->token, entry2->token);
2397 static void saveSetupFileHash(SetupFileHash *hash, char *filename)
2399 int item_count = hashtable_count(hash);
2400 int item_size = sizeof(struct ConfigInfo);
2401 struct ConfigInfo *sort_array = checked_malloc(item_count * item_size);
2405 // copy string pointers from hash to array
2406 BEGIN_HASH_ITERATION(hash, itr)
2408 sort_array[i].token = HASH_ITERATION_TOKEN(itr);
2409 sort_array[i].value = HASH_ITERATION_VALUE(itr);
2413 if (i > item_count) // should never happen
2416 END_HASH_ITERATION(hash, itr)
2418 // sort string pointers from hash in array
2419 qsort(sort_array, item_count, item_size, compareSetupFileData);
2421 if (!(file = fopen(filename, MODE_WRITE)))
2423 Warn("cannot write configuration file '%s'", filename);
2428 fprintf(file, "%s\n\n", getFormattedSetupEntry("program.version",
2429 program.version_string));
2430 for (i = 0; i < item_count; i++)
2431 fprintf(file, "%s\n", getFormattedSetupEntry(sort_array[i].token,
2432 sort_array[i].value));
2435 checked_free(sort_array);
2438 SetupFileList *loadSetupFileList(char *filename)
2440 SetupFileList *setup_file_list = newSetupFileList("", "");
2441 SetupFileList *first_valid_list_entry;
2443 if (!loadSetupFileData(setup_file_list, filename, TRUE, FALSE))
2445 freeSetupFileList(setup_file_list);
2450 first_valid_list_entry = setup_file_list->next;
2452 // free empty list header
2453 setup_file_list->next = NULL;
2454 freeSetupFileList(setup_file_list);
2456 return first_valid_list_entry;
2459 SetupFileHash *loadSetupFileHash(char *filename)
2461 SetupFileHash *setup_file_hash = newSetupFileHash();
2463 if (!loadSetupFileData(setup_file_hash, filename, TRUE, TRUE))
2465 freeSetupFileHash(setup_file_hash);
2470 return setup_file_hash;
2474 // ============================================================================
2476 // ============================================================================
2478 #define TOKEN_STR_LAST_LEVEL_SERIES "last_level_series"
2479 #define TOKEN_STR_LAST_PLAYED_LEVEL "last_played_level"
2480 #define TOKEN_STR_HANDICAP_LEVEL "handicap_level"
2481 #define TOKEN_STR_LAST_USER "last_user"
2483 // level directory info
2484 #define LEVELINFO_TOKEN_IDENTIFIER 0
2485 #define LEVELINFO_TOKEN_NAME 1
2486 #define LEVELINFO_TOKEN_NAME_SORTING 2
2487 #define LEVELINFO_TOKEN_AUTHOR 3
2488 #define LEVELINFO_TOKEN_YEAR 4
2489 #define LEVELINFO_TOKEN_PROGRAM_TITLE 5
2490 #define LEVELINFO_TOKEN_PROGRAM_COPYRIGHT 6
2491 #define LEVELINFO_TOKEN_PROGRAM_COMPANY 7
2492 #define LEVELINFO_TOKEN_IMPORTED_FROM 8
2493 #define LEVELINFO_TOKEN_IMPORTED_BY 9
2494 #define LEVELINFO_TOKEN_TESTED_BY 10
2495 #define LEVELINFO_TOKEN_LEVELS 11
2496 #define LEVELINFO_TOKEN_FIRST_LEVEL 12
2497 #define LEVELINFO_TOKEN_SORT_PRIORITY 13
2498 #define LEVELINFO_TOKEN_LATEST_ENGINE 14
2499 #define LEVELINFO_TOKEN_LEVEL_GROUP 15
2500 #define LEVELINFO_TOKEN_READONLY 16
2501 #define LEVELINFO_TOKEN_GRAPHICS_SET_ECS 17
2502 #define LEVELINFO_TOKEN_GRAPHICS_SET_AGA 18
2503 #define LEVELINFO_TOKEN_GRAPHICS_SET 19
2504 #define LEVELINFO_TOKEN_SOUNDS_SET_DEFAULT 20
2505 #define LEVELINFO_TOKEN_SOUNDS_SET_LOWPASS 21
2506 #define LEVELINFO_TOKEN_SOUNDS_SET 22
2507 #define LEVELINFO_TOKEN_MUSIC_SET 23
2508 #define LEVELINFO_TOKEN_FILENAME 24
2509 #define LEVELINFO_TOKEN_FILETYPE 25
2510 #define LEVELINFO_TOKEN_SPECIAL_FLAGS 26
2511 #define LEVELINFO_TOKEN_HANDICAP 27
2512 #define LEVELINFO_TOKEN_SKIP_LEVELS 28
2513 #define LEVELINFO_TOKEN_USE_EMC_TILES 29
2515 #define NUM_LEVELINFO_TOKENS 30
2517 static LevelDirTree ldi;
2519 static struct TokenInfo levelinfo_tokens[] =
2521 // level directory info
2522 { TYPE_STRING, &ldi.identifier, "identifier" },
2523 { TYPE_STRING, &ldi.name, "name" },
2524 { TYPE_STRING, &ldi.name_sorting, "name_sorting" },
2525 { TYPE_STRING, &ldi.author, "author" },
2526 { TYPE_STRING, &ldi.year, "year" },
2527 { TYPE_STRING, &ldi.program_title, "program_title" },
2528 { TYPE_STRING, &ldi.program_copyright, "program_copyright" },
2529 { TYPE_STRING, &ldi.program_company, "program_company" },
2530 { TYPE_STRING, &ldi.imported_from, "imported_from" },
2531 { TYPE_STRING, &ldi.imported_by, "imported_by" },
2532 { TYPE_STRING, &ldi.tested_by, "tested_by" },
2533 { TYPE_INTEGER, &ldi.levels, "levels" },
2534 { TYPE_INTEGER, &ldi.first_level, "first_level" },
2535 { TYPE_INTEGER, &ldi.sort_priority, "sort_priority" },
2536 { TYPE_BOOLEAN, &ldi.latest_engine, "latest_engine" },
2537 { TYPE_BOOLEAN, &ldi.level_group, "level_group" },
2538 { TYPE_BOOLEAN, &ldi.readonly, "readonly" },
2539 { TYPE_STRING, &ldi.graphics_set_ecs, "graphics_set.ecs" },
2540 { TYPE_STRING, &ldi.graphics_set_aga, "graphics_set.aga" },
2541 { TYPE_STRING, &ldi.graphics_set, "graphics_set" },
2542 { TYPE_STRING, &ldi.sounds_set_default,"sounds_set.default" },
2543 { TYPE_STRING, &ldi.sounds_set_lowpass,"sounds_set.lowpass" },
2544 { TYPE_STRING, &ldi.sounds_set, "sounds_set" },
2545 { TYPE_STRING, &ldi.music_set, "music_set" },
2546 { TYPE_STRING, &ldi.level_filename, "filename" },
2547 { TYPE_STRING, &ldi.level_filetype, "filetype" },
2548 { TYPE_STRING, &ldi.special_flags, "special_flags" },
2549 { TYPE_BOOLEAN, &ldi.handicap, "handicap" },
2550 { TYPE_BOOLEAN, &ldi.skip_levels, "skip_levels" },
2551 { TYPE_BOOLEAN, &ldi.use_emc_tiles, "use_emc_tiles" }
2554 static struct TokenInfo artworkinfo_tokens[] =
2556 // artwork directory info
2557 { TYPE_STRING, &ldi.identifier, "identifier" },
2558 { TYPE_STRING, &ldi.subdir, "subdir" },
2559 { TYPE_STRING, &ldi.name, "name" },
2560 { TYPE_STRING, &ldi.name_sorting, "name_sorting" },
2561 { TYPE_STRING, &ldi.author, "author" },
2562 { TYPE_STRING, &ldi.program_title, "program_title" },
2563 { TYPE_STRING, &ldi.program_copyright, "program_copyright" },
2564 { TYPE_STRING, &ldi.program_company, "program_company" },
2565 { TYPE_INTEGER, &ldi.sort_priority, "sort_priority" },
2566 { TYPE_STRING, &ldi.basepath, "basepath" },
2567 { TYPE_STRING, &ldi.fullpath, "fullpath" },
2568 { TYPE_BOOLEAN, &ldi.in_user_dir, "in_user_dir" },
2569 { TYPE_STRING, &ldi.class_desc, "class_desc" },
2574 static char *optional_tokens[] =
2577 "program_copyright",
2583 static void setTreeInfoToDefaults(TreeInfo *ti, int type)
2587 ti->node_top = (ti->type == TREE_TYPE_LEVEL_DIR ? &leveldir_first :
2588 ti->type == TREE_TYPE_GRAPHICS_DIR ? &artwork.gfx_first :
2589 ti->type == TREE_TYPE_SOUNDS_DIR ? &artwork.snd_first :
2590 ti->type == TREE_TYPE_MUSIC_DIR ? &artwork.mus_first :
2593 ti->node_parent = NULL;
2594 ti->node_group = NULL;
2601 ti->fullpath = NULL;
2602 ti->basepath = NULL;
2603 ti->identifier = NULL;
2604 ti->name = getStringCopy(ANONYMOUS_NAME);
2605 ti->name_sorting = NULL;
2606 ti->author = getStringCopy(ANONYMOUS_NAME);
2609 ti->program_title = NULL;
2610 ti->program_copyright = NULL;
2611 ti->program_company = NULL;
2613 ti->sort_priority = LEVELCLASS_UNDEFINED; // default: least priority
2614 ti->latest_engine = FALSE; // default: get from level
2615 ti->parent_link = FALSE;
2616 ti->is_copy = FALSE;
2617 ti->in_user_dir = FALSE;
2618 ti->user_defined = FALSE;
2620 ti->class_desc = NULL;
2622 ti->infotext = getStringCopy(TREE_INFOTEXT(ti->type));
2624 if (ti->type == TREE_TYPE_LEVEL_DIR)
2626 ti->imported_from = NULL;
2627 ti->imported_by = NULL;
2628 ti->tested_by = NULL;
2630 ti->graphics_set_ecs = NULL;
2631 ti->graphics_set_aga = NULL;
2632 ti->graphics_set = NULL;
2633 ti->sounds_set_default = NULL;
2634 ti->sounds_set_lowpass = NULL;
2635 ti->sounds_set = NULL;
2636 ti->music_set = NULL;
2637 ti->graphics_path = getStringCopy(UNDEFINED_FILENAME);
2638 ti->sounds_path = getStringCopy(UNDEFINED_FILENAME);
2639 ti->music_path = getStringCopy(UNDEFINED_FILENAME);
2641 ti->level_filename = NULL;
2642 ti->level_filetype = NULL;
2644 ti->special_flags = NULL;
2647 ti->first_level = 0;
2649 ti->level_group = FALSE;
2650 ti->handicap_level = 0;
2651 ti->readonly = TRUE;
2652 ti->handicap = TRUE;
2653 ti->skip_levels = FALSE;
2655 ti->use_emc_tiles = FALSE;
2659 static void setTreeInfoToDefaultsFromParent(TreeInfo *ti, TreeInfo *parent)
2663 Warn("setTreeInfoToDefaultsFromParent(): parent == NULL");
2665 setTreeInfoToDefaults(ti, TREE_TYPE_UNDEFINED);
2670 // copy all values from the parent structure
2672 ti->type = parent->type;
2674 ti->node_top = parent->node_top;
2675 ti->node_parent = parent;
2676 ti->node_group = NULL;
2683 ti->fullpath = NULL;
2684 ti->basepath = NULL;
2685 ti->identifier = NULL;
2686 ti->name = getStringCopy(ANONYMOUS_NAME);
2687 ti->name_sorting = NULL;
2688 ti->author = getStringCopy(parent->author);
2689 ti->year = getStringCopy(parent->year);
2691 ti->program_title = getStringCopy(parent->program_title);
2692 ti->program_copyright = getStringCopy(parent->program_copyright);
2693 ti->program_company = getStringCopy(parent->program_company);
2695 ti->sort_priority = parent->sort_priority;
2696 ti->latest_engine = parent->latest_engine;
2697 ti->parent_link = FALSE;
2698 ti->is_copy = FALSE;
2699 ti->in_user_dir = parent->in_user_dir;
2700 ti->user_defined = parent->user_defined;
2701 ti->color = parent->color;
2702 ti->class_desc = getStringCopy(parent->class_desc);
2704 ti->infotext = getStringCopy(parent->infotext);
2706 if (ti->type == TREE_TYPE_LEVEL_DIR)
2708 ti->imported_from = getStringCopy(parent->imported_from);
2709 ti->imported_by = getStringCopy(parent->imported_by);
2710 ti->tested_by = getStringCopy(parent->tested_by);
2712 ti->graphics_set_ecs = getStringCopy(parent->graphics_set_ecs);
2713 ti->graphics_set_aga = getStringCopy(parent->graphics_set_aga);
2714 ti->graphics_set = getStringCopy(parent->graphics_set);
2715 ti->sounds_set_default = getStringCopy(parent->sounds_set_default);
2716 ti->sounds_set_lowpass = getStringCopy(parent->sounds_set_lowpass);
2717 ti->sounds_set = getStringCopy(parent->sounds_set);
2718 ti->music_set = getStringCopy(parent->music_set);
2719 ti->graphics_path = getStringCopy(UNDEFINED_FILENAME);
2720 ti->sounds_path = getStringCopy(UNDEFINED_FILENAME);
2721 ti->music_path = getStringCopy(UNDEFINED_FILENAME);
2723 ti->level_filename = getStringCopy(parent->level_filename);
2724 ti->level_filetype = getStringCopy(parent->level_filetype);
2726 ti->special_flags = getStringCopy(parent->special_flags);
2728 ti->levels = parent->levels;
2729 ti->first_level = parent->first_level;
2730 ti->last_level = parent->last_level;
2731 ti->level_group = FALSE;
2732 ti->handicap_level = parent->handicap_level;
2733 ti->readonly = parent->readonly;
2734 ti->handicap = parent->handicap;
2735 ti->skip_levels = parent->skip_levels;
2737 ti->use_emc_tiles = parent->use_emc_tiles;
2741 static TreeInfo *getTreeInfoCopy(TreeInfo *ti)
2743 TreeInfo *ti_copy = newTreeInfo();
2745 // copy all values from the original structure
2747 ti_copy->type = ti->type;
2749 ti_copy->node_top = ti->node_top;
2750 ti_copy->node_parent = ti->node_parent;
2751 ti_copy->node_group = ti->node_group;
2752 ti_copy->next = ti->next;
2754 ti_copy->cl_first = ti->cl_first;
2755 ti_copy->cl_cursor = ti->cl_cursor;
2757 ti_copy->subdir = getStringCopy(ti->subdir);
2758 ti_copy->fullpath = getStringCopy(ti->fullpath);
2759 ti_copy->basepath = getStringCopy(ti->basepath);
2760 ti_copy->identifier = getStringCopy(ti->identifier);
2761 ti_copy->name = getStringCopy(ti->name);
2762 ti_copy->name_sorting = getStringCopy(ti->name_sorting);
2763 ti_copy->author = getStringCopy(ti->author);
2764 ti_copy->year = getStringCopy(ti->year);
2766 ti_copy->program_title = getStringCopy(ti->program_title);
2767 ti_copy->program_copyright = getStringCopy(ti->program_copyright);
2768 ti_copy->program_company = getStringCopy(ti->program_company);
2770 ti_copy->imported_from = getStringCopy(ti->imported_from);
2771 ti_copy->imported_by = getStringCopy(ti->imported_by);
2772 ti_copy->tested_by = getStringCopy(ti->tested_by);
2774 ti_copy->graphics_set_ecs = getStringCopy(ti->graphics_set_ecs);
2775 ti_copy->graphics_set_aga = getStringCopy(ti->graphics_set_aga);
2776 ti_copy->graphics_set = getStringCopy(ti->graphics_set);
2777 ti_copy->sounds_set_default = getStringCopy(ti->sounds_set_default);
2778 ti_copy->sounds_set_lowpass = getStringCopy(ti->sounds_set_lowpass);
2779 ti_copy->sounds_set = getStringCopy(ti->sounds_set);
2780 ti_copy->music_set = getStringCopy(ti->music_set);
2781 ti_copy->graphics_path = getStringCopy(ti->graphics_path);
2782 ti_copy->sounds_path = getStringCopy(ti->sounds_path);
2783 ti_copy->music_path = getStringCopy(ti->music_path);
2785 ti_copy->level_filename = getStringCopy(ti->level_filename);
2786 ti_copy->level_filetype = getStringCopy(ti->level_filetype);
2788 ti_copy->special_flags = getStringCopy(ti->special_flags);
2790 ti_copy->levels = ti->levels;
2791 ti_copy->first_level = ti->first_level;
2792 ti_copy->last_level = ti->last_level;
2793 ti_copy->sort_priority = ti->sort_priority;
2795 ti_copy->latest_engine = ti->latest_engine;
2797 ti_copy->level_group = ti->level_group;
2798 ti_copy->parent_link = ti->parent_link;
2799 ti_copy->is_copy = ti->is_copy;
2800 ti_copy->in_user_dir = ti->in_user_dir;
2801 ti_copy->user_defined = ti->user_defined;
2802 ti_copy->readonly = ti->readonly;
2803 ti_copy->handicap = ti->handicap;
2804 ti_copy->skip_levels = ti->skip_levels;
2806 ti_copy->use_emc_tiles = ti->use_emc_tiles;
2808 ti_copy->color = ti->color;
2809 ti_copy->class_desc = getStringCopy(ti->class_desc);
2810 ti_copy->handicap_level = ti->handicap_level;
2812 ti_copy->infotext = getStringCopy(ti->infotext);
2817 void freeTreeInfo(TreeInfo *ti)
2822 checked_free(ti->subdir);
2823 checked_free(ti->fullpath);
2824 checked_free(ti->basepath);
2825 checked_free(ti->identifier);
2827 checked_free(ti->name);
2828 checked_free(ti->name_sorting);
2829 checked_free(ti->author);
2830 checked_free(ti->year);
2832 checked_free(ti->program_title);
2833 checked_free(ti->program_copyright);
2834 checked_free(ti->program_company);
2836 checked_free(ti->class_desc);
2838 checked_free(ti->infotext);
2840 if (ti->type == TREE_TYPE_LEVEL_DIR)
2842 checked_free(ti->imported_from);
2843 checked_free(ti->imported_by);
2844 checked_free(ti->tested_by);
2846 checked_free(ti->graphics_set_ecs);
2847 checked_free(ti->graphics_set_aga);
2848 checked_free(ti->graphics_set);
2849 checked_free(ti->sounds_set_default);
2850 checked_free(ti->sounds_set_lowpass);
2851 checked_free(ti->sounds_set);
2852 checked_free(ti->music_set);
2854 checked_free(ti->graphics_path);
2855 checked_free(ti->sounds_path);
2856 checked_free(ti->music_path);
2858 checked_free(ti->level_filename);
2859 checked_free(ti->level_filetype);
2861 checked_free(ti->special_flags);
2864 // recursively free child node
2866 freeTreeInfo(ti->node_group);
2868 // recursively free next node
2870 freeTreeInfo(ti->next);
2875 void setSetupInfo(struct TokenInfo *token_info,
2876 int token_nr, char *token_value)
2878 int token_type = token_info[token_nr].type;
2879 void *setup_value = token_info[token_nr].value;
2881 if (token_value == NULL)
2884 // set setup field to corresponding token value
2889 *(boolean *)setup_value = get_boolean_from_string(token_value);
2893 *(int *)setup_value = get_switch3_from_string(token_value);
2897 *(Key *)setup_value = getKeyFromKeyName(token_value);
2901 *(Key *)setup_value = getKeyFromX11KeyName(token_value);
2905 *(int *)setup_value = get_integer_from_string(token_value);
2909 checked_free(*(char **)setup_value);
2910 *(char **)setup_value = getStringCopy(token_value);
2914 *(int *)setup_value = get_player_nr_from_string(token_value);
2922 static int compareTreeInfoEntries(const void *object1, const void *object2)
2924 const TreeInfo *entry1 = *((TreeInfo **)object1);
2925 const TreeInfo *entry2 = *((TreeInfo **)object2);
2926 int tree_sorting1 = TREE_SORTING(entry1);
2927 int tree_sorting2 = TREE_SORTING(entry2);
2929 if (tree_sorting1 != tree_sorting2)
2930 return (tree_sorting1 - tree_sorting2);
2932 return strcasecmp(entry1->name_sorting, entry2->name_sorting);
2935 static TreeInfo *createParentTreeInfoNode(TreeInfo *node_parent)
2939 if (node_parent == NULL)
2942 ti_new = newTreeInfo();
2943 setTreeInfoToDefaults(ti_new, node_parent->type);
2945 ti_new->node_parent = node_parent;
2946 ti_new->parent_link = TRUE;
2948 setString(&ti_new->identifier, node_parent->identifier);
2949 setString(&ti_new->name, BACKLINK_TEXT_PARENT);
2950 setString(&ti_new->name_sorting, ti_new->name);
2952 setString(&ti_new->subdir, STRING_PARENT_DIRECTORY);
2953 setString(&ti_new->fullpath, node_parent->fullpath);
2955 ti_new->sort_priority = LEVELCLASS_PARENT;
2956 ti_new->latest_engine = node_parent->latest_engine;
2958 setString(&ti_new->class_desc, getLevelClassDescription(ti_new));
2960 pushTreeInfo(&node_parent->node_group, ti_new);
2965 static TreeInfo *createTopTreeInfoNode(TreeInfo *node_first)
2967 if (node_first == NULL)
2970 TreeInfo *ti_new = newTreeInfo();
2971 int type = node_first->type;
2973 setTreeInfoToDefaults(ti_new, type);
2975 ti_new->node_parent = NULL;
2976 ti_new->parent_link = FALSE;
2978 setString(&ti_new->identifier, "top_tree_node");
2979 setString(&ti_new->name, TREE_INFOTEXT(type));
2980 setString(&ti_new->name_sorting, ti_new->name);
2982 setString(&ti_new->subdir, STRING_TOP_DIRECTORY);
2983 setString(&ti_new->fullpath, ".");
2985 ti_new->sort_priority = LEVELCLASS_TOP;
2986 ti_new->latest_engine = node_first->latest_engine;
2988 setString(&ti_new->class_desc, TREE_INFOTEXT(type));
2990 ti_new->node_group = node_first;
2991 ti_new->level_group = TRUE;
2993 TreeInfo *ti_new2 = createParentTreeInfoNode(ti_new);
2995 setString(&ti_new2->name, TREE_BACKLINK_TEXT(type));
2996 setString(&ti_new2->name_sorting, ti_new2->name);
3001 static void setTreeInfoParentNodes(TreeInfo *node, TreeInfo *node_parent)
3005 if (node->node_group)
3006 setTreeInfoParentNodes(node->node_group, node);
3008 node->node_parent = node_parent;
3015 // ----------------------------------------------------------------------------
3016 // functions for handling level and custom artwork info cache
3017 // ----------------------------------------------------------------------------
3019 static void LoadArtworkInfoCache(void)
3021 InitCacheDirectory();
3023 if (artworkinfo_cache_old == NULL)
3025 char *filename = getPath2(getCacheDir(), ARTWORKINFO_CACHE_FILE);
3027 // try to load artwork info hash from already existing cache file
3028 artworkinfo_cache_old = loadSetupFileHash(filename);
3030 // try to get program version that artwork info cache was written with
3031 char *version = getHashEntry(artworkinfo_cache_old, "program.version");
3033 // check program version of artwork info cache against current version
3034 if (!strEqual(version, program.version_string))
3036 freeSetupFileHash(artworkinfo_cache_old);
3038 artworkinfo_cache_old = NULL;
3041 // if no artwork info cache file was found, start with empty hash
3042 if (artworkinfo_cache_old == NULL)
3043 artworkinfo_cache_old = newSetupFileHash();
3048 if (artworkinfo_cache_new == NULL)
3049 artworkinfo_cache_new = newSetupFileHash();
3051 update_artworkinfo_cache = FALSE;
3054 static void SaveArtworkInfoCache(void)
3056 if (!update_artworkinfo_cache)
3059 char *filename = getPath2(getCacheDir(), ARTWORKINFO_CACHE_FILE);
3061 InitCacheDirectory();
3063 saveSetupFileHash(artworkinfo_cache_new, filename);
3068 static char *getCacheTokenPrefix(char *prefix1, char *prefix2)
3070 static char *prefix = NULL;
3072 checked_free(prefix);
3074 prefix = getStringCat2WithSeparator(prefix1, prefix2, ".");
3079 // (identical to above function, but separate string buffer needed -- nasty)
3080 static char *getCacheToken(char *prefix, char *suffix)
3082 static char *token = NULL;
3084 checked_free(token);
3086 token = getStringCat2WithSeparator(prefix, suffix, ".");
3091 static char *getFileTimestampString(char *filename)
3093 return getStringCopy(i_to_a(getFileTimestampEpochSeconds(filename)));
3096 static boolean modifiedFileTimestamp(char *filename, char *timestamp_string)
3098 struct stat file_status;
3100 if (timestamp_string == NULL)
3103 if (!fileExists(filename)) // file does not exist
3104 return (atoi(timestamp_string) != 0);
3106 if (stat(filename, &file_status) != 0) // cannot stat file
3109 return (file_status.st_mtime != atoi(timestamp_string));
3112 static TreeInfo *getArtworkInfoCacheEntry(LevelDirTree *level_node, int type)
3114 char *identifier = level_node->subdir;
3115 char *type_string = ARTWORK_DIRECTORY(type);
3116 char *token_prefix = getCacheTokenPrefix(type_string, identifier);
3117 char *token_main = getCacheToken(token_prefix, "CACHED");
3118 char *cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
3119 boolean cached = (cache_entry != NULL && strEqual(cache_entry, "true"));
3120 TreeInfo *artwork_info = NULL;
3122 if (!use_artworkinfo_cache)
3125 if (optional_tokens_hash == NULL)
3129 // create hash from list of optional tokens (for quick access)
3130 optional_tokens_hash = newSetupFileHash();
3131 for (i = 0; optional_tokens[i] != NULL; i++)
3132 setHashEntry(optional_tokens_hash, optional_tokens[i], "");
3139 artwork_info = newTreeInfo();
3140 setTreeInfoToDefaults(artwork_info, type);
3142 // set all structure fields according to the token/value pairs
3143 ldi = *artwork_info;
3144 for (i = 0; artworkinfo_tokens[i].type != -1; i++)
3146 char *token_suffix = artworkinfo_tokens[i].text;
3147 char *token = getCacheToken(token_prefix, token_suffix);
3148 char *value = getHashEntry(artworkinfo_cache_old, token);
3150 (getHashEntry(optional_tokens_hash, token_suffix) != NULL);
3152 setSetupInfo(artworkinfo_tokens, i, value);
3154 // check if cache entry for this item is mandatory, but missing
3155 if (value == NULL && !optional)
3157 Warn("missing cache entry '%s'", token);
3163 *artwork_info = ldi;
3168 char *filename_levelinfo = getPath2(getLevelDirFromTreeInfo(level_node),
3169 LEVELINFO_FILENAME);
3170 char *filename_artworkinfo = getPath2(getSetupArtworkDir(artwork_info),
3171 ARTWORKINFO_FILENAME(type));
3173 // check if corresponding "levelinfo.conf" file has changed
3174 token_main = getCacheToken(token_prefix, "TIMESTAMP_LEVELINFO");
3175 cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
3177 if (modifiedFileTimestamp(filename_levelinfo, cache_entry))
3180 // check if corresponding "<artworkinfo>.conf" file has changed
3181 token_main = getCacheToken(token_prefix, "TIMESTAMP_ARTWORKINFO");
3182 cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
3184 if (modifiedFileTimestamp(filename_artworkinfo, cache_entry))
3187 checked_free(filename_levelinfo);
3188 checked_free(filename_artworkinfo);
3191 if (!cached && artwork_info != NULL)
3193 freeTreeInfo(artwork_info);
3198 return artwork_info;
3201 static void setArtworkInfoCacheEntry(TreeInfo *artwork_info,
3202 LevelDirTree *level_node, int type)
3204 char *identifier = level_node->subdir;
3205 char *type_string = ARTWORK_DIRECTORY(type);
3206 char *token_prefix = getCacheTokenPrefix(type_string, identifier);
3207 char *token_main = getCacheToken(token_prefix, "CACHED");
3208 boolean set_cache_timestamps = TRUE;
3211 setHashEntry(artworkinfo_cache_new, token_main, "true");
3213 if (set_cache_timestamps)
3215 char *filename_levelinfo = getPath2(getLevelDirFromTreeInfo(level_node),
3216 LEVELINFO_FILENAME);
3217 char *filename_artworkinfo = getPath2(getSetupArtworkDir(artwork_info),
3218 ARTWORKINFO_FILENAME(type));
3219 char *timestamp_levelinfo = getFileTimestampString(filename_levelinfo);
3220 char *timestamp_artworkinfo = getFileTimestampString(filename_artworkinfo);
3222 token_main = getCacheToken(token_prefix, "TIMESTAMP_LEVELINFO");
3223 setHashEntry(artworkinfo_cache_new, token_main, timestamp_levelinfo);
3225 token_main = getCacheToken(token_prefix, "TIMESTAMP_ARTWORKINFO");
3226 setHashEntry(artworkinfo_cache_new, token_main, timestamp_artworkinfo);
3228 checked_free(filename_levelinfo);
3229 checked_free(filename_artworkinfo);
3230 checked_free(timestamp_levelinfo);
3231 checked_free(timestamp_artworkinfo);
3234 ldi = *artwork_info;
3235 for (i = 0; artworkinfo_tokens[i].type != -1; i++)
3237 char *token = getCacheToken(token_prefix, artworkinfo_tokens[i].text);
3238 char *value = getSetupValue(artworkinfo_tokens[i].type,
3239 artworkinfo_tokens[i].value);
3241 setHashEntry(artworkinfo_cache_new, token, value);
3246 // ----------------------------------------------------------------------------
3247 // functions for loading level info and custom artwork info
3248 // ----------------------------------------------------------------------------
3250 int GetZipFileTreeType(char *zip_filename)
3252 static char *top_dir_path = NULL;
3253 static char *top_dir_conf_filename[NUM_BASE_TREE_TYPES] = { NULL };
3254 static char *conf_basename[NUM_BASE_TREE_TYPES] =
3256 GRAPHICSINFO_FILENAME,
3257 SOUNDSINFO_FILENAME,
3263 checked_free(top_dir_path);
3264 top_dir_path = NULL;
3266 for (j = 0; j < NUM_BASE_TREE_TYPES; j++)
3268 checked_free(top_dir_conf_filename[j]);
3269 top_dir_conf_filename[j] = NULL;
3272 char **zip_entries = zip_list(zip_filename);
3274 // check if zip file successfully opened
3275 if (zip_entries == NULL || zip_entries[0] == NULL)
3276 return TREE_TYPE_UNDEFINED;
3278 // first zip file entry is expected to be top level directory
3279 char *top_dir = zip_entries[0];
3281 // check if valid top level directory found in zip file
3282 if (!strSuffix(top_dir, "/"))
3283 return TREE_TYPE_UNDEFINED;
3285 // get filenames of valid configuration files in top level directory
3286 for (j = 0; j < NUM_BASE_TREE_TYPES; j++)
3287 top_dir_conf_filename[j] = getStringCat2(top_dir, conf_basename[j]);
3289 int tree_type = TREE_TYPE_UNDEFINED;
3292 while (zip_entries[e] != NULL)
3294 // check if every zip file entry is below top level directory
3295 if (!strPrefix(zip_entries[e], top_dir))
3296 return TREE_TYPE_UNDEFINED;
3298 // check if this zip file entry is a valid configuration filename
3299 for (j = 0; j < NUM_BASE_TREE_TYPES; j++)
3301 if (strEqual(zip_entries[e], top_dir_conf_filename[j]))
3303 // only exactly one valid configuration file allowed
3304 if (tree_type != TREE_TYPE_UNDEFINED)
3305 return TREE_TYPE_UNDEFINED;
3317 static boolean CheckZipFileForDirectory(char *zip_filename, char *directory,
3320 static char *top_dir_path = NULL;
3321 static char *top_dir_conf_filename = NULL;
3323 checked_free(top_dir_path);
3324 checked_free(top_dir_conf_filename);
3326 top_dir_path = NULL;
3327 top_dir_conf_filename = NULL;
3329 char *conf_basename = (tree_type == TREE_TYPE_LEVEL_DIR ? LEVELINFO_FILENAME :
3330 ARTWORKINFO_FILENAME(tree_type));
3332 // check if valid configuration filename determined
3333 if (conf_basename == NULL || strEqual(conf_basename, ""))
3336 char **zip_entries = zip_list(zip_filename);
3338 // check if zip file successfully opened
3339 if (zip_entries == NULL || zip_entries[0] == NULL)
3342 // first zip file entry is expected to be top level directory
3343 char *top_dir = zip_entries[0];
3345 // check if valid top level directory found in zip file
3346 if (!strSuffix(top_dir, "/"))
3349 // get path of extracted top level directory
3350 top_dir_path = getPath2(directory, top_dir);
3352 // remove trailing directory separator from top level directory path
3353 // (required to be able to check for file and directory in next step)
3354 top_dir_path[strlen(top_dir_path) - 1] = '\0';
3356 // check if zip file's top level directory already exists in target directory
3357 if (fileExists(top_dir_path)) // (checks for file and directory)
3360 // get filename of configuration file in top level directory
3361 top_dir_conf_filename = getStringCat2(top_dir, conf_basename);
3363 boolean found_top_dir_conf_filename = FALSE;
3366 while (zip_entries[i] != NULL)
3368 // check if every zip file entry is below top level directory
3369 if (!strPrefix(zip_entries[i], top_dir))
3372 // check if this zip file entry is the configuration filename
3373 if (strEqual(zip_entries[i], top_dir_conf_filename))
3374 found_top_dir_conf_filename = TRUE;
3379 // check if valid configuration filename was found in zip file
3380 if (!found_top_dir_conf_filename)
3386 char *ExtractZipFileIntoDirectory(char *zip_filename, char *directory,
3389 boolean zip_file_valid = CheckZipFileForDirectory(zip_filename, directory,
3392 if (!zip_file_valid)
3394 Warn("zip file '%s' rejected!", zip_filename);
3399 char **zip_entries = zip_extract(zip_filename, directory);
3401 if (zip_entries == NULL)
3403 Warn("zip file '%s' could not be extracted!", zip_filename);
3408 Info("zip file '%s' successfully extracted!", zip_filename);
3410 // first zip file entry contains top level directory
3411 char *top_dir = zip_entries[0];
3413 // remove trailing directory separator from top level directory
3414 top_dir[strlen(top_dir) - 1] = '\0';
3419 static void ProcessZipFilesInDirectory(char *directory, int tree_type)
3422 DirectoryEntry *dir_entry;
3424 if ((dir = openDirectory(directory)) == NULL)
3426 // display error if directory is main "options.graphics_directory" etc.
3427 if (tree_type == TREE_TYPE_LEVEL_DIR ||
3428 directory == OPTIONS_ARTWORK_DIRECTORY(tree_type))
3429 Warn("cannot read directory '%s'", directory);
3434 while ((dir_entry = readDirectory(dir)) != NULL) // loop all entries
3436 // skip non-zip files (and also directories with zip extension)
3437 if (!strSuffixLower(dir_entry->basename, ".zip") || dir_entry->is_directory)
3440 char *zip_filename = getPath2(directory, dir_entry->basename);
3441 char *zip_filename_extracted = getStringCat2(zip_filename, ".extracted");
3442 char *zip_filename_rejected = getStringCat2(zip_filename, ".rejected");
3444 // check if zip file hasn't already been extracted or rejected
3445 if (!fileExists(zip_filename_extracted) &&
3446 !fileExists(zip_filename_rejected))
3448 char *top_dir = ExtractZipFileIntoDirectory(zip_filename, directory,
3450 char *marker_filename = (top_dir != NULL ? zip_filename_extracted :
3451 zip_filename_rejected);
3454 // create empty file to mark zip file as extracted or rejected
3455 if ((marker_file = fopen(marker_filename, MODE_WRITE)))
3456 fclose(marker_file);
3459 free(zip_filename_extracted);
3460 free(zip_filename_rejected);
3464 closeDirectory(dir);
3467 // forward declaration for recursive call by "LoadLevelInfoFromLevelDir()"
3468 static void LoadLevelInfoFromLevelDir(TreeInfo **, TreeInfo *, char *);
3470 static boolean LoadLevelInfoFromLevelConf(TreeInfo **node_first,
3471 TreeInfo *node_parent,
3472 char *level_directory,
3473 char *directory_name)
3475 char *directory_path = getPath2(level_directory, directory_name);
3476 char *filename = getPath2(directory_path, LEVELINFO_FILENAME);
3477 SetupFileHash *setup_file_hash;
3478 LevelDirTree *leveldir_new = NULL;
3481 // unless debugging, silently ignore directories without "levelinfo.conf"
3482 if (!options.debug && !fileExists(filename))
3484 free(directory_path);
3490 setup_file_hash = loadSetupFileHash(filename);
3492 if (setup_file_hash == NULL)
3494 #if DEBUG_NO_CONFIG_FILE
3495 Debug("setup", "ignoring level directory '%s'", directory_path);
3498 free(directory_path);
3504 leveldir_new = newTreeInfo();
3507 setTreeInfoToDefaultsFromParent(leveldir_new, node_parent);
3509 setTreeInfoToDefaults(leveldir_new, TREE_TYPE_LEVEL_DIR);
3511 leveldir_new->subdir = getStringCopy(directory_name);
3513 // set all structure fields according to the token/value pairs
3514 ldi = *leveldir_new;
3515 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
3516 setSetupInfo(levelinfo_tokens, i,
3517 getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
3518 *leveldir_new = ldi;
3520 if (strEqual(leveldir_new->name, ANONYMOUS_NAME))
3521 setString(&leveldir_new->name, leveldir_new->subdir);
3523 if (leveldir_new->identifier == NULL)
3524 leveldir_new->identifier = getStringCopy(leveldir_new->subdir);
3526 if (leveldir_new->name_sorting == NULL)
3527 leveldir_new->name_sorting = getStringCopy(leveldir_new->name);
3529 if (node_parent == NULL) // top level group
3531 leveldir_new->basepath = getStringCopy(level_directory);
3532 leveldir_new->fullpath = getStringCopy(leveldir_new->subdir);
3534 else // sub level group
3536 leveldir_new->basepath = getStringCopy(node_parent->basepath);
3537 leveldir_new->fullpath = getPath2(node_parent->fullpath, directory_name);
3540 leveldir_new->last_level =
3541 leveldir_new->first_level + leveldir_new->levels - 1;
3543 leveldir_new->in_user_dir =
3544 (!strEqual(leveldir_new->basepath, options.level_directory));
3546 // adjust some settings if user's private level directory was detected
3547 if (leveldir_new->sort_priority == LEVELCLASS_UNDEFINED &&
3548 leveldir_new->in_user_dir &&
3549 (strEqual(leveldir_new->subdir, getLoginName()) ||
3550 strEqual(leveldir_new->name, getLoginName()) ||
3551 strEqual(leveldir_new->author, getRealName())))
3553 leveldir_new->sort_priority = LEVELCLASS_PRIVATE_START;
3554 leveldir_new->readonly = FALSE;
3557 leveldir_new->user_defined =
3558 (leveldir_new->in_user_dir && IS_LEVELCLASS_PRIVATE(leveldir_new));
3560 setString(&leveldir_new->class_desc, getLevelClassDescription(leveldir_new));
3562 leveldir_new->handicap_level = // set handicap to default value
3563 (leveldir_new->user_defined || !leveldir_new->handicap ?
3564 leveldir_new->last_level : leveldir_new->first_level);
3566 DrawInitText(leveldir_new->name, 150, FC_YELLOW);
3568 pushTreeInfo(node_first, leveldir_new);
3570 freeSetupFileHash(setup_file_hash);
3572 if (leveldir_new->level_group)
3574 // create node to link back to current level directory
3575 createParentTreeInfoNode(leveldir_new);
3577 // recursively step into sub-directory and look for more level series
3578 LoadLevelInfoFromLevelDir(&leveldir_new->node_group,
3579 leveldir_new, directory_path);
3582 free(directory_path);
3588 static void LoadLevelInfoFromLevelDir(TreeInfo **node_first,
3589 TreeInfo *node_parent,
3590 char *level_directory)
3592 // ---------- 1st stage: process any level set zip files ----------
3594 ProcessZipFilesInDirectory(level_directory, TREE_TYPE_LEVEL_DIR);
3596 // ---------- 2nd stage: check for level set directories ----------
3599 DirectoryEntry *dir_entry;
3600 boolean valid_entry_found = FALSE;
3602 if ((dir = openDirectory(level_directory)) == NULL)
3604 Warn("cannot read level directory '%s'", level_directory);
3609 while ((dir_entry = readDirectory(dir)) != NULL) // loop all entries
3611 char *directory_name = dir_entry->basename;
3612 char *directory_path = getPath2(level_directory, directory_name);
3614 // skip entries for current and parent directory
3615 if (strEqual(directory_name, ".") ||
3616 strEqual(directory_name, ".."))
3618 free(directory_path);
3623 // find out if directory entry is itself a directory
3624 if (!dir_entry->is_directory) // not a directory
3626 free(directory_path);
3631 free(directory_path);
3633 if (strEqual(directory_name, GRAPHICS_DIRECTORY) ||
3634 strEqual(directory_name, SOUNDS_DIRECTORY) ||
3635 strEqual(directory_name, MUSIC_DIRECTORY))
3638 valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
3643 closeDirectory(dir);
3645 // special case: top level directory may directly contain "levelinfo.conf"
3646 if (node_parent == NULL && !valid_entry_found)
3648 // check if this directory directly contains a file "levelinfo.conf"
3649 valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
3650 level_directory, ".");
3653 if (!valid_entry_found)
3654 Warn("cannot find any valid level series in directory '%s'",
3658 boolean AdjustGraphicsForEMC(void)
3660 boolean settings_changed = FALSE;
3662 settings_changed |= adjustTreeGraphicsForEMC(leveldir_first_all);
3663 settings_changed |= adjustTreeGraphicsForEMC(leveldir_first);
3665 return settings_changed;
3668 boolean AdjustSoundsForEMC(void)
3670 boolean settings_changed = FALSE;
3672 settings_changed |= adjustTreeSoundsForEMC(leveldir_first_all);
3673 settings_changed |= adjustTreeSoundsForEMC(leveldir_first);
3675 return settings_changed;
3678 void LoadLevelInfo(void)
3680 InitUserLevelDirectory(getLoginName());
3682 DrawInitText("Loading level series", 120, FC_GREEN);
3684 LoadLevelInfoFromLevelDir(&leveldir_first, NULL, options.level_directory);
3685 LoadLevelInfoFromLevelDir(&leveldir_first, NULL, getUserLevelDir(NULL));
3687 leveldir_first = createTopTreeInfoNode(leveldir_first);
3689 /* after loading all level set information, clone the level directory tree
3690 and remove all level sets without levels (these may still contain artwork
3691 to be offered in the setup menu as "custom artwork", and are therefore
3692 checked for existing artwork in the function "LoadLevelArtworkInfo()") */
3693 leveldir_first_all = leveldir_first;
3694 cloneTree(&leveldir_first, leveldir_first_all, TRUE);
3696 AdjustGraphicsForEMC();
3697 AdjustSoundsForEMC();
3699 // before sorting, the first entries will be from the user directory
3700 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
3702 if (leveldir_first == NULL)
3703 Fail("cannot find any valid level series in any directory");
3705 sortTreeInfo(&leveldir_first);
3707 #if ENABLE_UNUSED_CODE
3708 dumpTreeInfo(leveldir_first, 0);
3712 static boolean LoadArtworkInfoFromArtworkConf(TreeInfo **node_first,
3713 TreeInfo *node_parent,
3714 char *base_directory,
3715 char *directory_name, int type)
3717 char *directory_path = getPath2(base_directory, directory_name);
3718 char *filename = getPath2(directory_path, ARTWORKINFO_FILENAME(type));
3719 SetupFileHash *setup_file_hash = NULL;
3720 TreeInfo *artwork_new = NULL;
3723 if (fileExists(filename))
3724 setup_file_hash = loadSetupFileHash(filename);
3726 if (setup_file_hash == NULL) // no config file -- look for artwork files
3729 DirectoryEntry *dir_entry;
3730 boolean valid_file_found = FALSE;
3732 if ((dir = openDirectory(directory_path)) != NULL)
3734 while ((dir_entry = readDirectory(dir)) != NULL)
3736 if (FileIsArtworkType(dir_entry->filename, type))
3738 valid_file_found = TRUE;
3744 closeDirectory(dir);
3747 if (!valid_file_found)
3749 #if DEBUG_NO_CONFIG_FILE
3750 if (!strEqual(directory_name, "."))
3751 Debug("setup", "ignoring artwork directory '%s'", directory_path);
3754 free(directory_path);
3761 artwork_new = newTreeInfo();
3764 setTreeInfoToDefaultsFromParent(artwork_new, node_parent);
3766 setTreeInfoToDefaults(artwork_new, type);
3768 artwork_new->subdir = getStringCopy(directory_name);
3770 if (setup_file_hash) // (before defining ".color" and ".class_desc")
3772 // set all structure fields according to the token/value pairs
3774 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
3775 setSetupInfo(levelinfo_tokens, i,
3776 getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
3779 if (strEqual(artwork_new->name, ANONYMOUS_NAME))
3780 setString(&artwork_new->name, artwork_new->subdir);
3782 if (artwork_new->identifier == NULL)
3783 artwork_new->identifier = getStringCopy(artwork_new->subdir);
3785 if (artwork_new->name_sorting == NULL)
3786 artwork_new->name_sorting = getStringCopy(artwork_new->name);
3789 if (node_parent == NULL) // top level group
3791 artwork_new->basepath = getStringCopy(base_directory);
3792 artwork_new->fullpath = getStringCopy(artwork_new->subdir);
3794 else // sub level group
3796 artwork_new->basepath = getStringCopy(node_parent->basepath);
3797 artwork_new->fullpath = getPath2(node_parent->fullpath, directory_name);
3800 artwork_new->in_user_dir =
3801 (!strEqual(artwork_new->basepath, OPTIONS_ARTWORK_DIRECTORY(type)));
3803 setString(&artwork_new->class_desc, getLevelClassDescription(artwork_new));
3805 if (setup_file_hash == NULL) // (after determining ".user_defined")
3807 if (strEqual(artwork_new->subdir, "."))
3809 if (artwork_new->user_defined)
3811 setString(&artwork_new->identifier, "private");
3812 artwork_new->sort_priority = ARTWORKCLASS_PRIVATE;
3816 setString(&artwork_new->identifier, "classic");
3817 artwork_new->sort_priority = ARTWORKCLASS_CLASSICS;
3820 setString(&artwork_new->class_desc,
3821 getLevelClassDescription(artwork_new));
3825 setString(&artwork_new->identifier, artwork_new->subdir);
3828 setString(&artwork_new->name, artwork_new->identifier);
3829 setString(&artwork_new->name_sorting, artwork_new->name);
3832 pushTreeInfo(node_first, artwork_new);
3834 freeSetupFileHash(setup_file_hash);
3836 free(directory_path);
3842 static void LoadArtworkInfoFromArtworkDir(TreeInfo **node_first,
3843 TreeInfo *node_parent,
3844 char *base_directory, int type)
3846 // ---------- 1st stage: process any artwork set zip files ----------
3848 ProcessZipFilesInDirectory(base_directory, type);
3850 // ---------- 2nd stage: check for artwork set directories ----------
3853 DirectoryEntry *dir_entry;
3854 boolean valid_entry_found = FALSE;
3856 if ((dir = openDirectory(base_directory)) == NULL)
3858 // display error if directory is main "options.graphics_directory" etc.
3859 if (base_directory == OPTIONS_ARTWORK_DIRECTORY(type))
3860 Warn("cannot read directory '%s'", base_directory);
3865 while ((dir_entry = readDirectory(dir)) != NULL) // loop all entries
3867 char *directory_name = dir_entry->basename;
3868 char *directory_path = getPath2(base_directory, directory_name);
3870 // skip directory entries for current and parent directory
3871 if (strEqual(directory_name, ".") ||
3872 strEqual(directory_name, ".."))
3874 free(directory_path);
3879 // skip directory entries which are not a directory
3880 if (!dir_entry->is_directory) // not a directory
3882 free(directory_path);
3887 free(directory_path);
3889 // check if this directory contains artwork with or without config file
3890 valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
3892 directory_name, type);
3895 closeDirectory(dir);
3897 // check if this directory directly contains artwork itself
3898 valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
3899 base_directory, ".",
3901 if (!valid_entry_found)
3902 Warn("cannot find any valid artwork in directory '%s'", base_directory);
3905 static TreeInfo *getDummyArtworkInfo(int type)
3907 // this is only needed when there is completely no artwork available
3908 TreeInfo *artwork_new = newTreeInfo();
3910 setTreeInfoToDefaults(artwork_new, type);
3912 setString(&artwork_new->subdir, UNDEFINED_FILENAME);
3913 setString(&artwork_new->fullpath, UNDEFINED_FILENAME);
3914 setString(&artwork_new->basepath, UNDEFINED_FILENAME);
3916 setString(&artwork_new->identifier, UNDEFINED_FILENAME);
3917 setString(&artwork_new->name, UNDEFINED_FILENAME);
3918 setString(&artwork_new->name_sorting, UNDEFINED_FILENAME);
3923 void SetCurrentArtwork(int type)
3925 ArtworkDirTree **current_ptr = ARTWORK_CURRENT_PTR(artwork, type);
3926 ArtworkDirTree *first_node = ARTWORK_FIRST_NODE(artwork, type);
3927 char *setup_set = SETUP_ARTWORK_SET(setup, type);
3928 char *default_subdir = ARTWORK_DEFAULT_SUBDIR(type);
3930 // set current artwork to artwork configured in setup menu
3931 *current_ptr = getTreeInfoFromIdentifier(first_node, setup_set);
3933 // if not found, set current artwork to default artwork
3934 if (*current_ptr == NULL)
3935 *current_ptr = getTreeInfoFromIdentifier(first_node, default_subdir);
3937 // if not found, set current artwork to first artwork in tree
3938 if (*current_ptr == NULL)
3939 *current_ptr = getFirstValidTreeInfoEntry(first_node);
3942 void ChangeCurrentArtworkIfNeeded(int type)
3944 char *current_identifier = ARTWORK_CURRENT_IDENTIFIER(artwork, type);
3945 char *setup_set = SETUP_ARTWORK_SET(setup, type);
3947 if (!strEqual(current_identifier, setup_set))
3948 SetCurrentArtwork(type);
3951 void LoadArtworkInfo(void)
3953 LoadArtworkInfoCache();
3955 DrawInitText("Looking for custom artwork", 120, FC_GREEN);
3957 LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
3958 options.graphics_directory,
3959 TREE_TYPE_GRAPHICS_DIR);
3960 LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
3961 getUserGraphicsDir(),
3962 TREE_TYPE_GRAPHICS_DIR);
3964 LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
3965 options.sounds_directory,
3966 TREE_TYPE_SOUNDS_DIR);
3967 LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
3969 TREE_TYPE_SOUNDS_DIR);
3971 LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
3972 options.music_directory,
3973 TREE_TYPE_MUSIC_DIR);
3974 LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
3976 TREE_TYPE_MUSIC_DIR);
3978 if (artwork.gfx_first == NULL)
3979 artwork.gfx_first = getDummyArtworkInfo(TREE_TYPE_GRAPHICS_DIR);
3980 if (artwork.snd_first == NULL)
3981 artwork.snd_first = getDummyArtworkInfo(TREE_TYPE_SOUNDS_DIR);
3982 if (artwork.mus_first == NULL)
3983 artwork.mus_first = getDummyArtworkInfo(TREE_TYPE_MUSIC_DIR);
3985 // before sorting, the first entries will be from the user directory
3986 SetCurrentArtwork(ARTWORK_TYPE_GRAPHICS);
3987 SetCurrentArtwork(ARTWORK_TYPE_SOUNDS);
3988 SetCurrentArtwork(ARTWORK_TYPE_MUSIC);
3990 artwork.gfx_current_identifier = artwork.gfx_current->identifier;
3991 artwork.snd_current_identifier = artwork.snd_current->identifier;
3992 artwork.mus_current_identifier = artwork.mus_current->identifier;
3994 #if ENABLE_UNUSED_CODE
3995 Debug("setup:LoadArtworkInfo", "graphics set == %s",
3996 artwork.gfx_current_identifier);
3997 Debug("setup:LoadArtworkInfo", "sounds set == %s",
3998 artwork.snd_current_identifier);
3999 Debug("setup:LoadArtworkInfo", "music set == %s",
4000 artwork.mus_current_identifier);
4003 sortTreeInfo(&artwork.gfx_first);
4004 sortTreeInfo(&artwork.snd_first);
4005 sortTreeInfo(&artwork.mus_first);
4007 #if ENABLE_UNUSED_CODE
4008 dumpTreeInfo(artwork.gfx_first, 0);
4009 dumpTreeInfo(artwork.snd_first, 0);
4010 dumpTreeInfo(artwork.mus_first, 0);
4014 static void MoveArtworkInfoIntoSubTree(ArtworkDirTree **artwork_node)
4016 ArtworkDirTree *artwork_new = newTreeInfo();
4017 char *top_node_name = "standalone artwork";
4019 setTreeInfoToDefaults(artwork_new, (*artwork_node)->type);
4021 artwork_new->level_group = TRUE;
4023 setString(&artwork_new->identifier, top_node_name);
4024 setString(&artwork_new->name, top_node_name);
4025 setString(&artwork_new->name_sorting, top_node_name);
4027 // create node to link back to current custom artwork directory
4028 createParentTreeInfoNode(artwork_new);
4030 // move existing custom artwork tree into newly created sub-tree
4031 artwork_new->node_group->next = *artwork_node;
4033 // change custom artwork tree to contain only newly created node
4034 *artwork_node = artwork_new;
4037 static void LoadArtworkInfoFromLevelInfoExt(ArtworkDirTree **artwork_node,
4038 ArtworkDirTree *node_parent,
4039 LevelDirTree *level_node,
4040 boolean empty_level_set_mode)
4042 int type = (*artwork_node)->type;
4044 // recursively check all level directories for artwork sub-directories
4048 boolean empty_level_set = (level_node->levels == 0);
4050 // check all tree entries for artwork, but skip parent link entries
4051 if (!level_node->parent_link && empty_level_set == empty_level_set_mode)
4053 TreeInfo *artwork_new = getArtworkInfoCacheEntry(level_node, type);
4054 boolean cached = (artwork_new != NULL);
4058 pushTreeInfo(artwork_node, artwork_new);
4062 TreeInfo *topnode_last = *artwork_node;
4063 char *path = getPath2(getLevelDirFromTreeInfo(level_node),
4064 ARTWORK_DIRECTORY(type));
4066 LoadArtworkInfoFromArtworkDir(artwork_node, NULL, path, type);
4068 if (topnode_last != *artwork_node) // check for newly added node
4070 artwork_new = *artwork_node;
4072 setString(&artwork_new->identifier, level_node->subdir);
4073 setString(&artwork_new->name, level_node->name);
4074 setString(&artwork_new->name_sorting, level_node->name_sorting);
4076 artwork_new->sort_priority = level_node->sort_priority;
4077 artwork_new->in_user_dir = level_node->in_user_dir;
4079 update_artworkinfo_cache = TRUE;
4085 // insert artwork info (from old cache or filesystem) into new cache
4086 if (artwork_new != NULL)
4087 setArtworkInfoCacheEntry(artwork_new, level_node, type);
4090 DrawInitText(level_node->name, 150, FC_YELLOW);
4092 if (level_node->node_group != NULL)
4094 TreeInfo *artwork_new = newTreeInfo();
4097 setTreeInfoToDefaultsFromParent(artwork_new, node_parent);
4099 setTreeInfoToDefaults(artwork_new, type);
4101 artwork_new->level_group = TRUE;
4103 setString(&artwork_new->identifier, level_node->subdir);
4105 if (node_parent == NULL) // check for top tree node
4107 char *top_node_name = (empty_level_set_mode ?
4108 "artwork for certain level sets" :
4109 "artwork included in level sets");
4111 setString(&artwork_new->name, top_node_name);
4112 setString(&artwork_new->name_sorting, top_node_name);
4116 setString(&artwork_new->name, level_node->name);
4117 setString(&artwork_new->name_sorting, level_node->name_sorting);
4120 pushTreeInfo(artwork_node, artwork_new);
4122 // create node to link back to current custom artwork directory
4123 createParentTreeInfoNode(artwork_new);
4125 // recursively step into sub-directory and look for more custom artwork
4126 LoadArtworkInfoFromLevelInfoExt(&artwork_new->node_group, artwork_new,
4127 level_node->node_group,
4128 empty_level_set_mode);
4130 // if sub-tree has no custom artwork at all, remove it
4131 if (artwork_new->node_group->next == NULL)
4132 removeTreeInfo(artwork_node);
4135 level_node = level_node->next;
4139 static void LoadArtworkInfoFromLevelInfo(ArtworkDirTree **artwork_node)
4141 // move peviously loaded artwork tree into separate sub-tree
4142 MoveArtworkInfoIntoSubTree(artwork_node);
4144 // load artwork from level sets into separate sub-trees
4145 LoadArtworkInfoFromLevelInfoExt(artwork_node, NULL, leveldir_first_all, TRUE);
4146 LoadArtworkInfoFromLevelInfoExt(artwork_node, NULL, leveldir_first_all, FALSE);
4148 // add top tree node over all three separate sub-trees
4149 *artwork_node = createTopTreeInfoNode(*artwork_node);
4151 // set all parent links (back links) in complete artwork tree
4152 setTreeInfoParentNodes(*artwork_node, NULL);
4155 void LoadLevelArtworkInfo(void)
4157 print_timestamp_init("LoadLevelArtworkInfo");
4159 DrawInitText("Looking for custom level artwork", 120, FC_GREEN);
4161 print_timestamp_time("DrawTimeText");
4163 LoadArtworkInfoFromLevelInfo(&artwork.gfx_first);
4164 print_timestamp_time("LoadArtworkInfoFromLevelInfo (gfx)");
4165 LoadArtworkInfoFromLevelInfo(&artwork.snd_first);
4166 print_timestamp_time("LoadArtworkInfoFromLevelInfo (snd)");
4167 LoadArtworkInfoFromLevelInfo(&artwork.mus_first);
4168 print_timestamp_time("LoadArtworkInfoFromLevelInfo (mus)");
4170 SaveArtworkInfoCache();
4172 print_timestamp_time("SaveArtworkInfoCache");
4174 // needed for reloading level artwork not known at ealier stage
4175 ChangeCurrentArtworkIfNeeded(ARTWORK_TYPE_GRAPHICS);
4176 ChangeCurrentArtworkIfNeeded(ARTWORK_TYPE_SOUNDS);
4177 ChangeCurrentArtworkIfNeeded(ARTWORK_TYPE_MUSIC);
4179 print_timestamp_time("getTreeInfoFromIdentifier");
4181 sortTreeInfo(&artwork.gfx_first);
4182 sortTreeInfo(&artwork.snd_first);
4183 sortTreeInfo(&artwork.mus_first);
4185 print_timestamp_time("sortTreeInfo");
4187 #if ENABLE_UNUSED_CODE
4188 dumpTreeInfo(artwork.gfx_first, 0);
4189 dumpTreeInfo(artwork.snd_first, 0);
4190 dumpTreeInfo(artwork.mus_first, 0);
4193 print_timestamp_done("LoadLevelArtworkInfo");
4196 static boolean AddTreeSetToTreeInfoExt(TreeInfo *tree_node_old, char *tree_dir,
4197 char *tree_subdir_new, int type)
4199 if (tree_node_old == NULL)
4201 if (type == TREE_TYPE_LEVEL_DIR)
4203 // get level info tree node of personal user level set
4204 tree_node_old = getTreeInfoFromIdentifier(leveldir_first, getLoginName());
4206 // this may happen if "setup.internal.create_user_levelset" is FALSE
4207 // or if file "levelinfo.conf" is missing in personal user level set
4208 if (tree_node_old == NULL)
4209 tree_node_old = leveldir_first->node_group;
4213 // get artwork info tree node of first artwork set
4214 tree_node_old = ARTWORK_FIRST_NODE(artwork, type);
4218 if (tree_dir == NULL)
4219 tree_dir = TREE_USERDIR(type);
4221 if (tree_node_old == NULL ||
4223 tree_subdir_new == NULL) // should not happen
4226 int draw_deactivation_mask = GetDrawDeactivationMask();
4228 // override draw deactivation mask (temporarily disable drawing)
4229 SetDrawDeactivationMask(REDRAW_ALL);
4231 if (type == TREE_TYPE_LEVEL_DIR)
4233 // load new level set config and add it next to first user level set
4234 LoadLevelInfoFromLevelConf(&tree_node_old->next,
4235 tree_node_old->node_parent,
4236 tree_dir, tree_subdir_new);
4240 // load new artwork set config and add it next to first artwork set
4241 LoadArtworkInfoFromArtworkConf(&tree_node_old->next,
4242 tree_node_old->node_parent,
4243 tree_dir, tree_subdir_new, type);
4246 // set draw deactivation mask to previous value
4247 SetDrawDeactivationMask(draw_deactivation_mask);
4249 // get first node of level or artwork info tree
4250 TreeInfo **tree_node_first = TREE_FIRST_NODE_PTR(type);
4252 // get tree info node of newly added level or artwork set
4253 TreeInfo *tree_node_new = getTreeInfoFromIdentifier(*tree_node_first,
4256 if (tree_node_new == NULL) // should not happen
4259 // correct top link and parent node link of newly created tree node
4260 tree_node_new->node_top = tree_node_old->node_top;
4261 tree_node_new->node_parent = tree_node_old->node_parent;
4263 // sort tree info to adjust position of newly added tree set
4264 sortTreeInfo(tree_node_first);
4269 void AddTreeSetToTreeInfo(TreeInfo *tree_node, char *tree_dir,
4270 char *tree_subdir_new, int type)
4272 if (!AddTreeSetToTreeInfoExt(tree_node, tree_dir, tree_subdir_new, type))
4273 Fail("internal tree info structure corrupted -- aborting");
4276 void AddUserLevelSetToLevelInfo(char *level_subdir_new)
4278 AddTreeSetToTreeInfo(NULL, NULL, level_subdir_new, TREE_TYPE_LEVEL_DIR);
4281 char *getArtworkIdentifierForUserLevelSet(int type)
4283 char *classic_artwork_set = getClassicArtworkSet(type);
4285 // check for custom artwork configured in "levelinfo.conf"
4286 char *leveldir_artwork_set =
4287 *LEVELDIR_ARTWORK_SET_PTR(leveldir_current, type);
4288 boolean has_leveldir_artwork_set =
4289 (leveldir_artwork_set != NULL && !strEqual(leveldir_artwork_set,
4290 classic_artwork_set));
4292 // check for custom artwork in sub-directory "graphics" etc.
4293 TreeInfo *artwork_first_node = ARTWORK_FIRST_NODE(artwork, type);
4294 char *leveldir_identifier = leveldir_current->identifier;
4295 boolean has_artwork_subdir =
4296 (getTreeInfoFromIdentifier(artwork_first_node,
4297 leveldir_identifier) != NULL);
4299 return (has_leveldir_artwork_set ? leveldir_artwork_set :
4300 has_artwork_subdir ? leveldir_identifier :
4301 classic_artwork_set);
4304 TreeInfo *getArtworkTreeInfoForUserLevelSet(int type)
4306 char *artwork_set = getArtworkIdentifierForUserLevelSet(type);
4307 TreeInfo *artwork_first_node = ARTWORK_FIRST_NODE(artwork, type);
4308 TreeInfo *ti = getTreeInfoFromIdentifier(artwork_first_node, artwork_set);
4312 ti = getTreeInfoFromIdentifier(artwork_first_node,
4313 ARTWORK_DEFAULT_SUBDIR(type));
4315 Fail("cannot find default graphics -- should not happen");
4321 boolean checkIfCustomArtworkExistsForCurrentLevelSet(void)
4323 char *graphics_set =
4324 getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_GRAPHICS);
4326 getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_SOUNDS);
4328 getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_MUSIC);
4330 return (!strEqual(graphics_set, GFX_CLASSIC_SUBDIR) ||
4331 !strEqual(sounds_set, SND_CLASSIC_SUBDIR) ||
4332 !strEqual(music_set, MUS_CLASSIC_SUBDIR));
4335 boolean UpdateUserLevelSet(char *level_subdir, char *level_name,
4336 char *level_author, int num_levels)
4338 char *filename = getPath2(getUserLevelDir(level_subdir), LEVELINFO_FILENAME);
4339 char *filename_tmp = getStringCat2(filename, ".tmp");
4341 FILE *file_tmp = NULL;
4342 char line[MAX_LINE_LEN];
4343 boolean success = FALSE;
4344 LevelDirTree *leveldir = getTreeInfoFromIdentifier(leveldir_first,
4346 // update values in level directory tree
4348 if (level_name != NULL)
4349 setString(&leveldir->name, level_name);
4351 if (level_author != NULL)
4352 setString(&leveldir->author, level_author);
4354 if (num_levels != -1)
4355 leveldir->levels = num_levels;
4357 // update values that depend on other values
4359 setString(&leveldir->name_sorting, leveldir->name);
4361 leveldir->last_level = leveldir->first_level + leveldir->levels - 1;
4363 // sort order of level sets may have changed
4364 sortTreeInfo(&leveldir_first);
4366 if ((file = fopen(filename, MODE_READ)) &&
4367 (file_tmp = fopen(filename_tmp, MODE_WRITE)))
4369 while (fgets(line, MAX_LINE_LEN, file))
4371 if (strPrefix(line, "name:") && level_name != NULL)
4372 fprintf(file_tmp, "%-32s%s\n", "name:", level_name);
4373 else if (strPrefix(line, "author:") && level_author != NULL)
4374 fprintf(file_tmp, "%-32s%s\n", "author:", level_author);
4375 else if (strPrefix(line, "levels:") && num_levels != -1)
4376 fprintf(file_tmp, "%-32s%d\n", "levels:", num_levels);
4378 fputs(line, file_tmp);
4391 success = (rename(filename_tmp, filename) == 0);
4399 boolean CreateUserLevelSet(char *level_subdir, char *level_name,
4400 char *level_author, int num_levels,
4401 boolean use_artwork_set)
4403 LevelDirTree *level_info;
4408 // create user level sub-directory, if needed
4409 createDirectory(getUserLevelDir(level_subdir), "user level", PERMS_PRIVATE);
4411 filename = getPath2(getUserLevelDir(level_subdir), LEVELINFO_FILENAME);
4413 if (!(file = fopen(filename, MODE_WRITE)))
4415 Warn("cannot write level info file '%s'", filename);
4422 level_info = newTreeInfo();
4424 // always start with reliable default values
4425 setTreeInfoToDefaults(level_info, TREE_TYPE_LEVEL_DIR);
4427 setString(&level_info->name, level_name);
4428 setString(&level_info->author, level_author);
4429 level_info->levels = num_levels;
4430 level_info->first_level = 1;
4431 level_info->sort_priority = LEVELCLASS_PRIVATE_START;
4432 level_info->readonly = FALSE;
4434 if (use_artwork_set)
4436 level_info->graphics_set =
4437 getStringCopy(getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_GRAPHICS));
4438 level_info->sounds_set =
4439 getStringCopy(getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_SOUNDS));
4440 level_info->music_set =
4441 getStringCopy(getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_MUSIC));
4444 token_value_position = TOKEN_VALUE_POSITION_SHORT;
4446 fprintFileHeader(file, LEVELINFO_FILENAME);
4449 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
4451 if (i == LEVELINFO_TOKEN_NAME ||
4452 i == LEVELINFO_TOKEN_AUTHOR ||
4453 i == LEVELINFO_TOKEN_LEVELS ||
4454 i == LEVELINFO_TOKEN_FIRST_LEVEL ||
4455 i == LEVELINFO_TOKEN_SORT_PRIORITY ||
4456 i == LEVELINFO_TOKEN_READONLY ||
4457 (use_artwork_set && (i == LEVELINFO_TOKEN_GRAPHICS_SET ||
4458 i == LEVELINFO_TOKEN_SOUNDS_SET ||
4459 i == LEVELINFO_TOKEN_MUSIC_SET)))
4460 fprintf(file, "%s\n", getSetupLine(levelinfo_tokens, "", i));
4462 // just to make things nicer :)
4463 if (i == LEVELINFO_TOKEN_AUTHOR ||
4464 i == LEVELINFO_TOKEN_FIRST_LEVEL ||
4465 (use_artwork_set && i == LEVELINFO_TOKEN_READONLY))
4466 fprintf(file, "\n");
4469 token_value_position = TOKEN_VALUE_POSITION_DEFAULT;
4473 SetFilePermissions(filename, PERMS_PRIVATE);
4475 freeTreeInfo(level_info);
4481 static void SaveUserLevelInfo(void)
4483 CreateUserLevelSet(getLoginName(), getLoginName(), getRealName(), 100, FALSE);
4486 char *getSetupValue(int type, void *value)
4488 static char value_string[MAX_LINE_LEN];
4496 strcpy(value_string, (*(boolean *)value ? "true" : "false"));
4500 strcpy(value_string, (*(boolean *)value ? "on" : "off"));
4504 strcpy(value_string, (*(int *)value == AUTO ? "auto" :
4505 *(int *)value == FALSE ? "off" : "on"));
4509 strcpy(value_string, (*(boolean *)value ? "yes" : "no"));
4512 case TYPE_YES_NO_AUTO:
4513 strcpy(value_string, (*(int *)value == AUTO ? "auto" :
4514 *(int *)value == FALSE ? "no" : "yes"));
4518 strcpy(value_string, (*(boolean *)value ? "AGA" : "ECS"));
4522 strcpy(value_string, getKeyNameFromKey(*(Key *)value));
4526 strcpy(value_string, getX11KeyNameFromKey(*(Key *)value));
4530 sprintf(value_string, "%d", *(int *)value);
4534 if (*(char **)value == NULL)
4537 strcpy(value_string, *(char **)value);
4541 sprintf(value_string, "player_%d", *(int *)value + 1);
4545 value_string[0] = '\0';
4549 if (type & TYPE_GHOSTED)
4550 strcpy(value_string, "n/a");
4552 return value_string;
4555 char *getSetupLine(struct TokenInfo *token_info, char *prefix, int token_nr)
4559 static char token_string[MAX_LINE_LEN];
4560 int token_type = token_info[token_nr].type;
4561 void *setup_value = token_info[token_nr].value;
4562 char *token_text = token_info[token_nr].text;
4563 char *value_string = getSetupValue(token_type, setup_value);
4565 // build complete token string
4566 sprintf(token_string, "%s%s", prefix, token_text);
4568 // build setup entry line
4569 line = getFormattedSetupEntry(token_string, value_string);
4571 if (token_type == TYPE_KEY_X11)
4573 Key key = *(Key *)setup_value;
4574 char *keyname = getKeyNameFromKey(key);
4576 // add comment, if useful
4577 if (!strEqual(keyname, "(undefined)") &&
4578 !strEqual(keyname, "(unknown)"))
4580 // add at least one whitespace
4582 for (i = strlen(line); i < token_comment_position; i++)
4586 strcat(line, keyname);
4593 static void InitLastPlayedLevels_ParentNode(void)
4595 LevelDirTree **leveldir_top = &leveldir_first->node_group->next;
4596 LevelDirTree *leveldir_new = NULL;
4598 // check if parent node for last played levels already exists
4599 if (strEqual((*leveldir_top)->identifier, TOKEN_STR_LAST_LEVEL_SERIES))
4602 leveldir_new = newTreeInfo();
4604 setTreeInfoToDefaultsFromParent(leveldir_new, leveldir_first);
4606 leveldir_new->level_group = TRUE;
4607 leveldir_new->sort_priority = LEVELCLASS_LAST_PLAYED_LEVEL;
4609 setString(&leveldir_new->identifier, TOKEN_STR_LAST_LEVEL_SERIES);
4610 setString(&leveldir_new->name, "<< (last played level sets)");
4611 setString(&leveldir_new->name_sorting, leveldir_new->name);
4613 pushTreeInfo(leveldir_top, leveldir_new);
4615 // create node to link back to current level directory
4616 createParentTreeInfoNode(leveldir_new);
4619 void UpdateLastPlayedLevels_TreeInfo(void)
4621 char **last_level_series = setup.level_setup.last_level_series;
4622 LevelDirTree *leveldir_last;
4623 TreeInfo **node_new = NULL;
4626 if (last_level_series[0] == NULL)
4629 InitLastPlayedLevels_ParentNode();
4631 leveldir_last = getTreeInfoFromIdentifierExt(leveldir_first,
4632 TOKEN_STR_LAST_LEVEL_SERIES,
4633 TREE_NODE_TYPE_GROUP);
4634 if (leveldir_last == NULL)
4637 node_new = &leveldir_last->node_group->next;
4639 freeTreeInfo(*node_new);
4643 for (i = 0; last_level_series[i] != NULL; i++)
4645 LevelDirTree *node_last = getTreeInfoFromIdentifier(leveldir_first,
4646 last_level_series[i]);
4647 if (node_last == NULL)
4650 *node_new = getTreeInfoCopy(node_last); // copy complete node
4652 (*node_new)->node_top = &leveldir_first; // correct top node link
4653 (*node_new)->node_parent = leveldir_last; // correct parent node link
4655 (*node_new)->is_copy = TRUE; // mark entry as node copy
4657 (*node_new)->node_group = NULL;
4658 (*node_new)->next = NULL;
4660 (*node_new)->cl_first = -1; // force setting tree cursor
4662 node_new = &((*node_new)->next);
4666 static void UpdateLastPlayedLevels_List(void)
4668 char **last_level_series = setup.level_setup.last_level_series;
4669 int pos = MAX_LEVELDIR_HISTORY - 1;
4672 // search for potentially already existing entry in list of level sets
4673 for (i = 0; last_level_series[i] != NULL; i++)
4674 if (strEqual(last_level_series[i], leveldir_current->identifier))
4677 // move list of level sets one entry down (using potentially free entry)
4678 for (i = pos; i > 0; i--)
4679 setString(&last_level_series[i], last_level_series[i - 1]);
4681 // put last played level set at top position
4682 setString(&last_level_series[0], leveldir_current->identifier);
4685 static TreeInfo *StoreOrRestoreLastPlayedLevels(TreeInfo *node, boolean store)
4687 static char *identifier = NULL;
4691 setString(&identifier, (node && node->is_copy ? node->identifier : NULL));
4693 return NULL; // not used
4697 TreeInfo *node_new = getTreeInfoFromIdentifierExt(leveldir_first,
4699 TREE_NODE_TYPE_COPY);
4700 return (node_new != NULL ? node_new : node);
4704 void StoreLastPlayedLevels(TreeInfo *node)
4706 StoreOrRestoreLastPlayedLevels(node, TRUE);
4709 void RestoreLastPlayedLevels(TreeInfo **node)
4711 *node = StoreOrRestoreLastPlayedLevels(*node, FALSE);
4714 void LoadLevelSetup_LastSeries(void)
4716 // --------------------------------------------------------------------------
4717 // ~/.<program>/levelsetup.conf
4718 // --------------------------------------------------------------------------
4720 char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
4721 SetupFileHash *level_setup_hash = NULL;
4725 // always start with reliable default values
4726 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
4728 // start with empty history of last played level sets
4729 setString(&setup.level_setup.last_level_series[0], NULL);
4731 if (!strEqual(DEFAULT_LEVELSET, UNDEFINED_LEVELSET))
4733 leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
4735 if (leveldir_current == NULL)
4736 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
4739 if ((level_setup_hash = loadSetupFileHash(filename)))
4741 char *last_level_series =
4742 getHashEntry(level_setup_hash, TOKEN_STR_LAST_LEVEL_SERIES);
4744 leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
4746 if (leveldir_current == NULL)
4747 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
4749 for (i = 0; i < MAX_LEVELDIR_HISTORY; i++)
4751 char token[strlen(TOKEN_STR_LAST_LEVEL_SERIES) + 10];
4752 LevelDirTree *leveldir_last;
4754 sprintf(token, "%s.%03d", TOKEN_STR_LAST_LEVEL_SERIES, i);
4756 last_level_series = getHashEntry(level_setup_hash, token);
4758 leveldir_last = getTreeInfoFromIdentifier(leveldir_first,
4760 if (leveldir_last != NULL)
4761 setString(&setup.level_setup.last_level_series[pos++],
4765 setString(&setup.level_setup.last_level_series[pos], NULL);
4767 freeSetupFileHash(level_setup_hash);
4771 Debug("setup", "using default setup values");
4777 static void SaveLevelSetup_LastSeries_Ext(boolean deactivate_last_level_series)
4779 // --------------------------------------------------------------------------
4780 // ~/.<program>/levelsetup.conf
4781 // --------------------------------------------------------------------------
4783 // check if the current level directory structure is available at this point
4784 if (leveldir_current == NULL)
4787 char **last_level_series = setup.level_setup.last_level_series;
4788 char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
4792 InitUserDataDirectory();
4794 UpdateLastPlayedLevels_List();
4796 if (!(file = fopen(filename, MODE_WRITE)))
4798 Warn("cannot write setup file '%s'", filename);
4805 fprintFileHeader(file, LEVELSETUP_FILENAME);
4807 if (deactivate_last_level_series)
4808 fprintf(file, "# %s\n# ", "the following level set may have caused a problem and was deactivated");
4810 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_LAST_LEVEL_SERIES,
4811 leveldir_current->identifier));
4813 for (i = 0; last_level_series[i] != NULL; i++)
4815 char token[strlen(TOKEN_STR_LAST_LEVEL_SERIES) + 10];
4817 sprintf(token, "%s.%03d", TOKEN_STR_LAST_LEVEL_SERIES, i);
4819 fprintf(file, "%s\n", getFormattedSetupEntry(token, last_level_series[i]));
4824 SetFilePermissions(filename, PERMS_PRIVATE);
4829 void SaveLevelSetup_LastSeries(void)
4831 SaveLevelSetup_LastSeries_Ext(FALSE);
4834 void SaveLevelSetup_LastSeries_Deactivate(void)
4836 SaveLevelSetup_LastSeries_Ext(TRUE);
4839 static void checkSeriesInfo(void)
4841 static char *level_directory = NULL;
4844 DirectoryEntry *dir_entry;
4847 checked_free(level_directory);
4849 // check for more levels besides the 'levels' field of 'levelinfo.conf'
4851 level_directory = getPath2((leveldir_current->in_user_dir ?
4852 getUserLevelDir(NULL) :
4853 options.level_directory),
4854 leveldir_current->fullpath);
4856 if ((dir = openDirectory(level_directory)) == NULL)
4858 Warn("cannot read level directory '%s'", level_directory);
4864 while ((dir_entry = readDirectory(dir)) != NULL) // loop all entries
4866 if (strlen(dir_entry->basename) > 4 &&
4867 dir_entry->basename[3] == '.' &&
4868 strEqual(&dir_entry->basename[4], LEVELFILE_EXTENSION))
4870 char levelnum_str[4];
4873 strncpy(levelnum_str, dir_entry->basename, 3);
4874 levelnum_str[3] = '\0';
4876 levelnum_value = atoi(levelnum_str);
4878 if (levelnum_value < leveldir_current->first_level)
4880 Warn("additional level %d found", levelnum_value);
4882 leveldir_current->first_level = levelnum_value;
4884 else if (levelnum_value > leveldir_current->last_level)
4886 Warn("additional level %d found", levelnum_value);
4888 leveldir_current->last_level = levelnum_value;
4894 closeDirectory(dir);
4897 void LoadLevelSetup_SeriesInfo(void)
4900 SetupFileHash *level_setup_hash = NULL;
4901 char *level_subdir = leveldir_current->subdir;
4904 // always start with reliable default values
4905 level_nr = leveldir_current->first_level;
4907 for (i = 0; i < MAX_LEVELS; i++)
4909 LevelStats_setPlayed(i, 0);
4910 LevelStats_setSolved(i, 0);
4915 // --------------------------------------------------------------------------
4916 // ~/.<program>/levelsetup/<level series>/levelsetup.conf
4917 // --------------------------------------------------------------------------
4919 level_subdir = leveldir_current->subdir;
4921 filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
4923 if ((level_setup_hash = loadSetupFileHash(filename)))
4927 // get last played level in this level set
4929 token_value = getHashEntry(level_setup_hash, TOKEN_STR_LAST_PLAYED_LEVEL);
4933 level_nr = atoi(token_value);
4935 if (level_nr < leveldir_current->first_level)
4936 level_nr = leveldir_current->first_level;
4937 if (level_nr > leveldir_current->last_level)
4938 level_nr = leveldir_current->last_level;
4941 // get handicap level in this level set
4943 token_value = getHashEntry(level_setup_hash, TOKEN_STR_HANDICAP_LEVEL);
4947 int level_nr = atoi(token_value);
4949 if (level_nr < leveldir_current->first_level)
4950 level_nr = leveldir_current->first_level;
4951 if (level_nr > leveldir_current->last_level + 1)
4952 level_nr = leveldir_current->last_level;
4954 if (leveldir_current->user_defined || !leveldir_current->handicap)
4955 level_nr = leveldir_current->last_level;
4957 leveldir_current->handicap_level = level_nr;
4960 // get number of played and solved levels in this level set
4962 BEGIN_HASH_ITERATION(level_setup_hash, itr)
4964 char *token = HASH_ITERATION_TOKEN(itr);
4965 char *value = HASH_ITERATION_VALUE(itr);
4967 if (strlen(token) == 3 &&
4968 token[0] >= '0' && token[0] <= '9' &&
4969 token[1] >= '0' && token[1] <= '9' &&
4970 token[2] >= '0' && token[2] <= '9')
4972 int level_nr = atoi(token);
4975 LevelStats_setPlayed(level_nr, atoi(value)); // read 1st column
4977 value = strchr(value, ' ');
4980 LevelStats_setSolved(level_nr, atoi(value)); // read 2nd column
4983 END_HASH_ITERATION(hash, itr)
4985 freeSetupFileHash(level_setup_hash);
4989 Debug("setup", "using default setup values");
4995 void SaveLevelSetup_SeriesInfo(void)
4998 char *level_subdir = leveldir_current->subdir;
4999 char *level_nr_str = int2str(level_nr, 0);
5000 char *handicap_level_str = int2str(leveldir_current->handicap_level, 0);
5004 // --------------------------------------------------------------------------
5005 // ~/.<program>/levelsetup/<level series>/levelsetup.conf
5006 // --------------------------------------------------------------------------
5008 InitLevelSetupDirectory(level_subdir);
5010 filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
5012 if (!(file = fopen(filename, MODE_WRITE)))
5014 Warn("cannot write setup file '%s'", filename);
5021 fprintFileHeader(file, LEVELSETUP_FILENAME);
5023 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_PLAYED_LEVEL,
5025 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_HANDICAP_LEVEL,
5026 handicap_level_str));
5028 for (i = leveldir_current->first_level; i <= leveldir_current->last_level;
5031 if (LevelStats_getPlayed(i) > 0 ||
5032 LevelStats_getSolved(i) > 0)
5037 sprintf(token, "%03d", i);
5038 sprintf(value, "%d %d", LevelStats_getPlayed(i), LevelStats_getSolved(i));
5040 fprintf(file, "%s\n", getFormattedSetupEntry(token, value));
5046 SetFilePermissions(filename, PERMS_PRIVATE);
5051 int LevelStats_getPlayed(int nr)
5053 return (nr >= 0 && nr < MAX_LEVELS ? level_stats[nr].played : 0);
5056 int LevelStats_getSolved(int nr)
5058 return (nr >= 0 && nr < MAX_LEVELS ? level_stats[nr].solved : 0);
5061 void LevelStats_setPlayed(int nr, int value)
5063 if (nr >= 0 && nr < MAX_LEVELS)
5064 level_stats[nr].played = value;
5067 void LevelStats_setSolved(int nr, int value)
5069 if (nr >= 0 && nr < MAX_LEVELS)
5070 level_stats[nr].solved = value;
5073 void LevelStats_incPlayed(int nr)
5075 if (nr >= 0 && nr < MAX_LEVELS)
5076 level_stats[nr].played++;
5079 void LevelStats_incSolved(int nr)
5081 if (nr >= 0 && nr < MAX_LEVELS)
5082 level_stats[nr].solved++;
5085 void LoadUserSetup(void)
5087 // --------------------------------------------------------------------------
5088 // ~/.<program>/usersetup.conf
5089 // --------------------------------------------------------------------------
5091 char *filename = getPath2(getMainUserGameDataDir(), USERSETUP_FILENAME);
5092 SetupFileHash *user_setup_hash = NULL;
5094 // always start with reliable default values
5097 if ((user_setup_hash = loadSetupFileHash(filename)))
5101 // get last selected user number
5102 token_value = getHashEntry(user_setup_hash, TOKEN_STR_LAST_USER);
5105 user.nr = MIN(MAX(0, atoi(token_value)), MAX_PLAYER_NAMES - 1);
5107 freeSetupFileHash(user_setup_hash);
5111 Debug("setup", "using default setup values");
5117 void SaveUserSetup(void)
5119 // --------------------------------------------------------------------------
5120 // ~/.<program>/usersetup.conf
5121 // --------------------------------------------------------------------------
5123 char *filename = getPath2(getMainUserGameDataDir(), USERSETUP_FILENAME);
5126 InitMainUserDataDirectory();
5128 if (!(file = fopen(filename, MODE_WRITE)))
5130 Warn("cannot write setup file '%s'", filename);
5137 fprintFileHeader(file, USERSETUP_FILENAME);
5139 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_USER,
5143 SetFilePermissions(filename, PERMS_PRIVATE);