1 // ============================================================================
2 // Artsoft Retro-Game Library
3 // ----------------------------------------------------------------------------
4 // (c) 1995-2014 by Artsoft Entertainment
7 // https://www.artsoft.org/
8 // ----------------------------------------------------------------------------
10 // ============================================================================
12 #include <sys/types.h>
26 #include "zip/miniunz.h"
29 #define ENABLE_UNUSED_CODE FALSE // for currently unused functions
30 #define DEBUG_NO_CONFIG_FILE FALSE // for extra-verbose debug output
32 #define NUM_LEVELCLASS_DESC 8
34 static char *levelclass_desc[NUM_LEVELCLASS_DESC] =
46 #define TOKEN_VALUE_POSITION_SHORT 32
47 #define TOKEN_VALUE_POSITION_DEFAULT 40
48 #define TOKEN_COMMENT_POSITION_DEFAULT 60
50 #define MAX_COOKIE_LEN 256
52 #define TREE_NODE_TYPE_DEFAULT 0
53 #define TREE_NODE_TYPE_PARENT 1
54 #define TREE_NODE_TYPE_GROUP 2
55 #define TREE_NODE_TYPE_COPY 3
57 #define TREE_NODE_TYPE(ti) (ti->node_group ? TREE_NODE_TYPE_GROUP : \
58 ti->parent_link ? TREE_NODE_TYPE_PARENT : \
59 ti->is_copy ? TREE_NODE_TYPE_COPY : \
60 TREE_NODE_TYPE_DEFAULT)
63 static void setTreeInfoToDefaults(TreeInfo *, int);
64 static TreeInfo *getTreeInfoCopy(TreeInfo *ti);
65 static int compareTreeInfoEntries(const void *, const void *);
67 static int token_value_position = TOKEN_VALUE_POSITION_DEFAULT;
68 static int token_comment_position = TOKEN_COMMENT_POSITION_DEFAULT;
70 static SetupFileHash *artworkinfo_cache_old = NULL;
71 static SetupFileHash *artworkinfo_cache_new = NULL;
72 static SetupFileHash *optional_tokens_hash = NULL;
73 static boolean use_artworkinfo_cache = TRUE;
74 static boolean update_artworkinfo_cache = FALSE;
77 // ----------------------------------------------------------------------------
79 // ----------------------------------------------------------------------------
81 static char *getLevelClassDescription(TreeInfo *ti)
83 int position = ti->sort_priority / 100;
85 if (position >= 0 && position < NUM_LEVELCLASS_DESC)
86 return levelclass_desc[position];
88 return "Unknown Level Class";
91 static char *getCacheDir(void)
93 static char *cache_dir = NULL;
95 if (cache_dir == NULL)
96 cache_dir = getPath2(getMainUserGameDataDir(), CACHE_DIRECTORY);
101 static char *getScoreDir(char *level_subdir)
103 static char *score_dir = NULL;
104 static char *score_level_dir = NULL;
105 char *score_subdir = SCORES_DIRECTORY;
107 if (score_dir == NULL)
108 score_dir = getPath2(getMainUserGameDataDir(), score_subdir);
110 if (level_subdir != NULL)
112 checked_free(score_level_dir);
114 score_level_dir = getPath2(score_dir, level_subdir);
116 return score_level_dir;
122 static char *getScoreCacheDir(char *level_subdir)
124 static char *score_dir = NULL;
125 static char *score_level_dir = NULL;
126 char *score_subdir = SCORES_DIRECTORY;
128 if (score_dir == NULL)
129 score_dir = getPath2(getCacheDir(), score_subdir);
131 if (level_subdir != NULL)
133 checked_free(score_level_dir);
135 score_level_dir = getPath2(score_dir, level_subdir);
137 return score_level_dir;
143 static char *getScoreTapeDir(char *level_subdir, int nr)
145 static char *score_tape_dir = NULL;
146 char tape_subdir[MAX_FILENAME_LEN];
148 checked_free(score_tape_dir);
150 sprintf(tape_subdir, "%03d", nr);
151 score_tape_dir = getPath2(getScoreDir(level_subdir), tape_subdir);
153 return score_tape_dir;
156 static char *getUserSubdir(int nr)
158 static char user_subdir[16] = { 0 };
160 sprintf(user_subdir, "%03d", nr);
165 static char *getUserDir(int nr)
167 static char *user_dir = NULL;
168 char *main_data_dir = getMainUserGameDataDir();
169 char *users_subdir = USERS_DIRECTORY;
170 char *user_subdir = getUserSubdir(nr);
172 checked_free(user_dir);
175 user_dir = getPath3(main_data_dir, users_subdir, user_subdir);
177 user_dir = getPath2(main_data_dir, users_subdir);
182 static char *getLevelSetupDir(char *level_subdir)
184 static char *levelsetup_dir = NULL;
185 char *data_dir = getUserGameDataDir();
186 char *levelsetup_subdir = LEVELSETUP_DIRECTORY;
188 checked_free(levelsetup_dir);
190 if (level_subdir != NULL)
191 levelsetup_dir = getPath3(data_dir, levelsetup_subdir, level_subdir);
193 levelsetup_dir = getPath2(data_dir, levelsetup_subdir);
195 return levelsetup_dir;
198 static char *getNetworkDir(void)
200 static char *network_dir = NULL;
202 if (network_dir == NULL)
203 network_dir = getPath2(getMainUserGameDataDir(), NETWORK_DIRECTORY);
208 char *getLevelDirFromTreeInfo(TreeInfo *node)
210 static char *level_dir = NULL;
213 return options.level_directory;
215 checked_free(level_dir);
217 level_dir = getPath2((node->in_user_dir ? getUserLevelDir(NULL) :
218 options.level_directory), node->fullpath);
223 char *getUserLevelDir(char *level_subdir)
225 static char *userlevel_dir = NULL;
226 char *data_dir = getMainUserGameDataDir();
227 char *userlevel_subdir = LEVELS_DIRECTORY;
229 checked_free(userlevel_dir);
231 if (level_subdir != NULL)
232 userlevel_dir = getPath3(data_dir, userlevel_subdir, level_subdir);
234 userlevel_dir = getPath2(data_dir, userlevel_subdir);
236 return userlevel_dir;
239 char *getNetworkLevelDir(char *level_subdir)
241 static char *network_level_dir = NULL;
242 char *data_dir = getNetworkDir();
243 char *networklevel_subdir = LEVELS_DIRECTORY;
245 checked_free(network_level_dir);
247 if (level_subdir != NULL)
248 network_level_dir = getPath3(data_dir, networklevel_subdir, level_subdir);
250 network_level_dir = getPath2(data_dir, networklevel_subdir);
252 return network_level_dir;
255 char *getCurrentLevelDir(void)
257 return getLevelDirFromTreeInfo(leveldir_current);
260 char *getNewUserLevelSubdir(void)
262 static char *new_level_subdir = NULL;
263 char *subdir_prefix = getLoginName();
264 char subdir_suffix[10];
265 int max_suffix_number = 1000;
268 while (++i < max_suffix_number)
270 sprintf(subdir_suffix, "_%d", i);
272 checked_free(new_level_subdir);
273 new_level_subdir = getStringCat2(subdir_prefix, subdir_suffix);
275 if (!directoryExists(getUserLevelDir(new_level_subdir)))
279 return new_level_subdir;
282 static char *getTapeDir(char *level_subdir)
284 static char *tape_dir = NULL;
285 char *data_dir = getUserGameDataDir();
286 char *tape_subdir = TAPES_DIRECTORY;
288 checked_free(tape_dir);
290 if (level_subdir != NULL)
291 tape_dir = getPath3(data_dir, tape_subdir, level_subdir);
293 tape_dir = getPath2(data_dir, tape_subdir);
298 static char *getSolutionTapeDir(void)
300 static char *tape_dir = NULL;
301 char *data_dir = getCurrentLevelDir();
302 char *tape_subdir = TAPES_DIRECTORY;
304 checked_free(tape_dir);
306 tape_dir = getPath2(data_dir, tape_subdir);
311 static char *getDefaultGraphicsDir(char *graphics_subdir)
313 static char *graphics_dir = NULL;
315 if (graphics_subdir == NULL)
316 return options.graphics_directory;
318 checked_free(graphics_dir);
320 graphics_dir = getPath2(options.graphics_directory, graphics_subdir);
325 static char *getDefaultSoundsDir(char *sounds_subdir)
327 static char *sounds_dir = NULL;
329 if (sounds_subdir == NULL)
330 return options.sounds_directory;
332 checked_free(sounds_dir);
334 sounds_dir = getPath2(options.sounds_directory, sounds_subdir);
339 static char *getDefaultMusicDir(char *music_subdir)
341 static char *music_dir = NULL;
343 if (music_subdir == NULL)
344 return options.music_directory;
346 checked_free(music_dir);
348 music_dir = getPath2(options.music_directory, music_subdir);
353 static char *getClassicArtworkSet(int type)
355 return (type == TREE_TYPE_GRAPHICS_DIR ? GFX_CLASSIC_SUBDIR :
356 type == TREE_TYPE_SOUNDS_DIR ? SND_CLASSIC_SUBDIR :
357 type == TREE_TYPE_MUSIC_DIR ? MUS_CLASSIC_SUBDIR : "");
360 static char *getClassicArtworkDir(int type)
362 return (type == TREE_TYPE_GRAPHICS_DIR ?
363 getDefaultGraphicsDir(GFX_CLASSIC_SUBDIR) :
364 type == TREE_TYPE_SOUNDS_DIR ?
365 getDefaultSoundsDir(SND_CLASSIC_SUBDIR) :
366 type == TREE_TYPE_MUSIC_DIR ?
367 getDefaultMusicDir(MUS_CLASSIC_SUBDIR) : "");
370 char *getUserGraphicsDir(void)
372 static char *usergraphics_dir = NULL;
374 if (usergraphics_dir == NULL)
375 usergraphics_dir = getPath2(getMainUserGameDataDir(), GRAPHICS_DIRECTORY);
377 return usergraphics_dir;
380 char *getUserSoundsDir(void)
382 static char *usersounds_dir = NULL;
384 if (usersounds_dir == NULL)
385 usersounds_dir = getPath2(getMainUserGameDataDir(), SOUNDS_DIRECTORY);
387 return usersounds_dir;
390 char *getUserMusicDir(void)
392 static char *usermusic_dir = NULL;
394 if (usermusic_dir == NULL)
395 usermusic_dir = getPath2(getMainUserGameDataDir(), MUSIC_DIRECTORY);
397 return usermusic_dir;
400 static char *getSetupArtworkDir(TreeInfo *ti)
402 static char *artwork_dir = NULL;
407 checked_free(artwork_dir);
409 artwork_dir = getPath2(ti->basepath, ti->fullpath);
414 char *setLevelArtworkDir(TreeInfo *ti)
416 char **artwork_path_ptr, **artwork_set_ptr;
417 TreeInfo *level_artwork;
419 if (ti == NULL || leveldir_current == NULL)
422 artwork_path_ptr = LEVELDIR_ARTWORK_PATH_PTR(leveldir_current, ti->type);
423 artwork_set_ptr = LEVELDIR_ARTWORK_SET_PTR( leveldir_current, ti->type);
425 checked_free(*artwork_path_ptr);
427 if ((level_artwork = getTreeInfoFromIdentifier(ti, *artwork_set_ptr)))
429 *artwork_path_ptr = getStringCopy(getSetupArtworkDir(level_artwork));
434 No (or non-existing) artwork configured in "levelinfo.conf". This would
435 normally result in using the artwork configured in the setup menu. But
436 if an artwork subdirectory exists (which might contain custom artwork
437 or an artwork configuration file), this level artwork must be treated
438 as relative to the default "classic" artwork, not to the artwork that
439 is currently configured in the setup menu.
441 Update: For "special" versions of R'n'D (like "R'n'D jue"), do not use
442 the "default" artwork (which would be "jue0" for "R'n'D jue"), but use
443 the real "classic" artwork from the original R'n'D (like "gfx_classic").
446 char *dir = getPath2(getCurrentLevelDir(), ARTWORK_DIRECTORY(ti->type));
448 checked_free(*artwork_set_ptr);
450 if (directoryExists(dir))
452 *artwork_path_ptr = getStringCopy(getClassicArtworkDir(ti->type));
453 *artwork_set_ptr = getStringCopy(getClassicArtworkSet(ti->type));
457 *artwork_path_ptr = getStringCopy(UNDEFINED_FILENAME);
458 *artwork_set_ptr = NULL;
464 return *artwork_set_ptr;
467 static char *getLevelArtworkSet(int type)
469 if (leveldir_current == NULL)
472 return LEVELDIR_ARTWORK_SET(leveldir_current, type);
475 static char *getLevelArtworkDir(int type)
477 if (leveldir_current == NULL)
478 return UNDEFINED_FILENAME;
480 return LEVELDIR_ARTWORK_PATH(leveldir_current, type);
483 char *getProgramMainDataPath(char *command_filename, char *base_path)
485 // check if the program's main data base directory is configured
486 if (!strEqual(base_path, "."))
487 return getStringCopy(base_path);
489 /* if the program is configured to start from current directory (default),
490 determine program package directory from program binary (some versions
491 of KDE/Konqueror and Mac OS X (especially "Mavericks") apparently do not
492 set the current working directory to the program package directory) */
493 char *main_data_path = getBasePath(command_filename);
495 #if defined(PLATFORM_MACOSX)
496 if (strSuffix(main_data_path, MAC_APP_BINARY_SUBDIR))
498 char *main_data_path_old = main_data_path;
500 // cut relative path to Mac OS X application binary directory from path
501 main_data_path[strlen(main_data_path) -
502 strlen(MAC_APP_BINARY_SUBDIR)] = '\0';
504 // cut trailing path separator from path (but not if path is root directory)
505 if (strSuffix(main_data_path, "/") && !strEqual(main_data_path, "/"))
506 main_data_path[strlen(main_data_path) - 1] = '\0';
508 // replace empty path with current directory
509 if (strEqual(main_data_path, ""))
510 main_data_path = ".";
512 // add relative path to Mac OS X application resources directory to path
513 main_data_path = getPath2(main_data_path, MAC_APP_FILES_SUBDIR);
515 free(main_data_path_old);
519 return main_data_path;
522 char *getProgramConfigFilename(char *command_filename)
524 static char *config_filename_1 = NULL;
525 static char *config_filename_2 = NULL;
526 static char *config_filename_3 = NULL;
527 static boolean initialized = FALSE;
531 char *command_filename_1 = getStringCopy(command_filename);
533 // strip trailing executable suffix from command filename
534 if (strSuffix(command_filename_1, ".exe"))
535 command_filename_1[strlen(command_filename_1) - 4] = '\0';
537 char *base_path = getProgramMainDataPath(command_filename, BASE_PATH);
538 char *conf_directory = getPath2(base_path, CONF_DIRECTORY);
540 char *command_basepath = getBasePath(command_filename);
541 char *command_basename = getBaseNameNoSuffix(command_filename);
542 char *command_filename_2 = getPath2(command_basepath, command_basename);
544 config_filename_1 = getStringCat2(command_filename_1, ".conf");
545 config_filename_2 = getStringCat2(command_filename_2, ".conf");
546 config_filename_3 = getPath2(conf_directory, SETUP_FILENAME);
548 checked_free(base_path);
549 checked_free(conf_directory);
551 checked_free(command_basepath);
552 checked_free(command_basename);
554 checked_free(command_filename_1);
555 checked_free(command_filename_2);
560 // 1st try: look for config file that exactly matches the binary filename
561 if (fileExists(config_filename_1))
562 return config_filename_1;
564 // 2nd try: look for config file that matches binary filename without suffix
565 if (fileExists(config_filename_2))
566 return config_filename_2;
568 // 3rd try: return setup config filename in global program config directory
569 return config_filename_3;
572 char *getTapeFilename(int nr)
574 static char *filename = NULL;
575 char basename[MAX_FILENAME_LEN];
577 checked_free(filename);
579 sprintf(basename, "%03d.%s", nr, TAPEFILE_EXTENSION);
580 filename = getPath2(getTapeDir(leveldir_current->subdir), basename);
585 char *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 InitTapeDirectory(char *level_subdir)
1160 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
1161 createDirectory(getTapeDir(NULL), "main tape", PERMS_PRIVATE);
1162 createDirectory(getTapeDir(level_subdir), "level tape", PERMS_PRIVATE);
1165 void InitScoreDirectory(char *level_subdir)
1167 createDirectory(getMainUserGameDataDir(), "main user data", PERMS_PRIVATE);
1168 createDirectory(getScoreDir(NULL), "main score", PERMS_PRIVATE);
1169 createDirectory(getScoreDir(level_subdir), "level score", PERMS_PRIVATE);
1172 void InitScoreCacheDirectory(char *level_subdir)
1174 createDirectory(getMainUserGameDataDir(), "main user data", PERMS_PRIVATE);
1175 createDirectory(getCacheDir(), "cache data", PERMS_PRIVATE);
1176 createDirectory(getScoreCacheDir(NULL), "main score", PERMS_PRIVATE);
1177 createDirectory(getScoreCacheDir(level_subdir), "level score", PERMS_PRIVATE);
1180 void InitScoreTapeDirectory(char *level_subdir, int nr)
1182 InitScoreDirectory(level_subdir);
1184 createDirectory(getScoreTapeDir(level_subdir, nr), "score tape", PERMS_PRIVATE);
1187 static void SaveUserLevelInfo(void);
1189 void InitUserLevelDirectory(char *level_subdir)
1191 if (!directoryExists(getUserLevelDir(level_subdir)))
1193 createDirectory(getMainUserGameDataDir(), "main user data", PERMS_PRIVATE);
1194 createDirectory(getUserLevelDir(NULL), "main user level", PERMS_PRIVATE);
1195 createDirectory(getUserLevelDir(level_subdir), "user level", PERMS_PRIVATE);
1197 if (setup.internal.create_user_levelset)
1198 SaveUserLevelInfo();
1202 void InitNetworkLevelDirectory(char *level_subdir)
1204 if (!directoryExists(getNetworkLevelDir(level_subdir)))
1206 createDirectory(getMainUserGameDataDir(), "main user data", PERMS_PRIVATE);
1207 createDirectory(getNetworkDir(), "network data", PERMS_PRIVATE);
1208 createDirectory(getNetworkLevelDir(NULL), "main network level", PERMS_PRIVATE);
1209 createDirectory(getNetworkLevelDir(level_subdir), "network level", PERMS_PRIVATE);
1213 void InitLevelSetupDirectory(char *level_subdir)
1215 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
1216 createDirectory(getLevelSetupDir(NULL), "main level setup", PERMS_PRIVATE);
1217 createDirectory(getLevelSetupDir(level_subdir), "level setup", PERMS_PRIVATE);
1220 static void InitCacheDirectory(void)
1222 createDirectory(getMainUserGameDataDir(), "main user data", PERMS_PRIVATE);
1223 createDirectory(getCacheDir(), "cache data", PERMS_PRIVATE);
1227 // ----------------------------------------------------------------------------
1228 // some functions to handle lists of level and artwork directories
1229 // ----------------------------------------------------------------------------
1231 TreeInfo *newTreeInfo(void)
1233 return checked_calloc(sizeof(TreeInfo));
1236 TreeInfo *newTreeInfo_setDefaults(int type)
1238 TreeInfo *ti = newTreeInfo();
1240 setTreeInfoToDefaults(ti, type);
1245 void pushTreeInfo(TreeInfo **node_first, TreeInfo *node_new)
1247 node_new->next = *node_first;
1248 *node_first = node_new;
1251 void removeTreeInfo(TreeInfo **node_first)
1253 TreeInfo *node_old = *node_first;
1255 *node_first = node_old->next;
1256 node_old->next = NULL;
1258 freeTreeInfo(node_old);
1261 int numTreeInfo(TreeInfo *node)
1274 boolean validLevelSeries(TreeInfo *node)
1276 // in a number of cases, tree node is no valid level set
1277 if (node == NULL || node->node_group || node->parent_link || node->is_copy)
1283 TreeInfo *getValidLevelSeries(TreeInfo *node, TreeInfo *default_node)
1285 if (validLevelSeries(node))
1287 else if (node->is_copy)
1288 return getTreeInfoFromIdentifier(leveldir_first, node->identifier);
1290 return getFirstValidTreeInfoEntry(default_node);
1293 TreeInfo *getFirstValidTreeInfoEntry(TreeInfo *node)
1298 if (node->node_group) // enter node group (step down into tree)
1299 return getFirstValidTreeInfoEntry(node->node_group);
1301 if (node->parent_link) // skip first node (back link) of node group
1303 if (node->next) // get next regular node
1304 return getFirstValidTreeInfoEntry(node->next);
1305 else // leave empty node group and go on
1306 return getFirstValidTreeInfoEntry(node->node_parent->next);
1309 // this is a regular tree node
1313 TreeInfo *getTreeInfoFirstGroupEntry(TreeInfo *node)
1318 if (node->node_parent == NULL) // top level group
1319 return *node->node_top;
1320 else // sub level group
1321 return node->node_parent->node_group;
1324 int numTreeInfoInGroup(TreeInfo *node)
1326 return numTreeInfo(getTreeInfoFirstGroupEntry(node));
1329 int getPosFromTreeInfo(TreeInfo *node)
1331 TreeInfo *node_cmp = getTreeInfoFirstGroupEntry(node);
1336 if (node_cmp == node)
1340 node_cmp = node_cmp->next;
1346 TreeInfo *getTreeInfoFromPos(TreeInfo *node, int pos)
1348 TreeInfo *node_default = node;
1360 return node_default;
1363 static TreeInfo *getTreeInfoFromIdentifierExt(TreeInfo *node, char *identifier,
1364 int node_type_wanted)
1366 if (identifier == NULL)
1371 if (TREE_NODE_TYPE(node) == node_type_wanted &&
1372 strEqual(identifier, node->identifier))
1375 if (node->node_group)
1377 TreeInfo *node_group = getTreeInfoFromIdentifierExt(node->node_group,
1390 TreeInfo *getTreeInfoFromIdentifier(TreeInfo *node, char *identifier)
1392 return getTreeInfoFromIdentifierExt(node, identifier, TREE_NODE_TYPE_DEFAULT);
1395 static TreeInfo *cloneTreeNode(TreeInfo **node_top, TreeInfo *node_parent,
1396 TreeInfo *node, boolean skip_sets_without_levels)
1403 if (!node->parent_link && !node->level_group &&
1404 skip_sets_without_levels && node->levels == 0)
1405 return cloneTreeNode(node_top, node_parent, node->next,
1406 skip_sets_without_levels);
1408 node_new = getTreeInfoCopy(node); // copy complete node
1410 node_new->node_top = node_top; // correct top node link
1411 node_new->node_parent = node_parent; // correct parent node link
1413 if (node->level_group)
1414 node_new->node_group = cloneTreeNode(node_top, node_new, node->node_group,
1415 skip_sets_without_levels);
1417 node_new->next = cloneTreeNode(node_top, node_parent, node->next,
1418 skip_sets_without_levels);
1423 static void cloneTree(TreeInfo **ti_new, TreeInfo *ti, boolean skip_empty_sets)
1425 TreeInfo *ti_cloned = cloneTreeNode(ti_new, NULL, ti, skip_empty_sets);
1427 *ti_new = ti_cloned;
1430 static boolean adjustTreeGraphicsForEMC(TreeInfo *node)
1432 boolean settings_changed = FALSE;
1436 boolean want_ecs = (setup.prefer_aga_graphics == FALSE);
1437 boolean want_aga = (setup.prefer_aga_graphics == TRUE);
1438 boolean has_only_ecs = (!node->graphics_set && !node->graphics_set_aga);
1439 boolean has_only_aga = (!node->graphics_set && !node->graphics_set_ecs);
1440 char *graphics_set = NULL;
1442 if (node->graphics_set_ecs && (want_ecs || has_only_ecs))
1443 graphics_set = node->graphics_set_ecs;
1445 if (node->graphics_set_aga && (want_aga || has_only_aga))
1446 graphics_set = node->graphics_set_aga;
1448 if (graphics_set && !strEqual(node->graphics_set, graphics_set))
1450 setString(&node->graphics_set, graphics_set);
1451 settings_changed = TRUE;
1454 if (node->node_group != NULL)
1455 settings_changed |= adjustTreeGraphicsForEMC(node->node_group);
1460 return settings_changed;
1463 static boolean adjustTreeSoundsForEMC(TreeInfo *node)
1465 boolean settings_changed = FALSE;
1469 boolean want_default = (setup.prefer_lowpass_sounds == FALSE);
1470 boolean want_lowpass = (setup.prefer_lowpass_sounds == TRUE);
1471 boolean has_only_default = (!node->sounds_set && !node->sounds_set_lowpass);
1472 boolean has_only_lowpass = (!node->sounds_set && !node->sounds_set_default);
1473 char *sounds_set = NULL;
1475 if (node->sounds_set_default && (want_default || has_only_default))
1476 sounds_set = node->sounds_set_default;
1478 if (node->sounds_set_lowpass && (want_lowpass || has_only_lowpass))
1479 sounds_set = node->sounds_set_lowpass;
1481 if (sounds_set && !strEqual(node->sounds_set, sounds_set))
1483 setString(&node->sounds_set, sounds_set);
1484 settings_changed = TRUE;
1487 if (node->node_group != NULL)
1488 settings_changed |= adjustTreeSoundsForEMC(node->node_group);
1493 return settings_changed;
1496 int dumpTreeInfo(TreeInfo *node, int depth)
1498 char bullet_list[] = { '-', '*', 'o' };
1499 int num_leaf_nodes = 0;
1503 Debug("tree", "Dumping TreeInfo:");
1507 char bullet = bullet_list[depth % ARRAY_SIZE(bullet_list)];
1509 for (i = 0; i < depth * 2; i++)
1510 DebugContinued("", " ");
1512 DebugContinued("tree", "%c '%s' ['%s] [PARENT: '%s'] %s\n",
1513 bullet, node->name, node->identifier,
1514 (node->node_parent ? node->node_parent->identifier : "-"),
1515 (node->node_group ? "[GROUP]" : ""));
1517 if (!node->node_group && !node->parent_link)
1521 // use for dumping artwork info tree
1522 Debug("tree", "subdir == '%s' ['%s', '%s'] [%d])",
1523 node->subdir, node->fullpath, node->basepath, node->in_user_dir);
1526 if (node->node_group != NULL)
1527 num_leaf_nodes += dumpTreeInfo(node->node_group, depth + 1);
1533 Debug("tree", "Summary: %d leaf nodes found", num_leaf_nodes);
1535 return num_leaf_nodes;
1538 void sortTreeInfoBySortFunction(TreeInfo **node_first,
1539 int (*compare_function)(const void *,
1542 int num_nodes = numTreeInfo(*node_first);
1543 TreeInfo **sort_array;
1544 TreeInfo *node = *node_first;
1550 // allocate array for sorting structure pointers
1551 sort_array = checked_calloc(num_nodes * sizeof(TreeInfo *));
1553 // writing structure pointers to sorting array
1554 while (i < num_nodes && node) // double boundary check...
1556 sort_array[i] = node;
1562 // sorting the structure pointers in the sorting array
1563 qsort(sort_array, num_nodes, sizeof(TreeInfo *),
1566 // update the linkage of list elements with the sorted node array
1567 for (i = 0; i < num_nodes - 1; i++)
1568 sort_array[i]->next = sort_array[i + 1];
1569 sort_array[num_nodes - 1]->next = NULL;
1571 // update the linkage of the main list anchor pointer
1572 *node_first = sort_array[0];
1576 // now recursively sort the level group structures
1580 if (node->node_group != NULL)
1581 sortTreeInfoBySortFunction(&node->node_group, compare_function);
1587 void sortTreeInfo(TreeInfo **node_first)
1589 sortTreeInfoBySortFunction(node_first, compareTreeInfoEntries);
1593 // ============================================================================
1594 // some stuff from "files.c"
1595 // ============================================================================
1597 #if defined(PLATFORM_WIN32)
1599 #define S_IRGRP S_IRUSR
1602 #define S_IROTH S_IRUSR
1605 #define S_IWGRP S_IWUSR
1608 #define S_IWOTH S_IWUSR
1611 #define S_IXGRP S_IXUSR
1614 #define S_IXOTH S_IXUSR
1617 #define S_IRWXG (S_IRGRP | S_IWGRP | S_IXGRP)
1622 #endif // PLATFORM_WIN32
1624 // file permissions for newly written files
1625 #define MODE_R_ALL (S_IRUSR | S_IRGRP | S_IROTH)
1626 #define MODE_W_ALL (S_IWUSR | S_IWGRP | S_IWOTH)
1627 #define MODE_X_ALL (S_IXUSR | S_IXGRP | S_IXOTH)
1629 #define MODE_W_PRIVATE (S_IWUSR)
1630 #define MODE_W_PUBLIC_FILE (S_IWUSR | S_IWGRP)
1631 #define MODE_W_PUBLIC_DIR (S_IWUSR | S_IWGRP | S_ISGID)
1633 #define DIR_PERMS_PRIVATE (MODE_R_ALL | MODE_X_ALL | MODE_W_PRIVATE)
1634 #define DIR_PERMS_PUBLIC (MODE_R_ALL | MODE_X_ALL | MODE_W_PUBLIC_DIR)
1635 #define DIR_PERMS_PUBLIC_ALL (MODE_R_ALL | MODE_X_ALL | MODE_W_ALL)
1637 #define FILE_PERMS_PRIVATE (MODE_R_ALL | MODE_W_PRIVATE)
1638 #define FILE_PERMS_PUBLIC (MODE_R_ALL | MODE_W_PUBLIC_FILE)
1639 #define FILE_PERMS_PUBLIC_ALL (MODE_R_ALL | MODE_W_ALL)
1642 char *getHomeDir(void)
1644 static char *dir = NULL;
1646 #if defined(PLATFORM_WIN32)
1649 dir = checked_malloc(MAX_PATH + 1);
1651 if (!SUCCEEDED(SHGetFolderPath(NULL, CSIDL_PERSONAL, NULL, 0, dir)))
1654 #elif defined(PLATFORM_EMSCRIPTEN)
1655 dir = "/persistent";
1656 #elif defined(PLATFORM_UNIX)
1659 if ((dir = getenv("HOME")) == NULL)
1661 dir = getUnixHomeDir();
1664 dir = getStringCopy(dir);
1676 char *getPersonalDataDir(void)
1678 static char *personal_data_dir = NULL;
1680 #if defined(PLATFORM_MACOSX)
1681 if (personal_data_dir == NULL)
1682 personal_data_dir = getPath2(getHomeDir(), "Documents");
1684 if (personal_data_dir == NULL)
1685 personal_data_dir = getHomeDir();
1688 return personal_data_dir;
1691 char *getMainUserGameDataDir(void)
1693 static char *main_user_data_dir = NULL;
1695 #if defined(PLATFORM_ANDROID)
1696 if (main_user_data_dir == NULL)
1697 main_user_data_dir = (char *)(SDL_AndroidGetExternalStorageState() &
1698 SDL_ANDROID_EXTERNAL_STORAGE_WRITE ?
1699 SDL_AndroidGetExternalStoragePath() :
1700 SDL_AndroidGetInternalStoragePath());
1702 if (main_user_data_dir == NULL)
1703 main_user_data_dir = getPath2(getPersonalDataDir(),
1704 program.userdata_subdir);
1707 return main_user_data_dir;
1710 char *getUserGameDataDir(void)
1713 return getMainUserGameDataDir();
1715 return getUserDir(user.nr);
1718 char *getSetupDir(void)
1720 return getUserGameDataDir();
1723 static mode_t posix_umask(mode_t mask)
1725 #if defined(PLATFORM_UNIX)
1732 static int posix_mkdir(const char *pathname, mode_t mode)
1734 #if defined(PLATFORM_WIN32)
1735 return mkdir(pathname);
1737 return mkdir(pathname, mode);
1741 static boolean posix_process_running_setgid(void)
1743 #if defined(PLATFORM_UNIX)
1744 return (getgid() != getegid());
1750 void createDirectory(char *dir, char *text, int permission_class)
1752 if (directoryExists(dir))
1755 // leave "other" permissions in umask untouched, but ensure group parts
1756 // of USERDATA_DIR_MODE are not masked
1757 mode_t dir_mode = (permission_class == PERMS_PRIVATE ?
1758 DIR_PERMS_PRIVATE : DIR_PERMS_PUBLIC);
1759 mode_t last_umask = posix_umask(0);
1760 mode_t group_umask = ~(dir_mode & S_IRWXG);
1761 int running_setgid = posix_process_running_setgid();
1763 if (permission_class == PERMS_PUBLIC)
1765 // if we're setgid, protect files against "other"
1766 // else keep umask(0) to make the dir world-writable
1769 posix_umask(last_umask & group_umask);
1771 dir_mode = DIR_PERMS_PUBLIC_ALL;
1774 if (posix_mkdir(dir, dir_mode) != 0)
1775 Warn("cannot create %s directory '%s': %s", text, dir, strerror(errno));
1777 if (permission_class == PERMS_PUBLIC && !running_setgid)
1778 chmod(dir, dir_mode);
1780 posix_umask(last_umask); // restore previous umask
1783 void InitMainUserDataDirectory(void)
1785 createDirectory(getMainUserGameDataDir(), "main user data", PERMS_PRIVATE);
1788 void InitUserDataDirectory(void)
1790 createDirectory(getMainUserGameDataDir(), "main user data", PERMS_PRIVATE);
1794 createDirectory(getUserDir(-1), "users", PERMS_PRIVATE);
1795 createDirectory(getUserDir(user.nr), "user data", PERMS_PRIVATE);
1799 void SetFilePermissions(char *filename, int permission_class)
1801 int running_setgid = posix_process_running_setgid();
1802 int perms = (permission_class == PERMS_PRIVATE ?
1803 FILE_PERMS_PRIVATE : FILE_PERMS_PUBLIC);
1805 if (permission_class == PERMS_PUBLIC && !running_setgid)
1806 perms = FILE_PERMS_PUBLIC_ALL;
1808 chmod(filename, perms);
1811 char *getCookie(char *file_type)
1813 static char cookie[MAX_COOKIE_LEN + 1];
1815 if (strlen(program.cookie_prefix) + 1 +
1816 strlen(file_type) + strlen("_FILE_VERSION_x.x") > MAX_COOKIE_LEN)
1817 return "[COOKIE ERROR]"; // should never happen
1819 sprintf(cookie, "%s_%s_FILE_VERSION_%d.%d",
1820 program.cookie_prefix, file_type,
1821 program.version_super, program.version_major);
1826 void fprintFileHeader(FILE *file, char *basename)
1828 char *prefix = "# ";
1831 fprintf_line_with_prefix(file, prefix, sep1, 77);
1832 fprintf(file, "%s%s\n", prefix, basename);
1833 fprintf_line_with_prefix(file, prefix, sep1, 77);
1834 fprintf(file, "\n");
1837 int getFileVersionFromCookieString(const char *cookie)
1839 const char *ptr_cookie1, *ptr_cookie2;
1840 const char *pattern1 = "_FILE_VERSION_";
1841 const char *pattern2 = "?.?";
1842 const int len_cookie = strlen(cookie);
1843 const int len_pattern1 = strlen(pattern1);
1844 const int len_pattern2 = strlen(pattern2);
1845 const int len_pattern = len_pattern1 + len_pattern2;
1846 int version_super, version_major;
1848 if (len_cookie <= len_pattern)
1851 ptr_cookie1 = &cookie[len_cookie - len_pattern];
1852 ptr_cookie2 = &cookie[len_cookie - len_pattern2];
1854 if (strncmp(ptr_cookie1, pattern1, len_pattern1) != 0)
1857 if (ptr_cookie2[0] < '0' || ptr_cookie2[0] > '9' ||
1858 ptr_cookie2[1] != '.' ||
1859 ptr_cookie2[2] < '0' || ptr_cookie2[2] > '9')
1862 version_super = ptr_cookie2[0] - '0';
1863 version_major = ptr_cookie2[2] - '0';
1865 return VERSION_IDENT(version_super, version_major, 0, 0);
1868 boolean checkCookieString(const char *cookie, const char *template)
1870 const char *pattern = "_FILE_VERSION_?.?";
1871 const int len_cookie = strlen(cookie);
1872 const int len_template = strlen(template);
1873 const int len_pattern = strlen(pattern);
1875 if (len_cookie != len_template)
1878 if (strncmp(cookie, template, len_cookie - len_pattern) != 0)
1885 // ----------------------------------------------------------------------------
1886 // setup file list and hash handling functions
1887 // ----------------------------------------------------------------------------
1889 char *getFormattedSetupEntry(char *token, char *value)
1892 static char entry[MAX_LINE_LEN];
1894 // if value is an empty string, just return token without value
1898 // start with the token and some spaces to format output line
1899 sprintf(entry, "%s:", token);
1900 for (i = strlen(entry); i < token_value_position; i++)
1903 // continue with the token's value
1904 strcat(entry, value);
1909 SetupFileList *newSetupFileList(char *token, char *value)
1911 SetupFileList *new = checked_malloc(sizeof(SetupFileList));
1913 new->token = getStringCopy(token);
1914 new->value = getStringCopy(value);
1921 void freeSetupFileList(SetupFileList *list)
1926 checked_free(list->token);
1927 checked_free(list->value);
1930 freeSetupFileList(list->next);
1935 char *getListEntry(SetupFileList *list, char *token)
1940 if (strEqual(list->token, token))
1943 return getListEntry(list->next, token);
1946 SetupFileList *setListEntry(SetupFileList *list, char *token, char *value)
1951 if (strEqual(list->token, token))
1953 checked_free(list->value);
1955 list->value = getStringCopy(value);
1959 else if (list->next == NULL)
1960 return (list->next = newSetupFileList(token, value));
1962 return setListEntry(list->next, token, value);
1965 SetupFileList *addListEntry(SetupFileList *list, char *token, char *value)
1970 if (list->next == NULL)
1971 return (list->next = newSetupFileList(token, value));
1973 return addListEntry(list->next, token, value);
1976 #if ENABLE_UNUSED_CODE
1978 static void printSetupFileList(SetupFileList *list)
1983 Debug("setup:printSetupFileList", "token: '%s'", list->token);
1984 Debug("setup:printSetupFileList", "value: '%s'", list->value);
1986 printSetupFileList(list->next);
1992 DEFINE_HASHTABLE_INSERT(insert_hash_entry, char, char);
1993 DEFINE_HASHTABLE_SEARCH(search_hash_entry, char, char);
1994 DEFINE_HASHTABLE_CHANGE(change_hash_entry, char, char);
1995 DEFINE_HASHTABLE_REMOVE(remove_hash_entry, char, char);
1997 #define insert_hash_entry hashtable_insert
1998 #define search_hash_entry hashtable_search
1999 #define change_hash_entry hashtable_change
2000 #define remove_hash_entry hashtable_remove
2003 unsigned int get_hash_from_key(void *key)
2008 This algorithm (k=33) was first reported by Dan Bernstein many years ago in
2009 'comp.lang.c'. Another version of this algorithm (now favored by Bernstein)
2010 uses XOR: hash(i) = hash(i - 1) * 33 ^ str[i]; the magic of number 33 (why
2011 it works better than many other constants, prime or not) has never been
2012 adequately explained.
2014 If you just want to have a good hash function, and cannot wait, djb2
2015 is one of the best string hash functions i know. It has excellent
2016 distribution and speed on many different sets of keys and table sizes.
2017 You are not likely to do better with one of the "well known" functions
2018 such as PJW, K&R, etc.
2020 Ozan (oz) Yigit [http://www.cs.yorku.ca/~oz/hash.html]
2023 char *str = (char *)key;
2024 unsigned int hash = 5381;
2027 while ((c = *str++))
2028 hash = ((hash << 5) + hash) + c; // hash * 33 + c
2033 static int keys_are_equal(void *key1, void *key2)
2035 return (strEqual((char *)key1, (char *)key2));
2038 SetupFileHash *newSetupFileHash(void)
2040 SetupFileHash *new_hash =
2041 create_hashtable(16, 0.75, get_hash_from_key, keys_are_equal);
2043 if (new_hash == NULL)
2044 Fail("create_hashtable() failed -- out of memory");
2049 void freeSetupFileHash(SetupFileHash *hash)
2054 hashtable_destroy(hash, 1); // 1 == also free values stored in hash
2057 char *getHashEntry(SetupFileHash *hash, char *token)
2062 return search_hash_entry(hash, token);
2065 void setHashEntry(SetupFileHash *hash, char *token, char *value)
2072 value_copy = getStringCopy(value);
2074 // change value; if it does not exist, insert it as new
2075 if (!change_hash_entry(hash, token, value_copy))
2076 if (!insert_hash_entry(hash, getStringCopy(token), value_copy))
2077 Fail("cannot insert into hash -- aborting");
2080 char *removeHashEntry(SetupFileHash *hash, char *token)
2085 return remove_hash_entry(hash, token);
2088 #if ENABLE_UNUSED_CODE
2090 static void printSetupFileHash(SetupFileHash *hash)
2092 BEGIN_HASH_ITERATION(hash, itr)
2094 Debug("setup:printSetupFileHash", "token: '%s'", HASH_ITERATION_TOKEN(itr));
2095 Debug("setup:printSetupFileHash", "value: '%s'", HASH_ITERATION_VALUE(itr));
2097 END_HASH_ITERATION(hash, itr)
2102 #define ALLOW_TOKEN_VALUE_SEPARATOR_BEING_WHITESPACE 1
2103 #define CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING 0
2104 #define CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH 0
2106 static boolean token_value_separator_found = FALSE;
2107 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2108 static boolean token_value_separator_warning = FALSE;
2110 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2111 static boolean token_already_exists_warning = FALSE;
2114 static boolean getTokenValueFromSetupLineExt(char *line,
2115 char **token_ptr, char **value_ptr,
2116 char *filename, char *line_raw,
2118 boolean separator_required)
2120 static char line_copy[MAX_LINE_LEN + 1], line_raw_copy[MAX_LINE_LEN + 1];
2121 char *token, *value, *line_ptr;
2123 // when externally invoked via ReadTokenValueFromLine(), copy line buffers
2124 if (line_raw == NULL)
2126 strncpy(line_copy, line, MAX_LINE_LEN);
2127 line_copy[MAX_LINE_LEN] = '\0';
2130 strcpy(line_raw_copy, line_copy);
2131 line_raw = line_raw_copy;
2134 // cut trailing comment from input line
2135 for (line_ptr = line; *line_ptr; line_ptr++)
2137 if (*line_ptr == '#')
2144 // cut trailing whitespaces from input line
2145 for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
2146 if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
2149 // ignore empty lines
2153 // cut leading whitespaces from token
2154 for (token = line; *token; token++)
2155 if (*token != ' ' && *token != '\t')
2158 // start with empty value as reliable default
2161 token_value_separator_found = FALSE;
2163 // find end of token to determine start of value
2164 for (line_ptr = token; *line_ptr; line_ptr++)
2166 // first look for an explicit token/value separator, like ':' or '='
2167 if (*line_ptr == ':' || *line_ptr == '=')
2169 *line_ptr = '\0'; // terminate token string
2170 value = line_ptr + 1; // set beginning of value
2172 token_value_separator_found = TRUE;
2178 #if ALLOW_TOKEN_VALUE_SEPARATOR_BEING_WHITESPACE
2179 // fallback: if no token/value separator found, also allow whitespaces
2180 if (!token_value_separator_found && !separator_required)
2182 for (line_ptr = token; *line_ptr; line_ptr++)
2184 if (*line_ptr == ' ' || *line_ptr == '\t')
2186 *line_ptr = '\0'; // terminate token string
2187 value = line_ptr + 1; // set beginning of value
2189 token_value_separator_found = TRUE;
2195 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2196 if (token_value_separator_found)
2198 if (!token_value_separator_warning)
2200 Debug("setup", "---");
2202 if (filename != NULL)
2204 Debug("setup", "missing token/value separator(s) in config file:");
2205 Debug("setup", "- config file: '%s'", filename);
2209 Debug("setup", "missing token/value separator(s):");
2212 token_value_separator_warning = TRUE;
2215 if (filename != NULL)
2216 Debug("setup", "- line %d: '%s'", line_nr, line_raw);
2218 Debug("setup", "- line: '%s'", line_raw);
2224 // cut trailing whitespaces from token
2225 for (line_ptr = &token[strlen(token)]; line_ptr >= token; line_ptr--)
2226 if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
2229 // cut leading whitespaces from value
2230 for (; *value; value++)
2231 if (*value != ' ' && *value != '\t')
2240 boolean getTokenValueFromSetupLine(char *line, char **token, char **value)
2242 // while the internal (old) interface does not require a token/value
2243 // separator (for downwards compatibility with existing files which
2244 // don't use them), it is mandatory for the external (new) interface
2246 return getTokenValueFromSetupLineExt(line, token, value, NULL, NULL, 0, TRUE);
2249 static boolean loadSetupFileData(void *setup_file_data, char *filename,
2250 boolean top_recursion_level, boolean is_hash)
2252 static SetupFileHash *include_filename_hash = NULL;
2253 char line[MAX_LINE_LEN], line_raw[MAX_LINE_LEN], previous_line[MAX_LINE_LEN];
2254 char *token, *value, *line_ptr;
2255 void *insert_ptr = NULL;
2256 boolean read_continued_line = FALSE;
2258 int line_nr = 0, token_count = 0, include_count = 0;
2260 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2261 token_value_separator_warning = FALSE;
2264 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2265 token_already_exists_warning = FALSE;
2268 if (!(file = openFile(filename, MODE_READ)))
2270 #if DEBUG_NO_CONFIG_FILE
2271 Debug("setup", "cannot open configuration file '%s'", filename);
2277 // use "insert pointer" to store list end for constant insertion complexity
2279 insert_ptr = setup_file_data;
2281 // on top invocation, create hash to mark included files (to prevent loops)
2282 if (top_recursion_level)
2283 include_filename_hash = newSetupFileHash();
2285 // mark this file as already included (to prevent including it again)
2286 setHashEntry(include_filename_hash, getBaseNamePtr(filename), "true");
2288 while (!checkEndOfFile(file))
2290 // read next line of input file
2291 if (!getStringFromFile(file, line, MAX_LINE_LEN))
2294 // check if line was completely read and is terminated by line break
2295 if (strlen(line) > 0 && line[strlen(line) - 1] == '\n')
2298 // cut trailing line break (this can be newline and/or carriage return)
2299 for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
2300 if ((*line_ptr == '\n' || *line_ptr == '\r') && *(line_ptr + 1) == '\0')
2303 // copy raw input line for later use (mainly debugging output)
2304 strcpy(line_raw, line);
2306 if (read_continued_line)
2308 // append new line to existing line, if there is enough space
2309 if (strlen(previous_line) + strlen(line_ptr) < MAX_LINE_LEN)
2310 strcat(previous_line, line_ptr);
2312 strcpy(line, previous_line); // copy storage buffer to line
2314 read_continued_line = FALSE;
2317 // if the last character is '\', continue at next line
2318 if (strlen(line) > 0 && line[strlen(line) - 1] == '\\')
2320 line[strlen(line) - 1] = '\0'; // cut off trailing backslash
2321 strcpy(previous_line, line); // copy line to storage buffer
2323 read_continued_line = TRUE;
2328 if (!getTokenValueFromSetupLineExt(line, &token, &value, filename,
2329 line_raw, line_nr, FALSE))
2334 if (strEqual(token, "include"))
2336 if (getHashEntry(include_filename_hash, value) == NULL)
2338 char *basepath = getBasePath(filename);
2339 char *basename = getBaseName(value);
2340 char *filename_include = getPath2(basepath, basename);
2342 loadSetupFileData(setup_file_data, filename_include, FALSE, is_hash);
2346 free(filename_include);
2352 Warn("ignoring already processed file '%s'", value);
2359 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2361 getHashEntry((SetupFileHash *)setup_file_data, token);
2363 if (old_value != NULL)
2365 if (!token_already_exists_warning)
2367 Debug("setup", "---");
2368 Debug("setup", "duplicate token(s) found in config file:");
2369 Debug("setup", "- config file: '%s'", filename);
2371 token_already_exists_warning = TRUE;
2374 Debug("setup", "- token: '%s' (in line %d)", token, line_nr);
2375 Debug("setup", " old value: '%s'", old_value);
2376 Debug("setup", " new value: '%s'", value);
2380 setHashEntry((SetupFileHash *)setup_file_data, token, value);
2384 insert_ptr = addListEntry((SetupFileList *)insert_ptr, token, value);
2394 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2395 if (token_value_separator_warning)
2396 Debug("setup", "---");
2399 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2400 if (token_already_exists_warning)
2401 Debug("setup", "---");
2404 if (token_count == 0 && include_count == 0)
2405 Warn("configuration file '%s' is empty", filename);
2407 if (top_recursion_level)
2408 freeSetupFileHash(include_filename_hash);
2413 static int compareSetupFileData(const void *object1, const void *object2)
2415 const struct ConfigInfo *entry1 = (struct ConfigInfo *)object1;
2416 const struct ConfigInfo *entry2 = (struct ConfigInfo *)object2;
2418 return strcmp(entry1->token, entry2->token);
2421 static void saveSetupFileHash(SetupFileHash *hash, char *filename)
2423 int item_count = hashtable_count(hash);
2424 int item_size = sizeof(struct ConfigInfo);
2425 struct ConfigInfo *sort_array = checked_malloc(item_count * item_size);
2429 // copy string pointers from hash to array
2430 BEGIN_HASH_ITERATION(hash, itr)
2432 sort_array[i].token = HASH_ITERATION_TOKEN(itr);
2433 sort_array[i].value = HASH_ITERATION_VALUE(itr);
2437 if (i > item_count) // should never happen
2440 END_HASH_ITERATION(hash, itr)
2442 // sort string pointers from hash in array
2443 qsort(sort_array, item_count, item_size, compareSetupFileData);
2445 if (!(file = fopen(filename, MODE_WRITE)))
2447 Warn("cannot write configuration file '%s'", filename);
2452 fprintf(file, "%s\n\n", getFormattedSetupEntry("program.version",
2453 program.version_string));
2454 for (i = 0; i < item_count; i++)
2455 fprintf(file, "%s\n", getFormattedSetupEntry(sort_array[i].token,
2456 sort_array[i].value));
2459 checked_free(sort_array);
2462 SetupFileList *loadSetupFileList(char *filename)
2464 SetupFileList *setup_file_list = newSetupFileList("", "");
2465 SetupFileList *first_valid_list_entry;
2467 if (!loadSetupFileData(setup_file_list, filename, TRUE, FALSE))
2469 freeSetupFileList(setup_file_list);
2474 first_valid_list_entry = setup_file_list->next;
2476 // free empty list header
2477 setup_file_list->next = NULL;
2478 freeSetupFileList(setup_file_list);
2480 return first_valid_list_entry;
2483 SetupFileHash *loadSetupFileHash(char *filename)
2485 SetupFileHash *setup_file_hash = newSetupFileHash();
2487 if (!loadSetupFileData(setup_file_hash, filename, TRUE, TRUE))
2489 freeSetupFileHash(setup_file_hash);
2494 return setup_file_hash;
2498 // ============================================================================
2500 // ============================================================================
2502 #define TOKEN_STR_LAST_LEVEL_SERIES "last_level_series"
2503 #define TOKEN_STR_LAST_PLAYED_LEVEL "last_played_level"
2504 #define TOKEN_STR_HANDICAP_LEVEL "handicap_level"
2505 #define TOKEN_STR_LAST_USER "last_user"
2507 // level directory info
2508 #define LEVELINFO_TOKEN_IDENTIFIER 0
2509 #define LEVELINFO_TOKEN_NAME 1
2510 #define LEVELINFO_TOKEN_NAME_SORTING 2
2511 #define LEVELINFO_TOKEN_AUTHOR 3
2512 #define LEVELINFO_TOKEN_YEAR 4
2513 #define LEVELINFO_TOKEN_PROGRAM_TITLE 5
2514 #define LEVELINFO_TOKEN_PROGRAM_COPYRIGHT 6
2515 #define LEVELINFO_TOKEN_PROGRAM_COMPANY 7
2516 #define LEVELINFO_TOKEN_IMPORTED_FROM 8
2517 #define LEVELINFO_TOKEN_IMPORTED_BY 9
2518 #define LEVELINFO_TOKEN_TESTED_BY 10
2519 #define LEVELINFO_TOKEN_LEVELS 11
2520 #define LEVELINFO_TOKEN_FIRST_LEVEL 12
2521 #define LEVELINFO_TOKEN_SORT_PRIORITY 13
2522 #define LEVELINFO_TOKEN_LATEST_ENGINE 14
2523 #define LEVELINFO_TOKEN_LEVEL_GROUP 15
2524 #define LEVELINFO_TOKEN_READONLY 16
2525 #define LEVELINFO_TOKEN_GRAPHICS_SET_ECS 17
2526 #define LEVELINFO_TOKEN_GRAPHICS_SET_AGA 18
2527 #define LEVELINFO_TOKEN_GRAPHICS_SET 19
2528 #define LEVELINFO_TOKEN_SOUNDS_SET_DEFAULT 20
2529 #define LEVELINFO_TOKEN_SOUNDS_SET_LOWPASS 21
2530 #define LEVELINFO_TOKEN_SOUNDS_SET 22
2531 #define LEVELINFO_TOKEN_MUSIC_SET 23
2532 #define LEVELINFO_TOKEN_FILENAME 24
2533 #define LEVELINFO_TOKEN_FILETYPE 25
2534 #define LEVELINFO_TOKEN_SPECIAL_FLAGS 26
2535 #define LEVELINFO_TOKEN_HANDICAP 27
2536 #define LEVELINFO_TOKEN_SKIP_LEVELS 28
2537 #define LEVELINFO_TOKEN_USE_EMC_TILES 29
2539 #define NUM_LEVELINFO_TOKENS 30
2541 static LevelDirTree ldi;
2543 static struct TokenInfo levelinfo_tokens[] =
2545 // level directory info
2546 { TYPE_STRING, &ldi.identifier, "identifier" },
2547 { TYPE_STRING, &ldi.name, "name" },
2548 { TYPE_STRING, &ldi.name_sorting, "name_sorting" },
2549 { TYPE_STRING, &ldi.author, "author" },
2550 { TYPE_STRING, &ldi.year, "year" },
2551 { TYPE_STRING, &ldi.program_title, "program_title" },
2552 { TYPE_STRING, &ldi.program_copyright, "program_copyright" },
2553 { TYPE_STRING, &ldi.program_company, "program_company" },
2554 { TYPE_STRING, &ldi.imported_from, "imported_from" },
2555 { TYPE_STRING, &ldi.imported_by, "imported_by" },
2556 { TYPE_STRING, &ldi.tested_by, "tested_by" },
2557 { TYPE_INTEGER, &ldi.levels, "levels" },
2558 { TYPE_INTEGER, &ldi.first_level, "first_level" },
2559 { TYPE_INTEGER, &ldi.sort_priority, "sort_priority" },
2560 { TYPE_BOOLEAN, &ldi.latest_engine, "latest_engine" },
2561 { TYPE_BOOLEAN, &ldi.level_group, "level_group" },
2562 { TYPE_BOOLEAN, &ldi.readonly, "readonly" },
2563 { TYPE_STRING, &ldi.graphics_set_ecs, "graphics_set.ecs" },
2564 { TYPE_STRING, &ldi.graphics_set_aga, "graphics_set.aga" },
2565 { TYPE_STRING, &ldi.graphics_set, "graphics_set" },
2566 { TYPE_STRING, &ldi.sounds_set_default,"sounds_set.default" },
2567 { TYPE_STRING, &ldi.sounds_set_lowpass,"sounds_set.lowpass" },
2568 { TYPE_STRING, &ldi.sounds_set, "sounds_set" },
2569 { TYPE_STRING, &ldi.music_set, "music_set" },
2570 { TYPE_STRING, &ldi.level_filename, "filename" },
2571 { TYPE_STRING, &ldi.level_filetype, "filetype" },
2572 { TYPE_STRING, &ldi.special_flags, "special_flags" },
2573 { TYPE_BOOLEAN, &ldi.handicap, "handicap" },
2574 { TYPE_BOOLEAN, &ldi.skip_levels, "skip_levels" },
2575 { TYPE_BOOLEAN, &ldi.use_emc_tiles, "use_emc_tiles" }
2578 static struct TokenInfo artworkinfo_tokens[] =
2580 // artwork directory info
2581 { TYPE_STRING, &ldi.identifier, "identifier" },
2582 { TYPE_STRING, &ldi.subdir, "subdir" },
2583 { TYPE_STRING, &ldi.name, "name" },
2584 { TYPE_STRING, &ldi.name_sorting, "name_sorting" },
2585 { TYPE_STRING, &ldi.author, "author" },
2586 { TYPE_STRING, &ldi.program_title, "program_title" },
2587 { TYPE_STRING, &ldi.program_copyright, "program_copyright" },
2588 { TYPE_STRING, &ldi.program_company, "program_company" },
2589 { TYPE_INTEGER, &ldi.sort_priority, "sort_priority" },
2590 { TYPE_STRING, &ldi.basepath, "basepath" },
2591 { TYPE_STRING, &ldi.fullpath, "fullpath" },
2592 { TYPE_BOOLEAN, &ldi.in_user_dir, "in_user_dir" },
2593 { TYPE_STRING, &ldi.class_desc, "class_desc" },
2598 static char *optional_tokens[] =
2601 "program_copyright",
2607 static void setTreeInfoToDefaults(TreeInfo *ti, int type)
2611 ti->node_top = (ti->type == TREE_TYPE_LEVEL_DIR ? &leveldir_first :
2612 ti->type == TREE_TYPE_GRAPHICS_DIR ? &artwork.gfx_first :
2613 ti->type == TREE_TYPE_SOUNDS_DIR ? &artwork.snd_first :
2614 ti->type == TREE_TYPE_MUSIC_DIR ? &artwork.mus_first :
2617 ti->node_parent = NULL;
2618 ti->node_group = NULL;
2625 ti->fullpath = NULL;
2626 ti->basepath = NULL;
2627 ti->identifier = NULL;
2628 ti->name = getStringCopy(ANONYMOUS_NAME);
2629 ti->name_sorting = NULL;
2630 ti->author = getStringCopy(ANONYMOUS_NAME);
2633 ti->program_title = NULL;
2634 ti->program_copyright = NULL;
2635 ti->program_company = NULL;
2637 ti->sort_priority = LEVELCLASS_UNDEFINED; // default: least priority
2638 ti->latest_engine = FALSE; // default: get from level
2639 ti->parent_link = FALSE;
2640 ti->is_copy = FALSE;
2641 ti->in_user_dir = FALSE;
2642 ti->user_defined = FALSE;
2644 ti->class_desc = NULL;
2646 ti->infotext = getStringCopy(TREE_INFOTEXT(ti->type));
2648 if (ti->type == TREE_TYPE_LEVEL_DIR)
2650 ti->imported_from = NULL;
2651 ti->imported_by = NULL;
2652 ti->tested_by = NULL;
2654 ti->graphics_set_ecs = NULL;
2655 ti->graphics_set_aga = NULL;
2656 ti->graphics_set = NULL;
2657 ti->sounds_set_default = NULL;
2658 ti->sounds_set_lowpass = NULL;
2659 ti->sounds_set = NULL;
2660 ti->music_set = NULL;
2661 ti->graphics_path = getStringCopy(UNDEFINED_FILENAME);
2662 ti->sounds_path = getStringCopy(UNDEFINED_FILENAME);
2663 ti->music_path = getStringCopy(UNDEFINED_FILENAME);
2665 ti->level_filename = NULL;
2666 ti->level_filetype = NULL;
2668 ti->special_flags = NULL;
2671 ti->first_level = 0;
2673 ti->level_group = FALSE;
2674 ti->handicap_level = 0;
2675 ti->readonly = TRUE;
2676 ti->handicap = TRUE;
2677 ti->skip_levels = FALSE;
2679 ti->use_emc_tiles = FALSE;
2683 static void setTreeInfoToDefaultsFromParent(TreeInfo *ti, TreeInfo *parent)
2687 Warn("setTreeInfoToDefaultsFromParent(): parent == NULL");
2689 setTreeInfoToDefaults(ti, TREE_TYPE_UNDEFINED);
2694 // copy all values from the parent structure
2696 ti->type = parent->type;
2698 ti->node_top = parent->node_top;
2699 ti->node_parent = parent;
2700 ti->node_group = NULL;
2707 ti->fullpath = NULL;
2708 ti->basepath = NULL;
2709 ti->identifier = NULL;
2710 ti->name = getStringCopy(ANONYMOUS_NAME);
2711 ti->name_sorting = NULL;
2712 ti->author = getStringCopy(parent->author);
2713 ti->year = getStringCopy(parent->year);
2715 ti->program_title = getStringCopy(parent->program_title);
2716 ti->program_copyright = getStringCopy(parent->program_copyright);
2717 ti->program_company = getStringCopy(parent->program_company);
2719 ti->sort_priority = parent->sort_priority;
2720 ti->latest_engine = parent->latest_engine;
2721 ti->parent_link = FALSE;
2722 ti->is_copy = FALSE;
2723 ti->in_user_dir = parent->in_user_dir;
2724 ti->user_defined = parent->user_defined;
2725 ti->color = parent->color;
2726 ti->class_desc = getStringCopy(parent->class_desc);
2728 ti->infotext = getStringCopy(parent->infotext);
2730 if (ti->type == TREE_TYPE_LEVEL_DIR)
2732 ti->imported_from = getStringCopy(parent->imported_from);
2733 ti->imported_by = getStringCopy(parent->imported_by);
2734 ti->tested_by = getStringCopy(parent->tested_by);
2736 ti->graphics_set_ecs = getStringCopy(parent->graphics_set_ecs);
2737 ti->graphics_set_aga = getStringCopy(parent->graphics_set_aga);
2738 ti->graphics_set = getStringCopy(parent->graphics_set);
2739 ti->sounds_set_default = getStringCopy(parent->sounds_set_default);
2740 ti->sounds_set_lowpass = getStringCopy(parent->sounds_set_lowpass);
2741 ti->sounds_set = getStringCopy(parent->sounds_set);
2742 ti->music_set = getStringCopy(parent->music_set);
2743 ti->graphics_path = getStringCopy(UNDEFINED_FILENAME);
2744 ti->sounds_path = getStringCopy(UNDEFINED_FILENAME);
2745 ti->music_path = getStringCopy(UNDEFINED_FILENAME);
2747 ti->level_filename = getStringCopy(parent->level_filename);
2748 ti->level_filetype = getStringCopy(parent->level_filetype);
2750 ti->special_flags = getStringCopy(parent->special_flags);
2752 ti->levels = parent->levels;
2753 ti->first_level = parent->first_level;
2754 ti->last_level = parent->last_level;
2755 ti->level_group = FALSE;
2756 ti->handicap_level = parent->handicap_level;
2757 ti->readonly = parent->readonly;
2758 ti->handicap = parent->handicap;
2759 ti->skip_levels = parent->skip_levels;
2761 ti->use_emc_tiles = parent->use_emc_tiles;
2765 static TreeInfo *getTreeInfoCopy(TreeInfo *ti)
2767 TreeInfo *ti_copy = newTreeInfo();
2769 // copy all values from the original structure
2771 ti_copy->type = ti->type;
2773 ti_copy->node_top = ti->node_top;
2774 ti_copy->node_parent = ti->node_parent;
2775 ti_copy->node_group = ti->node_group;
2776 ti_copy->next = ti->next;
2778 ti_copy->cl_first = ti->cl_first;
2779 ti_copy->cl_cursor = ti->cl_cursor;
2781 ti_copy->subdir = getStringCopy(ti->subdir);
2782 ti_copy->fullpath = getStringCopy(ti->fullpath);
2783 ti_copy->basepath = getStringCopy(ti->basepath);
2784 ti_copy->identifier = getStringCopy(ti->identifier);
2785 ti_copy->name = getStringCopy(ti->name);
2786 ti_copy->name_sorting = getStringCopy(ti->name_sorting);
2787 ti_copy->author = getStringCopy(ti->author);
2788 ti_copy->year = getStringCopy(ti->year);
2790 ti_copy->program_title = getStringCopy(ti->program_title);
2791 ti_copy->program_copyright = getStringCopy(ti->program_copyright);
2792 ti_copy->program_company = getStringCopy(ti->program_company);
2794 ti_copy->imported_from = getStringCopy(ti->imported_from);
2795 ti_copy->imported_by = getStringCopy(ti->imported_by);
2796 ti_copy->tested_by = getStringCopy(ti->tested_by);
2798 ti_copy->graphics_set_ecs = getStringCopy(ti->graphics_set_ecs);
2799 ti_copy->graphics_set_aga = getStringCopy(ti->graphics_set_aga);
2800 ti_copy->graphics_set = getStringCopy(ti->graphics_set);
2801 ti_copy->sounds_set_default = getStringCopy(ti->sounds_set_default);
2802 ti_copy->sounds_set_lowpass = getStringCopy(ti->sounds_set_lowpass);
2803 ti_copy->sounds_set = getStringCopy(ti->sounds_set);
2804 ti_copy->music_set = getStringCopy(ti->music_set);
2805 ti_copy->graphics_path = getStringCopy(ti->graphics_path);
2806 ti_copy->sounds_path = getStringCopy(ti->sounds_path);
2807 ti_copy->music_path = getStringCopy(ti->music_path);
2809 ti_copy->level_filename = getStringCopy(ti->level_filename);
2810 ti_copy->level_filetype = getStringCopy(ti->level_filetype);
2812 ti_copy->special_flags = getStringCopy(ti->special_flags);
2814 ti_copy->levels = ti->levels;
2815 ti_copy->first_level = ti->first_level;
2816 ti_copy->last_level = ti->last_level;
2817 ti_copy->sort_priority = ti->sort_priority;
2819 ti_copy->latest_engine = ti->latest_engine;
2821 ti_copy->level_group = ti->level_group;
2822 ti_copy->parent_link = ti->parent_link;
2823 ti_copy->is_copy = ti->is_copy;
2824 ti_copy->in_user_dir = ti->in_user_dir;
2825 ti_copy->user_defined = ti->user_defined;
2826 ti_copy->readonly = ti->readonly;
2827 ti_copy->handicap = ti->handicap;
2828 ti_copy->skip_levels = ti->skip_levels;
2830 ti_copy->use_emc_tiles = ti->use_emc_tiles;
2832 ti_copy->color = ti->color;
2833 ti_copy->class_desc = getStringCopy(ti->class_desc);
2834 ti_copy->handicap_level = ti->handicap_level;
2836 ti_copy->infotext = getStringCopy(ti->infotext);
2841 void freeTreeInfo(TreeInfo *ti)
2846 checked_free(ti->subdir);
2847 checked_free(ti->fullpath);
2848 checked_free(ti->basepath);
2849 checked_free(ti->identifier);
2851 checked_free(ti->name);
2852 checked_free(ti->name_sorting);
2853 checked_free(ti->author);
2854 checked_free(ti->year);
2856 checked_free(ti->program_title);
2857 checked_free(ti->program_copyright);
2858 checked_free(ti->program_company);
2860 checked_free(ti->class_desc);
2862 checked_free(ti->infotext);
2864 if (ti->type == TREE_TYPE_LEVEL_DIR)
2866 checked_free(ti->imported_from);
2867 checked_free(ti->imported_by);
2868 checked_free(ti->tested_by);
2870 checked_free(ti->graphics_set_ecs);
2871 checked_free(ti->graphics_set_aga);
2872 checked_free(ti->graphics_set);
2873 checked_free(ti->sounds_set_default);
2874 checked_free(ti->sounds_set_lowpass);
2875 checked_free(ti->sounds_set);
2876 checked_free(ti->music_set);
2878 checked_free(ti->graphics_path);
2879 checked_free(ti->sounds_path);
2880 checked_free(ti->music_path);
2882 checked_free(ti->level_filename);
2883 checked_free(ti->level_filetype);
2885 checked_free(ti->special_flags);
2888 // recursively free child node
2890 freeTreeInfo(ti->node_group);
2892 // recursively free next node
2894 freeTreeInfo(ti->next);
2899 void setSetupInfo(struct TokenInfo *token_info,
2900 int token_nr, char *token_value)
2902 int token_type = token_info[token_nr].type;
2903 void *setup_value = token_info[token_nr].value;
2905 if (token_value == NULL)
2908 // set setup field to corresponding token value
2913 *(boolean *)setup_value = get_boolean_from_string(token_value);
2917 *(int *)setup_value = get_switch3_from_string(token_value);
2921 *(Key *)setup_value = getKeyFromKeyName(token_value);
2925 *(Key *)setup_value = getKeyFromX11KeyName(token_value);
2929 *(int *)setup_value = get_integer_from_string(token_value);
2933 checked_free(*(char **)setup_value);
2934 *(char **)setup_value = getStringCopy(token_value);
2938 *(int *)setup_value = get_player_nr_from_string(token_value);
2946 static int compareTreeInfoEntries(const void *object1, const void *object2)
2948 const TreeInfo *entry1 = *((TreeInfo **)object1);
2949 const TreeInfo *entry2 = *((TreeInfo **)object2);
2950 int tree_sorting1 = TREE_SORTING(entry1);
2951 int tree_sorting2 = TREE_SORTING(entry2);
2953 if (tree_sorting1 != tree_sorting2)
2954 return (tree_sorting1 - tree_sorting2);
2956 return strcasecmp(entry1->name_sorting, entry2->name_sorting);
2959 static TreeInfo *createParentTreeInfoNode(TreeInfo *node_parent)
2963 if (node_parent == NULL)
2966 ti_new = newTreeInfo();
2967 setTreeInfoToDefaults(ti_new, node_parent->type);
2969 ti_new->node_parent = node_parent;
2970 ti_new->parent_link = TRUE;
2972 setString(&ti_new->identifier, node_parent->identifier);
2973 setString(&ti_new->name, BACKLINK_TEXT_PARENT);
2974 setString(&ti_new->name_sorting, ti_new->name);
2976 setString(&ti_new->subdir, STRING_PARENT_DIRECTORY);
2977 setString(&ti_new->fullpath, node_parent->fullpath);
2979 ti_new->sort_priority = LEVELCLASS_PARENT;
2980 ti_new->latest_engine = node_parent->latest_engine;
2982 setString(&ti_new->class_desc, getLevelClassDescription(ti_new));
2984 pushTreeInfo(&node_parent->node_group, ti_new);
2989 static TreeInfo *createTopTreeInfoNode(TreeInfo *node_first)
2991 if (node_first == NULL)
2994 TreeInfo *ti_new = newTreeInfo();
2995 int type = node_first->type;
2997 setTreeInfoToDefaults(ti_new, type);
2999 ti_new->node_parent = NULL;
3000 ti_new->parent_link = FALSE;
3002 setString(&ti_new->identifier, "top_tree_node");
3003 setString(&ti_new->name, TREE_INFOTEXT(type));
3004 setString(&ti_new->name_sorting, ti_new->name);
3006 setString(&ti_new->subdir, STRING_TOP_DIRECTORY);
3007 setString(&ti_new->fullpath, ".");
3009 ti_new->sort_priority = LEVELCLASS_TOP;
3010 ti_new->latest_engine = node_first->latest_engine;
3012 setString(&ti_new->class_desc, TREE_INFOTEXT(type));
3014 ti_new->node_group = node_first;
3015 ti_new->level_group = TRUE;
3017 TreeInfo *ti_new2 = createParentTreeInfoNode(ti_new);
3019 setString(&ti_new2->name, TREE_BACKLINK_TEXT(type));
3020 setString(&ti_new2->name_sorting, ti_new2->name);
3025 static void setTreeInfoParentNodes(TreeInfo *node, TreeInfo *node_parent)
3029 if (node->node_group)
3030 setTreeInfoParentNodes(node->node_group, node);
3032 node->node_parent = node_parent;
3039 // ----------------------------------------------------------------------------
3040 // functions for handling level and custom artwork info cache
3041 // ----------------------------------------------------------------------------
3043 static void LoadArtworkInfoCache(void)
3045 InitCacheDirectory();
3047 if (artworkinfo_cache_old == NULL)
3049 char *filename = getPath2(getCacheDir(), ARTWORKINFO_CACHE_FILE);
3051 // try to load artwork info hash from already existing cache file
3052 artworkinfo_cache_old = loadSetupFileHash(filename);
3054 // try to get program version that artwork info cache was written with
3055 char *version = getHashEntry(artworkinfo_cache_old, "program.version");
3057 // check program version of artwork info cache against current version
3058 if (!strEqual(version, program.version_string))
3060 freeSetupFileHash(artworkinfo_cache_old);
3062 artworkinfo_cache_old = NULL;
3065 // if no artwork info cache file was found, start with empty hash
3066 if (artworkinfo_cache_old == NULL)
3067 artworkinfo_cache_old = newSetupFileHash();
3072 if (artworkinfo_cache_new == NULL)
3073 artworkinfo_cache_new = newSetupFileHash();
3075 update_artworkinfo_cache = FALSE;
3078 static void SaveArtworkInfoCache(void)
3080 if (!update_artworkinfo_cache)
3083 char *filename = getPath2(getCacheDir(), ARTWORKINFO_CACHE_FILE);
3085 InitCacheDirectory();
3087 saveSetupFileHash(artworkinfo_cache_new, filename);
3092 static char *getCacheTokenPrefix(char *prefix1, char *prefix2)
3094 static char *prefix = NULL;
3096 checked_free(prefix);
3098 prefix = getStringCat2WithSeparator(prefix1, prefix2, ".");
3103 // (identical to above function, but separate string buffer needed -- nasty)
3104 static char *getCacheToken(char *prefix, char *suffix)
3106 static char *token = NULL;
3108 checked_free(token);
3110 token = getStringCat2WithSeparator(prefix, suffix, ".");
3115 static char *getFileTimestampString(char *filename)
3117 return getStringCopy(i_to_a(getFileTimestampEpochSeconds(filename)));
3120 static boolean modifiedFileTimestamp(char *filename, char *timestamp_string)
3122 struct stat file_status;
3124 if (timestamp_string == NULL)
3127 if (!fileExists(filename)) // file does not exist
3128 return (atoi(timestamp_string) != 0);
3130 if (stat(filename, &file_status) != 0) // cannot stat file
3133 return (file_status.st_mtime != atoi(timestamp_string));
3136 static TreeInfo *getArtworkInfoCacheEntry(LevelDirTree *level_node, int type)
3138 char *identifier = level_node->subdir;
3139 char *type_string = ARTWORK_DIRECTORY(type);
3140 char *token_prefix = getCacheTokenPrefix(type_string, identifier);
3141 char *token_main = getCacheToken(token_prefix, "CACHED");
3142 char *cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
3143 boolean cached = (cache_entry != NULL && strEqual(cache_entry, "true"));
3144 TreeInfo *artwork_info = NULL;
3146 if (!use_artworkinfo_cache)
3149 if (optional_tokens_hash == NULL)
3153 // create hash from list of optional tokens (for quick access)
3154 optional_tokens_hash = newSetupFileHash();
3155 for (i = 0; optional_tokens[i] != NULL; i++)
3156 setHashEntry(optional_tokens_hash, optional_tokens[i], "");
3163 artwork_info = newTreeInfo();
3164 setTreeInfoToDefaults(artwork_info, type);
3166 // set all structure fields according to the token/value pairs
3167 ldi = *artwork_info;
3168 for (i = 0; artworkinfo_tokens[i].type != -1; i++)
3170 char *token_suffix = artworkinfo_tokens[i].text;
3171 char *token = getCacheToken(token_prefix, token_suffix);
3172 char *value = getHashEntry(artworkinfo_cache_old, token);
3174 (getHashEntry(optional_tokens_hash, token_suffix) != NULL);
3176 setSetupInfo(artworkinfo_tokens, i, value);
3178 // check if cache entry for this item is mandatory, but missing
3179 if (value == NULL && !optional)
3181 Warn("missing cache entry '%s'", token);
3187 *artwork_info = ldi;
3192 char *filename_levelinfo = getPath2(getLevelDirFromTreeInfo(level_node),
3193 LEVELINFO_FILENAME);
3194 char *filename_artworkinfo = getPath2(getSetupArtworkDir(artwork_info),
3195 ARTWORKINFO_FILENAME(type));
3197 // check if corresponding "levelinfo.conf" file has changed
3198 token_main = getCacheToken(token_prefix, "TIMESTAMP_LEVELINFO");
3199 cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
3201 if (modifiedFileTimestamp(filename_levelinfo, cache_entry))
3204 // check if corresponding "<artworkinfo>.conf" file has changed
3205 token_main = getCacheToken(token_prefix, "TIMESTAMP_ARTWORKINFO");
3206 cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
3208 if (modifiedFileTimestamp(filename_artworkinfo, cache_entry))
3211 checked_free(filename_levelinfo);
3212 checked_free(filename_artworkinfo);
3215 if (!cached && artwork_info != NULL)
3217 freeTreeInfo(artwork_info);
3222 return artwork_info;
3225 static void setArtworkInfoCacheEntry(TreeInfo *artwork_info,
3226 LevelDirTree *level_node, int type)
3228 char *identifier = level_node->subdir;
3229 char *type_string = ARTWORK_DIRECTORY(type);
3230 char *token_prefix = getCacheTokenPrefix(type_string, identifier);
3231 char *token_main = getCacheToken(token_prefix, "CACHED");
3232 boolean set_cache_timestamps = TRUE;
3235 setHashEntry(artworkinfo_cache_new, token_main, "true");
3237 if (set_cache_timestamps)
3239 char *filename_levelinfo = getPath2(getLevelDirFromTreeInfo(level_node),
3240 LEVELINFO_FILENAME);
3241 char *filename_artworkinfo = getPath2(getSetupArtworkDir(artwork_info),
3242 ARTWORKINFO_FILENAME(type));
3243 char *timestamp_levelinfo = getFileTimestampString(filename_levelinfo);
3244 char *timestamp_artworkinfo = getFileTimestampString(filename_artworkinfo);
3246 token_main = getCacheToken(token_prefix, "TIMESTAMP_LEVELINFO");
3247 setHashEntry(artworkinfo_cache_new, token_main, timestamp_levelinfo);
3249 token_main = getCacheToken(token_prefix, "TIMESTAMP_ARTWORKINFO");
3250 setHashEntry(artworkinfo_cache_new, token_main, timestamp_artworkinfo);
3252 checked_free(filename_levelinfo);
3253 checked_free(filename_artworkinfo);
3254 checked_free(timestamp_levelinfo);
3255 checked_free(timestamp_artworkinfo);
3258 ldi = *artwork_info;
3259 for (i = 0; artworkinfo_tokens[i].type != -1; i++)
3261 char *token = getCacheToken(token_prefix, artworkinfo_tokens[i].text);
3262 char *value = getSetupValue(artworkinfo_tokens[i].type,
3263 artworkinfo_tokens[i].value);
3265 setHashEntry(artworkinfo_cache_new, token, value);
3270 // ----------------------------------------------------------------------------
3271 // functions for loading level info and custom artwork info
3272 // ----------------------------------------------------------------------------
3274 int GetZipFileTreeType(char *zip_filename)
3276 static char *top_dir_path = NULL;
3277 static char *top_dir_conf_filename[NUM_BASE_TREE_TYPES] = { NULL };
3278 static char *conf_basename[NUM_BASE_TREE_TYPES] =
3280 GRAPHICSINFO_FILENAME,
3281 SOUNDSINFO_FILENAME,
3287 checked_free(top_dir_path);
3288 top_dir_path = NULL;
3290 for (j = 0; j < NUM_BASE_TREE_TYPES; j++)
3292 checked_free(top_dir_conf_filename[j]);
3293 top_dir_conf_filename[j] = NULL;
3296 char **zip_entries = zip_list(zip_filename);
3298 // check if zip file successfully opened
3299 if (zip_entries == NULL || zip_entries[0] == NULL)
3300 return TREE_TYPE_UNDEFINED;
3302 // first zip file entry is expected to be top level directory
3303 char *top_dir = zip_entries[0];
3305 // check if valid top level directory found in zip file
3306 if (!strSuffix(top_dir, "/"))
3307 return TREE_TYPE_UNDEFINED;
3309 // get filenames of valid configuration files in top level directory
3310 for (j = 0; j < NUM_BASE_TREE_TYPES; j++)
3311 top_dir_conf_filename[j] = getStringCat2(top_dir, conf_basename[j]);
3313 int tree_type = TREE_TYPE_UNDEFINED;
3316 while (zip_entries[e] != NULL)
3318 // check if every zip file entry is below top level directory
3319 if (!strPrefix(zip_entries[e], top_dir))
3320 return TREE_TYPE_UNDEFINED;
3322 // check if this zip file entry is a valid configuration filename
3323 for (j = 0; j < NUM_BASE_TREE_TYPES; j++)
3325 if (strEqual(zip_entries[e], top_dir_conf_filename[j]))
3327 // only exactly one valid configuration file allowed
3328 if (tree_type != TREE_TYPE_UNDEFINED)
3329 return TREE_TYPE_UNDEFINED;
3341 static boolean CheckZipFileForDirectory(char *zip_filename, char *directory,
3344 static char *top_dir_path = NULL;
3345 static char *top_dir_conf_filename = NULL;
3347 checked_free(top_dir_path);
3348 checked_free(top_dir_conf_filename);
3350 top_dir_path = NULL;
3351 top_dir_conf_filename = NULL;
3353 char *conf_basename = (tree_type == TREE_TYPE_LEVEL_DIR ? LEVELINFO_FILENAME :
3354 ARTWORKINFO_FILENAME(tree_type));
3356 // check if valid configuration filename determined
3357 if (conf_basename == NULL || strEqual(conf_basename, ""))
3360 char **zip_entries = zip_list(zip_filename);
3362 // check if zip file successfully opened
3363 if (zip_entries == NULL || zip_entries[0] == NULL)
3366 // first zip file entry is expected to be top level directory
3367 char *top_dir = zip_entries[0];
3369 // check if valid top level directory found in zip file
3370 if (!strSuffix(top_dir, "/"))
3373 // get path of extracted top level directory
3374 top_dir_path = getPath2(directory, top_dir);
3376 // remove trailing directory separator from top level directory path
3377 // (required to be able to check for file and directory in next step)
3378 top_dir_path[strlen(top_dir_path) - 1] = '\0';
3380 // check if zip file's top level directory already exists in target directory
3381 if (fileExists(top_dir_path)) // (checks for file and directory)
3384 // get filename of configuration file in top level directory
3385 top_dir_conf_filename = getStringCat2(top_dir, conf_basename);
3387 boolean found_top_dir_conf_filename = FALSE;
3390 while (zip_entries[i] != NULL)
3392 // check if every zip file entry is below top level directory
3393 if (!strPrefix(zip_entries[i], top_dir))
3396 // check if this zip file entry is the configuration filename
3397 if (strEqual(zip_entries[i], top_dir_conf_filename))
3398 found_top_dir_conf_filename = TRUE;
3403 // check if valid configuration filename was found in zip file
3404 if (!found_top_dir_conf_filename)
3410 char *ExtractZipFileIntoDirectory(char *zip_filename, char *directory,
3413 boolean zip_file_valid = CheckZipFileForDirectory(zip_filename, directory,
3416 if (!zip_file_valid)
3418 Warn("zip file '%s' rejected!", zip_filename);
3423 char **zip_entries = zip_extract(zip_filename, directory);
3425 if (zip_entries == NULL)
3427 Warn("zip file '%s' could not be extracted!", zip_filename);
3432 Info("zip file '%s' successfully extracted!", zip_filename);
3434 // first zip file entry contains top level directory
3435 char *top_dir = zip_entries[0];
3437 // remove trailing directory separator from top level directory
3438 top_dir[strlen(top_dir) - 1] = '\0';
3443 static void ProcessZipFilesInDirectory(char *directory, int tree_type)
3446 DirectoryEntry *dir_entry;
3448 if ((dir = openDirectory(directory)) == NULL)
3450 // display error if directory is main "options.graphics_directory" etc.
3451 if (tree_type == TREE_TYPE_LEVEL_DIR ||
3452 directory == OPTIONS_ARTWORK_DIRECTORY(tree_type))
3453 Warn("cannot read directory '%s'", directory);
3458 while ((dir_entry = readDirectory(dir)) != NULL) // loop all entries
3460 // skip non-zip files (and also directories with zip extension)
3461 if (!strSuffixLower(dir_entry->basename, ".zip") || dir_entry->is_directory)
3464 char *zip_filename = getPath2(directory, dir_entry->basename);
3465 char *zip_filename_extracted = getStringCat2(zip_filename, ".extracted");
3466 char *zip_filename_rejected = getStringCat2(zip_filename, ".rejected");
3468 // check if zip file hasn't already been extracted or rejected
3469 if (!fileExists(zip_filename_extracted) &&
3470 !fileExists(zip_filename_rejected))
3472 char *top_dir = ExtractZipFileIntoDirectory(zip_filename, directory,
3474 char *marker_filename = (top_dir != NULL ? zip_filename_extracted :
3475 zip_filename_rejected);
3478 // create empty file to mark zip file as extracted or rejected
3479 if ((marker_file = fopen(marker_filename, MODE_WRITE)))
3480 fclose(marker_file);
3483 free(zip_filename_extracted);
3484 free(zip_filename_rejected);
3488 closeDirectory(dir);
3491 // forward declaration for recursive call by "LoadLevelInfoFromLevelDir()"
3492 static void LoadLevelInfoFromLevelDir(TreeInfo **, TreeInfo *, char *);
3494 static boolean LoadLevelInfoFromLevelConf(TreeInfo **node_first,
3495 TreeInfo *node_parent,
3496 char *level_directory,
3497 char *directory_name)
3499 char *directory_path = getPath2(level_directory, directory_name);
3500 char *filename = getPath2(directory_path, LEVELINFO_FILENAME);
3501 SetupFileHash *setup_file_hash;
3502 LevelDirTree *leveldir_new = NULL;
3505 // unless debugging, silently ignore directories without "levelinfo.conf"
3506 if (!options.debug && !fileExists(filename))
3508 free(directory_path);
3514 setup_file_hash = loadSetupFileHash(filename);
3516 if (setup_file_hash == NULL)
3518 #if DEBUG_NO_CONFIG_FILE
3519 Debug("setup", "ignoring level directory '%s'", directory_path);
3522 free(directory_path);
3528 leveldir_new = newTreeInfo();
3531 setTreeInfoToDefaultsFromParent(leveldir_new, node_parent);
3533 setTreeInfoToDefaults(leveldir_new, TREE_TYPE_LEVEL_DIR);
3535 leveldir_new->subdir = getStringCopy(directory_name);
3537 // set all structure fields according to the token/value pairs
3538 ldi = *leveldir_new;
3539 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
3540 setSetupInfo(levelinfo_tokens, i,
3541 getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
3542 *leveldir_new = ldi;
3544 if (strEqual(leveldir_new->name, ANONYMOUS_NAME))
3545 setString(&leveldir_new->name, leveldir_new->subdir);
3547 if (leveldir_new->identifier == NULL)
3548 leveldir_new->identifier = getStringCopy(leveldir_new->subdir);
3550 if (leveldir_new->name_sorting == NULL)
3551 leveldir_new->name_sorting = getStringCopy(leveldir_new->name);
3553 if (node_parent == NULL) // top level group
3555 leveldir_new->basepath = getStringCopy(level_directory);
3556 leveldir_new->fullpath = getStringCopy(leveldir_new->subdir);
3558 else // sub level group
3560 leveldir_new->basepath = getStringCopy(node_parent->basepath);
3561 leveldir_new->fullpath = getPath2(node_parent->fullpath, directory_name);
3564 leveldir_new->last_level =
3565 leveldir_new->first_level + leveldir_new->levels - 1;
3567 leveldir_new->in_user_dir =
3568 (!strEqual(leveldir_new->basepath, options.level_directory));
3570 // adjust some settings if user's private level directory was detected
3571 if (leveldir_new->sort_priority == LEVELCLASS_UNDEFINED &&
3572 leveldir_new->in_user_dir &&
3573 (strEqual(leveldir_new->subdir, getLoginName()) ||
3574 strEqual(leveldir_new->name, getLoginName()) ||
3575 strEqual(leveldir_new->author, getRealName())))
3577 leveldir_new->sort_priority = LEVELCLASS_PRIVATE_START;
3578 leveldir_new->readonly = FALSE;
3581 leveldir_new->user_defined =
3582 (leveldir_new->in_user_dir && IS_LEVELCLASS_PRIVATE(leveldir_new));
3584 setString(&leveldir_new->class_desc, getLevelClassDescription(leveldir_new));
3586 leveldir_new->handicap_level = // set handicap to default value
3587 (leveldir_new->user_defined || !leveldir_new->handicap ?
3588 leveldir_new->last_level : leveldir_new->first_level);
3590 DrawInitText(leveldir_new->name, 150, FC_YELLOW);
3592 pushTreeInfo(node_first, leveldir_new);
3594 freeSetupFileHash(setup_file_hash);
3596 if (leveldir_new->level_group)
3598 // create node to link back to current level directory
3599 createParentTreeInfoNode(leveldir_new);
3601 // recursively step into sub-directory and look for more level series
3602 LoadLevelInfoFromLevelDir(&leveldir_new->node_group,
3603 leveldir_new, directory_path);
3606 free(directory_path);
3612 static void LoadLevelInfoFromLevelDir(TreeInfo **node_first,
3613 TreeInfo *node_parent,
3614 char *level_directory)
3616 // ---------- 1st stage: process any level set zip files ----------
3618 ProcessZipFilesInDirectory(level_directory, TREE_TYPE_LEVEL_DIR);
3620 // ---------- 2nd stage: check for level set directories ----------
3623 DirectoryEntry *dir_entry;
3624 boolean valid_entry_found = FALSE;
3626 if ((dir = openDirectory(level_directory)) == NULL)
3628 Warn("cannot read level directory '%s'", level_directory);
3633 while ((dir_entry = readDirectory(dir)) != NULL) // loop all entries
3635 char *directory_name = dir_entry->basename;
3636 char *directory_path = getPath2(level_directory, directory_name);
3638 // skip entries for current and parent directory
3639 if (strEqual(directory_name, ".") ||
3640 strEqual(directory_name, ".."))
3642 free(directory_path);
3647 // find out if directory entry is itself a directory
3648 if (!dir_entry->is_directory) // not a directory
3650 free(directory_path);
3655 free(directory_path);
3657 if (strEqual(directory_name, GRAPHICS_DIRECTORY) ||
3658 strEqual(directory_name, SOUNDS_DIRECTORY) ||
3659 strEqual(directory_name, MUSIC_DIRECTORY))
3662 valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
3667 closeDirectory(dir);
3669 // special case: top level directory may directly contain "levelinfo.conf"
3670 if (node_parent == NULL && !valid_entry_found)
3672 // check if this directory directly contains a file "levelinfo.conf"
3673 valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
3674 level_directory, ".");
3677 if (!valid_entry_found)
3678 Warn("cannot find any valid level series in directory '%s'",
3682 boolean AdjustGraphicsForEMC(void)
3684 boolean settings_changed = FALSE;
3686 settings_changed |= adjustTreeGraphicsForEMC(leveldir_first_all);
3687 settings_changed |= adjustTreeGraphicsForEMC(leveldir_first);
3689 return settings_changed;
3692 boolean AdjustSoundsForEMC(void)
3694 boolean settings_changed = FALSE;
3696 settings_changed |= adjustTreeSoundsForEMC(leveldir_first_all);
3697 settings_changed |= adjustTreeSoundsForEMC(leveldir_first);
3699 return settings_changed;
3702 void LoadLevelInfo(void)
3704 InitUserLevelDirectory(getLoginName());
3706 DrawInitText("Loading level series", 120, FC_GREEN);
3708 LoadLevelInfoFromLevelDir(&leveldir_first, NULL, options.level_directory);
3709 LoadLevelInfoFromLevelDir(&leveldir_first, NULL, getUserLevelDir(NULL));
3711 leveldir_first = createTopTreeInfoNode(leveldir_first);
3713 /* after loading all level set information, clone the level directory tree
3714 and remove all level sets without levels (these may still contain artwork
3715 to be offered in the setup menu as "custom artwork", and are therefore
3716 checked for existing artwork in the function "LoadLevelArtworkInfo()") */
3717 leveldir_first_all = leveldir_first;
3718 cloneTree(&leveldir_first, leveldir_first_all, TRUE);
3720 AdjustGraphicsForEMC();
3721 AdjustSoundsForEMC();
3723 // before sorting, the first entries will be from the user directory
3724 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
3726 if (leveldir_first == NULL)
3727 Fail("cannot find any valid level series in any directory");
3729 sortTreeInfo(&leveldir_first);
3731 #if ENABLE_UNUSED_CODE
3732 dumpTreeInfo(leveldir_first, 0);
3736 static boolean LoadArtworkInfoFromArtworkConf(TreeInfo **node_first,
3737 TreeInfo *node_parent,
3738 char *base_directory,
3739 char *directory_name, int type)
3741 char *directory_path = getPath2(base_directory, directory_name);
3742 char *filename = getPath2(directory_path, ARTWORKINFO_FILENAME(type));
3743 SetupFileHash *setup_file_hash = NULL;
3744 TreeInfo *artwork_new = NULL;
3747 if (fileExists(filename))
3748 setup_file_hash = loadSetupFileHash(filename);
3750 if (setup_file_hash == NULL) // no config file -- look for artwork files
3753 DirectoryEntry *dir_entry;
3754 boolean valid_file_found = FALSE;
3756 if ((dir = openDirectory(directory_path)) != NULL)
3758 while ((dir_entry = readDirectory(dir)) != NULL)
3760 if (FileIsArtworkType(dir_entry->filename, type))
3762 valid_file_found = TRUE;
3768 closeDirectory(dir);
3771 if (!valid_file_found)
3773 #if DEBUG_NO_CONFIG_FILE
3774 if (!strEqual(directory_name, "."))
3775 Debug("setup", "ignoring artwork directory '%s'", directory_path);
3778 free(directory_path);
3785 artwork_new = newTreeInfo();
3788 setTreeInfoToDefaultsFromParent(artwork_new, node_parent);
3790 setTreeInfoToDefaults(artwork_new, type);
3792 artwork_new->subdir = getStringCopy(directory_name);
3794 if (setup_file_hash) // (before defining ".color" and ".class_desc")
3796 // set all structure fields according to the token/value pairs
3798 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
3799 setSetupInfo(levelinfo_tokens, i,
3800 getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
3803 if (strEqual(artwork_new->name, ANONYMOUS_NAME))
3804 setString(&artwork_new->name, artwork_new->subdir);
3806 if (artwork_new->identifier == NULL)
3807 artwork_new->identifier = getStringCopy(artwork_new->subdir);
3809 if (artwork_new->name_sorting == NULL)
3810 artwork_new->name_sorting = getStringCopy(artwork_new->name);
3813 if (node_parent == NULL) // top level group
3815 artwork_new->basepath = getStringCopy(base_directory);
3816 artwork_new->fullpath = getStringCopy(artwork_new->subdir);
3818 else // sub level group
3820 artwork_new->basepath = getStringCopy(node_parent->basepath);
3821 artwork_new->fullpath = getPath2(node_parent->fullpath, directory_name);
3824 artwork_new->in_user_dir =
3825 (!strEqual(artwork_new->basepath, OPTIONS_ARTWORK_DIRECTORY(type)));
3827 setString(&artwork_new->class_desc, getLevelClassDescription(artwork_new));
3829 if (setup_file_hash == NULL) // (after determining ".user_defined")
3831 if (strEqual(artwork_new->subdir, "."))
3833 if (artwork_new->user_defined)
3835 setString(&artwork_new->identifier, "private");
3836 artwork_new->sort_priority = ARTWORKCLASS_PRIVATE;
3840 setString(&artwork_new->identifier, "classic");
3841 artwork_new->sort_priority = ARTWORKCLASS_CLASSICS;
3844 setString(&artwork_new->class_desc,
3845 getLevelClassDescription(artwork_new));
3849 setString(&artwork_new->identifier, artwork_new->subdir);
3852 setString(&artwork_new->name, artwork_new->identifier);
3853 setString(&artwork_new->name_sorting, artwork_new->name);
3856 pushTreeInfo(node_first, artwork_new);
3858 freeSetupFileHash(setup_file_hash);
3860 free(directory_path);
3866 static void LoadArtworkInfoFromArtworkDir(TreeInfo **node_first,
3867 TreeInfo *node_parent,
3868 char *base_directory, int type)
3870 // ---------- 1st stage: process any artwork set zip files ----------
3872 ProcessZipFilesInDirectory(base_directory, type);
3874 // ---------- 2nd stage: check for artwork set directories ----------
3877 DirectoryEntry *dir_entry;
3878 boolean valid_entry_found = FALSE;
3880 if ((dir = openDirectory(base_directory)) == NULL)
3882 // display error if directory is main "options.graphics_directory" etc.
3883 if (base_directory == OPTIONS_ARTWORK_DIRECTORY(type))
3884 Warn("cannot read directory '%s'", base_directory);
3889 while ((dir_entry = readDirectory(dir)) != NULL) // loop all entries
3891 char *directory_name = dir_entry->basename;
3892 char *directory_path = getPath2(base_directory, directory_name);
3894 // skip directory entries for current and parent directory
3895 if (strEqual(directory_name, ".") ||
3896 strEqual(directory_name, ".."))
3898 free(directory_path);
3903 // skip directory entries which are not a directory
3904 if (!dir_entry->is_directory) // not a directory
3906 free(directory_path);
3911 free(directory_path);
3913 // check if this directory contains artwork with or without config file
3914 valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
3916 directory_name, type);
3919 closeDirectory(dir);
3921 // check if this directory directly contains artwork itself
3922 valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
3923 base_directory, ".",
3925 if (!valid_entry_found)
3926 Warn("cannot find any valid artwork in directory '%s'", base_directory);
3929 static TreeInfo *getDummyArtworkInfo(int type)
3931 // this is only needed when there is completely no artwork available
3932 TreeInfo *artwork_new = newTreeInfo();
3934 setTreeInfoToDefaults(artwork_new, type);
3936 setString(&artwork_new->subdir, UNDEFINED_FILENAME);
3937 setString(&artwork_new->fullpath, UNDEFINED_FILENAME);
3938 setString(&artwork_new->basepath, UNDEFINED_FILENAME);
3940 setString(&artwork_new->identifier, UNDEFINED_FILENAME);
3941 setString(&artwork_new->name, UNDEFINED_FILENAME);
3942 setString(&artwork_new->name_sorting, UNDEFINED_FILENAME);
3947 void SetCurrentArtwork(int type)
3949 ArtworkDirTree **current_ptr = ARTWORK_CURRENT_PTR(artwork, type);
3950 ArtworkDirTree *first_node = ARTWORK_FIRST_NODE(artwork, type);
3951 char *setup_set = SETUP_ARTWORK_SET(setup, type);
3952 char *default_subdir = ARTWORK_DEFAULT_SUBDIR(type);
3954 // set current artwork to artwork configured in setup menu
3955 *current_ptr = getTreeInfoFromIdentifier(first_node, setup_set);
3957 // if not found, set current artwork to default artwork
3958 if (*current_ptr == NULL)
3959 *current_ptr = getTreeInfoFromIdentifier(first_node, default_subdir);
3961 // if not found, set current artwork to first artwork in tree
3962 if (*current_ptr == NULL)
3963 *current_ptr = getFirstValidTreeInfoEntry(first_node);
3966 void ChangeCurrentArtworkIfNeeded(int type)
3968 char *current_identifier = ARTWORK_CURRENT_IDENTIFIER(artwork, type);
3969 char *setup_set = SETUP_ARTWORK_SET(setup, type);
3971 if (!strEqual(current_identifier, setup_set))
3972 SetCurrentArtwork(type);
3975 void LoadArtworkInfo(void)
3977 LoadArtworkInfoCache();
3979 DrawInitText("Looking for custom artwork", 120, FC_GREEN);
3981 LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
3982 options.graphics_directory,
3983 TREE_TYPE_GRAPHICS_DIR);
3984 LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
3985 getUserGraphicsDir(),
3986 TREE_TYPE_GRAPHICS_DIR);
3988 LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
3989 options.sounds_directory,
3990 TREE_TYPE_SOUNDS_DIR);
3991 LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
3993 TREE_TYPE_SOUNDS_DIR);
3995 LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
3996 options.music_directory,
3997 TREE_TYPE_MUSIC_DIR);
3998 LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
4000 TREE_TYPE_MUSIC_DIR);
4002 if (artwork.gfx_first == NULL)
4003 artwork.gfx_first = getDummyArtworkInfo(TREE_TYPE_GRAPHICS_DIR);
4004 if (artwork.snd_first == NULL)
4005 artwork.snd_first = getDummyArtworkInfo(TREE_TYPE_SOUNDS_DIR);
4006 if (artwork.mus_first == NULL)
4007 artwork.mus_first = getDummyArtworkInfo(TREE_TYPE_MUSIC_DIR);
4009 // before sorting, the first entries will be from the user directory
4010 SetCurrentArtwork(ARTWORK_TYPE_GRAPHICS);
4011 SetCurrentArtwork(ARTWORK_TYPE_SOUNDS);
4012 SetCurrentArtwork(ARTWORK_TYPE_MUSIC);
4014 artwork.gfx_current_identifier = artwork.gfx_current->identifier;
4015 artwork.snd_current_identifier = artwork.snd_current->identifier;
4016 artwork.mus_current_identifier = artwork.mus_current->identifier;
4018 #if ENABLE_UNUSED_CODE
4019 Debug("setup:LoadArtworkInfo", "graphics set == %s",
4020 artwork.gfx_current_identifier);
4021 Debug("setup:LoadArtworkInfo", "sounds set == %s",
4022 artwork.snd_current_identifier);
4023 Debug("setup:LoadArtworkInfo", "music set == %s",
4024 artwork.mus_current_identifier);
4027 sortTreeInfo(&artwork.gfx_first);
4028 sortTreeInfo(&artwork.snd_first);
4029 sortTreeInfo(&artwork.mus_first);
4031 #if ENABLE_UNUSED_CODE
4032 dumpTreeInfo(artwork.gfx_first, 0);
4033 dumpTreeInfo(artwork.snd_first, 0);
4034 dumpTreeInfo(artwork.mus_first, 0);
4038 static void MoveArtworkInfoIntoSubTree(ArtworkDirTree **artwork_node)
4040 ArtworkDirTree *artwork_new = newTreeInfo();
4041 char *top_node_name = "standalone artwork";
4043 setTreeInfoToDefaults(artwork_new, (*artwork_node)->type);
4045 artwork_new->level_group = TRUE;
4047 setString(&artwork_new->identifier, top_node_name);
4048 setString(&artwork_new->name, top_node_name);
4049 setString(&artwork_new->name_sorting, top_node_name);
4051 // create node to link back to current custom artwork directory
4052 createParentTreeInfoNode(artwork_new);
4054 // move existing custom artwork tree into newly created sub-tree
4055 artwork_new->node_group->next = *artwork_node;
4057 // change custom artwork tree to contain only newly created node
4058 *artwork_node = artwork_new;
4061 static void LoadArtworkInfoFromLevelInfoExt(ArtworkDirTree **artwork_node,
4062 ArtworkDirTree *node_parent,
4063 LevelDirTree *level_node,
4064 boolean empty_level_set_mode)
4066 int type = (*artwork_node)->type;
4068 // recursively check all level directories for artwork sub-directories
4072 boolean empty_level_set = (level_node->levels == 0);
4074 // check all tree entries for artwork, but skip parent link entries
4075 if (!level_node->parent_link && empty_level_set == empty_level_set_mode)
4077 TreeInfo *artwork_new = getArtworkInfoCacheEntry(level_node, type);
4078 boolean cached = (artwork_new != NULL);
4082 pushTreeInfo(artwork_node, artwork_new);
4086 TreeInfo *topnode_last = *artwork_node;
4087 char *path = getPath2(getLevelDirFromTreeInfo(level_node),
4088 ARTWORK_DIRECTORY(type));
4090 LoadArtworkInfoFromArtworkDir(artwork_node, NULL, path, type);
4092 if (topnode_last != *artwork_node) // check for newly added node
4094 artwork_new = *artwork_node;
4096 setString(&artwork_new->identifier, level_node->subdir);
4097 setString(&artwork_new->name, level_node->name);
4098 setString(&artwork_new->name_sorting, level_node->name_sorting);
4100 artwork_new->sort_priority = level_node->sort_priority;
4101 artwork_new->in_user_dir = level_node->in_user_dir;
4103 update_artworkinfo_cache = TRUE;
4109 // insert artwork info (from old cache or filesystem) into new cache
4110 if (artwork_new != NULL)
4111 setArtworkInfoCacheEntry(artwork_new, level_node, type);
4114 DrawInitText(level_node->name, 150, FC_YELLOW);
4116 if (level_node->node_group != NULL)
4118 TreeInfo *artwork_new = newTreeInfo();
4121 setTreeInfoToDefaultsFromParent(artwork_new, node_parent);
4123 setTreeInfoToDefaults(artwork_new, type);
4125 artwork_new->level_group = TRUE;
4127 setString(&artwork_new->identifier, level_node->subdir);
4129 if (node_parent == NULL) // check for top tree node
4131 char *top_node_name = (empty_level_set_mode ?
4132 "artwork for certain level sets" :
4133 "artwork included in level sets");
4135 setString(&artwork_new->name, top_node_name);
4136 setString(&artwork_new->name_sorting, top_node_name);
4140 setString(&artwork_new->name, level_node->name);
4141 setString(&artwork_new->name_sorting, level_node->name_sorting);
4144 pushTreeInfo(artwork_node, artwork_new);
4146 // create node to link back to current custom artwork directory
4147 createParentTreeInfoNode(artwork_new);
4149 // recursively step into sub-directory and look for more custom artwork
4150 LoadArtworkInfoFromLevelInfoExt(&artwork_new->node_group, artwork_new,
4151 level_node->node_group,
4152 empty_level_set_mode);
4154 // if sub-tree has no custom artwork at all, remove it
4155 if (artwork_new->node_group->next == NULL)
4156 removeTreeInfo(artwork_node);
4159 level_node = level_node->next;
4163 static void LoadArtworkInfoFromLevelInfo(ArtworkDirTree **artwork_node)
4165 // move peviously loaded artwork tree into separate sub-tree
4166 MoveArtworkInfoIntoSubTree(artwork_node);
4168 // load artwork from level sets into separate sub-trees
4169 LoadArtworkInfoFromLevelInfoExt(artwork_node, NULL, leveldir_first_all, TRUE);
4170 LoadArtworkInfoFromLevelInfoExt(artwork_node, NULL, leveldir_first_all, FALSE);
4172 // add top tree node over all three separate sub-trees
4173 *artwork_node = createTopTreeInfoNode(*artwork_node);
4175 // set all parent links (back links) in complete artwork tree
4176 setTreeInfoParentNodes(*artwork_node, NULL);
4179 void LoadLevelArtworkInfo(void)
4181 print_timestamp_init("LoadLevelArtworkInfo");
4183 DrawInitText("Looking for custom level artwork", 120, FC_GREEN);
4185 print_timestamp_time("DrawTimeText");
4187 LoadArtworkInfoFromLevelInfo(&artwork.gfx_first);
4188 print_timestamp_time("LoadArtworkInfoFromLevelInfo (gfx)");
4189 LoadArtworkInfoFromLevelInfo(&artwork.snd_first);
4190 print_timestamp_time("LoadArtworkInfoFromLevelInfo (snd)");
4191 LoadArtworkInfoFromLevelInfo(&artwork.mus_first);
4192 print_timestamp_time("LoadArtworkInfoFromLevelInfo (mus)");
4194 SaveArtworkInfoCache();
4196 print_timestamp_time("SaveArtworkInfoCache");
4198 // needed for reloading level artwork not known at ealier stage
4199 ChangeCurrentArtworkIfNeeded(ARTWORK_TYPE_GRAPHICS);
4200 ChangeCurrentArtworkIfNeeded(ARTWORK_TYPE_SOUNDS);
4201 ChangeCurrentArtworkIfNeeded(ARTWORK_TYPE_MUSIC);
4203 print_timestamp_time("getTreeInfoFromIdentifier");
4205 sortTreeInfo(&artwork.gfx_first);
4206 sortTreeInfo(&artwork.snd_first);
4207 sortTreeInfo(&artwork.mus_first);
4209 print_timestamp_time("sortTreeInfo");
4211 #if ENABLE_UNUSED_CODE
4212 dumpTreeInfo(artwork.gfx_first, 0);
4213 dumpTreeInfo(artwork.snd_first, 0);
4214 dumpTreeInfo(artwork.mus_first, 0);
4217 print_timestamp_done("LoadLevelArtworkInfo");
4220 static boolean AddTreeSetToTreeInfoExt(TreeInfo *tree_node_old, char *tree_dir,
4221 char *tree_subdir_new, int type)
4223 if (tree_node_old == NULL)
4225 if (type == TREE_TYPE_LEVEL_DIR)
4227 // get level info tree node of personal user level set
4228 tree_node_old = getTreeInfoFromIdentifier(leveldir_first, getLoginName());
4230 // this may happen if "setup.internal.create_user_levelset" is FALSE
4231 // or if file "levelinfo.conf" is missing in personal user level set
4232 if (tree_node_old == NULL)
4233 tree_node_old = leveldir_first->node_group;
4237 // get artwork info tree node of first artwork set
4238 tree_node_old = ARTWORK_FIRST_NODE(artwork, type);
4242 if (tree_dir == NULL)
4243 tree_dir = TREE_USERDIR(type);
4245 if (tree_node_old == NULL ||
4247 tree_subdir_new == NULL) // should not happen
4250 int draw_deactivation_mask = GetDrawDeactivationMask();
4252 // override draw deactivation mask (temporarily disable drawing)
4253 SetDrawDeactivationMask(REDRAW_ALL);
4255 if (type == TREE_TYPE_LEVEL_DIR)
4257 // load new level set config and add it next to first user level set
4258 LoadLevelInfoFromLevelConf(&tree_node_old->next,
4259 tree_node_old->node_parent,
4260 tree_dir, tree_subdir_new);
4264 // load new artwork set config and add it next to first artwork set
4265 LoadArtworkInfoFromArtworkConf(&tree_node_old->next,
4266 tree_node_old->node_parent,
4267 tree_dir, tree_subdir_new, type);
4270 // set draw deactivation mask to previous value
4271 SetDrawDeactivationMask(draw_deactivation_mask);
4273 // get first node of level or artwork info tree
4274 TreeInfo **tree_node_first = TREE_FIRST_NODE_PTR(type);
4276 // get tree info node of newly added level or artwork set
4277 TreeInfo *tree_node_new = getTreeInfoFromIdentifier(*tree_node_first,
4280 if (tree_node_new == NULL) // should not happen
4283 // correct top link and parent node link of newly created tree node
4284 tree_node_new->node_top = tree_node_old->node_top;
4285 tree_node_new->node_parent = tree_node_old->node_parent;
4287 // sort tree info to adjust position of newly added tree set
4288 sortTreeInfo(tree_node_first);
4293 void AddTreeSetToTreeInfo(TreeInfo *tree_node, char *tree_dir,
4294 char *tree_subdir_new, int type)
4296 if (!AddTreeSetToTreeInfoExt(tree_node, tree_dir, tree_subdir_new, type))
4297 Fail("internal tree info structure corrupted -- aborting");
4300 void AddUserLevelSetToLevelInfo(char *level_subdir_new)
4302 AddTreeSetToTreeInfo(NULL, NULL, level_subdir_new, TREE_TYPE_LEVEL_DIR);
4305 char *getArtworkIdentifierForUserLevelSet(int type)
4307 char *classic_artwork_set = getClassicArtworkSet(type);
4309 // check for custom artwork configured in "levelinfo.conf"
4310 char *leveldir_artwork_set =
4311 *LEVELDIR_ARTWORK_SET_PTR(leveldir_current, type);
4312 boolean has_leveldir_artwork_set =
4313 (leveldir_artwork_set != NULL && !strEqual(leveldir_artwork_set,
4314 classic_artwork_set));
4316 // check for custom artwork in sub-directory "graphics" etc.
4317 TreeInfo *artwork_first_node = ARTWORK_FIRST_NODE(artwork, type);
4318 char *leveldir_identifier = leveldir_current->identifier;
4319 boolean has_artwork_subdir =
4320 (getTreeInfoFromIdentifier(artwork_first_node,
4321 leveldir_identifier) != NULL);
4323 return (has_leveldir_artwork_set ? leveldir_artwork_set :
4324 has_artwork_subdir ? leveldir_identifier :
4325 classic_artwork_set);
4328 TreeInfo *getArtworkTreeInfoForUserLevelSet(int type)
4330 char *artwork_set = getArtworkIdentifierForUserLevelSet(type);
4331 TreeInfo *artwork_first_node = ARTWORK_FIRST_NODE(artwork, type);
4332 TreeInfo *ti = getTreeInfoFromIdentifier(artwork_first_node, artwork_set);
4336 ti = getTreeInfoFromIdentifier(artwork_first_node,
4337 ARTWORK_DEFAULT_SUBDIR(type));
4339 Fail("cannot find default graphics -- should not happen");
4345 boolean checkIfCustomArtworkExistsForCurrentLevelSet(void)
4347 char *graphics_set =
4348 getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_GRAPHICS);
4350 getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_SOUNDS);
4352 getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_MUSIC);
4354 return (!strEqual(graphics_set, GFX_CLASSIC_SUBDIR) ||
4355 !strEqual(sounds_set, SND_CLASSIC_SUBDIR) ||
4356 !strEqual(music_set, MUS_CLASSIC_SUBDIR));
4359 boolean UpdateUserLevelSet(char *level_subdir, char *level_name,
4360 char *level_author, int num_levels)
4362 char *filename = getPath2(getUserLevelDir(level_subdir), LEVELINFO_FILENAME);
4363 char *filename_tmp = getStringCat2(filename, ".tmp");
4365 FILE *file_tmp = NULL;
4366 char line[MAX_LINE_LEN];
4367 boolean success = FALSE;
4368 LevelDirTree *leveldir = getTreeInfoFromIdentifier(leveldir_first,
4370 // update values in level directory tree
4372 if (level_name != NULL)
4373 setString(&leveldir->name, level_name);
4375 if (level_author != NULL)
4376 setString(&leveldir->author, level_author);
4378 if (num_levels != -1)
4379 leveldir->levels = num_levels;
4381 // update values that depend on other values
4383 setString(&leveldir->name_sorting, leveldir->name);
4385 leveldir->last_level = leveldir->first_level + leveldir->levels - 1;
4387 // sort order of level sets may have changed
4388 sortTreeInfo(&leveldir_first);
4390 if ((file = fopen(filename, MODE_READ)) &&
4391 (file_tmp = fopen(filename_tmp, MODE_WRITE)))
4393 while (fgets(line, MAX_LINE_LEN, file))
4395 if (strPrefix(line, "name:") && level_name != NULL)
4396 fprintf(file_tmp, "%-32s%s\n", "name:", level_name);
4397 else if (strPrefix(line, "author:") && level_author != NULL)
4398 fprintf(file_tmp, "%-32s%s\n", "author:", level_author);
4399 else if (strPrefix(line, "levels:") && num_levels != -1)
4400 fprintf(file_tmp, "%-32s%d\n", "levels:", num_levels);
4402 fputs(line, file_tmp);
4415 success = (rename(filename_tmp, filename) == 0);
4423 boolean CreateUserLevelSet(char *level_subdir, char *level_name,
4424 char *level_author, int num_levels,
4425 boolean use_artwork_set)
4427 LevelDirTree *level_info;
4432 // create user level sub-directory, if needed
4433 createDirectory(getUserLevelDir(level_subdir), "user level", PERMS_PRIVATE);
4435 filename = getPath2(getUserLevelDir(level_subdir), LEVELINFO_FILENAME);
4437 if (!(file = fopen(filename, MODE_WRITE)))
4439 Warn("cannot write level info file '%s'", filename);
4446 level_info = newTreeInfo();
4448 // always start with reliable default values
4449 setTreeInfoToDefaults(level_info, TREE_TYPE_LEVEL_DIR);
4451 setString(&level_info->name, level_name);
4452 setString(&level_info->author, level_author);
4453 level_info->levels = num_levels;
4454 level_info->first_level = 1;
4455 level_info->sort_priority = LEVELCLASS_PRIVATE_START;
4456 level_info->readonly = FALSE;
4458 if (use_artwork_set)
4460 level_info->graphics_set =
4461 getStringCopy(getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_GRAPHICS));
4462 level_info->sounds_set =
4463 getStringCopy(getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_SOUNDS));
4464 level_info->music_set =
4465 getStringCopy(getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_MUSIC));
4468 token_value_position = TOKEN_VALUE_POSITION_SHORT;
4470 fprintFileHeader(file, LEVELINFO_FILENAME);
4473 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
4475 if (i == LEVELINFO_TOKEN_NAME ||
4476 i == LEVELINFO_TOKEN_AUTHOR ||
4477 i == LEVELINFO_TOKEN_LEVELS ||
4478 i == LEVELINFO_TOKEN_FIRST_LEVEL ||
4479 i == LEVELINFO_TOKEN_SORT_PRIORITY ||
4480 i == LEVELINFO_TOKEN_READONLY ||
4481 (use_artwork_set && (i == LEVELINFO_TOKEN_GRAPHICS_SET ||
4482 i == LEVELINFO_TOKEN_SOUNDS_SET ||
4483 i == LEVELINFO_TOKEN_MUSIC_SET)))
4484 fprintf(file, "%s\n", getSetupLine(levelinfo_tokens, "", i));
4486 // just to make things nicer :)
4487 if (i == LEVELINFO_TOKEN_AUTHOR ||
4488 i == LEVELINFO_TOKEN_FIRST_LEVEL ||
4489 (use_artwork_set && i == LEVELINFO_TOKEN_READONLY))
4490 fprintf(file, "\n");
4493 token_value_position = TOKEN_VALUE_POSITION_DEFAULT;
4497 SetFilePermissions(filename, PERMS_PRIVATE);
4499 freeTreeInfo(level_info);
4505 static void SaveUserLevelInfo(void)
4507 CreateUserLevelSet(getLoginName(), getLoginName(), getRealName(), 100, FALSE);
4510 char *getSetupValue(int type, void *value)
4512 static char value_string[MAX_LINE_LEN];
4520 strcpy(value_string, (*(boolean *)value ? "true" : "false"));
4524 strcpy(value_string, (*(boolean *)value ? "on" : "off"));
4528 strcpy(value_string, (*(int *)value == AUTO ? "auto" :
4529 *(int *)value == FALSE ? "off" : "on"));
4533 strcpy(value_string, (*(boolean *)value ? "yes" : "no"));
4536 case TYPE_YES_NO_AUTO:
4537 strcpy(value_string, (*(int *)value == AUTO ? "auto" :
4538 *(int *)value == FALSE ? "no" : "yes"));
4542 strcpy(value_string, (*(boolean *)value ? "AGA" : "ECS"));
4546 strcpy(value_string, getKeyNameFromKey(*(Key *)value));
4550 strcpy(value_string, getX11KeyNameFromKey(*(Key *)value));
4554 sprintf(value_string, "%d", *(int *)value);
4558 if (*(char **)value == NULL)
4561 strcpy(value_string, *(char **)value);
4565 sprintf(value_string, "player_%d", *(int *)value + 1);
4569 value_string[0] = '\0';
4573 if (type & TYPE_GHOSTED)
4574 strcpy(value_string, "n/a");
4576 return value_string;
4579 char *getSetupLine(struct TokenInfo *token_info, char *prefix, int token_nr)
4583 static char token_string[MAX_LINE_LEN];
4584 int token_type = token_info[token_nr].type;
4585 void *setup_value = token_info[token_nr].value;
4586 char *token_text = token_info[token_nr].text;
4587 char *value_string = getSetupValue(token_type, setup_value);
4589 // build complete token string
4590 sprintf(token_string, "%s%s", prefix, token_text);
4592 // build setup entry line
4593 line = getFormattedSetupEntry(token_string, value_string);
4595 if (token_type == TYPE_KEY_X11)
4597 Key key = *(Key *)setup_value;
4598 char *keyname = getKeyNameFromKey(key);
4600 // add comment, if useful
4601 if (!strEqual(keyname, "(undefined)") &&
4602 !strEqual(keyname, "(unknown)"))
4604 // add at least one whitespace
4606 for (i = strlen(line); i < token_comment_position; i++)
4610 strcat(line, keyname);
4617 static void InitLastPlayedLevels_ParentNode(void)
4619 LevelDirTree **leveldir_top = &leveldir_first->node_group->next;
4620 LevelDirTree *leveldir_new = NULL;
4622 // check if parent node for last played levels already exists
4623 if (strEqual((*leveldir_top)->identifier, TOKEN_STR_LAST_LEVEL_SERIES))
4626 leveldir_new = newTreeInfo();
4628 setTreeInfoToDefaultsFromParent(leveldir_new, leveldir_first);
4630 leveldir_new->level_group = TRUE;
4631 leveldir_new->sort_priority = LEVELCLASS_LAST_PLAYED_LEVEL;
4633 setString(&leveldir_new->identifier, TOKEN_STR_LAST_LEVEL_SERIES);
4634 setString(&leveldir_new->name, "<< (last played level sets)");
4635 setString(&leveldir_new->name_sorting, leveldir_new->name);
4637 pushTreeInfo(leveldir_top, leveldir_new);
4639 // create node to link back to current level directory
4640 createParentTreeInfoNode(leveldir_new);
4643 void UpdateLastPlayedLevels_TreeInfo(void)
4645 char **last_level_series = setup.level_setup.last_level_series;
4646 LevelDirTree *leveldir_last;
4647 TreeInfo **node_new = NULL;
4650 if (last_level_series[0] == NULL)
4653 InitLastPlayedLevels_ParentNode();
4655 leveldir_last = getTreeInfoFromIdentifierExt(leveldir_first,
4656 TOKEN_STR_LAST_LEVEL_SERIES,
4657 TREE_NODE_TYPE_GROUP);
4658 if (leveldir_last == NULL)
4661 node_new = &leveldir_last->node_group->next;
4663 freeTreeInfo(*node_new);
4667 for (i = 0; last_level_series[i] != NULL; i++)
4669 LevelDirTree *node_last = getTreeInfoFromIdentifier(leveldir_first,
4670 last_level_series[i]);
4671 if (node_last == NULL)
4674 *node_new = getTreeInfoCopy(node_last); // copy complete node
4676 (*node_new)->node_top = &leveldir_first; // correct top node link
4677 (*node_new)->node_parent = leveldir_last; // correct parent node link
4679 (*node_new)->is_copy = TRUE; // mark entry as node copy
4681 (*node_new)->node_group = NULL;
4682 (*node_new)->next = NULL;
4684 (*node_new)->cl_first = -1; // force setting tree cursor
4686 node_new = &((*node_new)->next);
4690 static void UpdateLastPlayedLevels_List(void)
4692 char **last_level_series = setup.level_setup.last_level_series;
4693 int pos = MAX_LEVELDIR_HISTORY - 1;
4696 // search for potentially already existing entry in list of level sets
4697 for (i = 0; last_level_series[i] != NULL; i++)
4698 if (strEqual(last_level_series[i], leveldir_current->identifier))
4701 // move list of level sets one entry down (using potentially free entry)
4702 for (i = pos; i > 0; i--)
4703 setString(&last_level_series[i], last_level_series[i - 1]);
4705 // put last played level set at top position
4706 setString(&last_level_series[0], leveldir_current->identifier);
4709 static TreeInfo *StoreOrRestoreLastPlayedLevels(TreeInfo *node, boolean store)
4711 static char *identifier = NULL;
4715 setString(&identifier, (node && node->is_copy ? node->identifier : NULL));
4717 return NULL; // not used
4721 TreeInfo *node_new = getTreeInfoFromIdentifierExt(leveldir_first,
4723 TREE_NODE_TYPE_COPY);
4724 return (node_new != NULL ? node_new : node);
4728 void StoreLastPlayedLevels(TreeInfo *node)
4730 StoreOrRestoreLastPlayedLevels(node, TRUE);
4733 void RestoreLastPlayedLevels(TreeInfo **node)
4735 *node = StoreOrRestoreLastPlayedLevels(*node, FALSE);
4738 void LoadLevelSetup_LastSeries(void)
4740 // --------------------------------------------------------------------------
4741 // ~/.<program>/levelsetup.conf
4742 // --------------------------------------------------------------------------
4744 char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
4745 SetupFileHash *level_setup_hash = NULL;
4749 // always start with reliable default values
4750 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
4752 // start with empty history of last played level sets
4753 setString(&setup.level_setup.last_level_series[0], NULL);
4755 if (!strEqual(DEFAULT_LEVELSET, UNDEFINED_LEVELSET))
4757 leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
4759 if (leveldir_current == NULL)
4760 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
4763 if ((level_setup_hash = loadSetupFileHash(filename)))
4765 char *last_level_series =
4766 getHashEntry(level_setup_hash, TOKEN_STR_LAST_LEVEL_SERIES);
4768 leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
4770 if (leveldir_current == NULL)
4771 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
4773 for (i = 0; i < MAX_LEVELDIR_HISTORY; i++)
4775 char token[strlen(TOKEN_STR_LAST_LEVEL_SERIES) + 10];
4776 LevelDirTree *leveldir_last;
4778 sprintf(token, "%s.%03d", TOKEN_STR_LAST_LEVEL_SERIES, i);
4780 last_level_series = getHashEntry(level_setup_hash, token);
4782 leveldir_last = getTreeInfoFromIdentifier(leveldir_first,
4784 if (leveldir_last != NULL)
4785 setString(&setup.level_setup.last_level_series[pos++],
4789 setString(&setup.level_setup.last_level_series[pos], NULL);
4791 freeSetupFileHash(level_setup_hash);
4795 Debug("setup", "using default setup values");
4801 static void SaveLevelSetup_LastSeries_Ext(boolean deactivate_last_level_series)
4803 // --------------------------------------------------------------------------
4804 // ~/.<program>/levelsetup.conf
4805 // --------------------------------------------------------------------------
4807 // check if the current level directory structure is available at this point
4808 if (leveldir_current == NULL)
4811 char **last_level_series = setup.level_setup.last_level_series;
4812 char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
4816 InitUserDataDirectory();
4818 UpdateLastPlayedLevels_List();
4820 if (!(file = fopen(filename, MODE_WRITE)))
4822 Warn("cannot write setup file '%s'", filename);
4829 fprintFileHeader(file, LEVELSETUP_FILENAME);
4831 if (deactivate_last_level_series)
4832 fprintf(file, "# %s\n# ", "the following level set may have caused a problem and was deactivated");
4834 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_LAST_LEVEL_SERIES,
4835 leveldir_current->identifier));
4837 for (i = 0; last_level_series[i] != NULL; i++)
4839 char token[strlen(TOKEN_STR_LAST_LEVEL_SERIES) + 10];
4841 sprintf(token, "%s.%03d", TOKEN_STR_LAST_LEVEL_SERIES, i);
4843 fprintf(file, "%s\n", getFormattedSetupEntry(token, last_level_series[i]));
4848 SetFilePermissions(filename, PERMS_PRIVATE);
4853 void SaveLevelSetup_LastSeries(void)
4855 SaveLevelSetup_LastSeries_Ext(FALSE);
4858 void SaveLevelSetup_LastSeries_Deactivate(void)
4860 SaveLevelSetup_LastSeries_Ext(TRUE);
4863 static void checkSeriesInfo(void)
4865 static char *level_directory = NULL;
4868 DirectoryEntry *dir_entry;
4871 checked_free(level_directory);
4873 // check for more levels besides the 'levels' field of 'levelinfo.conf'
4875 level_directory = getPath2((leveldir_current->in_user_dir ?
4876 getUserLevelDir(NULL) :
4877 options.level_directory),
4878 leveldir_current->fullpath);
4880 if ((dir = openDirectory(level_directory)) == NULL)
4882 Warn("cannot read level directory '%s'", level_directory);
4888 while ((dir_entry = readDirectory(dir)) != NULL) // loop all entries
4890 if (strlen(dir_entry->basename) > 4 &&
4891 dir_entry->basename[3] == '.' &&
4892 strEqual(&dir_entry->basename[4], LEVELFILE_EXTENSION))
4894 char levelnum_str[4];
4897 strncpy(levelnum_str, dir_entry->basename, 3);
4898 levelnum_str[3] = '\0';
4900 levelnum_value = atoi(levelnum_str);
4902 if (levelnum_value < leveldir_current->first_level)
4904 Warn("additional level %d found", levelnum_value);
4906 leveldir_current->first_level = levelnum_value;
4908 else if (levelnum_value > leveldir_current->last_level)
4910 Warn("additional level %d found", levelnum_value);
4912 leveldir_current->last_level = levelnum_value;
4918 closeDirectory(dir);
4921 void LoadLevelSetup_SeriesInfo(void)
4924 SetupFileHash *level_setup_hash = NULL;
4925 char *level_subdir = leveldir_current->subdir;
4928 // always start with reliable default values
4929 level_nr = leveldir_current->first_level;
4931 for (i = 0; i < MAX_LEVELS; i++)
4933 LevelStats_setPlayed(i, 0);
4934 LevelStats_setSolved(i, 0);
4939 // --------------------------------------------------------------------------
4940 // ~/.<program>/levelsetup/<level series>/levelsetup.conf
4941 // --------------------------------------------------------------------------
4943 level_subdir = leveldir_current->subdir;
4945 filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
4947 if ((level_setup_hash = loadSetupFileHash(filename)))
4951 // get last played level in this level set
4953 token_value = getHashEntry(level_setup_hash, TOKEN_STR_LAST_PLAYED_LEVEL);
4957 level_nr = atoi(token_value);
4959 if (level_nr < leveldir_current->first_level)
4960 level_nr = leveldir_current->first_level;
4961 if (level_nr > leveldir_current->last_level)
4962 level_nr = leveldir_current->last_level;
4965 // get handicap level in this level set
4967 token_value = getHashEntry(level_setup_hash, TOKEN_STR_HANDICAP_LEVEL);
4971 int level_nr = atoi(token_value);
4973 if (level_nr < leveldir_current->first_level)
4974 level_nr = leveldir_current->first_level;
4975 if (level_nr > leveldir_current->last_level + 1)
4976 level_nr = leveldir_current->last_level;
4978 if (leveldir_current->user_defined || !leveldir_current->handicap)
4979 level_nr = leveldir_current->last_level;
4981 leveldir_current->handicap_level = level_nr;
4984 // get number of played and solved levels in this level set
4986 BEGIN_HASH_ITERATION(level_setup_hash, itr)
4988 char *token = HASH_ITERATION_TOKEN(itr);
4989 char *value = HASH_ITERATION_VALUE(itr);
4991 if (strlen(token) == 3 &&
4992 token[0] >= '0' && token[0] <= '9' &&
4993 token[1] >= '0' && token[1] <= '9' &&
4994 token[2] >= '0' && token[2] <= '9')
4996 int level_nr = atoi(token);
4999 LevelStats_setPlayed(level_nr, atoi(value)); // read 1st column
5001 value = strchr(value, ' ');
5004 LevelStats_setSolved(level_nr, atoi(value)); // read 2nd column
5007 END_HASH_ITERATION(hash, itr)
5009 freeSetupFileHash(level_setup_hash);
5013 Debug("setup", "using default setup values");
5019 void SaveLevelSetup_SeriesInfo(void)
5022 char *level_subdir = leveldir_current->subdir;
5023 char *level_nr_str = int2str(level_nr, 0);
5024 char *handicap_level_str = int2str(leveldir_current->handicap_level, 0);
5028 // --------------------------------------------------------------------------
5029 // ~/.<program>/levelsetup/<level series>/levelsetup.conf
5030 // --------------------------------------------------------------------------
5032 InitLevelSetupDirectory(level_subdir);
5034 filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
5036 if (!(file = fopen(filename, MODE_WRITE)))
5038 Warn("cannot write setup file '%s'", filename);
5045 fprintFileHeader(file, LEVELSETUP_FILENAME);
5047 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_PLAYED_LEVEL,
5049 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_HANDICAP_LEVEL,
5050 handicap_level_str));
5052 for (i = leveldir_current->first_level; i <= leveldir_current->last_level;
5055 if (LevelStats_getPlayed(i) > 0 ||
5056 LevelStats_getSolved(i) > 0)
5061 sprintf(token, "%03d", i);
5062 sprintf(value, "%d %d", LevelStats_getPlayed(i), LevelStats_getSolved(i));
5064 fprintf(file, "%s\n", getFormattedSetupEntry(token, value));
5070 SetFilePermissions(filename, PERMS_PRIVATE);
5075 int LevelStats_getPlayed(int nr)
5077 return (nr >= 0 && nr < MAX_LEVELS ? level_stats[nr].played : 0);
5080 int LevelStats_getSolved(int nr)
5082 return (nr >= 0 && nr < MAX_LEVELS ? level_stats[nr].solved : 0);
5085 void LevelStats_setPlayed(int nr, int value)
5087 if (nr >= 0 && nr < MAX_LEVELS)
5088 level_stats[nr].played = value;
5091 void LevelStats_setSolved(int nr, int value)
5093 if (nr >= 0 && nr < MAX_LEVELS)
5094 level_stats[nr].solved = value;
5097 void LevelStats_incPlayed(int nr)
5099 if (nr >= 0 && nr < MAX_LEVELS)
5100 level_stats[nr].played++;
5103 void LevelStats_incSolved(int nr)
5105 if (nr >= 0 && nr < MAX_LEVELS)
5106 level_stats[nr].solved++;
5109 void LoadUserSetup(void)
5111 // --------------------------------------------------------------------------
5112 // ~/.<program>/usersetup.conf
5113 // --------------------------------------------------------------------------
5115 char *filename = getPath2(getMainUserGameDataDir(), USERSETUP_FILENAME);
5116 SetupFileHash *user_setup_hash = NULL;
5118 // always start with reliable default values
5121 if ((user_setup_hash = loadSetupFileHash(filename)))
5125 // get last selected user number
5126 token_value = getHashEntry(user_setup_hash, TOKEN_STR_LAST_USER);
5129 user.nr = MIN(MAX(0, atoi(token_value)), MAX_PLAYER_NAMES - 1);
5131 freeSetupFileHash(user_setup_hash);
5135 Debug("setup", "using default setup values");
5141 void SaveUserSetup(void)
5143 // --------------------------------------------------------------------------
5144 // ~/.<program>/usersetup.conf
5145 // --------------------------------------------------------------------------
5147 char *filename = getPath2(getMainUserGameDataDir(), USERSETUP_FILENAME);
5150 InitMainUserDataDirectory();
5152 if (!(file = fopen(filename, MODE_WRITE)))
5154 Warn("cannot write setup file '%s'", filename);
5161 fprintFileHeader(file, USERSETUP_FILENAME);
5163 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_USER,
5167 SetFilePermissions(filename, PERMS_PRIVATE);