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;
3083 TreeInfo *addTopTreeInfoNode(TreeInfo *node_first)
3085 // add top tree node with back link node in previous tree
3086 node_first = createTopTreeInfoNode(node_first);
3088 // set all parent links (back links) in complete tree
3089 setTreeInfoParentNodes(node_first, NULL);
3095 // ----------------------------------------------------------------------------
3096 // functions for handling level and custom artwork info cache
3097 // ----------------------------------------------------------------------------
3099 static void LoadArtworkInfoCache(void)
3101 InitCacheDirectory();
3103 if (artworkinfo_cache_old == NULL)
3105 char *filename = getPath2(getCacheDir(), ARTWORKINFO_CACHE_FILE);
3107 // try to load artwork info hash from already existing cache file
3108 artworkinfo_cache_old = loadSetupFileHash(filename);
3110 // try to get program version that artwork info cache was written with
3111 char *version = getHashEntry(artworkinfo_cache_old, "program.version");
3113 // check program version of artwork info cache against current version
3114 if (!strEqual(version, program.version_string))
3116 freeSetupFileHash(artworkinfo_cache_old);
3118 artworkinfo_cache_old = NULL;
3121 // if no artwork info cache file was found, start with empty hash
3122 if (artworkinfo_cache_old == NULL)
3123 artworkinfo_cache_old = newSetupFileHash();
3128 if (artworkinfo_cache_new == NULL)
3129 artworkinfo_cache_new = newSetupFileHash();
3131 update_artworkinfo_cache = FALSE;
3134 static void SaveArtworkInfoCache(void)
3136 if (!update_artworkinfo_cache)
3139 char *filename = getPath2(getCacheDir(), ARTWORKINFO_CACHE_FILE);
3141 InitCacheDirectory();
3143 saveSetupFileHash(artworkinfo_cache_new, filename);
3148 static char *getCacheTokenPrefix(char *prefix1, char *prefix2)
3150 static char *prefix = NULL;
3152 checked_free(prefix);
3154 prefix = getStringCat2WithSeparator(prefix1, prefix2, ".");
3159 // (identical to above function, but separate string buffer needed -- nasty)
3160 static char *getCacheToken(char *prefix, char *suffix)
3162 static char *token = NULL;
3164 checked_free(token);
3166 token = getStringCat2WithSeparator(prefix, suffix, ".");
3171 static char *getFileTimestampString(char *filename)
3173 return getStringCopy(i_to_a(getFileTimestampEpochSeconds(filename)));
3176 static boolean modifiedFileTimestamp(char *filename, char *timestamp_string)
3178 struct stat file_status;
3180 if (timestamp_string == NULL)
3183 if (!fileExists(filename)) // file does not exist
3184 return (atoi(timestamp_string) != 0);
3186 if (stat(filename, &file_status) != 0) // cannot stat file
3189 return (file_status.st_mtime != atoi(timestamp_string));
3192 static TreeInfo *getArtworkInfoCacheEntry(LevelDirTree *level_node, int type)
3194 char *identifier = level_node->subdir;
3195 char *type_string = ARTWORK_DIRECTORY(type);
3196 char *token_prefix = getCacheTokenPrefix(type_string, identifier);
3197 char *token_main = getCacheToken(token_prefix, "CACHED");
3198 char *cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
3199 boolean cached = (cache_entry != NULL && strEqual(cache_entry, "true"));
3200 TreeInfo *artwork_info = NULL;
3202 if (!use_artworkinfo_cache)
3205 if (optional_tokens_hash == NULL)
3209 // create hash from list of optional tokens (for quick access)
3210 optional_tokens_hash = newSetupFileHash();
3211 for (i = 0; optional_tokens[i] != NULL; i++)
3212 setHashEntry(optional_tokens_hash, optional_tokens[i], "");
3219 artwork_info = newTreeInfo();
3220 setTreeInfoToDefaults(artwork_info, type);
3222 // set all structure fields according to the token/value pairs
3223 ldi = *artwork_info;
3224 for (i = 0; artworkinfo_tokens[i].type != -1; i++)
3226 char *token_suffix = artworkinfo_tokens[i].text;
3227 char *token = getCacheToken(token_prefix, token_suffix);
3228 char *value = getHashEntry(artworkinfo_cache_old, token);
3230 (getHashEntry(optional_tokens_hash, token_suffix) != NULL);
3232 setSetupInfo(artworkinfo_tokens, i, value);
3234 // check if cache entry for this item is mandatory, but missing
3235 if (value == NULL && !optional)
3237 Warn("missing cache entry '%s'", token);
3243 *artwork_info = ldi;
3248 char *filename_levelinfo = getPath2(getLevelDirFromTreeInfo(level_node),
3249 LEVELINFO_FILENAME);
3250 char *filename_artworkinfo = getPath2(getSetupArtworkDir(artwork_info),
3251 ARTWORKINFO_FILENAME(type));
3253 // check if corresponding "levelinfo.conf" file has changed
3254 token_main = getCacheToken(token_prefix, "TIMESTAMP_LEVELINFO");
3255 cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
3257 if (modifiedFileTimestamp(filename_levelinfo, cache_entry))
3260 // check if corresponding "<artworkinfo>.conf" file has changed
3261 token_main = getCacheToken(token_prefix, "TIMESTAMP_ARTWORKINFO");
3262 cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
3264 if (modifiedFileTimestamp(filename_artworkinfo, cache_entry))
3267 checked_free(filename_levelinfo);
3268 checked_free(filename_artworkinfo);
3271 if (!cached && artwork_info != NULL)
3273 freeTreeInfo(artwork_info);
3278 return artwork_info;
3281 static void setArtworkInfoCacheEntry(TreeInfo *artwork_info,
3282 LevelDirTree *level_node, int type)
3284 char *identifier = level_node->subdir;
3285 char *type_string = ARTWORK_DIRECTORY(type);
3286 char *token_prefix = getCacheTokenPrefix(type_string, identifier);
3287 char *token_main = getCacheToken(token_prefix, "CACHED");
3288 boolean set_cache_timestamps = TRUE;
3291 setHashEntry(artworkinfo_cache_new, token_main, "true");
3293 if (set_cache_timestamps)
3295 char *filename_levelinfo = getPath2(getLevelDirFromTreeInfo(level_node),
3296 LEVELINFO_FILENAME);
3297 char *filename_artworkinfo = getPath2(getSetupArtworkDir(artwork_info),
3298 ARTWORKINFO_FILENAME(type));
3299 char *timestamp_levelinfo = getFileTimestampString(filename_levelinfo);
3300 char *timestamp_artworkinfo = getFileTimestampString(filename_artworkinfo);
3302 token_main = getCacheToken(token_prefix, "TIMESTAMP_LEVELINFO");
3303 setHashEntry(artworkinfo_cache_new, token_main, timestamp_levelinfo);
3305 token_main = getCacheToken(token_prefix, "TIMESTAMP_ARTWORKINFO");
3306 setHashEntry(artworkinfo_cache_new, token_main, timestamp_artworkinfo);
3308 checked_free(filename_levelinfo);
3309 checked_free(filename_artworkinfo);
3310 checked_free(timestamp_levelinfo);
3311 checked_free(timestamp_artworkinfo);
3314 ldi = *artwork_info;
3315 for (i = 0; artworkinfo_tokens[i].type != -1; i++)
3317 char *token = getCacheToken(token_prefix, artworkinfo_tokens[i].text);
3318 char *value = getSetupValue(artworkinfo_tokens[i].type,
3319 artworkinfo_tokens[i].value);
3321 setHashEntry(artworkinfo_cache_new, token, value);
3326 // ----------------------------------------------------------------------------
3327 // functions for loading level info and custom artwork info
3328 // ----------------------------------------------------------------------------
3330 int GetZipFileTreeType(char *zip_filename)
3332 static char *top_dir_path = NULL;
3333 static char *top_dir_conf_filename[NUM_BASE_TREE_TYPES] = { NULL };
3334 static char *conf_basename[NUM_BASE_TREE_TYPES] =
3336 GRAPHICSINFO_FILENAME,
3337 SOUNDSINFO_FILENAME,
3343 checked_free(top_dir_path);
3344 top_dir_path = NULL;
3346 for (j = 0; j < NUM_BASE_TREE_TYPES; j++)
3348 checked_free(top_dir_conf_filename[j]);
3349 top_dir_conf_filename[j] = NULL;
3352 char **zip_entries = zip_list(zip_filename);
3354 // check if zip file successfully opened
3355 if (zip_entries == NULL || zip_entries[0] == NULL)
3356 return TREE_TYPE_UNDEFINED;
3358 // first zip file entry is expected to be top level directory
3359 char *top_dir = zip_entries[0];
3361 // check if valid top level directory found in zip file
3362 if (!strSuffix(top_dir, "/"))
3363 return TREE_TYPE_UNDEFINED;
3365 // get filenames of valid configuration files in top level directory
3366 for (j = 0; j < NUM_BASE_TREE_TYPES; j++)
3367 top_dir_conf_filename[j] = getStringCat2(top_dir, conf_basename[j]);
3369 int tree_type = TREE_TYPE_UNDEFINED;
3372 while (zip_entries[e] != NULL)
3374 // check if every zip file entry is below top level directory
3375 if (!strPrefix(zip_entries[e], top_dir))
3376 return TREE_TYPE_UNDEFINED;
3378 // check if this zip file entry is a valid configuration filename
3379 for (j = 0; j < NUM_BASE_TREE_TYPES; j++)
3381 if (strEqual(zip_entries[e], top_dir_conf_filename[j]))
3383 // only exactly one valid configuration file allowed
3384 if (tree_type != TREE_TYPE_UNDEFINED)
3385 return TREE_TYPE_UNDEFINED;
3397 static boolean CheckZipFileForDirectory(char *zip_filename, char *directory,
3400 static char *top_dir_path = NULL;
3401 static char *top_dir_conf_filename = NULL;
3403 checked_free(top_dir_path);
3404 checked_free(top_dir_conf_filename);
3406 top_dir_path = NULL;
3407 top_dir_conf_filename = NULL;
3409 char *conf_basename = (tree_type == TREE_TYPE_LEVEL_DIR ? LEVELINFO_FILENAME :
3410 ARTWORKINFO_FILENAME(tree_type));
3412 // check if valid configuration filename determined
3413 if (conf_basename == NULL || strEqual(conf_basename, ""))
3416 char **zip_entries = zip_list(zip_filename);
3418 // check if zip file successfully opened
3419 if (zip_entries == NULL || zip_entries[0] == NULL)
3422 // first zip file entry is expected to be top level directory
3423 char *top_dir = zip_entries[0];
3425 // check if valid top level directory found in zip file
3426 if (!strSuffix(top_dir, "/"))
3429 // get path of extracted top level directory
3430 top_dir_path = getPath2(directory, top_dir);
3432 // remove trailing directory separator from top level directory path
3433 // (required to be able to check for file and directory in next step)
3434 top_dir_path[strlen(top_dir_path) - 1] = '\0';
3436 // check if zip file's top level directory already exists in target directory
3437 if (fileExists(top_dir_path)) // (checks for file and directory)
3440 // get filename of configuration file in top level directory
3441 top_dir_conf_filename = getStringCat2(top_dir, conf_basename);
3443 boolean found_top_dir_conf_filename = FALSE;
3446 while (zip_entries[i] != NULL)
3448 // check if every zip file entry is below top level directory
3449 if (!strPrefix(zip_entries[i], top_dir))
3452 // check if this zip file entry is the configuration filename
3453 if (strEqual(zip_entries[i], top_dir_conf_filename))
3454 found_top_dir_conf_filename = TRUE;
3459 // check if valid configuration filename was found in zip file
3460 if (!found_top_dir_conf_filename)
3466 char *ExtractZipFileIntoDirectory(char *zip_filename, char *directory,
3469 boolean zip_file_valid = CheckZipFileForDirectory(zip_filename, directory,
3472 if (!zip_file_valid)
3474 Warn("zip file '%s' rejected!", zip_filename);
3479 char **zip_entries = zip_extract(zip_filename, directory);
3481 if (zip_entries == NULL)
3483 Warn("zip file '%s' could not be extracted!", zip_filename);
3488 Info("zip file '%s' successfully extracted!", zip_filename);
3490 // first zip file entry contains top level directory
3491 char *top_dir = zip_entries[0];
3493 // remove trailing directory separator from top level directory
3494 top_dir[strlen(top_dir) - 1] = '\0';
3499 static void ProcessZipFilesInDirectory(char *directory, int tree_type)
3502 DirectoryEntry *dir_entry;
3504 if ((dir = openDirectory(directory)) == NULL)
3506 // display error if directory is main "options.graphics_directory" etc.
3507 if (tree_type == TREE_TYPE_LEVEL_DIR ||
3508 directory == OPTIONS_ARTWORK_DIRECTORY(tree_type))
3509 Warn("cannot read directory '%s'", directory);
3514 while ((dir_entry = readDirectory(dir)) != NULL) // loop all entries
3516 // skip non-zip files (and also directories with zip extension)
3517 if (!strSuffixLower(dir_entry->basename, ".zip") || dir_entry->is_directory)
3520 char *zip_filename = getPath2(directory, dir_entry->basename);
3521 char *zip_filename_extracted = getStringCat2(zip_filename, ".extracted");
3522 char *zip_filename_rejected = getStringCat2(zip_filename, ".rejected");
3524 // check if zip file hasn't already been extracted or rejected
3525 if (!fileExists(zip_filename_extracted) &&
3526 !fileExists(zip_filename_rejected))
3528 char *top_dir = ExtractZipFileIntoDirectory(zip_filename, directory,
3530 char *marker_filename = (top_dir != NULL ? zip_filename_extracted :
3531 zip_filename_rejected);
3534 // create empty file to mark zip file as extracted or rejected
3535 if ((marker_file = fopen(marker_filename, MODE_WRITE)))
3536 fclose(marker_file);
3539 free(zip_filename_extracted);
3540 free(zip_filename_rejected);
3544 closeDirectory(dir);
3547 // forward declaration for recursive call by "LoadLevelInfoFromLevelDir()"
3548 static void LoadLevelInfoFromLevelDir(TreeInfo **, TreeInfo *, char *);
3550 static boolean LoadLevelInfoFromLevelConf(TreeInfo **node_first,
3551 TreeInfo *node_parent,
3552 char *level_directory,
3553 char *directory_name)
3555 char *directory_path = getPath2(level_directory, directory_name);
3556 char *filename = getPath2(directory_path, LEVELINFO_FILENAME);
3557 SetupFileHash *setup_file_hash;
3558 LevelDirTree *leveldir_new = NULL;
3561 // unless debugging, silently ignore directories without "levelinfo.conf"
3562 if (!options.debug && !fileExists(filename))
3564 free(directory_path);
3570 setup_file_hash = loadSetupFileHash(filename);
3572 if (setup_file_hash == NULL)
3574 #if DEBUG_NO_CONFIG_FILE
3575 Debug("setup", "ignoring level directory '%s'", directory_path);
3578 free(directory_path);
3584 leveldir_new = newTreeInfo();
3587 setTreeInfoToDefaultsFromParent(leveldir_new, node_parent);
3589 setTreeInfoToDefaults(leveldir_new, TREE_TYPE_LEVEL_DIR);
3591 leveldir_new->subdir = getStringCopy(directory_name);
3593 // set all structure fields according to the token/value pairs
3594 ldi = *leveldir_new;
3595 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
3596 setSetupInfo(levelinfo_tokens, i,
3597 getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
3598 *leveldir_new = ldi;
3600 if (strEqual(leveldir_new->name, ANONYMOUS_NAME))
3601 setString(&leveldir_new->name, leveldir_new->subdir);
3603 if (leveldir_new->identifier == NULL)
3604 leveldir_new->identifier = getStringCopy(leveldir_new->subdir);
3606 if (leveldir_new->name_sorting == NULL)
3607 leveldir_new->name_sorting = getStringCopy(leveldir_new->name);
3609 if (node_parent == NULL) // top level group
3611 leveldir_new->basepath = getStringCopy(level_directory);
3612 leveldir_new->fullpath = getStringCopy(leveldir_new->subdir);
3614 else // sub level group
3616 leveldir_new->basepath = getStringCopy(node_parent->basepath);
3617 leveldir_new->fullpath = getPath2(node_parent->fullpath, directory_name);
3620 leveldir_new->last_level =
3621 leveldir_new->first_level + leveldir_new->levels - 1;
3623 leveldir_new->in_user_dir =
3624 (!strEqual(leveldir_new->basepath, options.level_directory));
3626 // adjust some settings if user's private level directory was detected
3627 if (leveldir_new->sort_priority == LEVELCLASS_UNDEFINED &&
3628 leveldir_new->in_user_dir &&
3629 (strEqual(leveldir_new->subdir, getLoginName()) ||
3630 strEqual(leveldir_new->name, getLoginName()) ||
3631 strEqual(leveldir_new->author, getRealName())))
3633 leveldir_new->sort_priority = LEVELCLASS_PRIVATE_START;
3634 leveldir_new->readonly = FALSE;
3637 leveldir_new->user_defined =
3638 (leveldir_new->in_user_dir && IS_LEVELCLASS_PRIVATE(leveldir_new));
3640 setString(&leveldir_new->class_desc, getLevelClassDescription(leveldir_new));
3642 leveldir_new->handicap_level = // set handicap to default value
3643 (leveldir_new->user_defined || !leveldir_new->handicap ?
3644 leveldir_new->last_level : leveldir_new->first_level);
3646 DrawInitTextItem(leveldir_new->name);
3648 pushTreeInfo(node_first, leveldir_new);
3650 freeSetupFileHash(setup_file_hash);
3652 if (leveldir_new->level_group)
3654 // create node to link back to current level directory
3655 createParentTreeInfoNode(leveldir_new);
3657 // recursively step into sub-directory and look for more level series
3658 LoadLevelInfoFromLevelDir(&leveldir_new->node_group,
3659 leveldir_new, directory_path);
3662 free(directory_path);
3668 static void LoadLevelInfoFromLevelDir(TreeInfo **node_first,
3669 TreeInfo *node_parent,
3670 char *level_directory)
3672 // ---------- 1st stage: process any level set zip files ----------
3674 ProcessZipFilesInDirectory(level_directory, TREE_TYPE_LEVEL_DIR);
3676 // ---------- 2nd stage: check for level set directories ----------
3679 DirectoryEntry *dir_entry;
3680 boolean valid_entry_found = FALSE;
3682 if ((dir = openDirectory(level_directory)) == NULL)
3684 Warn("cannot read level directory '%s'", level_directory);
3689 while ((dir_entry = readDirectory(dir)) != NULL) // loop all entries
3691 char *directory_name = dir_entry->basename;
3692 char *directory_path = getPath2(level_directory, directory_name);
3694 // skip entries for current and parent directory
3695 if (strEqual(directory_name, ".") ||
3696 strEqual(directory_name, ".."))
3698 free(directory_path);
3703 // find out if directory entry is itself a directory
3704 if (!dir_entry->is_directory) // not a directory
3706 free(directory_path);
3711 free(directory_path);
3713 if (strEqual(directory_name, GRAPHICS_DIRECTORY) ||
3714 strEqual(directory_name, SOUNDS_DIRECTORY) ||
3715 strEqual(directory_name, MUSIC_DIRECTORY))
3718 valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
3723 closeDirectory(dir);
3725 // special case: top level directory may directly contain "levelinfo.conf"
3726 if (node_parent == NULL && !valid_entry_found)
3728 // check if this directory directly contains a file "levelinfo.conf"
3729 valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
3730 level_directory, ".");
3733 if (!valid_entry_found)
3734 Warn("cannot find any valid level series in directory '%s'",
3738 boolean AdjustGraphicsForEMC(void)
3740 boolean settings_changed = FALSE;
3742 settings_changed |= adjustTreeGraphicsForEMC(leveldir_first_all);
3743 settings_changed |= adjustTreeGraphicsForEMC(leveldir_first);
3745 return settings_changed;
3748 boolean AdjustSoundsForEMC(void)
3750 boolean settings_changed = FALSE;
3752 settings_changed |= adjustTreeSoundsForEMC(leveldir_first_all);
3753 settings_changed |= adjustTreeSoundsForEMC(leveldir_first);
3755 return settings_changed;
3758 void LoadLevelInfo(void)
3760 InitUserLevelDirectory(getLoginName());
3762 DrawInitTextHead("Loading level series");
3764 LoadLevelInfoFromLevelDir(&leveldir_first, NULL, options.level_directory);
3765 LoadLevelInfoFromLevelDir(&leveldir_first, NULL, getUserLevelDir(NULL));
3767 leveldir_first = createTopTreeInfoNode(leveldir_first);
3769 /* after loading all level set information, clone the level directory tree
3770 and remove all level sets without levels (these may still contain artwork
3771 to be offered in the setup menu as "custom artwork", and are therefore
3772 checked for existing artwork in the function "LoadLevelArtworkInfo()") */
3773 leveldir_first_all = leveldir_first;
3774 cloneTree(&leveldir_first, leveldir_first_all, TRUE);
3776 AdjustGraphicsForEMC();
3777 AdjustSoundsForEMC();
3779 // before sorting, the first entries will be from the user directory
3780 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
3782 if (leveldir_first == NULL)
3783 Fail("cannot find any valid level series in any directory");
3785 sortTreeInfo(&leveldir_first);
3787 #if ENABLE_UNUSED_CODE
3788 dumpTreeInfo(leveldir_first, 0);
3792 static boolean LoadArtworkInfoFromArtworkConf(TreeInfo **node_first,
3793 TreeInfo *node_parent,
3794 char *base_directory,
3795 char *directory_name, int type)
3797 char *directory_path = getPath2(base_directory, directory_name);
3798 char *filename = getPath2(directory_path, ARTWORKINFO_FILENAME(type));
3799 SetupFileHash *setup_file_hash = NULL;
3800 TreeInfo *artwork_new = NULL;
3803 if (fileExists(filename))
3804 setup_file_hash = loadSetupFileHash(filename);
3806 if (setup_file_hash == NULL) // no config file -- look for artwork files
3809 DirectoryEntry *dir_entry;
3810 boolean valid_file_found = FALSE;
3812 if ((dir = openDirectory(directory_path)) != NULL)
3814 while ((dir_entry = readDirectory(dir)) != NULL)
3816 if (FileIsArtworkType(dir_entry->filename, type))
3818 valid_file_found = TRUE;
3824 closeDirectory(dir);
3827 if (!valid_file_found)
3829 #if DEBUG_NO_CONFIG_FILE
3830 if (!strEqual(directory_name, "."))
3831 Debug("setup", "ignoring artwork directory '%s'", directory_path);
3834 free(directory_path);
3841 artwork_new = newTreeInfo();
3844 setTreeInfoToDefaultsFromParent(artwork_new, node_parent);
3846 setTreeInfoToDefaults(artwork_new, type);
3848 artwork_new->subdir = getStringCopy(directory_name);
3850 if (setup_file_hash) // (before defining ".color" and ".class_desc")
3852 // set all structure fields according to the token/value pairs
3854 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
3855 setSetupInfo(levelinfo_tokens, i,
3856 getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
3859 if (strEqual(artwork_new->name, ANONYMOUS_NAME))
3860 setString(&artwork_new->name, artwork_new->subdir);
3862 if (artwork_new->identifier == NULL)
3863 artwork_new->identifier = getStringCopy(artwork_new->subdir);
3865 if (artwork_new->name_sorting == NULL)
3866 artwork_new->name_sorting = getStringCopy(artwork_new->name);
3869 if (node_parent == NULL) // top level group
3871 artwork_new->basepath = getStringCopy(base_directory);
3872 artwork_new->fullpath = getStringCopy(artwork_new->subdir);
3874 else // sub level group
3876 artwork_new->basepath = getStringCopy(node_parent->basepath);
3877 artwork_new->fullpath = getPath2(node_parent->fullpath, directory_name);
3880 artwork_new->in_user_dir =
3881 (!strEqual(artwork_new->basepath, OPTIONS_ARTWORK_DIRECTORY(type)));
3883 setString(&artwork_new->class_desc, getLevelClassDescription(artwork_new));
3885 if (setup_file_hash == NULL) // (after determining ".user_defined")
3887 if (strEqual(artwork_new->subdir, "."))
3889 if (artwork_new->user_defined)
3891 setString(&artwork_new->identifier, "private");
3892 artwork_new->sort_priority = ARTWORKCLASS_PRIVATE;
3896 setString(&artwork_new->identifier, "classic");
3897 artwork_new->sort_priority = ARTWORKCLASS_CLASSICS;
3900 setString(&artwork_new->class_desc,
3901 getLevelClassDescription(artwork_new));
3905 setString(&artwork_new->identifier, artwork_new->subdir);
3908 setString(&artwork_new->name, artwork_new->identifier);
3909 setString(&artwork_new->name_sorting, artwork_new->name);
3912 pushTreeInfo(node_first, artwork_new);
3914 freeSetupFileHash(setup_file_hash);
3916 free(directory_path);
3922 static void LoadArtworkInfoFromArtworkDir(TreeInfo **node_first,
3923 TreeInfo *node_parent,
3924 char *base_directory, int type)
3926 // ---------- 1st stage: process any artwork set zip files ----------
3928 ProcessZipFilesInDirectory(base_directory, type);
3930 // ---------- 2nd stage: check for artwork set directories ----------
3933 DirectoryEntry *dir_entry;
3934 boolean valid_entry_found = FALSE;
3936 if ((dir = openDirectory(base_directory)) == NULL)
3938 // display error if directory is main "options.graphics_directory" etc.
3939 if (base_directory == OPTIONS_ARTWORK_DIRECTORY(type))
3940 Warn("cannot read directory '%s'", base_directory);
3945 while ((dir_entry = readDirectory(dir)) != NULL) // loop all entries
3947 char *directory_name = dir_entry->basename;
3948 char *directory_path = getPath2(base_directory, directory_name);
3950 // skip directory entries for current and parent directory
3951 if (strEqual(directory_name, ".") ||
3952 strEqual(directory_name, ".."))
3954 free(directory_path);
3959 // skip directory entries which are not a directory
3960 if (!dir_entry->is_directory) // not a directory
3962 free(directory_path);
3967 free(directory_path);
3969 // check if this directory contains artwork with or without config file
3970 valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
3972 directory_name, type);
3975 closeDirectory(dir);
3977 // check if this directory directly contains artwork itself
3978 valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
3979 base_directory, ".",
3981 if (!valid_entry_found)
3982 Warn("cannot find any valid artwork in directory '%s'", base_directory);
3985 static TreeInfo *getDummyArtworkInfo(int type)
3987 // this is only needed when there is completely no artwork available
3988 TreeInfo *artwork_new = newTreeInfo();
3990 setTreeInfoToDefaults(artwork_new, type);
3992 setString(&artwork_new->subdir, UNDEFINED_FILENAME);
3993 setString(&artwork_new->fullpath, UNDEFINED_FILENAME);
3994 setString(&artwork_new->basepath, UNDEFINED_FILENAME);
3996 setString(&artwork_new->identifier, UNDEFINED_FILENAME);
3997 setString(&artwork_new->name, UNDEFINED_FILENAME);
3998 setString(&artwork_new->name_sorting, UNDEFINED_FILENAME);
4003 void SetCurrentArtwork(int type)
4005 ArtworkDirTree **current_ptr = ARTWORK_CURRENT_PTR(artwork, type);
4006 ArtworkDirTree *first_node = ARTWORK_FIRST_NODE(artwork, type);
4007 char *setup_set = SETUP_ARTWORK_SET(setup, type);
4008 char *default_subdir = ARTWORK_DEFAULT_SUBDIR(type);
4010 // set current artwork to artwork configured in setup menu
4011 *current_ptr = getTreeInfoFromIdentifier(first_node, setup_set);
4013 // if not found, set current artwork to default artwork
4014 if (*current_ptr == NULL)
4015 *current_ptr = getTreeInfoFromIdentifier(first_node, default_subdir);
4017 // if not found, set current artwork to first artwork in tree
4018 if (*current_ptr == NULL)
4019 *current_ptr = getFirstValidTreeInfoEntry(first_node);
4022 void ChangeCurrentArtworkIfNeeded(int type)
4024 char *current_identifier = ARTWORK_CURRENT_IDENTIFIER(artwork, type);
4025 char *setup_set = SETUP_ARTWORK_SET(setup, type);
4027 if (!strEqual(current_identifier, setup_set))
4028 SetCurrentArtwork(type);
4031 void LoadArtworkInfo(void)
4033 LoadArtworkInfoCache();
4035 DrawInitTextHead("Looking for custom artwork");
4037 LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
4038 options.graphics_directory,
4039 TREE_TYPE_GRAPHICS_DIR);
4040 LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
4041 getUserGraphicsDir(),
4042 TREE_TYPE_GRAPHICS_DIR);
4044 LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
4045 options.sounds_directory,
4046 TREE_TYPE_SOUNDS_DIR);
4047 LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
4049 TREE_TYPE_SOUNDS_DIR);
4051 LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
4052 options.music_directory,
4053 TREE_TYPE_MUSIC_DIR);
4054 LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
4056 TREE_TYPE_MUSIC_DIR);
4058 if (artwork.gfx_first == NULL)
4059 artwork.gfx_first = getDummyArtworkInfo(TREE_TYPE_GRAPHICS_DIR);
4060 if (artwork.snd_first == NULL)
4061 artwork.snd_first = getDummyArtworkInfo(TREE_TYPE_SOUNDS_DIR);
4062 if (artwork.mus_first == NULL)
4063 artwork.mus_first = getDummyArtworkInfo(TREE_TYPE_MUSIC_DIR);
4065 // before sorting, the first entries will be from the user directory
4066 SetCurrentArtwork(ARTWORK_TYPE_GRAPHICS);
4067 SetCurrentArtwork(ARTWORK_TYPE_SOUNDS);
4068 SetCurrentArtwork(ARTWORK_TYPE_MUSIC);
4070 artwork.gfx_current_identifier = artwork.gfx_current->identifier;
4071 artwork.snd_current_identifier = artwork.snd_current->identifier;
4072 artwork.mus_current_identifier = artwork.mus_current->identifier;
4074 #if ENABLE_UNUSED_CODE
4075 Debug("setup:LoadArtworkInfo", "graphics set == %s",
4076 artwork.gfx_current_identifier);
4077 Debug("setup:LoadArtworkInfo", "sounds set == %s",
4078 artwork.snd_current_identifier);
4079 Debug("setup:LoadArtworkInfo", "music set == %s",
4080 artwork.mus_current_identifier);
4083 sortTreeInfo(&artwork.gfx_first);
4084 sortTreeInfo(&artwork.snd_first);
4085 sortTreeInfo(&artwork.mus_first);
4087 #if ENABLE_UNUSED_CODE
4088 dumpTreeInfo(artwork.gfx_first, 0);
4089 dumpTreeInfo(artwork.snd_first, 0);
4090 dumpTreeInfo(artwork.mus_first, 0);
4094 static void MoveArtworkInfoIntoSubTree(ArtworkDirTree **artwork_node)
4096 ArtworkDirTree *artwork_new = newTreeInfo();
4097 char *top_node_name = "standalone artwork";
4099 setTreeInfoToDefaults(artwork_new, (*artwork_node)->type);
4101 artwork_new->level_group = TRUE;
4103 setString(&artwork_new->identifier, top_node_name);
4104 setString(&artwork_new->name, top_node_name);
4105 setString(&artwork_new->name_sorting, top_node_name);
4107 // create node to link back to current custom artwork directory
4108 createParentTreeInfoNode(artwork_new);
4110 // move existing custom artwork tree into newly created sub-tree
4111 artwork_new->node_group->next = *artwork_node;
4113 // change custom artwork tree to contain only newly created node
4114 *artwork_node = artwork_new;
4117 static void LoadArtworkInfoFromLevelInfoExt(ArtworkDirTree **artwork_node,
4118 ArtworkDirTree *node_parent,
4119 LevelDirTree *level_node,
4120 boolean empty_level_set_mode)
4122 int type = (*artwork_node)->type;
4124 // recursively check all level directories for artwork sub-directories
4128 boolean empty_level_set = (level_node->levels == 0);
4130 // check all tree entries for artwork, but skip parent link entries
4131 if (!level_node->parent_link && empty_level_set == empty_level_set_mode)
4133 TreeInfo *artwork_new = getArtworkInfoCacheEntry(level_node, type);
4134 boolean cached = (artwork_new != NULL);
4138 pushTreeInfo(artwork_node, artwork_new);
4142 TreeInfo *topnode_last = *artwork_node;
4143 char *path = getPath2(getLevelDirFromTreeInfo(level_node),
4144 ARTWORK_DIRECTORY(type));
4146 LoadArtworkInfoFromArtworkDir(artwork_node, NULL, path, type);
4148 if (topnode_last != *artwork_node) // check for newly added node
4150 artwork_new = *artwork_node;
4152 setString(&artwork_new->identifier, level_node->subdir);
4153 setString(&artwork_new->name, level_node->name);
4154 setString(&artwork_new->name_sorting, level_node->name_sorting);
4156 artwork_new->sort_priority = level_node->sort_priority;
4157 artwork_new->in_user_dir = level_node->in_user_dir;
4159 update_artworkinfo_cache = TRUE;
4165 // insert artwork info (from old cache or filesystem) into new cache
4166 if (artwork_new != NULL)
4167 setArtworkInfoCacheEntry(artwork_new, level_node, type);
4170 DrawInitTextItem(level_node->name);
4172 if (level_node->node_group != NULL)
4174 TreeInfo *artwork_new = newTreeInfo();
4177 setTreeInfoToDefaultsFromParent(artwork_new, node_parent);
4179 setTreeInfoToDefaults(artwork_new, type);
4181 artwork_new->level_group = TRUE;
4183 setString(&artwork_new->identifier, level_node->subdir);
4185 if (node_parent == NULL) // check for top tree node
4187 char *top_node_name = (empty_level_set_mode ?
4188 "artwork for certain level sets" :
4189 "artwork included in level sets");
4191 setString(&artwork_new->name, top_node_name);
4192 setString(&artwork_new->name_sorting, top_node_name);
4196 setString(&artwork_new->name, level_node->name);
4197 setString(&artwork_new->name_sorting, level_node->name_sorting);
4200 pushTreeInfo(artwork_node, artwork_new);
4202 // create node to link back to current custom artwork directory
4203 createParentTreeInfoNode(artwork_new);
4205 // recursively step into sub-directory and look for more custom artwork
4206 LoadArtworkInfoFromLevelInfoExt(&artwork_new->node_group, artwork_new,
4207 level_node->node_group,
4208 empty_level_set_mode);
4210 // if sub-tree has no custom artwork at all, remove it
4211 if (artwork_new->node_group->next == NULL)
4212 removeTreeInfo(artwork_node);
4215 level_node = level_node->next;
4219 static void LoadArtworkInfoFromLevelInfo(ArtworkDirTree **artwork_node)
4221 // move peviously loaded artwork tree into separate sub-tree
4222 MoveArtworkInfoIntoSubTree(artwork_node);
4224 // load artwork from level sets into separate sub-trees
4225 LoadArtworkInfoFromLevelInfoExt(artwork_node, NULL, leveldir_first_all, TRUE);
4226 LoadArtworkInfoFromLevelInfoExt(artwork_node, NULL, leveldir_first_all, FALSE);
4228 // add top tree node over all sub-trees and set parent links
4229 *artwork_node = addTopTreeInfoNode(*artwork_node);
4232 void LoadLevelArtworkInfo(void)
4234 print_timestamp_init("LoadLevelArtworkInfo");
4236 DrawInitTextHead("Looking for custom level artwork");
4238 print_timestamp_time("DrawTimeText");
4240 LoadArtworkInfoFromLevelInfo(&artwork.gfx_first);
4241 print_timestamp_time("LoadArtworkInfoFromLevelInfo (gfx)");
4242 LoadArtworkInfoFromLevelInfo(&artwork.snd_first);
4243 print_timestamp_time("LoadArtworkInfoFromLevelInfo (snd)");
4244 LoadArtworkInfoFromLevelInfo(&artwork.mus_first);
4245 print_timestamp_time("LoadArtworkInfoFromLevelInfo (mus)");
4247 SaveArtworkInfoCache();
4249 print_timestamp_time("SaveArtworkInfoCache");
4251 // needed for reloading level artwork not known at ealier stage
4252 ChangeCurrentArtworkIfNeeded(ARTWORK_TYPE_GRAPHICS);
4253 ChangeCurrentArtworkIfNeeded(ARTWORK_TYPE_SOUNDS);
4254 ChangeCurrentArtworkIfNeeded(ARTWORK_TYPE_MUSIC);
4256 print_timestamp_time("getTreeInfoFromIdentifier");
4258 sortTreeInfo(&artwork.gfx_first);
4259 sortTreeInfo(&artwork.snd_first);
4260 sortTreeInfo(&artwork.mus_first);
4262 print_timestamp_time("sortTreeInfo");
4264 #if ENABLE_UNUSED_CODE
4265 dumpTreeInfo(artwork.gfx_first, 0);
4266 dumpTreeInfo(artwork.snd_first, 0);
4267 dumpTreeInfo(artwork.mus_first, 0);
4270 print_timestamp_done("LoadLevelArtworkInfo");
4273 static boolean AddTreeSetToTreeInfoExt(TreeInfo *tree_node_old, char *tree_dir,
4274 char *tree_subdir_new, int type)
4276 if (tree_node_old == NULL)
4278 if (type == TREE_TYPE_LEVEL_DIR)
4280 // get level info tree node of personal user level set
4281 tree_node_old = getTreeInfoFromIdentifier(leveldir_first, getLoginName());
4283 // this may happen if "setup.internal.create_user_levelset" is FALSE
4284 // or if file "levelinfo.conf" is missing in personal user level set
4285 if (tree_node_old == NULL)
4286 tree_node_old = leveldir_first->node_group;
4290 // get artwork info tree node of first artwork set
4291 tree_node_old = ARTWORK_FIRST_NODE(artwork, type);
4295 if (tree_dir == NULL)
4296 tree_dir = TREE_USERDIR(type);
4298 if (tree_node_old == NULL ||
4300 tree_subdir_new == NULL) // should not happen
4303 int draw_deactivation_mask = GetDrawDeactivationMask();
4305 // override draw deactivation mask (temporarily disable drawing)
4306 SetDrawDeactivationMask(REDRAW_ALL);
4308 if (type == TREE_TYPE_LEVEL_DIR)
4310 // load new level set config and add it next to first user level set
4311 LoadLevelInfoFromLevelConf(&tree_node_old->next,
4312 tree_node_old->node_parent,
4313 tree_dir, tree_subdir_new);
4317 // load new artwork set config and add it next to first artwork set
4318 LoadArtworkInfoFromArtworkConf(&tree_node_old->next,
4319 tree_node_old->node_parent,
4320 tree_dir, tree_subdir_new, type);
4323 // set draw deactivation mask to previous value
4324 SetDrawDeactivationMask(draw_deactivation_mask);
4326 // get first node of level or artwork info tree
4327 TreeInfo **tree_node_first = TREE_FIRST_NODE_PTR(type);
4329 // get tree info node of newly added level or artwork set
4330 TreeInfo *tree_node_new = getTreeInfoFromIdentifier(*tree_node_first,
4333 if (tree_node_new == NULL) // should not happen
4336 // correct top link and parent node link of newly created tree node
4337 tree_node_new->node_top = tree_node_old->node_top;
4338 tree_node_new->node_parent = tree_node_old->node_parent;
4340 // sort tree info to adjust position of newly added tree set
4341 sortTreeInfo(tree_node_first);
4346 void AddTreeSetToTreeInfo(TreeInfo *tree_node, char *tree_dir,
4347 char *tree_subdir_new, int type)
4349 if (!AddTreeSetToTreeInfoExt(tree_node, tree_dir, tree_subdir_new, type))
4350 Fail("internal tree info structure corrupted -- aborting");
4353 void AddUserLevelSetToLevelInfo(char *level_subdir_new)
4355 AddTreeSetToTreeInfo(NULL, NULL, level_subdir_new, TREE_TYPE_LEVEL_DIR);
4358 char *getArtworkIdentifierForUserLevelSet(int type)
4360 char *classic_artwork_set = getClassicArtworkSet(type);
4362 // check for custom artwork configured in "levelinfo.conf"
4363 char *leveldir_artwork_set =
4364 *LEVELDIR_ARTWORK_SET_PTR(leveldir_current, type);
4365 boolean has_leveldir_artwork_set =
4366 (leveldir_artwork_set != NULL && !strEqual(leveldir_artwork_set,
4367 classic_artwork_set));
4369 // check for custom artwork in sub-directory "graphics" etc.
4370 TreeInfo *artwork_first_node = ARTWORK_FIRST_NODE(artwork, type);
4371 char *leveldir_identifier = leveldir_current->identifier;
4372 boolean has_artwork_subdir =
4373 (getTreeInfoFromIdentifier(artwork_first_node,
4374 leveldir_identifier) != NULL);
4376 return (has_leveldir_artwork_set ? leveldir_artwork_set :
4377 has_artwork_subdir ? leveldir_identifier :
4378 classic_artwork_set);
4381 TreeInfo *getArtworkTreeInfoForUserLevelSet(int type)
4383 char *artwork_set = getArtworkIdentifierForUserLevelSet(type);
4384 TreeInfo *artwork_first_node = ARTWORK_FIRST_NODE(artwork, type);
4385 TreeInfo *ti = getTreeInfoFromIdentifier(artwork_first_node, artwork_set);
4389 ti = getTreeInfoFromIdentifier(artwork_first_node,
4390 ARTWORK_DEFAULT_SUBDIR(type));
4392 Fail("cannot find default graphics -- should not happen");
4398 boolean checkIfCustomArtworkExistsForCurrentLevelSet(void)
4400 char *graphics_set =
4401 getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_GRAPHICS);
4403 getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_SOUNDS);
4405 getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_MUSIC);
4407 return (!strEqual(graphics_set, GFX_CLASSIC_SUBDIR) ||
4408 !strEqual(sounds_set, SND_CLASSIC_SUBDIR) ||
4409 !strEqual(music_set, MUS_CLASSIC_SUBDIR));
4412 boolean UpdateUserLevelSet(char *level_subdir, char *level_name,
4413 char *level_author, int num_levels)
4415 char *filename = getPath2(getUserLevelDir(level_subdir), LEVELINFO_FILENAME);
4416 char *filename_tmp = getStringCat2(filename, ".tmp");
4418 FILE *file_tmp = NULL;
4419 char line[MAX_LINE_LEN];
4420 boolean success = FALSE;
4421 LevelDirTree *leveldir = getTreeInfoFromIdentifier(leveldir_first,
4423 // update values in level directory tree
4425 if (level_name != NULL)
4426 setString(&leveldir->name, level_name);
4428 if (level_author != NULL)
4429 setString(&leveldir->author, level_author);
4431 if (num_levels != -1)
4432 leveldir->levels = num_levels;
4434 // update values that depend on other values
4436 setString(&leveldir->name_sorting, leveldir->name);
4438 leveldir->last_level = leveldir->first_level + leveldir->levels - 1;
4440 // sort order of level sets may have changed
4441 sortTreeInfo(&leveldir_first);
4443 if ((file = fopen(filename, MODE_READ)) &&
4444 (file_tmp = fopen(filename_tmp, MODE_WRITE)))
4446 while (fgets(line, MAX_LINE_LEN, file))
4448 if (strPrefix(line, "name:") && level_name != NULL)
4449 fprintf(file_tmp, "%-32s%s\n", "name:", level_name);
4450 else if (strPrefix(line, "author:") && level_author != NULL)
4451 fprintf(file_tmp, "%-32s%s\n", "author:", level_author);
4452 else if (strPrefix(line, "levels:") && num_levels != -1)
4453 fprintf(file_tmp, "%-32s%d\n", "levels:", num_levels);
4455 fputs(line, file_tmp);
4468 success = (rename(filename_tmp, filename) == 0);
4476 boolean CreateUserLevelSet(char *level_subdir, char *level_name,
4477 char *level_author, int num_levels,
4478 boolean use_artwork_set)
4480 LevelDirTree *level_info;
4485 // create user level sub-directory, if needed
4486 createDirectory(getUserLevelDir(level_subdir), "user level", PERMS_PRIVATE);
4488 filename = getPath2(getUserLevelDir(level_subdir), LEVELINFO_FILENAME);
4490 if (!(file = fopen(filename, MODE_WRITE)))
4492 Warn("cannot write level info file '%s'", filename);
4499 level_info = newTreeInfo();
4501 // always start with reliable default values
4502 setTreeInfoToDefaults(level_info, TREE_TYPE_LEVEL_DIR);
4504 setString(&level_info->name, level_name);
4505 setString(&level_info->author, level_author);
4506 level_info->levels = num_levels;
4507 level_info->first_level = 1;
4508 level_info->sort_priority = LEVELCLASS_PRIVATE_START;
4509 level_info->readonly = FALSE;
4511 if (use_artwork_set)
4513 level_info->graphics_set =
4514 getStringCopy(getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_GRAPHICS));
4515 level_info->sounds_set =
4516 getStringCopy(getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_SOUNDS));
4517 level_info->music_set =
4518 getStringCopy(getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_MUSIC));
4521 token_value_position = TOKEN_VALUE_POSITION_SHORT;
4523 fprintFileHeader(file, LEVELINFO_FILENAME);
4526 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
4528 if (i == LEVELINFO_TOKEN_NAME ||
4529 i == LEVELINFO_TOKEN_AUTHOR ||
4530 i == LEVELINFO_TOKEN_LEVELS ||
4531 i == LEVELINFO_TOKEN_FIRST_LEVEL ||
4532 i == LEVELINFO_TOKEN_SORT_PRIORITY ||
4533 i == LEVELINFO_TOKEN_READONLY ||
4534 (use_artwork_set && (i == LEVELINFO_TOKEN_GRAPHICS_SET ||
4535 i == LEVELINFO_TOKEN_SOUNDS_SET ||
4536 i == LEVELINFO_TOKEN_MUSIC_SET)))
4537 fprintf(file, "%s\n", getSetupLine(levelinfo_tokens, "", i));
4539 // just to make things nicer :)
4540 if (i == LEVELINFO_TOKEN_AUTHOR ||
4541 i == LEVELINFO_TOKEN_FIRST_LEVEL ||
4542 (use_artwork_set && i == LEVELINFO_TOKEN_READONLY))
4543 fprintf(file, "\n");
4546 token_value_position = TOKEN_VALUE_POSITION_DEFAULT;
4550 SetFilePermissions(filename, PERMS_PRIVATE);
4552 freeTreeInfo(level_info);
4558 static void SaveUserLevelInfo(void)
4560 CreateUserLevelSet(getLoginName(), getLoginName(), getRealName(), 100, FALSE);
4563 char *getSetupValue(int type, void *value)
4565 static char value_string[MAX_LINE_LEN];
4573 strcpy(value_string, (*(boolean *)value ? "true" : "false"));
4577 strcpy(value_string, (*(boolean *)value ? "on" : "off"));
4581 strcpy(value_string, (*(int *)value == AUTO ? "auto" :
4582 *(int *)value == FALSE ? "off" : "on"));
4586 strcpy(value_string, (*(boolean *)value ? "yes" : "no"));
4589 case TYPE_YES_NO_AUTO:
4590 strcpy(value_string, (*(int *)value == AUTO ? "auto" :
4591 *(int *)value == FALSE ? "no" : "yes"));
4595 strcpy(value_string, (*(boolean *)value ? "AGA" : "ECS"));
4599 strcpy(value_string, getKeyNameFromKey(*(Key *)value));
4603 strcpy(value_string, getX11KeyNameFromKey(*(Key *)value));
4607 sprintf(value_string, "%d", *(int *)value);
4611 if (*(char **)value == NULL)
4614 strcpy(value_string, *(char **)value);
4618 sprintf(value_string, "player_%d", *(int *)value + 1);
4622 value_string[0] = '\0';
4626 if (type & TYPE_GHOSTED)
4627 strcpy(value_string, "n/a");
4629 return value_string;
4632 char *getSetupLine(struct TokenInfo *token_info, char *prefix, int token_nr)
4636 static char token_string[MAX_LINE_LEN];
4637 int token_type = token_info[token_nr].type;
4638 void *setup_value = token_info[token_nr].value;
4639 char *token_text = token_info[token_nr].text;
4640 char *value_string = getSetupValue(token_type, setup_value);
4642 // build complete token string
4643 sprintf(token_string, "%s%s", prefix, token_text);
4645 // build setup entry line
4646 line = getFormattedSetupEntry(token_string, value_string);
4648 if (token_type == TYPE_KEY_X11)
4650 Key key = *(Key *)setup_value;
4651 char *keyname = getKeyNameFromKey(key);
4653 // add comment, if useful
4654 if (!strEqual(keyname, "(undefined)") &&
4655 !strEqual(keyname, "(unknown)"))
4657 // add at least one whitespace
4659 for (i = strlen(line); i < token_comment_position; i++)
4663 strcat(line, keyname);
4670 static void InitLastPlayedLevels_ParentNode(void)
4672 LevelDirTree **leveldir_top = &leveldir_first->node_group->next;
4673 LevelDirTree *leveldir_new = NULL;
4675 // check if parent node for last played levels already exists
4676 if (strEqual((*leveldir_top)->identifier, TOKEN_STR_LAST_LEVEL_SERIES))
4679 leveldir_new = newTreeInfo();
4681 setTreeInfoToDefaultsFromParent(leveldir_new, leveldir_first);
4683 leveldir_new->level_group = TRUE;
4684 leveldir_new->sort_priority = LEVELCLASS_LAST_PLAYED_LEVEL;
4686 setString(&leveldir_new->identifier, TOKEN_STR_LAST_LEVEL_SERIES);
4687 setString(&leveldir_new->name, "<< (last played level sets)");
4688 setString(&leveldir_new->name_sorting, leveldir_new->name);
4690 pushTreeInfo(leveldir_top, leveldir_new);
4692 // create node to link back to current level directory
4693 createParentTreeInfoNode(leveldir_new);
4696 void UpdateLastPlayedLevels_TreeInfo(void)
4698 char **last_level_series = setup.level_setup.last_level_series;
4699 LevelDirTree *leveldir_last;
4700 TreeInfo **node_new = NULL;
4703 if (last_level_series[0] == NULL)
4706 InitLastPlayedLevels_ParentNode();
4708 leveldir_last = getTreeInfoFromIdentifierExt(leveldir_first,
4709 TOKEN_STR_LAST_LEVEL_SERIES,
4710 TREE_NODE_TYPE_GROUP);
4711 if (leveldir_last == NULL)
4714 node_new = &leveldir_last->node_group->next;
4716 freeTreeInfo(*node_new);
4720 for (i = 0; last_level_series[i] != NULL; i++)
4722 LevelDirTree *node_last = getTreeInfoFromIdentifier(leveldir_first,
4723 last_level_series[i]);
4724 if (node_last == NULL)
4727 *node_new = getTreeInfoCopy(node_last); // copy complete node
4729 (*node_new)->node_top = &leveldir_first; // correct top node link
4730 (*node_new)->node_parent = leveldir_last; // correct parent node link
4732 (*node_new)->is_copy = TRUE; // mark entry as node copy
4734 (*node_new)->node_group = NULL;
4735 (*node_new)->next = NULL;
4737 (*node_new)->cl_first = -1; // force setting tree cursor
4739 node_new = &((*node_new)->next);
4743 static void UpdateLastPlayedLevels_List(void)
4745 char **last_level_series = setup.level_setup.last_level_series;
4746 int pos = MAX_LEVELDIR_HISTORY - 1;
4749 // search for potentially already existing entry in list of level sets
4750 for (i = 0; last_level_series[i] != NULL; i++)
4751 if (strEqual(last_level_series[i], leveldir_current->identifier))
4754 // move list of level sets one entry down (using potentially free entry)
4755 for (i = pos; i > 0; i--)
4756 setString(&last_level_series[i], last_level_series[i - 1]);
4758 // put last played level set at top position
4759 setString(&last_level_series[0], leveldir_current->identifier);
4762 static TreeInfo *StoreOrRestoreLastPlayedLevels(TreeInfo *node, boolean store)
4764 static char *identifier = NULL;
4768 setString(&identifier, (node && node->is_copy ? node->identifier : NULL));
4770 return NULL; // not used
4774 TreeInfo *node_new = getTreeInfoFromIdentifierExt(leveldir_first,
4776 TREE_NODE_TYPE_COPY);
4777 return (node_new != NULL ? node_new : node);
4781 void StoreLastPlayedLevels(TreeInfo *node)
4783 StoreOrRestoreLastPlayedLevels(node, TRUE);
4786 void RestoreLastPlayedLevels(TreeInfo **node)
4788 *node = StoreOrRestoreLastPlayedLevels(*node, FALSE);
4791 void LoadLevelSetup_LastSeries(void)
4793 // --------------------------------------------------------------------------
4794 // ~/.<program>/levelsetup.conf
4795 // --------------------------------------------------------------------------
4797 char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
4798 SetupFileHash *level_setup_hash = NULL;
4802 // always start with reliable default values
4803 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
4805 // start with empty history of last played level sets
4806 setString(&setup.level_setup.last_level_series[0], NULL);
4808 if (!strEqual(DEFAULT_LEVELSET, UNDEFINED_LEVELSET))
4810 leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
4812 if (leveldir_current == NULL)
4813 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
4816 if ((level_setup_hash = loadSetupFileHash(filename)))
4818 char *last_level_series =
4819 getHashEntry(level_setup_hash, TOKEN_STR_LAST_LEVEL_SERIES);
4821 leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
4823 if (leveldir_current == NULL)
4824 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
4826 for (i = 0; i < MAX_LEVELDIR_HISTORY; i++)
4828 char token[strlen(TOKEN_STR_LAST_LEVEL_SERIES) + 10];
4829 LevelDirTree *leveldir_last;
4831 sprintf(token, "%s.%03d", TOKEN_STR_LAST_LEVEL_SERIES, i);
4833 last_level_series = getHashEntry(level_setup_hash, token);
4835 leveldir_last = getTreeInfoFromIdentifier(leveldir_first,
4837 if (leveldir_last != NULL)
4838 setString(&setup.level_setup.last_level_series[pos++],
4842 setString(&setup.level_setup.last_level_series[pos], NULL);
4844 freeSetupFileHash(level_setup_hash);
4848 Debug("setup", "using default setup values");
4854 static void SaveLevelSetup_LastSeries_Ext(boolean deactivate_last_level_series)
4856 // --------------------------------------------------------------------------
4857 // ~/.<program>/levelsetup.conf
4858 // --------------------------------------------------------------------------
4860 // check if the current level directory structure is available at this point
4861 if (leveldir_current == NULL)
4864 char **last_level_series = setup.level_setup.last_level_series;
4865 char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
4869 InitUserDataDirectory();
4871 UpdateLastPlayedLevels_List();
4873 if (!(file = fopen(filename, MODE_WRITE)))
4875 Warn("cannot write setup file '%s'", filename);
4882 fprintFileHeader(file, LEVELSETUP_FILENAME);
4884 if (deactivate_last_level_series)
4885 fprintf(file, "# %s\n# ", "the following level set may have caused a problem and was deactivated");
4887 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_LAST_LEVEL_SERIES,
4888 leveldir_current->identifier));
4890 for (i = 0; last_level_series[i] != NULL; i++)
4892 char token[strlen(TOKEN_STR_LAST_LEVEL_SERIES) + 1 + 10 + 1];
4894 sprintf(token, "%s.%03d", TOKEN_STR_LAST_LEVEL_SERIES, i);
4896 fprintf(file, "%s\n", getFormattedSetupEntry(token, last_level_series[i]));
4901 SetFilePermissions(filename, PERMS_PRIVATE);
4906 void SaveLevelSetup_LastSeries(void)
4908 SaveLevelSetup_LastSeries_Ext(FALSE);
4911 void SaveLevelSetup_LastSeries_Deactivate(void)
4913 SaveLevelSetup_LastSeries_Ext(TRUE);
4916 static void checkSeriesInfo(void)
4918 static char *level_directory = NULL;
4921 DirectoryEntry *dir_entry;
4924 checked_free(level_directory);
4926 // check for more levels besides the 'levels' field of 'levelinfo.conf'
4928 level_directory = getPath2((leveldir_current->in_user_dir ?
4929 getUserLevelDir(NULL) :
4930 options.level_directory),
4931 leveldir_current->fullpath);
4933 if ((dir = openDirectory(level_directory)) == NULL)
4935 Warn("cannot read level directory '%s'", level_directory);
4941 while ((dir_entry = readDirectory(dir)) != NULL) // loop all entries
4943 if (strlen(dir_entry->basename) > 4 &&
4944 dir_entry->basename[3] == '.' &&
4945 strEqual(&dir_entry->basename[4], LEVELFILE_EXTENSION))
4947 char levelnum_str[4];
4950 strncpy(levelnum_str, dir_entry->basename, 3);
4951 levelnum_str[3] = '\0';
4953 levelnum_value = atoi(levelnum_str);
4955 if (levelnum_value < leveldir_current->first_level)
4957 Warn("additional level %d found", levelnum_value);
4959 leveldir_current->first_level = levelnum_value;
4961 else if (levelnum_value > leveldir_current->last_level)
4963 Warn("additional level %d found", levelnum_value);
4965 leveldir_current->last_level = levelnum_value;
4971 closeDirectory(dir);
4974 void LoadLevelSetup_SeriesInfo(void)
4977 SetupFileHash *level_setup_hash = NULL;
4978 char *level_subdir = leveldir_current->subdir;
4981 // always start with reliable default values
4982 level_nr = leveldir_current->first_level;
4984 for (i = 0; i < MAX_LEVELS; i++)
4986 LevelStats_setPlayed(i, 0);
4987 LevelStats_setSolved(i, 0);
4992 // --------------------------------------------------------------------------
4993 // ~/.<program>/levelsetup/<level series>/levelsetup.conf
4994 // --------------------------------------------------------------------------
4996 level_subdir = leveldir_current->subdir;
4998 filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
5000 if ((level_setup_hash = loadSetupFileHash(filename)))
5004 // get last played level in this level set
5006 token_value = getHashEntry(level_setup_hash, TOKEN_STR_LAST_PLAYED_LEVEL);
5010 level_nr = atoi(token_value);
5012 if (level_nr < leveldir_current->first_level)
5013 level_nr = leveldir_current->first_level;
5014 if (level_nr > leveldir_current->last_level)
5015 level_nr = leveldir_current->last_level;
5018 // get handicap level in this level set
5020 token_value = getHashEntry(level_setup_hash, TOKEN_STR_HANDICAP_LEVEL);
5024 int level_nr = atoi(token_value);
5026 if (level_nr < leveldir_current->first_level)
5027 level_nr = leveldir_current->first_level;
5028 if (level_nr > leveldir_current->last_level + 1)
5029 level_nr = leveldir_current->last_level;
5031 if (leveldir_current->user_defined || !leveldir_current->handicap)
5032 level_nr = leveldir_current->last_level;
5034 leveldir_current->handicap_level = level_nr;
5037 // get number of played and solved levels in this level set
5039 BEGIN_HASH_ITERATION(level_setup_hash, itr)
5041 char *token = HASH_ITERATION_TOKEN(itr);
5042 char *value = HASH_ITERATION_VALUE(itr);
5044 if (strlen(token) == 3 &&
5045 token[0] >= '0' && token[0] <= '9' &&
5046 token[1] >= '0' && token[1] <= '9' &&
5047 token[2] >= '0' && token[2] <= '9')
5049 int level_nr = atoi(token);
5052 LevelStats_setPlayed(level_nr, atoi(value)); // read 1st column
5054 value = strchr(value, ' ');
5057 LevelStats_setSolved(level_nr, atoi(value)); // read 2nd column
5060 END_HASH_ITERATION(hash, itr)
5062 freeSetupFileHash(level_setup_hash);
5066 Debug("setup", "using default setup values");
5072 void SaveLevelSetup_SeriesInfo(void)
5075 char *level_subdir = leveldir_current->subdir;
5076 char *level_nr_str = int2str(level_nr, 0);
5077 char *handicap_level_str = int2str(leveldir_current->handicap_level, 0);
5081 // --------------------------------------------------------------------------
5082 // ~/.<program>/levelsetup/<level series>/levelsetup.conf
5083 // --------------------------------------------------------------------------
5085 InitLevelSetupDirectory(level_subdir);
5087 filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
5089 if (!(file = fopen(filename, MODE_WRITE)))
5091 Warn("cannot write setup file '%s'", filename);
5098 fprintFileHeader(file, LEVELSETUP_FILENAME);
5100 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_PLAYED_LEVEL,
5102 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_HANDICAP_LEVEL,
5103 handicap_level_str));
5105 for (i = leveldir_current->first_level; i <= leveldir_current->last_level;
5108 if (LevelStats_getPlayed(i) > 0 ||
5109 LevelStats_getSolved(i) > 0)
5114 sprintf(token, "%03d", i);
5115 sprintf(value, "%d %d", LevelStats_getPlayed(i), LevelStats_getSolved(i));
5117 fprintf(file, "%s\n", getFormattedSetupEntry(token, value));
5123 SetFilePermissions(filename, PERMS_PRIVATE);
5128 int LevelStats_getPlayed(int nr)
5130 return (nr >= 0 && nr < MAX_LEVELS ? level_stats[nr].played : 0);
5133 int LevelStats_getSolved(int nr)
5135 return (nr >= 0 && nr < MAX_LEVELS ? level_stats[nr].solved : 0);
5138 void LevelStats_setPlayed(int nr, int value)
5140 if (nr >= 0 && nr < MAX_LEVELS)
5141 level_stats[nr].played = value;
5144 void LevelStats_setSolved(int nr, int value)
5146 if (nr >= 0 && nr < MAX_LEVELS)
5147 level_stats[nr].solved = value;
5150 void LevelStats_incPlayed(int nr)
5152 if (nr >= 0 && nr < MAX_LEVELS)
5153 level_stats[nr].played++;
5156 void LevelStats_incSolved(int nr)
5158 if (nr >= 0 && nr < MAX_LEVELS)
5159 level_stats[nr].solved++;
5162 void LoadUserSetup(void)
5164 // --------------------------------------------------------------------------
5165 // ~/.<program>/usersetup.conf
5166 // --------------------------------------------------------------------------
5168 char *filename = getPath2(getMainUserGameDataDir(), USERSETUP_FILENAME);
5169 SetupFileHash *user_setup_hash = NULL;
5171 // always start with reliable default values
5174 if ((user_setup_hash = loadSetupFileHash(filename)))
5178 // get last selected user number
5179 token_value = getHashEntry(user_setup_hash, TOKEN_STR_LAST_USER);
5182 user.nr = MIN(MAX(0, atoi(token_value)), MAX_PLAYER_NAMES - 1);
5184 freeSetupFileHash(user_setup_hash);
5188 Debug("setup", "using default setup values");
5194 void SaveUserSetup(void)
5196 // --------------------------------------------------------------------------
5197 // ~/.<program>/usersetup.conf
5198 // --------------------------------------------------------------------------
5200 char *filename = getPath2(getMainUserGameDataDir(), USERSETUP_FILENAME);
5203 InitMainUserDataDirectory();
5205 if (!(file = fopen(filename, MODE_WRITE)))
5207 Warn("cannot write setup file '%s'", filename);
5214 fprintFileHeader(file, USERSETUP_FILENAME);
5216 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_USER,
5220 SetFilePermissions(filename, PERMS_PRIVATE);