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 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 *getTemporaryTapeFilename(void)
587 static char *filename = NULL;
588 char basename[MAX_FILENAME_LEN];
590 checked_free(filename);
592 sprintf(basename, "tmp.%s", TAPEFILE_EXTENSION);
593 filename = getPath2(getTapeDir(NULL), basename);
598 char *getDefaultSolutionTapeFilename(int nr)
600 static char *filename = NULL;
601 char basename[MAX_FILENAME_LEN];
603 checked_free(filename);
605 sprintf(basename, "%03d.%s", nr, TAPEFILE_EXTENSION);
606 filename = getPath2(getSolutionTapeDir(), basename);
611 char *getSokobanSolutionTapeFilename(int nr)
613 static char *filename = NULL;
614 char basename[MAX_FILENAME_LEN];
616 checked_free(filename);
618 sprintf(basename, "%03d.sln", nr);
619 filename = getPath2(getSolutionTapeDir(), basename);
624 char *getSolutionTapeFilename(int nr)
626 char *filename = getDefaultSolutionTapeFilename(nr);
628 if (!fileExists(filename))
630 char *filename2 = getSokobanSolutionTapeFilename(nr);
632 if (fileExists(filename2))
639 char *getScoreFilename(int nr)
641 static char *filename = NULL;
642 char basename[MAX_FILENAME_LEN];
644 checked_free(filename);
646 sprintf(basename, "%03d.%s", nr, SCOREFILE_EXTENSION);
648 // used instead of "leveldir_current->subdir" (for network games)
649 filename = getPath2(getScoreDir(levelset.identifier), basename);
654 char *getScoreCacheFilename(int nr)
656 static char *filename = NULL;
657 char basename[MAX_FILENAME_LEN];
659 checked_free(filename);
661 sprintf(basename, "%03d.%s", nr, SCOREFILE_EXTENSION);
663 // used instead of "leveldir_current->subdir" (for network games)
664 filename = getPath2(getScoreCacheDir(levelset.identifier), basename);
669 char *getScoreTapeBasename(char *name)
671 static char basename[MAX_FILENAME_LEN];
672 char basename_raw[MAX_FILENAME_LEN];
675 sprintf(timestamp, "%s", getCurrentTimestamp());
676 sprintf(basename_raw, "%s-%s", timestamp, name);
677 sprintf(basename, "%s-%08x", timestamp, get_hash_from_key(basename_raw));
682 char *getScoreTapeFilename(char *basename_no_ext, int nr)
684 static char *filename = NULL;
685 char basename[MAX_FILENAME_LEN];
687 checked_free(filename);
689 sprintf(basename, "%s.%s", basename_no_ext, TAPEFILE_EXTENSION);
691 // used instead of "leveldir_current->subdir" (for network games)
692 filename = getPath2(getScoreTapeDir(levelset.identifier, nr), basename);
697 char *getSetupFilename(void)
699 static char *filename = NULL;
701 checked_free(filename);
703 filename = getPath2(getSetupDir(), SETUP_FILENAME);
708 char *getDefaultSetupFilename(void)
710 return program.config_filename;
713 char *getEditorSetupFilename(void)
715 static char *filename = NULL;
717 checked_free(filename);
718 filename = getPath2(getCurrentLevelDir(), EDITORSETUP_FILENAME);
720 if (fileExists(filename))
723 checked_free(filename);
724 filename = getPath2(getSetupDir(), EDITORSETUP_FILENAME);
729 char *getHelpAnimFilename(void)
731 static char *filename = NULL;
733 checked_free(filename);
735 filename = getPath2(getCurrentLevelDir(), HELPANIM_FILENAME);
740 char *getHelpTextFilename(void)
742 static char *filename = NULL;
744 checked_free(filename);
746 filename = getPath2(getCurrentLevelDir(), HELPTEXT_FILENAME);
751 char *getLevelSetInfoFilename(void)
753 static char *filename = NULL;
768 for (i = 0; basenames[i] != NULL; i++)
770 checked_free(filename);
771 filename = getPath2(getCurrentLevelDir(), basenames[i]);
773 if (fileExists(filename))
780 static char *getLevelSetTitleMessageBasename(int nr, boolean initial)
782 static char basename[32];
784 sprintf(basename, "%s_%d.txt",
785 (initial ? "titlemessage_initial" : "titlemessage"), nr + 1);
790 char *getLevelSetTitleMessageFilename(int nr, boolean initial)
792 static char *filename = NULL;
794 boolean skip_setup_artwork = FALSE;
796 checked_free(filename);
798 basename = getLevelSetTitleMessageBasename(nr, initial);
800 if (!gfx.override_level_graphics)
802 // 1st try: look for special artwork in current level series directory
803 filename = getPath3(getCurrentLevelDir(), GRAPHICS_DIRECTORY, basename);
804 if (fileExists(filename))
809 // 2nd try: look for message file in current level set directory
810 filename = getPath2(getCurrentLevelDir(), basename);
811 if (fileExists(filename))
816 // check if there is special artwork configured in level series config
817 if (getLevelArtworkSet(ARTWORK_TYPE_GRAPHICS) != NULL)
819 // 3rd try: look for special artwork configured in level series config
820 filename = getPath2(getLevelArtworkDir(ARTWORK_TYPE_GRAPHICS), basename);
821 if (fileExists(filename))
826 // take missing artwork configured in level set config from default
827 skip_setup_artwork = TRUE;
831 if (!skip_setup_artwork)
833 // 4th try: look for special artwork in configured artwork directory
834 filename = getPath2(getSetupArtworkDir(artwork.gfx_current), basename);
835 if (fileExists(filename))
841 // 5th try: look for default artwork in new default artwork directory
842 filename = getPath2(getDefaultGraphicsDir(GFX_DEFAULT_SUBDIR), basename);
843 if (fileExists(filename))
848 // 6th try: look for default artwork in old default artwork directory
849 filename = getPath2(options.graphics_directory, basename);
850 if (fileExists(filename))
853 return NULL; // cannot find specified artwork file anywhere
856 static char *getCorrectedArtworkBasename(char *basename)
861 char *getCustomImageFilename(char *basename)
863 static char *filename = NULL;
864 boolean skip_setup_artwork = FALSE;
866 checked_free(filename);
868 basename = getCorrectedArtworkBasename(basename);
870 if (!gfx.override_level_graphics)
872 // 1st try: look for special artwork in current level series directory
873 filename = getImg3(getCurrentLevelDir(), GRAPHICS_DIRECTORY, basename);
874 if (fileExists(filename))
879 // check if there is special artwork configured in level series config
880 if (getLevelArtworkSet(ARTWORK_TYPE_GRAPHICS) != NULL)
882 // 2nd try: look for special artwork configured in level series config
883 filename = getImg2(getLevelArtworkDir(ARTWORK_TYPE_GRAPHICS), basename);
884 if (fileExists(filename))
889 // take missing artwork configured in level set config from default
890 skip_setup_artwork = TRUE;
894 if (!skip_setup_artwork)
896 // 3rd try: look for special artwork in configured artwork directory
897 filename = getImg2(getSetupArtworkDir(artwork.gfx_current), basename);
898 if (fileExists(filename))
904 // 4th try: look for default artwork in new default artwork directory
905 filename = getImg2(getDefaultGraphicsDir(GFX_DEFAULT_SUBDIR), basename);
906 if (fileExists(filename))
911 // 5th try: look for default artwork in old default artwork directory
912 filename = getImg2(options.graphics_directory, basename);
913 if (fileExists(filename))
916 if (!strEqual(GFX_FALLBACK_FILENAME, UNDEFINED_FILENAME))
920 Warn("cannot find artwork file '%s' (using fallback)", basename);
922 // 6th try: look for fallback artwork in old default artwork directory
923 // (needed to prevent errors when trying to access unused artwork files)
924 filename = getImg2(options.graphics_directory, GFX_FALLBACK_FILENAME);
925 if (fileExists(filename))
929 return NULL; // cannot find specified artwork file anywhere
932 char *getCustomSoundFilename(char *basename)
934 static char *filename = NULL;
935 boolean skip_setup_artwork = FALSE;
937 checked_free(filename);
939 basename = getCorrectedArtworkBasename(basename);
941 if (!gfx.override_level_sounds)
943 // 1st try: look for special artwork in current level series directory
944 filename = getPath3(getCurrentLevelDir(), SOUNDS_DIRECTORY, basename);
945 if (fileExists(filename))
950 // check if there is special artwork configured in level series config
951 if (getLevelArtworkSet(ARTWORK_TYPE_SOUNDS) != NULL)
953 // 2nd try: look for special artwork configured in level series config
954 filename = getPath2(getLevelArtworkDir(TREE_TYPE_SOUNDS_DIR), basename);
955 if (fileExists(filename))
960 // take missing artwork configured in level set config from default
961 skip_setup_artwork = TRUE;
965 if (!skip_setup_artwork)
967 // 3rd try: look for special artwork in configured artwork directory
968 filename = getPath2(getSetupArtworkDir(artwork.snd_current), basename);
969 if (fileExists(filename))
975 // 4th try: look for default artwork in new default artwork directory
976 filename = getPath2(getDefaultSoundsDir(SND_DEFAULT_SUBDIR), basename);
977 if (fileExists(filename))
982 // 5th try: look for default artwork in old default artwork directory
983 filename = getPath2(options.sounds_directory, basename);
984 if (fileExists(filename))
987 if (!strEqual(SND_FALLBACK_FILENAME, UNDEFINED_FILENAME))
991 Warn("cannot find artwork file '%s' (using fallback)", basename);
993 // 6th try: look for fallback artwork in old default artwork directory
994 // (needed to prevent errors when trying to access unused artwork files)
995 filename = getPath2(options.sounds_directory, SND_FALLBACK_FILENAME);
996 if (fileExists(filename))
1000 return NULL; // cannot find specified artwork file anywhere
1003 char *getCustomMusicFilename(char *basename)
1005 static char *filename = NULL;
1006 boolean skip_setup_artwork = FALSE;
1008 checked_free(filename);
1010 basename = getCorrectedArtworkBasename(basename);
1012 if (!gfx.override_level_music)
1014 // 1st try: look for special artwork in current level series directory
1015 filename = getPath3(getCurrentLevelDir(), MUSIC_DIRECTORY, basename);
1016 if (fileExists(filename))
1021 // check if there is special artwork configured in level series config
1022 if (getLevelArtworkSet(ARTWORK_TYPE_MUSIC) != NULL)
1024 // 2nd try: look for special artwork configured in level series config
1025 filename = getPath2(getLevelArtworkDir(TREE_TYPE_MUSIC_DIR), basename);
1026 if (fileExists(filename))
1031 // take missing artwork configured in level set config from default
1032 skip_setup_artwork = TRUE;
1036 if (!skip_setup_artwork)
1038 // 3rd try: look for special artwork in configured artwork directory
1039 filename = getPath2(getSetupArtworkDir(artwork.mus_current), basename);
1040 if (fileExists(filename))
1046 // 4th try: look for default artwork in new default artwork directory
1047 filename = getPath2(getDefaultMusicDir(MUS_DEFAULT_SUBDIR), basename);
1048 if (fileExists(filename))
1053 // 5th try: look for default artwork in old default artwork directory
1054 filename = getPath2(options.music_directory, basename);
1055 if (fileExists(filename))
1058 if (!strEqual(MUS_FALLBACK_FILENAME, UNDEFINED_FILENAME))
1062 Warn("cannot find artwork file '%s' (using fallback)", basename);
1064 // 6th try: look for fallback artwork in old default artwork directory
1065 // (needed to prevent errors when trying to access unused artwork files)
1066 filename = getPath2(options.music_directory, MUS_FALLBACK_FILENAME);
1067 if (fileExists(filename))
1071 return NULL; // cannot find specified artwork file anywhere
1074 char *getCustomArtworkFilename(char *basename, int type)
1076 if (type == ARTWORK_TYPE_GRAPHICS)
1077 return getCustomImageFilename(basename);
1078 else if (type == ARTWORK_TYPE_SOUNDS)
1079 return getCustomSoundFilename(basename);
1080 else if (type == ARTWORK_TYPE_MUSIC)
1081 return getCustomMusicFilename(basename);
1083 return UNDEFINED_FILENAME;
1086 char *getCustomArtworkConfigFilename(int type)
1088 return getCustomArtworkFilename(ARTWORKINFO_FILENAME(type), type);
1091 char *getCustomArtworkLevelConfigFilename(int type)
1093 static char *filename = NULL;
1095 checked_free(filename);
1097 filename = getPath2(getLevelArtworkDir(type), ARTWORKINFO_FILENAME(type));
1102 char *getCustomMusicDirectory(void)
1104 static char *directory = NULL;
1105 boolean skip_setup_artwork = FALSE;
1107 checked_free(directory);
1109 if (!gfx.override_level_music)
1111 // 1st try: look for special artwork in current level series directory
1112 directory = getPath2(getCurrentLevelDir(), MUSIC_DIRECTORY);
1113 if (directoryExists(directory))
1118 // check if there is special artwork configured in level series config
1119 if (getLevelArtworkSet(ARTWORK_TYPE_MUSIC) != NULL)
1121 // 2nd try: look for special artwork configured in level series config
1122 directory = getStringCopy(getLevelArtworkDir(TREE_TYPE_MUSIC_DIR));
1123 if (directoryExists(directory))
1128 // take missing artwork configured in level set config from default
1129 skip_setup_artwork = TRUE;
1133 if (!skip_setup_artwork)
1135 // 3rd try: look for special artwork in configured artwork directory
1136 directory = getStringCopy(getSetupArtworkDir(artwork.mus_current));
1137 if (directoryExists(directory))
1143 // 4th try: look for default artwork in new default artwork directory
1144 directory = getStringCopy(getDefaultMusicDir(MUS_DEFAULT_SUBDIR));
1145 if (directoryExists(directory))
1150 // 5th try: look for default artwork in old default artwork directory
1151 directory = getStringCopy(options.music_directory);
1152 if (directoryExists(directory))
1155 return NULL; // cannot find specified artwork file anywhere
1158 void MarkTapeDirectoryUploadsAsComplete(char *level_subdir)
1160 char *filename = getPath2(getTapeDir(level_subdir), UPLOADED_FILENAME);
1162 touchFile(filename);
1164 checked_free(filename);
1167 void MarkTapeDirectoryUploadsAsIncomplete(char *level_subdir)
1169 char *filename = getPath2(getTapeDir(level_subdir), UPLOADED_FILENAME);
1173 checked_free(filename);
1176 boolean CheckTapeDirectoryUploadsComplete(char *level_subdir)
1178 char *filename = getPath2(getTapeDir(level_subdir), UPLOADED_FILENAME);
1179 boolean success = fileExists(filename);
1181 checked_free(filename);
1186 void InitTapeDirectory(char *level_subdir)
1188 boolean new_tape_dir = !directoryExists(getTapeDir(level_subdir));
1190 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
1191 createDirectory(getTapeDir(NULL), "main tape", PERMS_PRIVATE);
1192 createDirectory(getTapeDir(level_subdir), "level tape", PERMS_PRIVATE);
1195 MarkTapeDirectoryUploadsAsComplete(level_subdir);
1198 void InitScoreDirectory(char *level_subdir)
1200 createDirectory(getMainUserGameDataDir(), "main user data", PERMS_PRIVATE);
1201 createDirectory(getScoreDir(NULL), "main score", PERMS_PRIVATE);
1202 createDirectory(getScoreDir(level_subdir), "level score", PERMS_PRIVATE);
1205 void InitScoreCacheDirectory(char *level_subdir)
1207 createDirectory(getMainUserGameDataDir(), "main user data", PERMS_PRIVATE);
1208 createDirectory(getCacheDir(), "cache data", PERMS_PRIVATE);
1209 createDirectory(getScoreCacheDir(NULL), "main score", PERMS_PRIVATE);
1210 createDirectory(getScoreCacheDir(level_subdir), "level score", PERMS_PRIVATE);
1213 void InitScoreTapeDirectory(char *level_subdir, int nr)
1215 InitScoreDirectory(level_subdir);
1217 createDirectory(getScoreTapeDir(level_subdir, nr), "score tape", PERMS_PRIVATE);
1220 static void SaveUserLevelInfo(void);
1222 void InitUserLevelDirectory(char *level_subdir)
1224 if (!directoryExists(getUserLevelDir(level_subdir)))
1226 createDirectory(getMainUserGameDataDir(), "main user data", PERMS_PRIVATE);
1227 createDirectory(getUserLevelDir(NULL), "main user level", PERMS_PRIVATE);
1228 createDirectory(getUserLevelDir(level_subdir), "user level", PERMS_PRIVATE);
1230 if (setup.internal.create_user_levelset)
1231 SaveUserLevelInfo();
1235 void InitNetworkLevelDirectory(char *level_subdir)
1237 if (!directoryExists(getNetworkLevelDir(level_subdir)))
1239 createDirectory(getMainUserGameDataDir(), "main user data", PERMS_PRIVATE);
1240 createDirectory(getNetworkDir(), "network data", PERMS_PRIVATE);
1241 createDirectory(getNetworkLevelDir(NULL), "main network level", PERMS_PRIVATE);
1242 createDirectory(getNetworkLevelDir(level_subdir), "network level", PERMS_PRIVATE);
1246 void InitLevelSetupDirectory(char *level_subdir)
1248 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
1249 createDirectory(getLevelSetupDir(NULL), "main level setup", PERMS_PRIVATE);
1250 createDirectory(getLevelSetupDir(level_subdir), "level setup", PERMS_PRIVATE);
1253 static void InitCacheDirectory(void)
1255 createDirectory(getMainUserGameDataDir(), "main user data", PERMS_PRIVATE);
1256 createDirectory(getCacheDir(), "cache data", PERMS_PRIVATE);
1260 // ----------------------------------------------------------------------------
1261 // some functions to handle lists of level and artwork directories
1262 // ----------------------------------------------------------------------------
1264 TreeInfo *newTreeInfo(void)
1266 return checked_calloc(sizeof(TreeInfo));
1269 TreeInfo *newTreeInfo_setDefaults(int type)
1271 TreeInfo *ti = newTreeInfo();
1273 setTreeInfoToDefaults(ti, type);
1278 void pushTreeInfo(TreeInfo **node_first, TreeInfo *node_new)
1280 node_new->next = *node_first;
1281 *node_first = node_new;
1284 void removeTreeInfo(TreeInfo **node_first)
1286 TreeInfo *node_old = *node_first;
1288 *node_first = node_old->next;
1289 node_old->next = NULL;
1291 freeTreeInfo(node_old);
1294 int numTreeInfo(TreeInfo *node)
1307 boolean validLevelSeries(TreeInfo *node)
1309 // in a number of cases, tree node is no valid level set
1310 if (node == NULL || node->node_group || node->parent_link || node->is_copy)
1316 TreeInfo *getValidLevelSeries(TreeInfo *node, TreeInfo *default_node)
1318 if (validLevelSeries(node))
1320 else if (node->is_copy)
1321 return getTreeInfoFromIdentifier(leveldir_first, node->identifier);
1323 return getFirstValidTreeInfoEntry(default_node);
1326 static TreeInfo *getValidTreeInfoEntryExt(TreeInfo *node, boolean get_next_node)
1331 if (node->node_group) // enter node group (step down into tree)
1332 return getFirstValidTreeInfoEntry(node->node_group);
1334 if (node->parent_link) // skip first node (back link) of node group
1335 get_next_node = TRUE;
1337 if (!get_next_node) // get current regular tree node
1340 // get next regular tree node, or step up until one is found
1341 while (node->next == NULL && node->node_parent != NULL)
1342 node = node->node_parent;
1344 return getFirstValidTreeInfoEntry(node->next);
1347 TreeInfo *getFirstValidTreeInfoEntry(TreeInfo *node)
1349 return getValidTreeInfoEntryExt(node, FALSE);
1352 TreeInfo *getNextValidTreeInfoEntry(TreeInfo *node)
1354 return getValidTreeInfoEntryExt(node, TRUE);
1357 TreeInfo *getTreeInfoFirstGroupEntry(TreeInfo *node)
1362 if (node->node_parent == NULL) // top level group
1363 return *node->node_top;
1364 else // sub level group
1365 return node->node_parent->node_group;
1368 int numTreeInfoInGroup(TreeInfo *node)
1370 return numTreeInfo(getTreeInfoFirstGroupEntry(node));
1373 int getPosFromTreeInfo(TreeInfo *node)
1375 TreeInfo *node_cmp = getTreeInfoFirstGroupEntry(node);
1380 if (node_cmp == node)
1384 node_cmp = node_cmp->next;
1390 TreeInfo *getTreeInfoFromPos(TreeInfo *node, int pos)
1392 TreeInfo *node_default = node;
1404 return node_default;
1407 static TreeInfo *getTreeInfoFromIdentifierExt(TreeInfo *node, char *identifier,
1408 int node_type_wanted)
1410 if (identifier == NULL)
1415 if (TREE_NODE_TYPE(node) == node_type_wanted &&
1416 strEqual(identifier, node->identifier))
1419 if (node->node_group)
1421 TreeInfo *node_group = getTreeInfoFromIdentifierExt(node->node_group,
1434 TreeInfo *getTreeInfoFromIdentifier(TreeInfo *node, char *identifier)
1436 return getTreeInfoFromIdentifierExt(node, identifier, TREE_NODE_TYPE_DEFAULT);
1439 static TreeInfo *cloneTreeNode(TreeInfo **node_top, TreeInfo *node_parent,
1440 TreeInfo *node, boolean skip_sets_without_levels)
1447 if (!node->parent_link && !node->level_group &&
1448 skip_sets_without_levels && node->levels == 0)
1449 return cloneTreeNode(node_top, node_parent, node->next,
1450 skip_sets_without_levels);
1452 node_new = getTreeInfoCopy(node); // copy complete node
1454 node_new->node_top = node_top; // correct top node link
1455 node_new->node_parent = node_parent; // correct parent node link
1457 if (node->level_group)
1458 node_new->node_group = cloneTreeNode(node_top, node_new, node->node_group,
1459 skip_sets_without_levels);
1461 node_new->next = cloneTreeNode(node_top, node_parent, node->next,
1462 skip_sets_without_levels);
1467 static void cloneTree(TreeInfo **ti_new, TreeInfo *ti, boolean skip_empty_sets)
1469 TreeInfo *ti_cloned = cloneTreeNode(ti_new, NULL, ti, skip_empty_sets);
1471 *ti_new = ti_cloned;
1474 static boolean adjustTreeGraphicsForEMC(TreeInfo *node)
1476 boolean settings_changed = FALSE;
1480 boolean want_ecs = (setup.prefer_aga_graphics == FALSE);
1481 boolean want_aga = (setup.prefer_aga_graphics == TRUE);
1482 boolean has_only_ecs = (!node->graphics_set && !node->graphics_set_aga);
1483 boolean has_only_aga = (!node->graphics_set && !node->graphics_set_ecs);
1484 char *graphics_set = NULL;
1486 if (node->graphics_set_ecs && (want_ecs || has_only_ecs))
1487 graphics_set = node->graphics_set_ecs;
1489 if (node->graphics_set_aga && (want_aga || has_only_aga))
1490 graphics_set = node->graphics_set_aga;
1492 if (graphics_set && !strEqual(node->graphics_set, graphics_set))
1494 setString(&node->graphics_set, graphics_set);
1495 settings_changed = TRUE;
1498 if (node->node_group != NULL)
1499 settings_changed |= adjustTreeGraphicsForEMC(node->node_group);
1504 return settings_changed;
1507 static boolean adjustTreeSoundsForEMC(TreeInfo *node)
1509 boolean settings_changed = FALSE;
1513 boolean want_default = (setup.prefer_lowpass_sounds == FALSE);
1514 boolean want_lowpass = (setup.prefer_lowpass_sounds == TRUE);
1515 boolean has_only_default = (!node->sounds_set && !node->sounds_set_lowpass);
1516 boolean has_only_lowpass = (!node->sounds_set && !node->sounds_set_default);
1517 char *sounds_set = NULL;
1519 if (node->sounds_set_default && (want_default || has_only_default))
1520 sounds_set = node->sounds_set_default;
1522 if (node->sounds_set_lowpass && (want_lowpass || has_only_lowpass))
1523 sounds_set = node->sounds_set_lowpass;
1525 if (sounds_set && !strEqual(node->sounds_set, sounds_set))
1527 setString(&node->sounds_set, sounds_set);
1528 settings_changed = TRUE;
1531 if (node->node_group != NULL)
1532 settings_changed |= adjustTreeSoundsForEMC(node->node_group);
1537 return settings_changed;
1540 int dumpTreeInfo(TreeInfo *node, int depth)
1542 char bullet_list[] = { '-', '*', 'o' };
1543 int num_leaf_nodes = 0;
1547 Debug("tree", "Dumping TreeInfo:");
1551 char bullet = bullet_list[depth % ARRAY_SIZE(bullet_list)];
1553 for (i = 0; i < depth * 2; i++)
1554 DebugContinued("", " ");
1556 DebugContinued("tree", "%c '%s' ['%s] [PARENT: '%s'] %s\n",
1557 bullet, node->name, node->identifier,
1558 (node->node_parent ? node->node_parent->identifier : "-"),
1559 (node->node_group ? "[GROUP]" :
1560 node->is_copy ? "[COPY]" : ""));
1562 if (!node->node_group && !node->parent_link)
1566 // use for dumping artwork info tree
1567 Debug("tree", "subdir == '%s' ['%s', '%s'] [%d])",
1568 node->subdir, node->fullpath, node->basepath, node->in_user_dir);
1571 if (node->node_group != NULL)
1572 num_leaf_nodes += dumpTreeInfo(node->node_group, depth + 1);
1578 Debug("tree", "Summary: %d leaf nodes found", num_leaf_nodes);
1580 return num_leaf_nodes;
1583 void sortTreeInfoBySortFunction(TreeInfo **node_first,
1584 int (*compare_function)(const void *,
1587 int num_nodes = numTreeInfo(*node_first);
1588 TreeInfo **sort_array;
1589 TreeInfo *node = *node_first;
1595 // allocate array for sorting structure pointers
1596 sort_array = checked_calloc(num_nodes * sizeof(TreeInfo *));
1598 // writing structure pointers to sorting array
1599 while (i < num_nodes && node) // double boundary check...
1601 sort_array[i] = node;
1607 // sorting the structure pointers in the sorting array
1608 qsort(sort_array, num_nodes, sizeof(TreeInfo *),
1611 // update the linkage of list elements with the sorted node array
1612 for (i = 0; i < num_nodes - 1; i++)
1613 sort_array[i]->next = sort_array[i + 1];
1614 sort_array[num_nodes - 1]->next = NULL;
1616 // update the linkage of the main list anchor pointer
1617 *node_first = sort_array[0];
1621 // now recursively sort the level group structures
1625 if (node->node_group != NULL)
1626 sortTreeInfoBySortFunction(&node->node_group, compare_function);
1632 void sortTreeInfo(TreeInfo **node_first)
1634 sortTreeInfoBySortFunction(node_first, compareTreeInfoEntries);
1638 // ============================================================================
1639 // some stuff from "files.c"
1640 // ============================================================================
1642 #if defined(PLATFORM_WIN32)
1644 #define S_IRGRP S_IRUSR
1647 #define S_IROTH S_IRUSR
1650 #define S_IWGRP S_IWUSR
1653 #define S_IWOTH S_IWUSR
1656 #define S_IXGRP S_IXUSR
1659 #define S_IXOTH S_IXUSR
1662 #define S_IRWXG (S_IRGRP | S_IWGRP | S_IXGRP)
1667 #endif // PLATFORM_WIN32
1669 // file permissions for newly written files
1670 #define MODE_R_ALL (S_IRUSR | S_IRGRP | S_IROTH)
1671 #define MODE_W_ALL (S_IWUSR | S_IWGRP | S_IWOTH)
1672 #define MODE_X_ALL (S_IXUSR | S_IXGRP | S_IXOTH)
1674 #define MODE_W_PRIVATE (S_IWUSR)
1675 #define MODE_W_PUBLIC_FILE (S_IWUSR | S_IWGRP)
1676 #define MODE_W_PUBLIC_DIR (S_IWUSR | S_IWGRP | S_ISGID)
1678 #define DIR_PERMS_PRIVATE (MODE_R_ALL | MODE_X_ALL | MODE_W_PRIVATE)
1679 #define DIR_PERMS_PUBLIC (MODE_R_ALL | MODE_X_ALL | MODE_W_PUBLIC_DIR)
1680 #define DIR_PERMS_PUBLIC_ALL (MODE_R_ALL | MODE_X_ALL | MODE_W_ALL)
1682 #define FILE_PERMS_PRIVATE (MODE_R_ALL | MODE_W_PRIVATE)
1683 #define FILE_PERMS_PUBLIC (MODE_R_ALL | MODE_W_PUBLIC_FILE)
1684 #define FILE_PERMS_PUBLIC_ALL (MODE_R_ALL | MODE_W_ALL)
1687 char *getHomeDir(void)
1689 static char *dir = NULL;
1691 #if defined(PLATFORM_WIN32)
1694 dir = checked_malloc(MAX_PATH + 1);
1696 if (!SUCCEEDED(SHGetFolderPath(NULL, CSIDL_PERSONAL, NULL, 0, dir)))
1699 #elif defined(PLATFORM_EMSCRIPTEN)
1700 dir = "/persistent";
1701 #elif defined(PLATFORM_UNIX)
1704 if ((dir = getenv("HOME")) == NULL)
1706 dir = getUnixHomeDir();
1709 dir = getStringCopy(dir);
1721 char *getPersonalDataDir(void)
1723 static char *personal_data_dir = NULL;
1725 #if defined(PLATFORM_MACOSX)
1726 if (personal_data_dir == NULL)
1727 personal_data_dir = getPath2(getHomeDir(), "Documents");
1729 if (personal_data_dir == NULL)
1730 personal_data_dir = getHomeDir();
1733 return personal_data_dir;
1736 char *getMainUserGameDataDir(void)
1738 static char *main_user_data_dir = NULL;
1740 #if defined(PLATFORM_ANDROID)
1741 if (main_user_data_dir == NULL)
1742 main_user_data_dir = (char *)(SDL_AndroidGetExternalStorageState() &
1743 SDL_ANDROID_EXTERNAL_STORAGE_WRITE ?
1744 SDL_AndroidGetExternalStoragePath() :
1745 SDL_AndroidGetInternalStoragePath());
1747 if (main_user_data_dir == NULL)
1748 main_user_data_dir = getPath2(getPersonalDataDir(),
1749 program.userdata_subdir);
1752 return main_user_data_dir;
1755 char *getUserGameDataDir(void)
1758 return getMainUserGameDataDir();
1760 return getUserDir(user.nr);
1763 char *getSetupDir(void)
1765 return getUserGameDataDir();
1768 static mode_t posix_umask(mode_t mask)
1770 #if defined(PLATFORM_UNIX)
1777 static int posix_mkdir(const char *pathname, mode_t mode)
1779 #if defined(PLATFORM_WIN32)
1780 return mkdir(pathname);
1782 return mkdir(pathname, mode);
1786 static boolean posix_process_running_setgid(void)
1788 #if defined(PLATFORM_UNIX)
1789 return (getgid() != getegid());
1795 void createDirectory(char *dir, char *text, int permission_class)
1797 if (directoryExists(dir))
1800 // leave "other" permissions in umask untouched, but ensure group parts
1801 // of USERDATA_DIR_MODE are not masked
1802 mode_t dir_mode = (permission_class == PERMS_PRIVATE ?
1803 DIR_PERMS_PRIVATE : DIR_PERMS_PUBLIC);
1804 mode_t last_umask = posix_umask(0);
1805 mode_t group_umask = ~(dir_mode & S_IRWXG);
1806 int running_setgid = posix_process_running_setgid();
1808 if (permission_class == PERMS_PUBLIC)
1810 // if we're setgid, protect files against "other"
1811 // else keep umask(0) to make the dir world-writable
1814 posix_umask(last_umask & group_umask);
1816 dir_mode = DIR_PERMS_PUBLIC_ALL;
1819 if (posix_mkdir(dir, dir_mode) != 0)
1820 Warn("cannot create %s directory '%s': %s", text, dir, strerror(errno));
1822 if (permission_class == PERMS_PUBLIC && !running_setgid)
1823 chmod(dir, dir_mode);
1825 posix_umask(last_umask); // restore previous umask
1828 void InitMainUserDataDirectory(void)
1830 createDirectory(getMainUserGameDataDir(), "main user data", PERMS_PRIVATE);
1833 void InitUserDataDirectory(void)
1835 createDirectory(getMainUserGameDataDir(), "main user data", PERMS_PRIVATE);
1839 createDirectory(getUserDir(-1), "users", PERMS_PRIVATE);
1840 createDirectory(getUserDir(user.nr), "user data", PERMS_PRIVATE);
1844 void SetFilePermissions(char *filename, int permission_class)
1846 int running_setgid = posix_process_running_setgid();
1847 int perms = (permission_class == PERMS_PRIVATE ?
1848 FILE_PERMS_PRIVATE : FILE_PERMS_PUBLIC);
1850 if (permission_class == PERMS_PUBLIC && !running_setgid)
1851 perms = FILE_PERMS_PUBLIC_ALL;
1853 chmod(filename, perms);
1856 char *getCookie(char *file_type)
1858 static char cookie[MAX_COOKIE_LEN + 1];
1860 if (strlen(program.cookie_prefix) + 1 +
1861 strlen(file_type) + strlen("_FILE_VERSION_x.x") > MAX_COOKIE_LEN)
1862 return "[COOKIE ERROR]"; // should never happen
1864 sprintf(cookie, "%s_%s_FILE_VERSION_%d.%d",
1865 program.cookie_prefix, file_type,
1866 program.version_super, program.version_major);
1871 void fprintFileHeader(FILE *file, char *basename)
1873 char *prefix = "# ";
1876 fprintf_line_with_prefix(file, prefix, sep1, 77);
1877 fprintf(file, "%s%s\n", prefix, basename);
1878 fprintf_line_with_prefix(file, prefix, sep1, 77);
1879 fprintf(file, "\n");
1882 int getFileVersionFromCookieString(const char *cookie)
1884 const char *ptr_cookie1, *ptr_cookie2;
1885 const char *pattern1 = "_FILE_VERSION_";
1886 const char *pattern2 = "?.?";
1887 const int len_cookie = strlen(cookie);
1888 const int len_pattern1 = strlen(pattern1);
1889 const int len_pattern2 = strlen(pattern2);
1890 const int len_pattern = len_pattern1 + len_pattern2;
1891 int version_super, version_major;
1893 if (len_cookie <= len_pattern)
1896 ptr_cookie1 = &cookie[len_cookie - len_pattern];
1897 ptr_cookie2 = &cookie[len_cookie - len_pattern2];
1899 if (strncmp(ptr_cookie1, pattern1, len_pattern1) != 0)
1902 if (ptr_cookie2[0] < '0' || ptr_cookie2[0] > '9' ||
1903 ptr_cookie2[1] != '.' ||
1904 ptr_cookie2[2] < '0' || ptr_cookie2[2] > '9')
1907 version_super = ptr_cookie2[0] - '0';
1908 version_major = ptr_cookie2[2] - '0';
1910 return VERSION_IDENT(version_super, version_major, 0, 0);
1913 boolean checkCookieString(const char *cookie, const char *template)
1915 const char *pattern = "_FILE_VERSION_?.?";
1916 const int len_cookie = strlen(cookie);
1917 const int len_template = strlen(template);
1918 const int len_pattern = strlen(pattern);
1920 if (len_cookie != len_template)
1923 if (strncmp(cookie, template, len_cookie - len_pattern) != 0)
1930 // ----------------------------------------------------------------------------
1931 // setup file list and hash handling functions
1932 // ----------------------------------------------------------------------------
1934 char *getFormattedSetupEntry(char *token, char *value)
1937 static char entry[MAX_LINE_LEN];
1939 // if value is an empty string, just return token without value
1943 // start with the token and some spaces to format output line
1944 sprintf(entry, "%s:", token);
1945 for (i = strlen(entry); i < token_value_position; i++)
1948 // continue with the token's value
1949 strcat(entry, value);
1954 SetupFileList *newSetupFileList(char *token, char *value)
1956 SetupFileList *new = checked_malloc(sizeof(SetupFileList));
1958 new->token = getStringCopy(token);
1959 new->value = getStringCopy(value);
1966 void freeSetupFileList(SetupFileList *list)
1971 checked_free(list->token);
1972 checked_free(list->value);
1975 freeSetupFileList(list->next);
1980 char *getListEntry(SetupFileList *list, char *token)
1985 if (strEqual(list->token, token))
1988 return getListEntry(list->next, token);
1991 SetupFileList *setListEntry(SetupFileList *list, char *token, char *value)
1996 if (strEqual(list->token, token))
1998 checked_free(list->value);
2000 list->value = getStringCopy(value);
2004 else if (list->next == NULL)
2005 return (list->next = newSetupFileList(token, value));
2007 return setListEntry(list->next, token, value);
2010 SetupFileList *addListEntry(SetupFileList *list, char *token, char *value)
2015 if (list->next == NULL)
2016 return (list->next = newSetupFileList(token, value));
2018 return addListEntry(list->next, token, value);
2021 #if ENABLE_UNUSED_CODE
2023 static void printSetupFileList(SetupFileList *list)
2028 Debug("setup:printSetupFileList", "token: '%s'", list->token);
2029 Debug("setup:printSetupFileList", "value: '%s'", list->value);
2031 printSetupFileList(list->next);
2037 DEFINE_HASHTABLE_INSERT(insert_hash_entry, char, char);
2038 DEFINE_HASHTABLE_SEARCH(search_hash_entry, char, char);
2039 DEFINE_HASHTABLE_CHANGE(change_hash_entry, char, char);
2040 DEFINE_HASHTABLE_REMOVE(remove_hash_entry, char, char);
2042 #define insert_hash_entry hashtable_insert
2043 #define search_hash_entry hashtable_search
2044 #define change_hash_entry hashtable_change
2045 #define remove_hash_entry hashtable_remove
2048 unsigned int get_hash_from_key(void *key)
2053 This algorithm (k=33) was first reported by Dan Bernstein many years ago in
2054 'comp.lang.c'. Another version of this algorithm (now favored by Bernstein)
2055 uses XOR: hash(i) = hash(i - 1) * 33 ^ str[i]; the magic of number 33 (why
2056 it works better than many other constants, prime or not) has never been
2057 adequately explained.
2059 If you just want to have a good hash function, and cannot wait, djb2
2060 is one of the best string hash functions i know. It has excellent
2061 distribution and speed on many different sets of keys and table sizes.
2062 You are not likely to do better with one of the "well known" functions
2063 such as PJW, K&R, etc.
2065 Ozan (oz) Yigit [http://www.cs.yorku.ca/~oz/hash.html]
2068 char *str = (char *)key;
2069 unsigned int hash = 5381;
2072 while ((c = *str++))
2073 hash = ((hash << 5) + hash) + c; // hash * 33 + c
2078 int hash_keys_are_equal(void *key1, void *key2)
2080 return (strEqual((char *)key1, (char *)key2));
2083 SetupFileHash *newSetupFileHash(void)
2085 SetupFileHash *new_hash =
2086 create_hashtable(16, 0.75, get_hash_from_key, hash_keys_are_equal);
2088 if (new_hash == NULL)
2089 Fail("create_hashtable() failed -- out of memory");
2094 void freeSetupFileHash(SetupFileHash *hash)
2099 hashtable_destroy(hash, 1); // 1 == also free values stored in hash
2102 char *getHashEntry(SetupFileHash *hash, char *token)
2107 return search_hash_entry(hash, token);
2110 void setHashEntry(SetupFileHash *hash, char *token, char *value)
2117 value_copy = getStringCopy(value);
2119 // change value; if it does not exist, insert it as new
2120 if (!change_hash_entry(hash, token, value_copy))
2121 if (!insert_hash_entry(hash, getStringCopy(token), value_copy))
2122 Fail("cannot insert into hash -- aborting");
2125 char *removeHashEntry(SetupFileHash *hash, char *token)
2130 return remove_hash_entry(hash, token);
2133 #if ENABLE_UNUSED_CODE
2135 static void printSetupFileHash(SetupFileHash *hash)
2137 BEGIN_HASH_ITERATION(hash, itr)
2139 Debug("setup:printSetupFileHash", "token: '%s'", HASH_ITERATION_TOKEN(itr));
2140 Debug("setup:printSetupFileHash", "value: '%s'", HASH_ITERATION_VALUE(itr));
2142 END_HASH_ITERATION(hash, itr)
2147 #define ALLOW_TOKEN_VALUE_SEPARATOR_BEING_WHITESPACE 1
2148 #define CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING 0
2149 #define CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH 0
2151 static boolean token_value_separator_found = FALSE;
2152 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2153 static boolean token_value_separator_warning = FALSE;
2155 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2156 static boolean token_already_exists_warning = FALSE;
2159 static boolean getTokenValueFromSetupLineExt(char *line,
2160 char **token_ptr, char **value_ptr,
2161 char *filename, char *line_raw,
2163 boolean separator_required)
2165 static char line_copy[MAX_LINE_LEN + 1], line_raw_copy[MAX_LINE_LEN + 1];
2166 char *token, *value, *line_ptr;
2168 // when externally invoked via ReadTokenValueFromLine(), copy line buffers
2169 if (line_raw == NULL)
2171 strncpy(line_copy, line, MAX_LINE_LEN);
2172 line_copy[MAX_LINE_LEN] = '\0';
2175 strcpy(line_raw_copy, line_copy);
2176 line_raw = line_raw_copy;
2179 // cut trailing comment from input line
2180 for (line_ptr = line; *line_ptr; line_ptr++)
2182 if (*line_ptr == '#')
2189 // cut trailing whitespaces from input line
2190 for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
2191 if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
2194 // ignore empty lines
2198 // cut leading whitespaces from token
2199 for (token = line; *token; token++)
2200 if (*token != ' ' && *token != '\t')
2203 // start with empty value as reliable default
2206 token_value_separator_found = FALSE;
2208 // find end of token to determine start of value
2209 for (line_ptr = token; *line_ptr; line_ptr++)
2211 // first look for an explicit token/value separator, like ':' or '='
2212 if (*line_ptr == ':' || *line_ptr == '=')
2214 *line_ptr = '\0'; // terminate token string
2215 value = line_ptr + 1; // set beginning of value
2217 token_value_separator_found = TRUE;
2223 #if ALLOW_TOKEN_VALUE_SEPARATOR_BEING_WHITESPACE
2224 // fallback: if no token/value separator found, also allow whitespaces
2225 if (!token_value_separator_found && !separator_required)
2227 for (line_ptr = token; *line_ptr; line_ptr++)
2229 if (*line_ptr == ' ' || *line_ptr == '\t')
2231 *line_ptr = '\0'; // terminate token string
2232 value = line_ptr + 1; // set beginning of value
2234 token_value_separator_found = TRUE;
2240 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2241 if (token_value_separator_found)
2243 if (!token_value_separator_warning)
2245 Debug("setup", "---");
2247 if (filename != NULL)
2249 Debug("setup", "missing token/value separator(s) in config file:");
2250 Debug("setup", "- config file: '%s'", filename);
2254 Debug("setup", "missing token/value separator(s):");
2257 token_value_separator_warning = TRUE;
2260 if (filename != NULL)
2261 Debug("setup", "- line %d: '%s'", line_nr, line_raw);
2263 Debug("setup", "- line: '%s'", line_raw);
2269 // cut trailing whitespaces from token
2270 for (line_ptr = &token[strlen(token)]; line_ptr >= token; line_ptr--)
2271 if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
2274 // cut leading whitespaces from value
2275 for (; *value; value++)
2276 if (*value != ' ' && *value != '\t')
2285 boolean getTokenValueFromSetupLine(char *line, char **token, char **value)
2287 // while the internal (old) interface does not require a token/value
2288 // separator (for downwards compatibility with existing files which
2289 // don't use them), it is mandatory for the external (new) interface
2291 return getTokenValueFromSetupLineExt(line, token, value, NULL, NULL, 0, TRUE);
2294 static boolean loadSetupFileData(void *setup_file_data, char *filename,
2295 boolean top_recursion_level, boolean is_hash)
2297 static SetupFileHash *include_filename_hash = NULL;
2298 char line[MAX_LINE_LEN], line_raw[MAX_LINE_LEN], previous_line[MAX_LINE_LEN];
2299 char *token, *value, *line_ptr;
2300 void *insert_ptr = NULL;
2301 boolean read_continued_line = FALSE;
2303 int line_nr = 0, token_count = 0, include_count = 0;
2305 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2306 token_value_separator_warning = FALSE;
2309 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2310 token_already_exists_warning = FALSE;
2313 if (!(file = openFile(filename, MODE_READ)))
2315 #if DEBUG_NO_CONFIG_FILE
2316 Debug("setup", "cannot open configuration file '%s'", filename);
2322 // use "insert pointer" to store list end for constant insertion complexity
2324 insert_ptr = setup_file_data;
2326 // on top invocation, create hash to mark included files (to prevent loops)
2327 if (top_recursion_level)
2328 include_filename_hash = newSetupFileHash();
2330 // mark this file as already included (to prevent including it again)
2331 setHashEntry(include_filename_hash, getBaseNamePtr(filename), "true");
2333 while (!checkEndOfFile(file))
2335 // read next line of input file
2336 if (!getStringFromFile(file, line, MAX_LINE_LEN))
2339 // check if line was completely read and is terminated by line break
2340 if (strlen(line) > 0 && line[strlen(line) - 1] == '\n')
2343 // cut trailing line break (this can be newline and/or carriage return)
2344 for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
2345 if ((*line_ptr == '\n' || *line_ptr == '\r') && *(line_ptr + 1) == '\0')
2348 // copy raw input line for later use (mainly debugging output)
2349 strcpy(line_raw, line);
2351 if (read_continued_line)
2353 // append new line to existing line, if there is enough space
2354 if (strlen(previous_line) + strlen(line_ptr) < MAX_LINE_LEN)
2355 strcat(previous_line, line_ptr);
2357 strcpy(line, previous_line); // copy storage buffer to line
2359 read_continued_line = FALSE;
2362 // if the last character is '\', continue at next line
2363 if (strlen(line) > 0 && line[strlen(line) - 1] == '\\')
2365 line[strlen(line) - 1] = '\0'; // cut off trailing backslash
2366 strcpy(previous_line, line); // copy line to storage buffer
2368 read_continued_line = TRUE;
2373 if (!getTokenValueFromSetupLineExt(line, &token, &value, filename,
2374 line_raw, line_nr, FALSE))
2379 if (strEqual(token, "include"))
2381 if (getHashEntry(include_filename_hash, value) == NULL)
2383 char *basepath = getBasePath(filename);
2384 char *basename = getBaseName(value);
2385 char *filename_include = getPath2(basepath, basename);
2387 loadSetupFileData(setup_file_data, filename_include, FALSE, is_hash);
2391 free(filename_include);
2397 Warn("ignoring already processed file '%s'", value);
2404 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2406 getHashEntry((SetupFileHash *)setup_file_data, token);
2408 if (old_value != NULL)
2410 if (!token_already_exists_warning)
2412 Debug("setup", "---");
2413 Debug("setup", "duplicate token(s) found in config file:");
2414 Debug("setup", "- config file: '%s'", filename);
2416 token_already_exists_warning = TRUE;
2419 Debug("setup", "- token: '%s' (in line %d)", token, line_nr);
2420 Debug("setup", " old value: '%s'", old_value);
2421 Debug("setup", " new value: '%s'", value);
2425 setHashEntry((SetupFileHash *)setup_file_data, token, value);
2429 insert_ptr = addListEntry((SetupFileList *)insert_ptr, token, value);
2439 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2440 if (token_value_separator_warning)
2441 Debug("setup", "---");
2444 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2445 if (token_already_exists_warning)
2446 Debug("setup", "---");
2449 if (token_count == 0 && include_count == 0)
2450 Warn("configuration file '%s' is empty", filename);
2452 if (top_recursion_level)
2453 freeSetupFileHash(include_filename_hash);
2458 static int compareSetupFileData(const void *object1, const void *object2)
2460 const struct ConfigInfo *entry1 = (struct ConfigInfo *)object1;
2461 const struct ConfigInfo *entry2 = (struct ConfigInfo *)object2;
2463 return strcmp(entry1->token, entry2->token);
2466 static void saveSetupFileHash(SetupFileHash *hash, char *filename)
2468 int item_count = hashtable_count(hash);
2469 int item_size = sizeof(struct ConfigInfo);
2470 struct ConfigInfo *sort_array = checked_malloc(item_count * item_size);
2474 // copy string pointers from hash to array
2475 BEGIN_HASH_ITERATION(hash, itr)
2477 sort_array[i].token = HASH_ITERATION_TOKEN(itr);
2478 sort_array[i].value = HASH_ITERATION_VALUE(itr);
2482 if (i > item_count) // should never happen
2485 END_HASH_ITERATION(hash, itr)
2487 // sort string pointers from hash in array
2488 qsort(sort_array, item_count, item_size, compareSetupFileData);
2490 if (!(file = fopen(filename, MODE_WRITE)))
2492 Warn("cannot write configuration file '%s'", filename);
2497 fprintf(file, "%s\n\n", getFormattedSetupEntry("program.version",
2498 program.version_string));
2499 for (i = 0; i < item_count; i++)
2500 fprintf(file, "%s\n", getFormattedSetupEntry(sort_array[i].token,
2501 sort_array[i].value));
2504 checked_free(sort_array);
2507 SetupFileList *loadSetupFileList(char *filename)
2509 SetupFileList *setup_file_list = newSetupFileList("", "");
2510 SetupFileList *first_valid_list_entry;
2512 if (!loadSetupFileData(setup_file_list, filename, TRUE, FALSE))
2514 freeSetupFileList(setup_file_list);
2519 first_valid_list_entry = setup_file_list->next;
2521 // free empty list header
2522 setup_file_list->next = NULL;
2523 freeSetupFileList(setup_file_list);
2525 return first_valid_list_entry;
2528 SetupFileHash *loadSetupFileHash(char *filename)
2530 SetupFileHash *setup_file_hash = newSetupFileHash();
2532 if (!loadSetupFileData(setup_file_hash, filename, TRUE, TRUE))
2534 freeSetupFileHash(setup_file_hash);
2539 return setup_file_hash;
2543 // ============================================================================
2545 // ============================================================================
2547 #define TOKEN_STR_LAST_LEVEL_SERIES "last_level_series"
2548 #define TOKEN_STR_LAST_PLAYED_LEVEL "last_played_level"
2549 #define TOKEN_STR_HANDICAP_LEVEL "handicap_level"
2550 #define TOKEN_STR_LAST_USER "last_user"
2552 // level directory info
2553 #define LEVELINFO_TOKEN_IDENTIFIER 0
2554 #define LEVELINFO_TOKEN_NAME 1
2555 #define LEVELINFO_TOKEN_NAME_SORTING 2
2556 #define LEVELINFO_TOKEN_AUTHOR 3
2557 #define LEVELINFO_TOKEN_YEAR 4
2558 #define LEVELINFO_TOKEN_PROGRAM_TITLE 5
2559 #define LEVELINFO_TOKEN_PROGRAM_COPYRIGHT 6
2560 #define LEVELINFO_TOKEN_PROGRAM_COMPANY 7
2561 #define LEVELINFO_TOKEN_IMPORTED_FROM 8
2562 #define LEVELINFO_TOKEN_IMPORTED_BY 9
2563 #define LEVELINFO_TOKEN_TESTED_BY 10
2564 #define LEVELINFO_TOKEN_LEVELS 11
2565 #define LEVELINFO_TOKEN_FIRST_LEVEL 12
2566 #define LEVELINFO_TOKEN_SORT_PRIORITY 13
2567 #define LEVELINFO_TOKEN_LATEST_ENGINE 14
2568 #define LEVELINFO_TOKEN_LEVEL_GROUP 15
2569 #define LEVELINFO_TOKEN_READONLY 16
2570 #define LEVELINFO_TOKEN_GRAPHICS_SET_ECS 17
2571 #define LEVELINFO_TOKEN_GRAPHICS_SET_AGA 18
2572 #define LEVELINFO_TOKEN_GRAPHICS_SET 19
2573 #define LEVELINFO_TOKEN_SOUNDS_SET_DEFAULT 20
2574 #define LEVELINFO_TOKEN_SOUNDS_SET_LOWPASS 21
2575 #define LEVELINFO_TOKEN_SOUNDS_SET 22
2576 #define LEVELINFO_TOKEN_MUSIC_SET 23
2577 #define LEVELINFO_TOKEN_FILENAME 24
2578 #define LEVELINFO_TOKEN_FILETYPE 25
2579 #define LEVELINFO_TOKEN_SPECIAL_FLAGS 26
2580 #define LEVELINFO_TOKEN_HANDICAP 27
2581 #define LEVELINFO_TOKEN_SKIP_LEVELS 28
2582 #define LEVELINFO_TOKEN_USE_EMC_TILES 29
2584 #define NUM_LEVELINFO_TOKENS 30
2586 static LevelDirTree ldi;
2588 static struct TokenInfo levelinfo_tokens[] =
2590 // level directory info
2591 { TYPE_STRING, &ldi.identifier, "identifier" },
2592 { TYPE_STRING, &ldi.name, "name" },
2593 { TYPE_STRING, &ldi.name_sorting, "name_sorting" },
2594 { TYPE_STRING, &ldi.author, "author" },
2595 { TYPE_STRING, &ldi.year, "year" },
2596 { TYPE_STRING, &ldi.program_title, "program_title" },
2597 { TYPE_STRING, &ldi.program_copyright, "program_copyright" },
2598 { TYPE_STRING, &ldi.program_company, "program_company" },
2599 { TYPE_STRING, &ldi.imported_from, "imported_from" },
2600 { TYPE_STRING, &ldi.imported_by, "imported_by" },
2601 { TYPE_STRING, &ldi.tested_by, "tested_by" },
2602 { TYPE_INTEGER, &ldi.levels, "levels" },
2603 { TYPE_INTEGER, &ldi.first_level, "first_level" },
2604 { TYPE_INTEGER, &ldi.sort_priority, "sort_priority" },
2605 { TYPE_BOOLEAN, &ldi.latest_engine, "latest_engine" },
2606 { TYPE_BOOLEAN, &ldi.level_group, "level_group" },
2607 { TYPE_BOOLEAN, &ldi.readonly, "readonly" },
2608 { TYPE_STRING, &ldi.graphics_set_ecs, "graphics_set.ecs" },
2609 { TYPE_STRING, &ldi.graphics_set_aga, "graphics_set.aga" },
2610 { TYPE_STRING, &ldi.graphics_set, "graphics_set" },
2611 { TYPE_STRING, &ldi.sounds_set_default,"sounds_set.default" },
2612 { TYPE_STRING, &ldi.sounds_set_lowpass,"sounds_set.lowpass" },
2613 { TYPE_STRING, &ldi.sounds_set, "sounds_set" },
2614 { TYPE_STRING, &ldi.music_set, "music_set" },
2615 { TYPE_STRING, &ldi.level_filename, "filename" },
2616 { TYPE_STRING, &ldi.level_filetype, "filetype" },
2617 { TYPE_STRING, &ldi.special_flags, "special_flags" },
2618 { TYPE_BOOLEAN, &ldi.handicap, "handicap" },
2619 { TYPE_BOOLEAN, &ldi.skip_levels, "skip_levels" },
2620 { TYPE_BOOLEAN, &ldi.use_emc_tiles, "use_emc_tiles" }
2623 static struct TokenInfo artworkinfo_tokens[] =
2625 // artwork directory info
2626 { TYPE_STRING, &ldi.identifier, "identifier" },
2627 { TYPE_STRING, &ldi.subdir, "subdir" },
2628 { TYPE_STRING, &ldi.name, "name" },
2629 { TYPE_STRING, &ldi.name_sorting, "name_sorting" },
2630 { TYPE_STRING, &ldi.author, "author" },
2631 { TYPE_STRING, &ldi.program_title, "program_title" },
2632 { TYPE_STRING, &ldi.program_copyright, "program_copyright" },
2633 { TYPE_STRING, &ldi.program_company, "program_company" },
2634 { TYPE_INTEGER, &ldi.sort_priority, "sort_priority" },
2635 { TYPE_STRING, &ldi.basepath, "basepath" },
2636 { TYPE_STRING, &ldi.fullpath, "fullpath" },
2637 { TYPE_BOOLEAN, &ldi.in_user_dir, "in_user_dir" },
2638 { TYPE_STRING, &ldi.class_desc, "class_desc" },
2643 static char *optional_tokens[] =
2646 "program_copyright",
2652 static void setTreeInfoToDefaults(TreeInfo *ti, int type)
2656 ti->node_top = (ti->type == TREE_TYPE_LEVEL_DIR ? &leveldir_first :
2657 ti->type == TREE_TYPE_GRAPHICS_DIR ? &artwork.gfx_first :
2658 ti->type == TREE_TYPE_SOUNDS_DIR ? &artwork.snd_first :
2659 ti->type == TREE_TYPE_MUSIC_DIR ? &artwork.mus_first :
2662 ti->node_parent = NULL;
2663 ti->node_group = NULL;
2670 ti->fullpath = NULL;
2671 ti->basepath = NULL;
2672 ti->identifier = NULL;
2673 ti->name = getStringCopy(ANONYMOUS_NAME);
2674 ti->name_sorting = NULL;
2675 ti->author = getStringCopy(ANONYMOUS_NAME);
2678 ti->program_title = NULL;
2679 ti->program_copyright = NULL;
2680 ti->program_company = NULL;
2682 ti->sort_priority = LEVELCLASS_UNDEFINED; // default: least priority
2683 ti->latest_engine = FALSE; // default: get from level
2684 ti->parent_link = FALSE;
2685 ti->is_copy = FALSE;
2686 ti->in_user_dir = FALSE;
2687 ti->user_defined = FALSE;
2689 ti->class_desc = NULL;
2691 ti->infotext = getStringCopy(TREE_INFOTEXT(ti->type));
2693 if (ti->type == TREE_TYPE_LEVEL_DIR)
2695 ti->imported_from = NULL;
2696 ti->imported_by = NULL;
2697 ti->tested_by = NULL;
2699 ti->graphics_set_ecs = NULL;
2700 ti->graphics_set_aga = NULL;
2701 ti->graphics_set = NULL;
2702 ti->sounds_set_default = NULL;
2703 ti->sounds_set_lowpass = NULL;
2704 ti->sounds_set = NULL;
2705 ti->music_set = NULL;
2706 ti->graphics_path = getStringCopy(UNDEFINED_FILENAME);
2707 ti->sounds_path = getStringCopy(UNDEFINED_FILENAME);
2708 ti->music_path = getStringCopy(UNDEFINED_FILENAME);
2710 ti->level_filename = NULL;
2711 ti->level_filetype = NULL;
2713 ti->special_flags = NULL;
2716 ti->first_level = 0;
2718 ti->level_group = FALSE;
2719 ti->handicap_level = 0;
2720 ti->readonly = TRUE;
2721 ti->handicap = TRUE;
2722 ti->skip_levels = FALSE;
2724 ti->use_emc_tiles = FALSE;
2728 static void setTreeInfoToDefaultsFromParent(TreeInfo *ti, TreeInfo *parent)
2732 Warn("setTreeInfoToDefaultsFromParent(): parent == NULL");
2734 setTreeInfoToDefaults(ti, TREE_TYPE_UNDEFINED);
2739 // copy all values from the parent structure
2741 ti->type = parent->type;
2743 ti->node_top = parent->node_top;
2744 ti->node_parent = parent;
2745 ti->node_group = NULL;
2752 ti->fullpath = NULL;
2753 ti->basepath = NULL;
2754 ti->identifier = NULL;
2755 ti->name = getStringCopy(ANONYMOUS_NAME);
2756 ti->name_sorting = NULL;
2757 ti->author = getStringCopy(parent->author);
2758 ti->year = getStringCopy(parent->year);
2760 ti->program_title = getStringCopy(parent->program_title);
2761 ti->program_copyright = getStringCopy(parent->program_copyright);
2762 ti->program_company = getStringCopy(parent->program_company);
2764 ti->sort_priority = parent->sort_priority;
2765 ti->latest_engine = parent->latest_engine;
2766 ti->parent_link = FALSE;
2767 ti->is_copy = FALSE;
2768 ti->in_user_dir = parent->in_user_dir;
2769 ti->user_defined = parent->user_defined;
2770 ti->color = parent->color;
2771 ti->class_desc = getStringCopy(parent->class_desc);
2773 ti->infotext = getStringCopy(parent->infotext);
2775 if (ti->type == TREE_TYPE_LEVEL_DIR)
2777 ti->imported_from = getStringCopy(parent->imported_from);
2778 ti->imported_by = getStringCopy(parent->imported_by);
2779 ti->tested_by = getStringCopy(parent->tested_by);
2781 ti->graphics_set_ecs = getStringCopy(parent->graphics_set_ecs);
2782 ti->graphics_set_aga = getStringCopy(parent->graphics_set_aga);
2783 ti->graphics_set = getStringCopy(parent->graphics_set);
2784 ti->sounds_set_default = getStringCopy(parent->sounds_set_default);
2785 ti->sounds_set_lowpass = getStringCopy(parent->sounds_set_lowpass);
2786 ti->sounds_set = getStringCopy(parent->sounds_set);
2787 ti->music_set = getStringCopy(parent->music_set);
2788 ti->graphics_path = getStringCopy(UNDEFINED_FILENAME);
2789 ti->sounds_path = getStringCopy(UNDEFINED_FILENAME);
2790 ti->music_path = getStringCopy(UNDEFINED_FILENAME);
2792 ti->level_filename = getStringCopy(parent->level_filename);
2793 ti->level_filetype = getStringCopy(parent->level_filetype);
2795 ti->special_flags = getStringCopy(parent->special_flags);
2797 ti->levels = parent->levels;
2798 ti->first_level = parent->first_level;
2799 ti->last_level = parent->last_level;
2800 ti->level_group = FALSE;
2801 ti->handicap_level = parent->handicap_level;
2802 ti->readonly = parent->readonly;
2803 ti->handicap = parent->handicap;
2804 ti->skip_levels = parent->skip_levels;
2806 ti->use_emc_tiles = parent->use_emc_tiles;
2810 static TreeInfo *getTreeInfoCopy(TreeInfo *ti)
2812 TreeInfo *ti_copy = newTreeInfo();
2814 // copy all values from the original structure
2816 ti_copy->type = ti->type;
2818 ti_copy->node_top = ti->node_top;
2819 ti_copy->node_parent = ti->node_parent;
2820 ti_copy->node_group = ti->node_group;
2821 ti_copy->next = ti->next;
2823 ti_copy->cl_first = ti->cl_first;
2824 ti_copy->cl_cursor = ti->cl_cursor;
2826 ti_copy->subdir = getStringCopy(ti->subdir);
2827 ti_copy->fullpath = getStringCopy(ti->fullpath);
2828 ti_copy->basepath = getStringCopy(ti->basepath);
2829 ti_copy->identifier = getStringCopy(ti->identifier);
2830 ti_copy->name = getStringCopy(ti->name);
2831 ti_copy->name_sorting = getStringCopy(ti->name_sorting);
2832 ti_copy->author = getStringCopy(ti->author);
2833 ti_copy->year = getStringCopy(ti->year);
2835 ti_copy->program_title = getStringCopy(ti->program_title);
2836 ti_copy->program_copyright = getStringCopy(ti->program_copyright);
2837 ti_copy->program_company = getStringCopy(ti->program_company);
2839 ti_copy->imported_from = getStringCopy(ti->imported_from);
2840 ti_copy->imported_by = getStringCopy(ti->imported_by);
2841 ti_copy->tested_by = getStringCopy(ti->tested_by);
2843 ti_copy->graphics_set_ecs = getStringCopy(ti->graphics_set_ecs);
2844 ti_copy->graphics_set_aga = getStringCopy(ti->graphics_set_aga);
2845 ti_copy->graphics_set = getStringCopy(ti->graphics_set);
2846 ti_copy->sounds_set_default = getStringCopy(ti->sounds_set_default);
2847 ti_copy->sounds_set_lowpass = getStringCopy(ti->sounds_set_lowpass);
2848 ti_copy->sounds_set = getStringCopy(ti->sounds_set);
2849 ti_copy->music_set = getStringCopy(ti->music_set);
2850 ti_copy->graphics_path = getStringCopy(ti->graphics_path);
2851 ti_copy->sounds_path = getStringCopy(ti->sounds_path);
2852 ti_copy->music_path = getStringCopy(ti->music_path);
2854 ti_copy->level_filename = getStringCopy(ti->level_filename);
2855 ti_copy->level_filetype = getStringCopy(ti->level_filetype);
2857 ti_copy->special_flags = getStringCopy(ti->special_flags);
2859 ti_copy->levels = ti->levels;
2860 ti_copy->first_level = ti->first_level;
2861 ti_copy->last_level = ti->last_level;
2862 ti_copy->sort_priority = ti->sort_priority;
2864 ti_copy->latest_engine = ti->latest_engine;
2866 ti_copy->level_group = ti->level_group;
2867 ti_copy->parent_link = ti->parent_link;
2868 ti_copy->is_copy = ti->is_copy;
2869 ti_copy->in_user_dir = ti->in_user_dir;
2870 ti_copy->user_defined = ti->user_defined;
2871 ti_copy->readonly = ti->readonly;
2872 ti_copy->handicap = ti->handicap;
2873 ti_copy->skip_levels = ti->skip_levels;
2875 ti_copy->use_emc_tiles = ti->use_emc_tiles;
2877 ti_copy->color = ti->color;
2878 ti_copy->class_desc = getStringCopy(ti->class_desc);
2879 ti_copy->handicap_level = ti->handicap_level;
2881 ti_copy->infotext = getStringCopy(ti->infotext);
2886 void freeTreeInfo(TreeInfo *ti)
2891 checked_free(ti->subdir);
2892 checked_free(ti->fullpath);
2893 checked_free(ti->basepath);
2894 checked_free(ti->identifier);
2896 checked_free(ti->name);
2897 checked_free(ti->name_sorting);
2898 checked_free(ti->author);
2899 checked_free(ti->year);
2901 checked_free(ti->program_title);
2902 checked_free(ti->program_copyright);
2903 checked_free(ti->program_company);
2905 checked_free(ti->class_desc);
2907 checked_free(ti->infotext);
2909 if (ti->type == TREE_TYPE_LEVEL_DIR)
2911 checked_free(ti->imported_from);
2912 checked_free(ti->imported_by);
2913 checked_free(ti->tested_by);
2915 checked_free(ti->graphics_set_ecs);
2916 checked_free(ti->graphics_set_aga);
2917 checked_free(ti->graphics_set);
2918 checked_free(ti->sounds_set_default);
2919 checked_free(ti->sounds_set_lowpass);
2920 checked_free(ti->sounds_set);
2921 checked_free(ti->music_set);
2923 checked_free(ti->graphics_path);
2924 checked_free(ti->sounds_path);
2925 checked_free(ti->music_path);
2927 checked_free(ti->level_filename);
2928 checked_free(ti->level_filetype);
2930 checked_free(ti->special_flags);
2933 // recursively free child node
2935 freeTreeInfo(ti->node_group);
2937 // recursively free next node
2939 freeTreeInfo(ti->next);
2944 void setSetupInfo(struct TokenInfo *token_info,
2945 int token_nr, char *token_value)
2947 int token_type = token_info[token_nr].type;
2948 void *setup_value = token_info[token_nr].value;
2950 if (token_value == NULL)
2953 // set setup field to corresponding token value
2958 *(boolean *)setup_value = get_boolean_from_string(token_value);
2962 *(int *)setup_value = get_switch3_from_string(token_value);
2966 *(Key *)setup_value = getKeyFromKeyName(token_value);
2970 *(Key *)setup_value = getKeyFromX11KeyName(token_value);
2974 *(int *)setup_value = get_integer_from_string(token_value);
2978 checked_free(*(char **)setup_value);
2979 *(char **)setup_value = getStringCopy(token_value);
2983 *(int *)setup_value = get_player_nr_from_string(token_value);
2991 static int compareTreeInfoEntries(const void *object1, const void *object2)
2993 const TreeInfo *entry1 = *((TreeInfo **)object1);
2994 const TreeInfo *entry2 = *((TreeInfo **)object2);
2995 int tree_sorting1 = TREE_SORTING(entry1);
2996 int tree_sorting2 = TREE_SORTING(entry2);
2998 if (tree_sorting1 != tree_sorting2)
2999 return (tree_sorting1 - tree_sorting2);
3001 return strcasecmp(entry1->name_sorting, entry2->name_sorting);
3004 static TreeInfo *createParentTreeInfoNode(TreeInfo *node_parent)
3008 if (node_parent == NULL)
3011 ti_new = newTreeInfo();
3012 setTreeInfoToDefaults(ti_new, node_parent->type);
3014 ti_new->node_parent = node_parent;
3015 ti_new->parent_link = TRUE;
3017 setString(&ti_new->identifier, node_parent->identifier);
3018 setString(&ti_new->name, BACKLINK_TEXT_PARENT);
3019 setString(&ti_new->name_sorting, ti_new->name);
3021 setString(&ti_new->subdir, STRING_PARENT_DIRECTORY);
3022 setString(&ti_new->fullpath, node_parent->fullpath);
3024 ti_new->sort_priority = LEVELCLASS_PARENT;
3025 ti_new->latest_engine = node_parent->latest_engine;
3027 setString(&ti_new->class_desc, getLevelClassDescription(ti_new));
3029 pushTreeInfo(&node_parent->node_group, ti_new);
3034 static TreeInfo *createTopTreeInfoNode(TreeInfo *node_first)
3036 if (node_first == NULL)
3039 TreeInfo *ti_new = newTreeInfo();
3040 int type = node_first->type;
3042 setTreeInfoToDefaults(ti_new, type);
3044 ti_new->node_parent = NULL;
3045 ti_new->parent_link = FALSE;
3047 setString(&ti_new->identifier, "top_tree_node");
3048 setString(&ti_new->name, TREE_INFOTEXT(type));
3049 setString(&ti_new->name_sorting, ti_new->name);
3051 setString(&ti_new->subdir, STRING_TOP_DIRECTORY);
3052 setString(&ti_new->fullpath, ".");
3054 ti_new->sort_priority = LEVELCLASS_TOP;
3055 ti_new->latest_engine = node_first->latest_engine;
3057 setString(&ti_new->class_desc, TREE_INFOTEXT(type));
3059 ti_new->node_group = node_first;
3060 ti_new->level_group = TRUE;
3062 TreeInfo *ti_new2 = createParentTreeInfoNode(ti_new);
3064 setString(&ti_new2->name, TREE_BACKLINK_TEXT(type));
3065 setString(&ti_new2->name_sorting, ti_new2->name);
3070 static void setTreeInfoParentNodes(TreeInfo *node, TreeInfo *node_parent)
3074 if (node->node_group)
3075 setTreeInfoParentNodes(node->node_group, node);
3077 node->node_parent = node_parent;
3084 // ----------------------------------------------------------------------------
3085 // functions for handling level and custom artwork info cache
3086 // ----------------------------------------------------------------------------
3088 static void LoadArtworkInfoCache(void)
3090 InitCacheDirectory();
3092 if (artworkinfo_cache_old == NULL)
3094 char *filename = getPath2(getCacheDir(), ARTWORKINFO_CACHE_FILE);
3096 // try to load artwork info hash from already existing cache file
3097 artworkinfo_cache_old = loadSetupFileHash(filename);
3099 // try to get program version that artwork info cache was written with
3100 char *version = getHashEntry(artworkinfo_cache_old, "program.version");
3102 // check program version of artwork info cache against current version
3103 if (!strEqual(version, program.version_string))
3105 freeSetupFileHash(artworkinfo_cache_old);
3107 artworkinfo_cache_old = NULL;
3110 // if no artwork info cache file was found, start with empty hash
3111 if (artworkinfo_cache_old == NULL)
3112 artworkinfo_cache_old = newSetupFileHash();
3117 if (artworkinfo_cache_new == NULL)
3118 artworkinfo_cache_new = newSetupFileHash();
3120 update_artworkinfo_cache = FALSE;
3123 static void SaveArtworkInfoCache(void)
3125 if (!update_artworkinfo_cache)
3128 char *filename = getPath2(getCacheDir(), ARTWORKINFO_CACHE_FILE);
3130 InitCacheDirectory();
3132 saveSetupFileHash(artworkinfo_cache_new, filename);
3137 static char *getCacheTokenPrefix(char *prefix1, char *prefix2)
3139 static char *prefix = NULL;
3141 checked_free(prefix);
3143 prefix = getStringCat2WithSeparator(prefix1, prefix2, ".");
3148 // (identical to above function, but separate string buffer needed -- nasty)
3149 static char *getCacheToken(char *prefix, char *suffix)
3151 static char *token = NULL;
3153 checked_free(token);
3155 token = getStringCat2WithSeparator(prefix, suffix, ".");
3160 static char *getFileTimestampString(char *filename)
3162 return getStringCopy(i_to_a(getFileTimestampEpochSeconds(filename)));
3165 static boolean modifiedFileTimestamp(char *filename, char *timestamp_string)
3167 struct stat file_status;
3169 if (timestamp_string == NULL)
3172 if (!fileExists(filename)) // file does not exist
3173 return (atoi(timestamp_string) != 0);
3175 if (stat(filename, &file_status) != 0) // cannot stat file
3178 return (file_status.st_mtime != atoi(timestamp_string));
3181 static TreeInfo *getArtworkInfoCacheEntry(LevelDirTree *level_node, int type)
3183 char *identifier = level_node->subdir;
3184 char *type_string = ARTWORK_DIRECTORY(type);
3185 char *token_prefix = getCacheTokenPrefix(type_string, identifier);
3186 char *token_main = getCacheToken(token_prefix, "CACHED");
3187 char *cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
3188 boolean cached = (cache_entry != NULL && strEqual(cache_entry, "true"));
3189 TreeInfo *artwork_info = NULL;
3191 if (!use_artworkinfo_cache)
3194 if (optional_tokens_hash == NULL)
3198 // create hash from list of optional tokens (for quick access)
3199 optional_tokens_hash = newSetupFileHash();
3200 for (i = 0; optional_tokens[i] != NULL; i++)
3201 setHashEntry(optional_tokens_hash, optional_tokens[i], "");
3208 artwork_info = newTreeInfo();
3209 setTreeInfoToDefaults(artwork_info, type);
3211 // set all structure fields according to the token/value pairs
3212 ldi = *artwork_info;
3213 for (i = 0; artworkinfo_tokens[i].type != -1; i++)
3215 char *token_suffix = artworkinfo_tokens[i].text;
3216 char *token = getCacheToken(token_prefix, token_suffix);
3217 char *value = getHashEntry(artworkinfo_cache_old, token);
3219 (getHashEntry(optional_tokens_hash, token_suffix) != NULL);
3221 setSetupInfo(artworkinfo_tokens, i, value);
3223 // check if cache entry for this item is mandatory, but missing
3224 if (value == NULL && !optional)
3226 Warn("missing cache entry '%s'", token);
3232 *artwork_info = ldi;
3237 char *filename_levelinfo = getPath2(getLevelDirFromTreeInfo(level_node),
3238 LEVELINFO_FILENAME);
3239 char *filename_artworkinfo = getPath2(getSetupArtworkDir(artwork_info),
3240 ARTWORKINFO_FILENAME(type));
3242 // check if corresponding "levelinfo.conf" file has changed
3243 token_main = getCacheToken(token_prefix, "TIMESTAMP_LEVELINFO");
3244 cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
3246 if (modifiedFileTimestamp(filename_levelinfo, cache_entry))
3249 // check if corresponding "<artworkinfo>.conf" file has changed
3250 token_main = getCacheToken(token_prefix, "TIMESTAMP_ARTWORKINFO");
3251 cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
3253 if (modifiedFileTimestamp(filename_artworkinfo, cache_entry))
3256 checked_free(filename_levelinfo);
3257 checked_free(filename_artworkinfo);
3260 if (!cached && artwork_info != NULL)
3262 freeTreeInfo(artwork_info);
3267 return artwork_info;
3270 static void setArtworkInfoCacheEntry(TreeInfo *artwork_info,
3271 LevelDirTree *level_node, int type)
3273 char *identifier = level_node->subdir;
3274 char *type_string = ARTWORK_DIRECTORY(type);
3275 char *token_prefix = getCacheTokenPrefix(type_string, identifier);
3276 char *token_main = getCacheToken(token_prefix, "CACHED");
3277 boolean set_cache_timestamps = TRUE;
3280 setHashEntry(artworkinfo_cache_new, token_main, "true");
3282 if (set_cache_timestamps)
3284 char *filename_levelinfo = getPath2(getLevelDirFromTreeInfo(level_node),
3285 LEVELINFO_FILENAME);
3286 char *filename_artworkinfo = getPath2(getSetupArtworkDir(artwork_info),
3287 ARTWORKINFO_FILENAME(type));
3288 char *timestamp_levelinfo = getFileTimestampString(filename_levelinfo);
3289 char *timestamp_artworkinfo = getFileTimestampString(filename_artworkinfo);
3291 token_main = getCacheToken(token_prefix, "TIMESTAMP_LEVELINFO");
3292 setHashEntry(artworkinfo_cache_new, token_main, timestamp_levelinfo);
3294 token_main = getCacheToken(token_prefix, "TIMESTAMP_ARTWORKINFO");
3295 setHashEntry(artworkinfo_cache_new, token_main, timestamp_artworkinfo);
3297 checked_free(filename_levelinfo);
3298 checked_free(filename_artworkinfo);
3299 checked_free(timestamp_levelinfo);
3300 checked_free(timestamp_artworkinfo);
3303 ldi = *artwork_info;
3304 for (i = 0; artworkinfo_tokens[i].type != -1; i++)
3306 char *token = getCacheToken(token_prefix, artworkinfo_tokens[i].text);
3307 char *value = getSetupValue(artworkinfo_tokens[i].type,
3308 artworkinfo_tokens[i].value);
3310 setHashEntry(artworkinfo_cache_new, token, value);
3315 // ----------------------------------------------------------------------------
3316 // functions for loading level info and custom artwork info
3317 // ----------------------------------------------------------------------------
3319 int GetZipFileTreeType(char *zip_filename)
3321 static char *top_dir_path = NULL;
3322 static char *top_dir_conf_filename[NUM_BASE_TREE_TYPES] = { NULL };
3323 static char *conf_basename[NUM_BASE_TREE_TYPES] =
3325 GRAPHICSINFO_FILENAME,
3326 SOUNDSINFO_FILENAME,
3332 checked_free(top_dir_path);
3333 top_dir_path = NULL;
3335 for (j = 0; j < NUM_BASE_TREE_TYPES; j++)
3337 checked_free(top_dir_conf_filename[j]);
3338 top_dir_conf_filename[j] = NULL;
3341 char **zip_entries = zip_list(zip_filename);
3343 // check if zip file successfully opened
3344 if (zip_entries == NULL || zip_entries[0] == NULL)
3345 return TREE_TYPE_UNDEFINED;
3347 // first zip file entry is expected to be top level directory
3348 char *top_dir = zip_entries[0];
3350 // check if valid top level directory found in zip file
3351 if (!strSuffix(top_dir, "/"))
3352 return TREE_TYPE_UNDEFINED;
3354 // get filenames of valid configuration files in top level directory
3355 for (j = 0; j < NUM_BASE_TREE_TYPES; j++)
3356 top_dir_conf_filename[j] = getStringCat2(top_dir, conf_basename[j]);
3358 int tree_type = TREE_TYPE_UNDEFINED;
3361 while (zip_entries[e] != NULL)
3363 // check if every zip file entry is below top level directory
3364 if (!strPrefix(zip_entries[e], top_dir))
3365 return TREE_TYPE_UNDEFINED;
3367 // check if this zip file entry is a valid configuration filename
3368 for (j = 0; j < NUM_BASE_TREE_TYPES; j++)
3370 if (strEqual(zip_entries[e], top_dir_conf_filename[j]))
3372 // only exactly one valid configuration file allowed
3373 if (tree_type != TREE_TYPE_UNDEFINED)
3374 return TREE_TYPE_UNDEFINED;
3386 static boolean CheckZipFileForDirectory(char *zip_filename, char *directory,
3389 static char *top_dir_path = NULL;
3390 static char *top_dir_conf_filename = NULL;
3392 checked_free(top_dir_path);
3393 checked_free(top_dir_conf_filename);
3395 top_dir_path = NULL;
3396 top_dir_conf_filename = NULL;
3398 char *conf_basename = (tree_type == TREE_TYPE_LEVEL_DIR ? LEVELINFO_FILENAME :
3399 ARTWORKINFO_FILENAME(tree_type));
3401 // check if valid configuration filename determined
3402 if (conf_basename == NULL || strEqual(conf_basename, ""))
3405 char **zip_entries = zip_list(zip_filename);
3407 // check if zip file successfully opened
3408 if (zip_entries == NULL || zip_entries[0] == NULL)
3411 // first zip file entry is expected to be top level directory
3412 char *top_dir = zip_entries[0];
3414 // check if valid top level directory found in zip file
3415 if (!strSuffix(top_dir, "/"))
3418 // get path of extracted top level directory
3419 top_dir_path = getPath2(directory, top_dir);
3421 // remove trailing directory separator from top level directory path
3422 // (required to be able to check for file and directory in next step)
3423 top_dir_path[strlen(top_dir_path) - 1] = '\0';
3425 // check if zip file's top level directory already exists in target directory
3426 if (fileExists(top_dir_path)) // (checks for file and directory)
3429 // get filename of configuration file in top level directory
3430 top_dir_conf_filename = getStringCat2(top_dir, conf_basename);
3432 boolean found_top_dir_conf_filename = FALSE;
3435 while (zip_entries[i] != NULL)
3437 // check if every zip file entry is below top level directory
3438 if (!strPrefix(zip_entries[i], top_dir))
3441 // check if this zip file entry is the configuration filename
3442 if (strEqual(zip_entries[i], top_dir_conf_filename))
3443 found_top_dir_conf_filename = TRUE;
3448 // check if valid configuration filename was found in zip file
3449 if (!found_top_dir_conf_filename)
3455 char *ExtractZipFileIntoDirectory(char *zip_filename, char *directory,
3458 boolean zip_file_valid = CheckZipFileForDirectory(zip_filename, directory,
3461 if (!zip_file_valid)
3463 Warn("zip file '%s' rejected!", zip_filename);
3468 char **zip_entries = zip_extract(zip_filename, directory);
3470 if (zip_entries == NULL)
3472 Warn("zip file '%s' could not be extracted!", zip_filename);
3477 Info("zip file '%s' successfully extracted!", zip_filename);
3479 // first zip file entry contains top level directory
3480 char *top_dir = zip_entries[0];
3482 // remove trailing directory separator from top level directory
3483 top_dir[strlen(top_dir) - 1] = '\0';
3488 static void ProcessZipFilesInDirectory(char *directory, int tree_type)
3491 DirectoryEntry *dir_entry;
3493 if ((dir = openDirectory(directory)) == NULL)
3495 // display error if directory is main "options.graphics_directory" etc.
3496 if (tree_type == TREE_TYPE_LEVEL_DIR ||
3497 directory == OPTIONS_ARTWORK_DIRECTORY(tree_type))
3498 Warn("cannot read directory '%s'", directory);
3503 while ((dir_entry = readDirectory(dir)) != NULL) // loop all entries
3505 // skip non-zip files (and also directories with zip extension)
3506 if (!strSuffixLower(dir_entry->basename, ".zip") || dir_entry->is_directory)
3509 char *zip_filename = getPath2(directory, dir_entry->basename);
3510 char *zip_filename_extracted = getStringCat2(zip_filename, ".extracted");
3511 char *zip_filename_rejected = getStringCat2(zip_filename, ".rejected");
3513 // check if zip file hasn't already been extracted or rejected
3514 if (!fileExists(zip_filename_extracted) &&
3515 !fileExists(zip_filename_rejected))
3517 char *top_dir = ExtractZipFileIntoDirectory(zip_filename, directory,
3519 char *marker_filename = (top_dir != NULL ? zip_filename_extracted :
3520 zip_filename_rejected);
3523 // create empty file to mark zip file as extracted or rejected
3524 if ((marker_file = fopen(marker_filename, MODE_WRITE)))
3525 fclose(marker_file);
3528 free(zip_filename_extracted);
3529 free(zip_filename_rejected);
3533 closeDirectory(dir);
3536 // forward declaration for recursive call by "LoadLevelInfoFromLevelDir()"
3537 static void LoadLevelInfoFromLevelDir(TreeInfo **, TreeInfo *, char *);
3539 static boolean LoadLevelInfoFromLevelConf(TreeInfo **node_first,
3540 TreeInfo *node_parent,
3541 char *level_directory,
3542 char *directory_name)
3544 char *directory_path = getPath2(level_directory, directory_name);
3545 char *filename = getPath2(directory_path, LEVELINFO_FILENAME);
3546 SetupFileHash *setup_file_hash;
3547 LevelDirTree *leveldir_new = NULL;
3550 // unless debugging, silently ignore directories without "levelinfo.conf"
3551 if (!options.debug && !fileExists(filename))
3553 free(directory_path);
3559 setup_file_hash = loadSetupFileHash(filename);
3561 if (setup_file_hash == NULL)
3563 #if DEBUG_NO_CONFIG_FILE
3564 Debug("setup", "ignoring level directory '%s'", directory_path);
3567 free(directory_path);
3573 leveldir_new = newTreeInfo();
3576 setTreeInfoToDefaultsFromParent(leveldir_new, node_parent);
3578 setTreeInfoToDefaults(leveldir_new, TREE_TYPE_LEVEL_DIR);
3580 leveldir_new->subdir = getStringCopy(directory_name);
3582 // set all structure fields according to the token/value pairs
3583 ldi = *leveldir_new;
3584 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
3585 setSetupInfo(levelinfo_tokens, i,
3586 getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
3587 *leveldir_new = ldi;
3589 if (strEqual(leveldir_new->name, ANONYMOUS_NAME))
3590 setString(&leveldir_new->name, leveldir_new->subdir);
3592 if (leveldir_new->identifier == NULL)
3593 leveldir_new->identifier = getStringCopy(leveldir_new->subdir);
3595 if (leveldir_new->name_sorting == NULL)
3596 leveldir_new->name_sorting = getStringCopy(leveldir_new->name);
3598 if (node_parent == NULL) // top level group
3600 leveldir_new->basepath = getStringCopy(level_directory);
3601 leveldir_new->fullpath = getStringCopy(leveldir_new->subdir);
3603 else // sub level group
3605 leveldir_new->basepath = getStringCopy(node_parent->basepath);
3606 leveldir_new->fullpath = getPath2(node_parent->fullpath, directory_name);
3609 leveldir_new->last_level =
3610 leveldir_new->first_level + leveldir_new->levels - 1;
3612 leveldir_new->in_user_dir =
3613 (!strEqual(leveldir_new->basepath, options.level_directory));
3615 // adjust some settings if user's private level directory was detected
3616 if (leveldir_new->sort_priority == LEVELCLASS_UNDEFINED &&
3617 leveldir_new->in_user_dir &&
3618 (strEqual(leveldir_new->subdir, getLoginName()) ||
3619 strEqual(leveldir_new->name, getLoginName()) ||
3620 strEqual(leveldir_new->author, getRealName())))
3622 leveldir_new->sort_priority = LEVELCLASS_PRIVATE_START;
3623 leveldir_new->readonly = FALSE;
3626 leveldir_new->user_defined =
3627 (leveldir_new->in_user_dir && IS_LEVELCLASS_PRIVATE(leveldir_new));
3629 setString(&leveldir_new->class_desc, getLevelClassDescription(leveldir_new));
3631 leveldir_new->handicap_level = // set handicap to default value
3632 (leveldir_new->user_defined || !leveldir_new->handicap ?
3633 leveldir_new->last_level : leveldir_new->first_level);
3635 DrawInitTextItem(leveldir_new->name);
3637 pushTreeInfo(node_first, leveldir_new);
3639 freeSetupFileHash(setup_file_hash);
3641 if (leveldir_new->level_group)
3643 // create node to link back to current level directory
3644 createParentTreeInfoNode(leveldir_new);
3646 // recursively step into sub-directory and look for more level series
3647 LoadLevelInfoFromLevelDir(&leveldir_new->node_group,
3648 leveldir_new, directory_path);
3651 free(directory_path);
3657 static void LoadLevelInfoFromLevelDir(TreeInfo **node_first,
3658 TreeInfo *node_parent,
3659 char *level_directory)
3661 // ---------- 1st stage: process any level set zip files ----------
3663 ProcessZipFilesInDirectory(level_directory, TREE_TYPE_LEVEL_DIR);
3665 // ---------- 2nd stage: check for level set directories ----------
3668 DirectoryEntry *dir_entry;
3669 boolean valid_entry_found = FALSE;
3671 if ((dir = openDirectory(level_directory)) == NULL)
3673 Warn("cannot read level directory '%s'", level_directory);
3678 while ((dir_entry = readDirectory(dir)) != NULL) // loop all entries
3680 char *directory_name = dir_entry->basename;
3681 char *directory_path = getPath2(level_directory, directory_name);
3683 // skip entries for current and parent directory
3684 if (strEqual(directory_name, ".") ||
3685 strEqual(directory_name, ".."))
3687 free(directory_path);
3692 // find out if directory entry is itself a directory
3693 if (!dir_entry->is_directory) // not a directory
3695 free(directory_path);
3700 free(directory_path);
3702 if (strEqual(directory_name, GRAPHICS_DIRECTORY) ||
3703 strEqual(directory_name, SOUNDS_DIRECTORY) ||
3704 strEqual(directory_name, MUSIC_DIRECTORY))
3707 valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
3712 closeDirectory(dir);
3714 // special case: top level directory may directly contain "levelinfo.conf"
3715 if (node_parent == NULL && !valid_entry_found)
3717 // check if this directory directly contains a file "levelinfo.conf"
3718 valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
3719 level_directory, ".");
3722 if (!valid_entry_found)
3723 Warn("cannot find any valid level series in directory '%s'",
3727 boolean AdjustGraphicsForEMC(void)
3729 boolean settings_changed = FALSE;
3731 settings_changed |= adjustTreeGraphicsForEMC(leveldir_first_all);
3732 settings_changed |= adjustTreeGraphicsForEMC(leveldir_first);
3734 return settings_changed;
3737 boolean AdjustSoundsForEMC(void)
3739 boolean settings_changed = FALSE;
3741 settings_changed |= adjustTreeSoundsForEMC(leveldir_first_all);
3742 settings_changed |= adjustTreeSoundsForEMC(leveldir_first);
3744 return settings_changed;
3747 void LoadLevelInfo(void)
3749 InitUserLevelDirectory(getLoginName());
3751 DrawInitTextHead("Loading level series");
3753 LoadLevelInfoFromLevelDir(&leveldir_first, NULL, options.level_directory);
3754 LoadLevelInfoFromLevelDir(&leveldir_first, NULL, getUserLevelDir(NULL));
3756 leveldir_first = createTopTreeInfoNode(leveldir_first);
3758 /* after loading all level set information, clone the level directory tree
3759 and remove all level sets without levels (these may still contain artwork
3760 to be offered in the setup menu as "custom artwork", and are therefore
3761 checked for existing artwork in the function "LoadLevelArtworkInfo()") */
3762 leveldir_first_all = leveldir_first;
3763 cloneTree(&leveldir_first, leveldir_first_all, TRUE);
3765 AdjustGraphicsForEMC();
3766 AdjustSoundsForEMC();
3768 // before sorting, the first entries will be from the user directory
3769 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
3771 if (leveldir_first == NULL)
3772 Fail("cannot find any valid level series in any directory");
3774 sortTreeInfo(&leveldir_first);
3776 #if ENABLE_UNUSED_CODE
3777 dumpTreeInfo(leveldir_first, 0);
3781 static boolean LoadArtworkInfoFromArtworkConf(TreeInfo **node_first,
3782 TreeInfo *node_parent,
3783 char *base_directory,
3784 char *directory_name, int type)
3786 char *directory_path = getPath2(base_directory, directory_name);
3787 char *filename = getPath2(directory_path, ARTWORKINFO_FILENAME(type));
3788 SetupFileHash *setup_file_hash = NULL;
3789 TreeInfo *artwork_new = NULL;
3792 if (fileExists(filename))
3793 setup_file_hash = loadSetupFileHash(filename);
3795 if (setup_file_hash == NULL) // no config file -- look for artwork files
3798 DirectoryEntry *dir_entry;
3799 boolean valid_file_found = FALSE;
3801 if ((dir = openDirectory(directory_path)) != NULL)
3803 while ((dir_entry = readDirectory(dir)) != NULL)
3805 if (FileIsArtworkType(dir_entry->filename, type))
3807 valid_file_found = TRUE;
3813 closeDirectory(dir);
3816 if (!valid_file_found)
3818 #if DEBUG_NO_CONFIG_FILE
3819 if (!strEqual(directory_name, "."))
3820 Debug("setup", "ignoring artwork directory '%s'", directory_path);
3823 free(directory_path);
3830 artwork_new = newTreeInfo();
3833 setTreeInfoToDefaultsFromParent(artwork_new, node_parent);
3835 setTreeInfoToDefaults(artwork_new, type);
3837 artwork_new->subdir = getStringCopy(directory_name);
3839 if (setup_file_hash) // (before defining ".color" and ".class_desc")
3841 // set all structure fields according to the token/value pairs
3843 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
3844 setSetupInfo(levelinfo_tokens, i,
3845 getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
3848 if (strEqual(artwork_new->name, ANONYMOUS_NAME))
3849 setString(&artwork_new->name, artwork_new->subdir);
3851 if (artwork_new->identifier == NULL)
3852 artwork_new->identifier = getStringCopy(artwork_new->subdir);
3854 if (artwork_new->name_sorting == NULL)
3855 artwork_new->name_sorting = getStringCopy(artwork_new->name);
3858 if (node_parent == NULL) // top level group
3860 artwork_new->basepath = getStringCopy(base_directory);
3861 artwork_new->fullpath = getStringCopy(artwork_new->subdir);
3863 else // sub level group
3865 artwork_new->basepath = getStringCopy(node_parent->basepath);
3866 artwork_new->fullpath = getPath2(node_parent->fullpath, directory_name);
3869 artwork_new->in_user_dir =
3870 (!strEqual(artwork_new->basepath, OPTIONS_ARTWORK_DIRECTORY(type)));
3872 setString(&artwork_new->class_desc, getLevelClassDescription(artwork_new));
3874 if (setup_file_hash == NULL) // (after determining ".user_defined")
3876 if (strEqual(artwork_new->subdir, "."))
3878 if (artwork_new->user_defined)
3880 setString(&artwork_new->identifier, "private");
3881 artwork_new->sort_priority = ARTWORKCLASS_PRIVATE;
3885 setString(&artwork_new->identifier, "classic");
3886 artwork_new->sort_priority = ARTWORKCLASS_CLASSICS;
3889 setString(&artwork_new->class_desc,
3890 getLevelClassDescription(artwork_new));
3894 setString(&artwork_new->identifier, artwork_new->subdir);
3897 setString(&artwork_new->name, artwork_new->identifier);
3898 setString(&artwork_new->name_sorting, artwork_new->name);
3901 pushTreeInfo(node_first, artwork_new);
3903 freeSetupFileHash(setup_file_hash);
3905 free(directory_path);
3911 static void LoadArtworkInfoFromArtworkDir(TreeInfo **node_first,
3912 TreeInfo *node_parent,
3913 char *base_directory, int type)
3915 // ---------- 1st stage: process any artwork set zip files ----------
3917 ProcessZipFilesInDirectory(base_directory, type);
3919 // ---------- 2nd stage: check for artwork set directories ----------
3922 DirectoryEntry *dir_entry;
3923 boolean valid_entry_found = FALSE;
3925 if ((dir = openDirectory(base_directory)) == NULL)
3927 // display error if directory is main "options.graphics_directory" etc.
3928 if (base_directory == OPTIONS_ARTWORK_DIRECTORY(type))
3929 Warn("cannot read directory '%s'", base_directory);
3934 while ((dir_entry = readDirectory(dir)) != NULL) // loop all entries
3936 char *directory_name = dir_entry->basename;
3937 char *directory_path = getPath2(base_directory, directory_name);
3939 // skip directory entries for current and parent directory
3940 if (strEqual(directory_name, ".") ||
3941 strEqual(directory_name, ".."))
3943 free(directory_path);
3948 // skip directory entries which are not a directory
3949 if (!dir_entry->is_directory) // not a directory
3951 free(directory_path);
3956 free(directory_path);
3958 // check if this directory contains artwork with or without config file
3959 valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
3961 directory_name, type);
3964 closeDirectory(dir);
3966 // check if this directory directly contains artwork itself
3967 valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
3968 base_directory, ".",
3970 if (!valid_entry_found)
3971 Warn("cannot find any valid artwork in directory '%s'", base_directory);
3974 static TreeInfo *getDummyArtworkInfo(int type)
3976 // this is only needed when there is completely no artwork available
3977 TreeInfo *artwork_new = newTreeInfo();
3979 setTreeInfoToDefaults(artwork_new, type);
3981 setString(&artwork_new->subdir, UNDEFINED_FILENAME);
3982 setString(&artwork_new->fullpath, UNDEFINED_FILENAME);
3983 setString(&artwork_new->basepath, UNDEFINED_FILENAME);
3985 setString(&artwork_new->identifier, UNDEFINED_FILENAME);
3986 setString(&artwork_new->name, UNDEFINED_FILENAME);
3987 setString(&artwork_new->name_sorting, UNDEFINED_FILENAME);
3992 void SetCurrentArtwork(int type)
3994 ArtworkDirTree **current_ptr = ARTWORK_CURRENT_PTR(artwork, type);
3995 ArtworkDirTree *first_node = ARTWORK_FIRST_NODE(artwork, type);
3996 char *setup_set = SETUP_ARTWORK_SET(setup, type);
3997 char *default_subdir = ARTWORK_DEFAULT_SUBDIR(type);
3999 // set current artwork to artwork configured in setup menu
4000 *current_ptr = getTreeInfoFromIdentifier(first_node, setup_set);
4002 // if not found, set current artwork to default artwork
4003 if (*current_ptr == NULL)
4004 *current_ptr = getTreeInfoFromIdentifier(first_node, default_subdir);
4006 // if not found, set current artwork to first artwork in tree
4007 if (*current_ptr == NULL)
4008 *current_ptr = getFirstValidTreeInfoEntry(first_node);
4011 void ChangeCurrentArtworkIfNeeded(int type)
4013 char *current_identifier = ARTWORK_CURRENT_IDENTIFIER(artwork, type);
4014 char *setup_set = SETUP_ARTWORK_SET(setup, type);
4016 if (!strEqual(current_identifier, setup_set))
4017 SetCurrentArtwork(type);
4020 void LoadArtworkInfo(void)
4022 LoadArtworkInfoCache();
4024 DrawInitTextHead("Looking for custom artwork");
4026 LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
4027 options.graphics_directory,
4028 TREE_TYPE_GRAPHICS_DIR);
4029 LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
4030 getUserGraphicsDir(),
4031 TREE_TYPE_GRAPHICS_DIR);
4033 LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
4034 options.sounds_directory,
4035 TREE_TYPE_SOUNDS_DIR);
4036 LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
4038 TREE_TYPE_SOUNDS_DIR);
4040 LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
4041 options.music_directory,
4042 TREE_TYPE_MUSIC_DIR);
4043 LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
4045 TREE_TYPE_MUSIC_DIR);
4047 if (artwork.gfx_first == NULL)
4048 artwork.gfx_first = getDummyArtworkInfo(TREE_TYPE_GRAPHICS_DIR);
4049 if (artwork.snd_first == NULL)
4050 artwork.snd_first = getDummyArtworkInfo(TREE_TYPE_SOUNDS_DIR);
4051 if (artwork.mus_first == NULL)
4052 artwork.mus_first = getDummyArtworkInfo(TREE_TYPE_MUSIC_DIR);
4054 // before sorting, the first entries will be from the user directory
4055 SetCurrentArtwork(ARTWORK_TYPE_GRAPHICS);
4056 SetCurrentArtwork(ARTWORK_TYPE_SOUNDS);
4057 SetCurrentArtwork(ARTWORK_TYPE_MUSIC);
4059 artwork.gfx_current_identifier = artwork.gfx_current->identifier;
4060 artwork.snd_current_identifier = artwork.snd_current->identifier;
4061 artwork.mus_current_identifier = artwork.mus_current->identifier;
4063 #if ENABLE_UNUSED_CODE
4064 Debug("setup:LoadArtworkInfo", "graphics set == %s",
4065 artwork.gfx_current_identifier);
4066 Debug("setup:LoadArtworkInfo", "sounds set == %s",
4067 artwork.snd_current_identifier);
4068 Debug("setup:LoadArtworkInfo", "music set == %s",
4069 artwork.mus_current_identifier);
4072 sortTreeInfo(&artwork.gfx_first);
4073 sortTreeInfo(&artwork.snd_first);
4074 sortTreeInfo(&artwork.mus_first);
4076 #if ENABLE_UNUSED_CODE
4077 dumpTreeInfo(artwork.gfx_first, 0);
4078 dumpTreeInfo(artwork.snd_first, 0);
4079 dumpTreeInfo(artwork.mus_first, 0);
4083 static void MoveArtworkInfoIntoSubTree(ArtworkDirTree **artwork_node)
4085 ArtworkDirTree *artwork_new = newTreeInfo();
4086 char *top_node_name = "standalone artwork";
4088 setTreeInfoToDefaults(artwork_new, (*artwork_node)->type);
4090 artwork_new->level_group = TRUE;
4092 setString(&artwork_new->identifier, top_node_name);
4093 setString(&artwork_new->name, top_node_name);
4094 setString(&artwork_new->name_sorting, top_node_name);
4096 // create node to link back to current custom artwork directory
4097 createParentTreeInfoNode(artwork_new);
4099 // move existing custom artwork tree into newly created sub-tree
4100 artwork_new->node_group->next = *artwork_node;
4102 // change custom artwork tree to contain only newly created node
4103 *artwork_node = artwork_new;
4106 static void LoadArtworkInfoFromLevelInfoExt(ArtworkDirTree **artwork_node,
4107 ArtworkDirTree *node_parent,
4108 LevelDirTree *level_node,
4109 boolean empty_level_set_mode)
4111 int type = (*artwork_node)->type;
4113 // recursively check all level directories for artwork sub-directories
4117 boolean empty_level_set = (level_node->levels == 0);
4119 // check all tree entries for artwork, but skip parent link entries
4120 if (!level_node->parent_link && empty_level_set == empty_level_set_mode)
4122 TreeInfo *artwork_new = getArtworkInfoCacheEntry(level_node, type);
4123 boolean cached = (artwork_new != NULL);
4127 pushTreeInfo(artwork_node, artwork_new);
4131 TreeInfo *topnode_last = *artwork_node;
4132 char *path = getPath2(getLevelDirFromTreeInfo(level_node),
4133 ARTWORK_DIRECTORY(type));
4135 LoadArtworkInfoFromArtworkDir(artwork_node, NULL, path, type);
4137 if (topnode_last != *artwork_node) // check for newly added node
4139 artwork_new = *artwork_node;
4141 setString(&artwork_new->identifier, level_node->subdir);
4142 setString(&artwork_new->name, level_node->name);
4143 setString(&artwork_new->name_sorting, level_node->name_sorting);
4145 artwork_new->sort_priority = level_node->sort_priority;
4146 artwork_new->in_user_dir = level_node->in_user_dir;
4148 update_artworkinfo_cache = TRUE;
4154 // insert artwork info (from old cache or filesystem) into new cache
4155 if (artwork_new != NULL)
4156 setArtworkInfoCacheEntry(artwork_new, level_node, type);
4159 DrawInitTextItem(level_node->name);
4161 if (level_node->node_group != NULL)
4163 TreeInfo *artwork_new = newTreeInfo();
4166 setTreeInfoToDefaultsFromParent(artwork_new, node_parent);
4168 setTreeInfoToDefaults(artwork_new, type);
4170 artwork_new->level_group = TRUE;
4172 setString(&artwork_new->identifier, level_node->subdir);
4174 if (node_parent == NULL) // check for top tree node
4176 char *top_node_name = (empty_level_set_mode ?
4177 "artwork for certain level sets" :
4178 "artwork included in level sets");
4180 setString(&artwork_new->name, top_node_name);
4181 setString(&artwork_new->name_sorting, top_node_name);
4185 setString(&artwork_new->name, level_node->name);
4186 setString(&artwork_new->name_sorting, level_node->name_sorting);
4189 pushTreeInfo(artwork_node, artwork_new);
4191 // create node to link back to current custom artwork directory
4192 createParentTreeInfoNode(artwork_new);
4194 // recursively step into sub-directory and look for more custom artwork
4195 LoadArtworkInfoFromLevelInfoExt(&artwork_new->node_group, artwork_new,
4196 level_node->node_group,
4197 empty_level_set_mode);
4199 // if sub-tree has no custom artwork at all, remove it
4200 if (artwork_new->node_group->next == NULL)
4201 removeTreeInfo(artwork_node);
4204 level_node = level_node->next;
4208 static void LoadArtworkInfoFromLevelInfo(ArtworkDirTree **artwork_node)
4210 // move peviously loaded artwork tree into separate sub-tree
4211 MoveArtworkInfoIntoSubTree(artwork_node);
4213 // load artwork from level sets into separate sub-trees
4214 LoadArtworkInfoFromLevelInfoExt(artwork_node, NULL, leveldir_first_all, TRUE);
4215 LoadArtworkInfoFromLevelInfoExt(artwork_node, NULL, leveldir_first_all, FALSE);
4217 // add top tree node over all three separate sub-trees
4218 *artwork_node = createTopTreeInfoNode(*artwork_node);
4220 // set all parent links (back links) in complete artwork tree
4221 setTreeInfoParentNodes(*artwork_node, NULL);
4224 void LoadLevelArtworkInfo(void)
4226 print_timestamp_init("LoadLevelArtworkInfo");
4228 DrawInitTextHead("Looking for custom level artwork");
4230 print_timestamp_time("DrawTimeText");
4232 LoadArtworkInfoFromLevelInfo(&artwork.gfx_first);
4233 print_timestamp_time("LoadArtworkInfoFromLevelInfo (gfx)");
4234 LoadArtworkInfoFromLevelInfo(&artwork.snd_first);
4235 print_timestamp_time("LoadArtworkInfoFromLevelInfo (snd)");
4236 LoadArtworkInfoFromLevelInfo(&artwork.mus_first);
4237 print_timestamp_time("LoadArtworkInfoFromLevelInfo (mus)");
4239 SaveArtworkInfoCache();
4241 print_timestamp_time("SaveArtworkInfoCache");
4243 // needed for reloading level artwork not known at ealier stage
4244 ChangeCurrentArtworkIfNeeded(ARTWORK_TYPE_GRAPHICS);
4245 ChangeCurrentArtworkIfNeeded(ARTWORK_TYPE_SOUNDS);
4246 ChangeCurrentArtworkIfNeeded(ARTWORK_TYPE_MUSIC);
4248 print_timestamp_time("getTreeInfoFromIdentifier");
4250 sortTreeInfo(&artwork.gfx_first);
4251 sortTreeInfo(&artwork.snd_first);
4252 sortTreeInfo(&artwork.mus_first);
4254 print_timestamp_time("sortTreeInfo");
4256 #if ENABLE_UNUSED_CODE
4257 dumpTreeInfo(artwork.gfx_first, 0);
4258 dumpTreeInfo(artwork.snd_first, 0);
4259 dumpTreeInfo(artwork.mus_first, 0);
4262 print_timestamp_done("LoadLevelArtworkInfo");
4265 static boolean AddTreeSetToTreeInfoExt(TreeInfo *tree_node_old, char *tree_dir,
4266 char *tree_subdir_new, int type)
4268 if (tree_node_old == NULL)
4270 if (type == TREE_TYPE_LEVEL_DIR)
4272 // get level info tree node of personal user level set
4273 tree_node_old = getTreeInfoFromIdentifier(leveldir_first, getLoginName());
4275 // this may happen if "setup.internal.create_user_levelset" is FALSE
4276 // or if file "levelinfo.conf" is missing in personal user level set
4277 if (tree_node_old == NULL)
4278 tree_node_old = leveldir_first->node_group;
4282 // get artwork info tree node of first artwork set
4283 tree_node_old = ARTWORK_FIRST_NODE(artwork, type);
4287 if (tree_dir == NULL)
4288 tree_dir = TREE_USERDIR(type);
4290 if (tree_node_old == NULL ||
4292 tree_subdir_new == NULL) // should not happen
4295 int draw_deactivation_mask = GetDrawDeactivationMask();
4297 // override draw deactivation mask (temporarily disable drawing)
4298 SetDrawDeactivationMask(REDRAW_ALL);
4300 if (type == TREE_TYPE_LEVEL_DIR)
4302 // load new level set config and add it next to first user level set
4303 LoadLevelInfoFromLevelConf(&tree_node_old->next,
4304 tree_node_old->node_parent,
4305 tree_dir, tree_subdir_new);
4309 // load new artwork set config and add it next to first artwork set
4310 LoadArtworkInfoFromArtworkConf(&tree_node_old->next,
4311 tree_node_old->node_parent,
4312 tree_dir, tree_subdir_new, type);
4315 // set draw deactivation mask to previous value
4316 SetDrawDeactivationMask(draw_deactivation_mask);
4318 // get first node of level or artwork info tree
4319 TreeInfo **tree_node_first = TREE_FIRST_NODE_PTR(type);
4321 // get tree info node of newly added level or artwork set
4322 TreeInfo *tree_node_new = getTreeInfoFromIdentifier(*tree_node_first,
4325 if (tree_node_new == NULL) // should not happen
4328 // correct top link and parent node link of newly created tree node
4329 tree_node_new->node_top = tree_node_old->node_top;
4330 tree_node_new->node_parent = tree_node_old->node_parent;
4332 // sort tree info to adjust position of newly added tree set
4333 sortTreeInfo(tree_node_first);
4338 void AddTreeSetToTreeInfo(TreeInfo *tree_node, char *tree_dir,
4339 char *tree_subdir_new, int type)
4341 if (!AddTreeSetToTreeInfoExt(tree_node, tree_dir, tree_subdir_new, type))
4342 Fail("internal tree info structure corrupted -- aborting");
4345 void AddUserLevelSetToLevelInfo(char *level_subdir_new)
4347 AddTreeSetToTreeInfo(NULL, NULL, level_subdir_new, TREE_TYPE_LEVEL_DIR);
4350 char *getArtworkIdentifierForUserLevelSet(int type)
4352 char *classic_artwork_set = getClassicArtworkSet(type);
4354 // check for custom artwork configured in "levelinfo.conf"
4355 char *leveldir_artwork_set =
4356 *LEVELDIR_ARTWORK_SET_PTR(leveldir_current, type);
4357 boolean has_leveldir_artwork_set =
4358 (leveldir_artwork_set != NULL && !strEqual(leveldir_artwork_set,
4359 classic_artwork_set));
4361 // check for custom artwork in sub-directory "graphics" etc.
4362 TreeInfo *artwork_first_node = ARTWORK_FIRST_NODE(artwork, type);
4363 char *leveldir_identifier = leveldir_current->identifier;
4364 boolean has_artwork_subdir =
4365 (getTreeInfoFromIdentifier(artwork_first_node,
4366 leveldir_identifier) != NULL);
4368 return (has_leveldir_artwork_set ? leveldir_artwork_set :
4369 has_artwork_subdir ? leveldir_identifier :
4370 classic_artwork_set);
4373 TreeInfo *getArtworkTreeInfoForUserLevelSet(int type)
4375 char *artwork_set = getArtworkIdentifierForUserLevelSet(type);
4376 TreeInfo *artwork_first_node = ARTWORK_FIRST_NODE(artwork, type);
4377 TreeInfo *ti = getTreeInfoFromIdentifier(artwork_first_node, artwork_set);
4381 ti = getTreeInfoFromIdentifier(artwork_first_node,
4382 ARTWORK_DEFAULT_SUBDIR(type));
4384 Fail("cannot find default graphics -- should not happen");
4390 boolean checkIfCustomArtworkExistsForCurrentLevelSet(void)
4392 char *graphics_set =
4393 getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_GRAPHICS);
4395 getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_SOUNDS);
4397 getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_MUSIC);
4399 return (!strEqual(graphics_set, GFX_CLASSIC_SUBDIR) ||
4400 !strEqual(sounds_set, SND_CLASSIC_SUBDIR) ||
4401 !strEqual(music_set, MUS_CLASSIC_SUBDIR));
4404 boolean UpdateUserLevelSet(char *level_subdir, char *level_name,
4405 char *level_author, int num_levels)
4407 char *filename = getPath2(getUserLevelDir(level_subdir), LEVELINFO_FILENAME);
4408 char *filename_tmp = getStringCat2(filename, ".tmp");
4410 FILE *file_tmp = NULL;
4411 char line[MAX_LINE_LEN];
4412 boolean success = FALSE;
4413 LevelDirTree *leveldir = getTreeInfoFromIdentifier(leveldir_first,
4415 // update values in level directory tree
4417 if (level_name != NULL)
4418 setString(&leveldir->name, level_name);
4420 if (level_author != NULL)
4421 setString(&leveldir->author, level_author);
4423 if (num_levels != -1)
4424 leveldir->levels = num_levels;
4426 // update values that depend on other values
4428 setString(&leveldir->name_sorting, leveldir->name);
4430 leveldir->last_level = leveldir->first_level + leveldir->levels - 1;
4432 // sort order of level sets may have changed
4433 sortTreeInfo(&leveldir_first);
4435 if ((file = fopen(filename, MODE_READ)) &&
4436 (file_tmp = fopen(filename_tmp, MODE_WRITE)))
4438 while (fgets(line, MAX_LINE_LEN, file))
4440 if (strPrefix(line, "name:") && level_name != NULL)
4441 fprintf(file_tmp, "%-32s%s\n", "name:", level_name);
4442 else if (strPrefix(line, "author:") && level_author != NULL)
4443 fprintf(file_tmp, "%-32s%s\n", "author:", level_author);
4444 else if (strPrefix(line, "levels:") && num_levels != -1)
4445 fprintf(file_tmp, "%-32s%d\n", "levels:", num_levels);
4447 fputs(line, file_tmp);
4460 success = (rename(filename_tmp, filename) == 0);
4468 boolean CreateUserLevelSet(char *level_subdir, char *level_name,
4469 char *level_author, int num_levels,
4470 boolean use_artwork_set)
4472 LevelDirTree *level_info;
4477 // create user level sub-directory, if needed
4478 createDirectory(getUserLevelDir(level_subdir), "user level", PERMS_PRIVATE);
4480 filename = getPath2(getUserLevelDir(level_subdir), LEVELINFO_FILENAME);
4482 if (!(file = fopen(filename, MODE_WRITE)))
4484 Warn("cannot write level info file '%s'", filename);
4491 level_info = newTreeInfo();
4493 // always start with reliable default values
4494 setTreeInfoToDefaults(level_info, TREE_TYPE_LEVEL_DIR);
4496 setString(&level_info->name, level_name);
4497 setString(&level_info->author, level_author);
4498 level_info->levels = num_levels;
4499 level_info->first_level = 1;
4500 level_info->sort_priority = LEVELCLASS_PRIVATE_START;
4501 level_info->readonly = FALSE;
4503 if (use_artwork_set)
4505 level_info->graphics_set =
4506 getStringCopy(getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_GRAPHICS));
4507 level_info->sounds_set =
4508 getStringCopy(getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_SOUNDS));
4509 level_info->music_set =
4510 getStringCopy(getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_MUSIC));
4513 token_value_position = TOKEN_VALUE_POSITION_SHORT;
4515 fprintFileHeader(file, LEVELINFO_FILENAME);
4518 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
4520 if (i == LEVELINFO_TOKEN_NAME ||
4521 i == LEVELINFO_TOKEN_AUTHOR ||
4522 i == LEVELINFO_TOKEN_LEVELS ||
4523 i == LEVELINFO_TOKEN_FIRST_LEVEL ||
4524 i == LEVELINFO_TOKEN_SORT_PRIORITY ||
4525 i == LEVELINFO_TOKEN_READONLY ||
4526 (use_artwork_set && (i == LEVELINFO_TOKEN_GRAPHICS_SET ||
4527 i == LEVELINFO_TOKEN_SOUNDS_SET ||
4528 i == LEVELINFO_TOKEN_MUSIC_SET)))
4529 fprintf(file, "%s\n", getSetupLine(levelinfo_tokens, "", i));
4531 // just to make things nicer :)
4532 if (i == LEVELINFO_TOKEN_AUTHOR ||
4533 i == LEVELINFO_TOKEN_FIRST_LEVEL ||
4534 (use_artwork_set && i == LEVELINFO_TOKEN_READONLY))
4535 fprintf(file, "\n");
4538 token_value_position = TOKEN_VALUE_POSITION_DEFAULT;
4542 SetFilePermissions(filename, PERMS_PRIVATE);
4544 freeTreeInfo(level_info);
4550 static void SaveUserLevelInfo(void)
4552 CreateUserLevelSet(getLoginName(), getLoginName(), getRealName(), 100, FALSE);
4555 char *getSetupValue(int type, void *value)
4557 static char value_string[MAX_LINE_LEN];
4565 strcpy(value_string, (*(boolean *)value ? "true" : "false"));
4569 strcpy(value_string, (*(boolean *)value ? "on" : "off"));
4573 strcpy(value_string, (*(int *)value == AUTO ? "auto" :
4574 *(int *)value == FALSE ? "off" : "on"));
4578 strcpy(value_string, (*(boolean *)value ? "yes" : "no"));
4581 case TYPE_YES_NO_AUTO:
4582 strcpy(value_string, (*(int *)value == AUTO ? "auto" :
4583 *(int *)value == FALSE ? "no" : "yes"));
4587 strcpy(value_string, (*(boolean *)value ? "AGA" : "ECS"));
4591 strcpy(value_string, getKeyNameFromKey(*(Key *)value));
4595 strcpy(value_string, getX11KeyNameFromKey(*(Key *)value));
4599 sprintf(value_string, "%d", *(int *)value);
4603 if (*(char **)value == NULL)
4606 strcpy(value_string, *(char **)value);
4610 sprintf(value_string, "player_%d", *(int *)value + 1);
4614 value_string[0] = '\0';
4618 if (type & TYPE_GHOSTED)
4619 strcpy(value_string, "n/a");
4621 return value_string;
4624 char *getSetupLine(struct TokenInfo *token_info, char *prefix, int token_nr)
4628 static char token_string[MAX_LINE_LEN];
4629 int token_type = token_info[token_nr].type;
4630 void *setup_value = token_info[token_nr].value;
4631 char *token_text = token_info[token_nr].text;
4632 char *value_string = getSetupValue(token_type, setup_value);
4634 // build complete token string
4635 sprintf(token_string, "%s%s", prefix, token_text);
4637 // build setup entry line
4638 line = getFormattedSetupEntry(token_string, value_string);
4640 if (token_type == TYPE_KEY_X11)
4642 Key key = *(Key *)setup_value;
4643 char *keyname = getKeyNameFromKey(key);
4645 // add comment, if useful
4646 if (!strEqual(keyname, "(undefined)") &&
4647 !strEqual(keyname, "(unknown)"))
4649 // add at least one whitespace
4651 for (i = strlen(line); i < token_comment_position; i++)
4655 strcat(line, keyname);
4662 static void InitLastPlayedLevels_ParentNode(void)
4664 LevelDirTree **leveldir_top = &leveldir_first->node_group->next;
4665 LevelDirTree *leveldir_new = NULL;
4667 // check if parent node for last played levels already exists
4668 if (strEqual((*leveldir_top)->identifier, TOKEN_STR_LAST_LEVEL_SERIES))
4671 leveldir_new = newTreeInfo();
4673 setTreeInfoToDefaultsFromParent(leveldir_new, leveldir_first);
4675 leveldir_new->level_group = TRUE;
4676 leveldir_new->sort_priority = LEVELCLASS_LAST_PLAYED_LEVEL;
4678 setString(&leveldir_new->identifier, TOKEN_STR_LAST_LEVEL_SERIES);
4679 setString(&leveldir_new->name, "<< (last played level sets)");
4680 setString(&leveldir_new->name_sorting, leveldir_new->name);
4682 pushTreeInfo(leveldir_top, leveldir_new);
4684 // create node to link back to current level directory
4685 createParentTreeInfoNode(leveldir_new);
4688 void UpdateLastPlayedLevels_TreeInfo(void)
4690 char **last_level_series = setup.level_setup.last_level_series;
4691 LevelDirTree *leveldir_last;
4692 TreeInfo **node_new = NULL;
4695 if (last_level_series[0] == NULL)
4698 InitLastPlayedLevels_ParentNode();
4700 leveldir_last = getTreeInfoFromIdentifierExt(leveldir_first,
4701 TOKEN_STR_LAST_LEVEL_SERIES,
4702 TREE_NODE_TYPE_GROUP);
4703 if (leveldir_last == NULL)
4706 node_new = &leveldir_last->node_group->next;
4708 freeTreeInfo(*node_new);
4712 for (i = 0; last_level_series[i] != NULL; i++)
4714 LevelDirTree *node_last = getTreeInfoFromIdentifier(leveldir_first,
4715 last_level_series[i]);
4716 if (node_last == NULL)
4719 *node_new = getTreeInfoCopy(node_last); // copy complete node
4721 (*node_new)->node_top = &leveldir_first; // correct top node link
4722 (*node_new)->node_parent = leveldir_last; // correct parent node link
4724 (*node_new)->is_copy = TRUE; // mark entry as node copy
4726 (*node_new)->node_group = NULL;
4727 (*node_new)->next = NULL;
4729 (*node_new)->cl_first = -1; // force setting tree cursor
4731 node_new = &((*node_new)->next);
4735 static void UpdateLastPlayedLevels_List(void)
4737 char **last_level_series = setup.level_setup.last_level_series;
4738 int pos = MAX_LEVELDIR_HISTORY - 1;
4741 // search for potentially already existing entry in list of level sets
4742 for (i = 0; last_level_series[i] != NULL; i++)
4743 if (strEqual(last_level_series[i], leveldir_current->identifier))
4746 // move list of level sets one entry down (using potentially free entry)
4747 for (i = pos; i > 0; i--)
4748 setString(&last_level_series[i], last_level_series[i - 1]);
4750 // put last played level set at top position
4751 setString(&last_level_series[0], leveldir_current->identifier);
4754 static TreeInfo *StoreOrRestoreLastPlayedLevels(TreeInfo *node, boolean store)
4756 static char *identifier = NULL;
4760 setString(&identifier, (node && node->is_copy ? node->identifier : NULL));
4762 return NULL; // not used
4766 TreeInfo *node_new = getTreeInfoFromIdentifierExt(leveldir_first,
4768 TREE_NODE_TYPE_COPY);
4769 return (node_new != NULL ? node_new : node);
4773 void StoreLastPlayedLevels(TreeInfo *node)
4775 StoreOrRestoreLastPlayedLevels(node, TRUE);
4778 void RestoreLastPlayedLevels(TreeInfo **node)
4780 *node = StoreOrRestoreLastPlayedLevels(*node, FALSE);
4783 void LoadLevelSetup_LastSeries(void)
4785 // --------------------------------------------------------------------------
4786 // ~/.<program>/levelsetup.conf
4787 // --------------------------------------------------------------------------
4789 char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
4790 SetupFileHash *level_setup_hash = NULL;
4794 // always start with reliable default values
4795 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
4797 // start with empty history of last played level sets
4798 setString(&setup.level_setup.last_level_series[0], NULL);
4800 if (!strEqual(DEFAULT_LEVELSET, UNDEFINED_LEVELSET))
4802 leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
4804 if (leveldir_current == NULL)
4805 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
4808 if ((level_setup_hash = loadSetupFileHash(filename)))
4810 char *last_level_series =
4811 getHashEntry(level_setup_hash, TOKEN_STR_LAST_LEVEL_SERIES);
4813 leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
4815 if (leveldir_current == NULL)
4816 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
4818 for (i = 0; i < MAX_LEVELDIR_HISTORY; i++)
4820 char token[strlen(TOKEN_STR_LAST_LEVEL_SERIES) + 10];
4821 LevelDirTree *leveldir_last;
4823 sprintf(token, "%s.%03d", TOKEN_STR_LAST_LEVEL_SERIES, i);
4825 last_level_series = getHashEntry(level_setup_hash, token);
4827 leveldir_last = getTreeInfoFromIdentifier(leveldir_first,
4829 if (leveldir_last != NULL)
4830 setString(&setup.level_setup.last_level_series[pos++],
4834 setString(&setup.level_setup.last_level_series[pos], NULL);
4836 freeSetupFileHash(level_setup_hash);
4840 Debug("setup", "using default setup values");
4846 static void SaveLevelSetup_LastSeries_Ext(boolean deactivate_last_level_series)
4848 // --------------------------------------------------------------------------
4849 // ~/.<program>/levelsetup.conf
4850 // --------------------------------------------------------------------------
4852 // check if the current level directory structure is available at this point
4853 if (leveldir_current == NULL)
4856 char **last_level_series = setup.level_setup.last_level_series;
4857 char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
4861 InitUserDataDirectory();
4863 UpdateLastPlayedLevels_List();
4865 if (!(file = fopen(filename, MODE_WRITE)))
4867 Warn("cannot write setup file '%s'", filename);
4874 fprintFileHeader(file, LEVELSETUP_FILENAME);
4876 if (deactivate_last_level_series)
4877 fprintf(file, "# %s\n# ", "the following level set may have caused a problem and was deactivated");
4879 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_LAST_LEVEL_SERIES,
4880 leveldir_current->identifier));
4882 for (i = 0; last_level_series[i] != NULL; i++)
4884 char token[strlen(TOKEN_STR_LAST_LEVEL_SERIES) + 1 + 10 + 1];
4886 sprintf(token, "%s.%03d", TOKEN_STR_LAST_LEVEL_SERIES, i);
4888 fprintf(file, "%s\n", getFormattedSetupEntry(token, last_level_series[i]));
4893 SetFilePermissions(filename, PERMS_PRIVATE);
4898 void SaveLevelSetup_LastSeries(void)
4900 SaveLevelSetup_LastSeries_Ext(FALSE);
4903 void SaveLevelSetup_LastSeries_Deactivate(void)
4905 SaveLevelSetup_LastSeries_Ext(TRUE);
4908 static void checkSeriesInfo(void)
4910 static char *level_directory = NULL;
4913 DirectoryEntry *dir_entry;
4916 checked_free(level_directory);
4918 // check for more levels besides the 'levels' field of 'levelinfo.conf'
4920 level_directory = getPath2((leveldir_current->in_user_dir ?
4921 getUserLevelDir(NULL) :
4922 options.level_directory),
4923 leveldir_current->fullpath);
4925 if ((dir = openDirectory(level_directory)) == NULL)
4927 Warn("cannot read level directory '%s'", level_directory);
4933 while ((dir_entry = readDirectory(dir)) != NULL) // loop all entries
4935 if (strlen(dir_entry->basename) > 4 &&
4936 dir_entry->basename[3] == '.' &&
4937 strEqual(&dir_entry->basename[4], LEVELFILE_EXTENSION))
4939 char levelnum_str[4];
4942 strncpy(levelnum_str, dir_entry->basename, 3);
4943 levelnum_str[3] = '\0';
4945 levelnum_value = atoi(levelnum_str);
4947 if (levelnum_value < leveldir_current->first_level)
4949 Warn("additional level %d found", levelnum_value);
4951 leveldir_current->first_level = levelnum_value;
4953 else if (levelnum_value > leveldir_current->last_level)
4955 Warn("additional level %d found", levelnum_value);
4957 leveldir_current->last_level = levelnum_value;
4963 closeDirectory(dir);
4966 void LoadLevelSetup_SeriesInfo(void)
4969 SetupFileHash *level_setup_hash = NULL;
4970 char *level_subdir = leveldir_current->subdir;
4973 // always start with reliable default values
4974 level_nr = leveldir_current->first_level;
4976 for (i = 0; i < MAX_LEVELS; i++)
4978 LevelStats_setPlayed(i, 0);
4979 LevelStats_setSolved(i, 0);
4984 // --------------------------------------------------------------------------
4985 // ~/.<program>/levelsetup/<level series>/levelsetup.conf
4986 // --------------------------------------------------------------------------
4988 level_subdir = leveldir_current->subdir;
4990 filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
4992 if ((level_setup_hash = loadSetupFileHash(filename)))
4996 // get last played level in this level set
4998 token_value = getHashEntry(level_setup_hash, TOKEN_STR_LAST_PLAYED_LEVEL);
5002 level_nr = atoi(token_value);
5004 if (level_nr < leveldir_current->first_level)
5005 level_nr = leveldir_current->first_level;
5006 if (level_nr > leveldir_current->last_level)
5007 level_nr = leveldir_current->last_level;
5010 // get handicap level in this level set
5012 token_value = getHashEntry(level_setup_hash, TOKEN_STR_HANDICAP_LEVEL);
5016 int level_nr = atoi(token_value);
5018 if (level_nr < leveldir_current->first_level)
5019 level_nr = leveldir_current->first_level;
5020 if (level_nr > leveldir_current->last_level + 1)
5021 level_nr = leveldir_current->last_level;
5023 if (leveldir_current->user_defined || !leveldir_current->handicap)
5024 level_nr = leveldir_current->last_level;
5026 leveldir_current->handicap_level = level_nr;
5029 // get number of played and solved levels in this level set
5031 BEGIN_HASH_ITERATION(level_setup_hash, itr)
5033 char *token = HASH_ITERATION_TOKEN(itr);
5034 char *value = HASH_ITERATION_VALUE(itr);
5036 if (strlen(token) == 3 &&
5037 token[0] >= '0' && token[0] <= '9' &&
5038 token[1] >= '0' && token[1] <= '9' &&
5039 token[2] >= '0' && token[2] <= '9')
5041 int level_nr = atoi(token);
5044 LevelStats_setPlayed(level_nr, atoi(value)); // read 1st column
5046 value = strchr(value, ' ');
5049 LevelStats_setSolved(level_nr, atoi(value)); // read 2nd column
5052 END_HASH_ITERATION(hash, itr)
5054 freeSetupFileHash(level_setup_hash);
5058 Debug("setup", "using default setup values");
5064 void SaveLevelSetup_SeriesInfo(void)
5067 char *level_subdir = leveldir_current->subdir;
5068 char *level_nr_str = int2str(level_nr, 0);
5069 char *handicap_level_str = int2str(leveldir_current->handicap_level, 0);
5073 // --------------------------------------------------------------------------
5074 // ~/.<program>/levelsetup/<level series>/levelsetup.conf
5075 // --------------------------------------------------------------------------
5077 InitLevelSetupDirectory(level_subdir);
5079 filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
5081 if (!(file = fopen(filename, MODE_WRITE)))
5083 Warn("cannot write setup file '%s'", filename);
5090 fprintFileHeader(file, LEVELSETUP_FILENAME);
5092 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_PLAYED_LEVEL,
5094 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_HANDICAP_LEVEL,
5095 handicap_level_str));
5097 for (i = leveldir_current->first_level; i <= leveldir_current->last_level;
5100 if (LevelStats_getPlayed(i) > 0 ||
5101 LevelStats_getSolved(i) > 0)
5106 sprintf(token, "%03d", i);
5107 sprintf(value, "%d %d", LevelStats_getPlayed(i), LevelStats_getSolved(i));
5109 fprintf(file, "%s\n", getFormattedSetupEntry(token, value));
5115 SetFilePermissions(filename, PERMS_PRIVATE);
5120 int LevelStats_getPlayed(int nr)
5122 return (nr >= 0 && nr < MAX_LEVELS ? level_stats[nr].played : 0);
5125 int LevelStats_getSolved(int nr)
5127 return (nr >= 0 && nr < MAX_LEVELS ? level_stats[nr].solved : 0);
5130 void LevelStats_setPlayed(int nr, int value)
5132 if (nr >= 0 && nr < MAX_LEVELS)
5133 level_stats[nr].played = value;
5136 void LevelStats_setSolved(int nr, int value)
5138 if (nr >= 0 && nr < MAX_LEVELS)
5139 level_stats[nr].solved = value;
5142 void LevelStats_incPlayed(int nr)
5144 if (nr >= 0 && nr < MAX_LEVELS)
5145 level_stats[nr].played++;
5148 void LevelStats_incSolved(int nr)
5150 if (nr >= 0 && nr < MAX_LEVELS)
5151 level_stats[nr].solved++;
5154 void LoadUserSetup(void)
5156 // --------------------------------------------------------------------------
5157 // ~/.<program>/usersetup.conf
5158 // --------------------------------------------------------------------------
5160 char *filename = getPath2(getMainUserGameDataDir(), USERSETUP_FILENAME);
5161 SetupFileHash *user_setup_hash = NULL;
5163 // always start with reliable default values
5166 if ((user_setup_hash = loadSetupFileHash(filename)))
5170 // get last selected user number
5171 token_value = getHashEntry(user_setup_hash, TOKEN_STR_LAST_USER);
5174 user.nr = MIN(MAX(0, atoi(token_value)), MAX_PLAYER_NAMES - 1);
5176 freeSetupFileHash(user_setup_hash);
5180 Debug("setup", "using default setup values");
5186 void SaveUserSetup(void)
5188 // --------------------------------------------------------------------------
5189 // ~/.<program>/usersetup.conf
5190 // --------------------------------------------------------------------------
5192 char *filename = getPath2(getMainUserGameDataDir(), USERSETUP_FILENAME);
5195 InitMainUserDataDirectory();
5197 if (!(file = fopen(filename, MODE_WRITE)))
5199 Warn("cannot write setup file '%s'", filename);
5206 fprintFileHeader(file, USERSETUP_FILENAME);
5208 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_USER,
5212 SetFilePermissions(filename, PERMS_PRIVATE);