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 static TreeInfo *getValidTreeInfoEntryExt(TreeInfo *node, boolean get_next_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
1302 get_next_node = TRUE;
1304 if (!get_next_node) // get current regular tree node
1307 // get next regular tree node, or step up until one is found
1308 while (node->next == NULL && node->node_parent != NULL)
1309 node = node->node_parent;
1311 return getFirstValidTreeInfoEntry(node->next);
1314 TreeInfo *getFirstValidTreeInfoEntry(TreeInfo *node)
1316 return getValidTreeInfoEntryExt(node, FALSE);
1319 TreeInfo *getNextValidTreeInfoEntry(TreeInfo *node)
1321 return getValidTreeInfoEntryExt(node, TRUE);
1324 TreeInfo *getTreeInfoFirstGroupEntry(TreeInfo *node)
1329 if (node->node_parent == NULL) // top level group
1330 return *node->node_top;
1331 else // sub level group
1332 return node->node_parent->node_group;
1335 int numTreeInfoInGroup(TreeInfo *node)
1337 return numTreeInfo(getTreeInfoFirstGroupEntry(node));
1340 int getPosFromTreeInfo(TreeInfo *node)
1342 TreeInfo *node_cmp = getTreeInfoFirstGroupEntry(node);
1347 if (node_cmp == node)
1351 node_cmp = node_cmp->next;
1357 TreeInfo *getTreeInfoFromPos(TreeInfo *node, int pos)
1359 TreeInfo *node_default = node;
1371 return node_default;
1374 static TreeInfo *getTreeInfoFromIdentifierExt(TreeInfo *node, char *identifier,
1375 int node_type_wanted)
1377 if (identifier == NULL)
1382 if (TREE_NODE_TYPE(node) == node_type_wanted &&
1383 strEqual(identifier, node->identifier))
1386 if (node->node_group)
1388 TreeInfo *node_group = getTreeInfoFromIdentifierExt(node->node_group,
1401 TreeInfo *getTreeInfoFromIdentifier(TreeInfo *node, char *identifier)
1403 return getTreeInfoFromIdentifierExt(node, identifier, TREE_NODE_TYPE_DEFAULT);
1406 static TreeInfo *cloneTreeNode(TreeInfo **node_top, TreeInfo *node_parent,
1407 TreeInfo *node, boolean skip_sets_without_levels)
1414 if (!node->parent_link && !node->level_group &&
1415 skip_sets_without_levels && node->levels == 0)
1416 return cloneTreeNode(node_top, node_parent, node->next,
1417 skip_sets_without_levels);
1419 node_new = getTreeInfoCopy(node); // copy complete node
1421 node_new->node_top = node_top; // correct top node link
1422 node_new->node_parent = node_parent; // correct parent node link
1424 if (node->level_group)
1425 node_new->node_group = cloneTreeNode(node_top, node_new, node->node_group,
1426 skip_sets_without_levels);
1428 node_new->next = cloneTreeNode(node_top, node_parent, node->next,
1429 skip_sets_without_levels);
1434 static void cloneTree(TreeInfo **ti_new, TreeInfo *ti, boolean skip_empty_sets)
1436 TreeInfo *ti_cloned = cloneTreeNode(ti_new, NULL, ti, skip_empty_sets);
1438 *ti_new = ti_cloned;
1441 static boolean adjustTreeGraphicsForEMC(TreeInfo *node)
1443 boolean settings_changed = FALSE;
1447 boolean want_ecs = (setup.prefer_aga_graphics == FALSE);
1448 boolean want_aga = (setup.prefer_aga_graphics == TRUE);
1449 boolean has_only_ecs = (!node->graphics_set && !node->graphics_set_aga);
1450 boolean has_only_aga = (!node->graphics_set && !node->graphics_set_ecs);
1451 char *graphics_set = NULL;
1453 if (node->graphics_set_ecs && (want_ecs || has_only_ecs))
1454 graphics_set = node->graphics_set_ecs;
1456 if (node->graphics_set_aga && (want_aga || has_only_aga))
1457 graphics_set = node->graphics_set_aga;
1459 if (graphics_set && !strEqual(node->graphics_set, graphics_set))
1461 setString(&node->graphics_set, graphics_set);
1462 settings_changed = TRUE;
1465 if (node->node_group != NULL)
1466 settings_changed |= adjustTreeGraphicsForEMC(node->node_group);
1471 return settings_changed;
1474 static boolean adjustTreeSoundsForEMC(TreeInfo *node)
1476 boolean settings_changed = FALSE;
1480 boolean want_default = (setup.prefer_lowpass_sounds == FALSE);
1481 boolean want_lowpass = (setup.prefer_lowpass_sounds == TRUE);
1482 boolean has_only_default = (!node->sounds_set && !node->sounds_set_lowpass);
1483 boolean has_only_lowpass = (!node->sounds_set && !node->sounds_set_default);
1484 char *sounds_set = NULL;
1486 if (node->sounds_set_default && (want_default || has_only_default))
1487 sounds_set = node->sounds_set_default;
1489 if (node->sounds_set_lowpass && (want_lowpass || has_only_lowpass))
1490 sounds_set = node->sounds_set_lowpass;
1492 if (sounds_set && !strEqual(node->sounds_set, sounds_set))
1494 setString(&node->sounds_set, sounds_set);
1495 settings_changed = TRUE;
1498 if (node->node_group != NULL)
1499 settings_changed |= adjustTreeSoundsForEMC(node->node_group);
1504 return settings_changed;
1507 int dumpTreeInfo(TreeInfo *node, int depth)
1509 char bullet_list[] = { '-', '*', 'o' };
1510 int num_leaf_nodes = 0;
1514 Debug("tree", "Dumping TreeInfo:");
1518 char bullet = bullet_list[depth % ARRAY_SIZE(bullet_list)];
1520 for (i = 0; i < depth * 2; i++)
1521 DebugContinued("", " ");
1523 DebugContinued("tree", "%c '%s' ['%s] [PARENT: '%s'] %s\n",
1524 bullet, node->name, node->identifier,
1525 (node->node_parent ? node->node_parent->identifier : "-"),
1526 (node->node_group ? "[GROUP]" : ""));
1528 if (!node->node_group && !node->parent_link)
1532 // use for dumping artwork info tree
1533 Debug("tree", "subdir == '%s' ['%s', '%s'] [%d])",
1534 node->subdir, node->fullpath, node->basepath, node->in_user_dir);
1537 if (node->node_group != NULL)
1538 num_leaf_nodes += dumpTreeInfo(node->node_group, depth + 1);
1544 Debug("tree", "Summary: %d leaf nodes found", num_leaf_nodes);
1546 return num_leaf_nodes;
1549 void sortTreeInfoBySortFunction(TreeInfo **node_first,
1550 int (*compare_function)(const void *,
1553 int num_nodes = numTreeInfo(*node_first);
1554 TreeInfo **sort_array;
1555 TreeInfo *node = *node_first;
1561 // allocate array for sorting structure pointers
1562 sort_array = checked_calloc(num_nodes * sizeof(TreeInfo *));
1564 // writing structure pointers to sorting array
1565 while (i < num_nodes && node) // double boundary check...
1567 sort_array[i] = node;
1573 // sorting the structure pointers in the sorting array
1574 qsort(sort_array, num_nodes, sizeof(TreeInfo *),
1577 // update the linkage of list elements with the sorted node array
1578 for (i = 0; i < num_nodes - 1; i++)
1579 sort_array[i]->next = sort_array[i + 1];
1580 sort_array[num_nodes - 1]->next = NULL;
1582 // update the linkage of the main list anchor pointer
1583 *node_first = sort_array[0];
1587 // now recursively sort the level group structures
1591 if (node->node_group != NULL)
1592 sortTreeInfoBySortFunction(&node->node_group, compare_function);
1598 void sortTreeInfo(TreeInfo **node_first)
1600 sortTreeInfoBySortFunction(node_first, compareTreeInfoEntries);
1604 // ============================================================================
1605 // some stuff from "files.c"
1606 // ============================================================================
1608 #if defined(PLATFORM_WIN32)
1610 #define S_IRGRP S_IRUSR
1613 #define S_IROTH S_IRUSR
1616 #define S_IWGRP S_IWUSR
1619 #define S_IWOTH S_IWUSR
1622 #define S_IXGRP S_IXUSR
1625 #define S_IXOTH S_IXUSR
1628 #define S_IRWXG (S_IRGRP | S_IWGRP | S_IXGRP)
1633 #endif // PLATFORM_WIN32
1635 // file permissions for newly written files
1636 #define MODE_R_ALL (S_IRUSR | S_IRGRP | S_IROTH)
1637 #define MODE_W_ALL (S_IWUSR | S_IWGRP | S_IWOTH)
1638 #define MODE_X_ALL (S_IXUSR | S_IXGRP | S_IXOTH)
1640 #define MODE_W_PRIVATE (S_IWUSR)
1641 #define MODE_W_PUBLIC_FILE (S_IWUSR | S_IWGRP)
1642 #define MODE_W_PUBLIC_DIR (S_IWUSR | S_IWGRP | S_ISGID)
1644 #define DIR_PERMS_PRIVATE (MODE_R_ALL | MODE_X_ALL | MODE_W_PRIVATE)
1645 #define DIR_PERMS_PUBLIC (MODE_R_ALL | MODE_X_ALL | MODE_W_PUBLIC_DIR)
1646 #define DIR_PERMS_PUBLIC_ALL (MODE_R_ALL | MODE_X_ALL | MODE_W_ALL)
1648 #define FILE_PERMS_PRIVATE (MODE_R_ALL | MODE_W_PRIVATE)
1649 #define FILE_PERMS_PUBLIC (MODE_R_ALL | MODE_W_PUBLIC_FILE)
1650 #define FILE_PERMS_PUBLIC_ALL (MODE_R_ALL | MODE_W_ALL)
1653 char *getHomeDir(void)
1655 static char *dir = NULL;
1657 #if defined(PLATFORM_WIN32)
1660 dir = checked_malloc(MAX_PATH + 1);
1662 if (!SUCCEEDED(SHGetFolderPath(NULL, CSIDL_PERSONAL, NULL, 0, dir)))
1665 #elif defined(PLATFORM_EMSCRIPTEN)
1666 dir = "/persistent";
1667 #elif defined(PLATFORM_UNIX)
1670 if ((dir = getenv("HOME")) == NULL)
1672 dir = getUnixHomeDir();
1675 dir = getStringCopy(dir);
1687 char *getPersonalDataDir(void)
1689 static char *personal_data_dir = NULL;
1691 #if defined(PLATFORM_MACOSX)
1692 if (personal_data_dir == NULL)
1693 personal_data_dir = getPath2(getHomeDir(), "Documents");
1695 if (personal_data_dir == NULL)
1696 personal_data_dir = getHomeDir();
1699 return personal_data_dir;
1702 char *getMainUserGameDataDir(void)
1704 static char *main_user_data_dir = NULL;
1706 #if defined(PLATFORM_ANDROID)
1707 if (main_user_data_dir == NULL)
1708 main_user_data_dir = (char *)(SDL_AndroidGetExternalStorageState() &
1709 SDL_ANDROID_EXTERNAL_STORAGE_WRITE ?
1710 SDL_AndroidGetExternalStoragePath() :
1711 SDL_AndroidGetInternalStoragePath());
1713 if (main_user_data_dir == NULL)
1714 main_user_data_dir = getPath2(getPersonalDataDir(),
1715 program.userdata_subdir);
1718 return main_user_data_dir;
1721 char *getUserGameDataDir(void)
1724 return getMainUserGameDataDir();
1726 return getUserDir(user.nr);
1729 char *getSetupDir(void)
1731 return getUserGameDataDir();
1734 static mode_t posix_umask(mode_t mask)
1736 #if defined(PLATFORM_UNIX)
1743 static int posix_mkdir(const char *pathname, mode_t mode)
1745 #if defined(PLATFORM_WIN32)
1746 return mkdir(pathname);
1748 return mkdir(pathname, mode);
1752 static boolean posix_process_running_setgid(void)
1754 #if defined(PLATFORM_UNIX)
1755 return (getgid() != getegid());
1761 void createDirectory(char *dir, char *text, int permission_class)
1763 if (directoryExists(dir))
1766 // leave "other" permissions in umask untouched, but ensure group parts
1767 // of USERDATA_DIR_MODE are not masked
1768 mode_t dir_mode = (permission_class == PERMS_PRIVATE ?
1769 DIR_PERMS_PRIVATE : DIR_PERMS_PUBLIC);
1770 mode_t last_umask = posix_umask(0);
1771 mode_t group_umask = ~(dir_mode & S_IRWXG);
1772 int running_setgid = posix_process_running_setgid();
1774 if (permission_class == PERMS_PUBLIC)
1776 // if we're setgid, protect files against "other"
1777 // else keep umask(0) to make the dir world-writable
1780 posix_umask(last_umask & group_umask);
1782 dir_mode = DIR_PERMS_PUBLIC_ALL;
1785 if (posix_mkdir(dir, dir_mode) != 0)
1786 Warn("cannot create %s directory '%s': %s", text, dir, strerror(errno));
1788 if (permission_class == PERMS_PUBLIC && !running_setgid)
1789 chmod(dir, dir_mode);
1791 posix_umask(last_umask); // restore previous umask
1794 void InitMainUserDataDirectory(void)
1796 createDirectory(getMainUserGameDataDir(), "main user data", PERMS_PRIVATE);
1799 void InitUserDataDirectory(void)
1801 createDirectory(getMainUserGameDataDir(), "main user data", PERMS_PRIVATE);
1805 createDirectory(getUserDir(-1), "users", PERMS_PRIVATE);
1806 createDirectory(getUserDir(user.nr), "user data", PERMS_PRIVATE);
1810 void SetFilePermissions(char *filename, int permission_class)
1812 int running_setgid = posix_process_running_setgid();
1813 int perms = (permission_class == PERMS_PRIVATE ?
1814 FILE_PERMS_PRIVATE : FILE_PERMS_PUBLIC);
1816 if (permission_class == PERMS_PUBLIC && !running_setgid)
1817 perms = FILE_PERMS_PUBLIC_ALL;
1819 chmod(filename, perms);
1822 char *getCookie(char *file_type)
1824 static char cookie[MAX_COOKIE_LEN + 1];
1826 if (strlen(program.cookie_prefix) + 1 +
1827 strlen(file_type) + strlen("_FILE_VERSION_x.x") > MAX_COOKIE_LEN)
1828 return "[COOKIE ERROR]"; // should never happen
1830 sprintf(cookie, "%s_%s_FILE_VERSION_%d.%d",
1831 program.cookie_prefix, file_type,
1832 program.version_super, program.version_major);
1837 void fprintFileHeader(FILE *file, char *basename)
1839 char *prefix = "# ";
1842 fprintf_line_with_prefix(file, prefix, sep1, 77);
1843 fprintf(file, "%s%s\n", prefix, basename);
1844 fprintf_line_with_prefix(file, prefix, sep1, 77);
1845 fprintf(file, "\n");
1848 int getFileVersionFromCookieString(const char *cookie)
1850 const char *ptr_cookie1, *ptr_cookie2;
1851 const char *pattern1 = "_FILE_VERSION_";
1852 const char *pattern2 = "?.?";
1853 const int len_cookie = strlen(cookie);
1854 const int len_pattern1 = strlen(pattern1);
1855 const int len_pattern2 = strlen(pattern2);
1856 const int len_pattern = len_pattern1 + len_pattern2;
1857 int version_super, version_major;
1859 if (len_cookie <= len_pattern)
1862 ptr_cookie1 = &cookie[len_cookie - len_pattern];
1863 ptr_cookie2 = &cookie[len_cookie - len_pattern2];
1865 if (strncmp(ptr_cookie1, pattern1, len_pattern1) != 0)
1868 if (ptr_cookie2[0] < '0' || ptr_cookie2[0] > '9' ||
1869 ptr_cookie2[1] != '.' ||
1870 ptr_cookie2[2] < '0' || ptr_cookie2[2] > '9')
1873 version_super = ptr_cookie2[0] - '0';
1874 version_major = ptr_cookie2[2] - '0';
1876 return VERSION_IDENT(version_super, version_major, 0, 0);
1879 boolean checkCookieString(const char *cookie, const char *template)
1881 const char *pattern = "_FILE_VERSION_?.?";
1882 const int len_cookie = strlen(cookie);
1883 const int len_template = strlen(template);
1884 const int len_pattern = strlen(pattern);
1886 if (len_cookie != len_template)
1889 if (strncmp(cookie, template, len_cookie - len_pattern) != 0)
1896 // ----------------------------------------------------------------------------
1897 // setup file list and hash handling functions
1898 // ----------------------------------------------------------------------------
1900 char *getFormattedSetupEntry(char *token, char *value)
1903 static char entry[MAX_LINE_LEN];
1905 // if value is an empty string, just return token without value
1909 // start with the token and some spaces to format output line
1910 sprintf(entry, "%s:", token);
1911 for (i = strlen(entry); i < token_value_position; i++)
1914 // continue with the token's value
1915 strcat(entry, value);
1920 SetupFileList *newSetupFileList(char *token, char *value)
1922 SetupFileList *new = checked_malloc(sizeof(SetupFileList));
1924 new->token = getStringCopy(token);
1925 new->value = getStringCopy(value);
1932 void freeSetupFileList(SetupFileList *list)
1937 checked_free(list->token);
1938 checked_free(list->value);
1941 freeSetupFileList(list->next);
1946 char *getListEntry(SetupFileList *list, char *token)
1951 if (strEqual(list->token, token))
1954 return getListEntry(list->next, token);
1957 SetupFileList *setListEntry(SetupFileList *list, char *token, char *value)
1962 if (strEqual(list->token, token))
1964 checked_free(list->value);
1966 list->value = getStringCopy(value);
1970 else if (list->next == NULL)
1971 return (list->next = newSetupFileList(token, value));
1973 return setListEntry(list->next, token, value);
1976 SetupFileList *addListEntry(SetupFileList *list, char *token, char *value)
1981 if (list->next == NULL)
1982 return (list->next = newSetupFileList(token, value));
1984 return addListEntry(list->next, token, value);
1987 #if ENABLE_UNUSED_CODE
1989 static void printSetupFileList(SetupFileList *list)
1994 Debug("setup:printSetupFileList", "token: '%s'", list->token);
1995 Debug("setup:printSetupFileList", "value: '%s'", list->value);
1997 printSetupFileList(list->next);
2003 DEFINE_HASHTABLE_INSERT(insert_hash_entry, char, char);
2004 DEFINE_HASHTABLE_SEARCH(search_hash_entry, char, char);
2005 DEFINE_HASHTABLE_CHANGE(change_hash_entry, char, char);
2006 DEFINE_HASHTABLE_REMOVE(remove_hash_entry, char, char);
2008 #define insert_hash_entry hashtable_insert
2009 #define search_hash_entry hashtable_search
2010 #define change_hash_entry hashtable_change
2011 #define remove_hash_entry hashtable_remove
2014 unsigned int get_hash_from_key(void *key)
2019 This algorithm (k=33) was first reported by Dan Bernstein many years ago in
2020 'comp.lang.c'. Another version of this algorithm (now favored by Bernstein)
2021 uses XOR: hash(i) = hash(i - 1) * 33 ^ str[i]; the magic of number 33 (why
2022 it works better than many other constants, prime or not) has never been
2023 adequately explained.
2025 If you just want to have a good hash function, and cannot wait, djb2
2026 is one of the best string hash functions i know. It has excellent
2027 distribution and speed on many different sets of keys and table sizes.
2028 You are not likely to do better with one of the "well known" functions
2029 such as PJW, K&R, etc.
2031 Ozan (oz) Yigit [http://www.cs.yorku.ca/~oz/hash.html]
2034 char *str = (char *)key;
2035 unsigned int hash = 5381;
2038 while ((c = *str++))
2039 hash = ((hash << 5) + hash) + c; // hash * 33 + c
2044 static int keys_are_equal(void *key1, void *key2)
2046 return (strEqual((char *)key1, (char *)key2));
2049 SetupFileHash *newSetupFileHash(void)
2051 SetupFileHash *new_hash =
2052 create_hashtable(16, 0.75, get_hash_from_key, keys_are_equal);
2054 if (new_hash == NULL)
2055 Fail("create_hashtable() failed -- out of memory");
2060 void freeSetupFileHash(SetupFileHash *hash)
2065 hashtable_destroy(hash, 1); // 1 == also free values stored in hash
2068 char *getHashEntry(SetupFileHash *hash, char *token)
2073 return search_hash_entry(hash, token);
2076 void setHashEntry(SetupFileHash *hash, char *token, char *value)
2083 value_copy = getStringCopy(value);
2085 // change value; if it does not exist, insert it as new
2086 if (!change_hash_entry(hash, token, value_copy))
2087 if (!insert_hash_entry(hash, getStringCopy(token), value_copy))
2088 Fail("cannot insert into hash -- aborting");
2091 char *removeHashEntry(SetupFileHash *hash, char *token)
2096 return remove_hash_entry(hash, token);
2099 #if ENABLE_UNUSED_CODE
2101 static void printSetupFileHash(SetupFileHash *hash)
2103 BEGIN_HASH_ITERATION(hash, itr)
2105 Debug("setup:printSetupFileHash", "token: '%s'", HASH_ITERATION_TOKEN(itr));
2106 Debug("setup:printSetupFileHash", "value: '%s'", HASH_ITERATION_VALUE(itr));
2108 END_HASH_ITERATION(hash, itr)
2113 #define ALLOW_TOKEN_VALUE_SEPARATOR_BEING_WHITESPACE 1
2114 #define CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING 0
2115 #define CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH 0
2117 static boolean token_value_separator_found = FALSE;
2118 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2119 static boolean token_value_separator_warning = FALSE;
2121 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2122 static boolean token_already_exists_warning = FALSE;
2125 static boolean getTokenValueFromSetupLineExt(char *line,
2126 char **token_ptr, char **value_ptr,
2127 char *filename, char *line_raw,
2129 boolean separator_required)
2131 static char line_copy[MAX_LINE_LEN + 1], line_raw_copy[MAX_LINE_LEN + 1];
2132 char *token, *value, *line_ptr;
2134 // when externally invoked via ReadTokenValueFromLine(), copy line buffers
2135 if (line_raw == NULL)
2137 strncpy(line_copy, line, MAX_LINE_LEN);
2138 line_copy[MAX_LINE_LEN] = '\0';
2141 strcpy(line_raw_copy, line_copy);
2142 line_raw = line_raw_copy;
2145 // cut trailing comment from input line
2146 for (line_ptr = line; *line_ptr; line_ptr++)
2148 if (*line_ptr == '#')
2155 // cut trailing whitespaces from input line
2156 for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
2157 if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
2160 // ignore empty lines
2164 // cut leading whitespaces from token
2165 for (token = line; *token; token++)
2166 if (*token != ' ' && *token != '\t')
2169 // start with empty value as reliable default
2172 token_value_separator_found = FALSE;
2174 // find end of token to determine start of value
2175 for (line_ptr = token; *line_ptr; line_ptr++)
2177 // first look for an explicit token/value separator, like ':' or '='
2178 if (*line_ptr == ':' || *line_ptr == '=')
2180 *line_ptr = '\0'; // terminate token string
2181 value = line_ptr + 1; // set beginning of value
2183 token_value_separator_found = TRUE;
2189 #if ALLOW_TOKEN_VALUE_SEPARATOR_BEING_WHITESPACE
2190 // fallback: if no token/value separator found, also allow whitespaces
2191 if (!token_value_separator_found && !separator_required)
2193 for (line_ptr = token; *line_ptr; line_ptr++)
2195 if (*line_ptr == ' ' || *line_ptr == '\t')
2197 *line_ptr = '\0'; // terminate token string
2198 value = line_ptr + 1; // set beginning of value
2200 token_value_separator_found = TRUE;
2206 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2207 if (token_value_separator_found)
2209 if (!token_value_separator_warning)
2211 Debug("setup", "---");
2213 if (filename != NULL)
2215 Debug("setup", "missing token/value separator(s) in config file:");
2216 Debug("setup", "- config file: '%s'", filename);
2220 Debug("setup", "missing token/value separator(s):");
2223 token_value_separator_warning = TRUE;
2226 if (filename != NULL)
2227 Debug("setup", "- line %d: '%s'", line_nr, line_raw);
2229 Debug("setup", "- line: '%s'", line_raw);
2235 // cut trailing whitespaces from token
2236 for (line_ptr = &token[strlen(token)]; line_ptr >= token; line_ptr--)
2237 if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
2240 // cut leading whitespaces from value
2241 for (; *value; value++)
2242 if (*value != ' ' && *value != '\t')
2251 boolean getTokenValueFromSetupLine(char *line, char **token, char **value)
2253 // while the internal (old) interface does not require a token/value
2254 // separator (for downwards compatibility with existing files which
2255 // don't use them), it is mandatory for the external (new) interface
2257 return getTokenValueFromSetupLineExt(line, token, value, NULL, NULL, 0, TRUE);
2260 static boolean loadSetupFileData(void *setup_file_data, char *filename,
2261 boolean top_recursion_level, boolean is_hash)
2263 static SetupFileHash *include_filename_hash = NULL;
2264 char line[MAX_LINE_LEN], line_raw[MAX_LINE_LEN], previous_line[MAX_LINE_LEN];
2265 char *token, *value, *line_ptr;
2266 void *insert_ptr = NULL;
2267 boolean read_continued_line = FALSE;
2269 int line_nr = 0, token_count = 0, include_count = 0;
2271 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2272 token_value_separator_warning = FALSE;
2275 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2276 token_already_exists_warning = FALSE;
2279 if (!(file = openFile(filename, MODE_READ)))
2281 #if DEBUG_NO_CONFIG_FILE
2282 Debug("setup", "cannot open configuration file '%s'", filename);
2288 // use "insert pointer" to store list end for constant insertion complexity
2290 insert_ptr = setup_file_data;
2292 // on top invocation, create hash to mark included files (to prevent loops)
2293 if (top_recursion_level)
2294 include_filename_hash = newSetupFileHash();
2296 // mark this file as already included (to prevent including it again)
2297 setHashEntry(include_filename_hash, getBaseNamePtr(filename), "true");
2299 while (!checkEndOfFile(file))
2301 // read next line of input file
2302 if (!getStringFromFile(file, line, MAX_LINE_LEN))
2305 // check if line was completely read and is terminated by line break
2306 if (strlen(line) > 0 && line[strlen(line) - 1] == '\n')
2309 // cut trailing line break (this can be newline and/or carriage return)
2310 for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
2311 if ((*line_ptr == '\n' || *line_ptr == '\r') && *(line_ptr + 1) == '\0')
2314 // copy raw input line for later use (mainly debugging output)
2315 strcpy(line_raw, line);
2317 if (read_continued_line)
2319 // append new line to existing line, if there is enough space
2320 if (strlen(previous_line) + strlen(line_ptr) < MAX_LINE_LEN)
2321 strcat(previous_line, line_ptr);
2323 strcpy(line, previous_line); // copy storage buffer to line
2325 read_continued_line = FALSE;
2328 // if the last character is '\', continue at next line
2329 if (strlen(line) > 0 && line[strlen(line) - 1] == '\\')
2331 line[strlen(line) - 1] = '\0'; // cut off trailing backslash
2332 strcpy(previous_line, line); // copy line to storage buffer
2334 read_continued_line = TRUE;
2339 if (!getTokenValueFromSetupLineExt(line, &token, &value, filename,
2340 line_raw, line_nr, FALSE))
2345 if (strEqual(token, "include"))
2347 if (getHashEntry(include_filename_hash, value) == NULL)
2349 char *basepath = getBasePath(filename);
2350 char *basename = getBaseName(value);
2351 char *filename_include = getPath2(basepath, basename);
2353 loadSetupFileData(setup_file_data, filename_include, FALSE, is_hash);
2357 free(filename_include);
2363 Warn("ignoring already processed file '%s'", value);
2370 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2372 getHashEntry((SetupFileHash *)setup_file_data, token);
2374 if (old_value != NULL)
2376 if (!token_already_exists_warning)
2378 Debug("setup", "---");
2379 Debug("setup", "duplicate token(s) found in config file:");
2380 Debug("setup", "- config file: '%s'", filename);
2382 token_already_exists_warning = TRUE;
2385 Debug("setup", "- token: '%s' (in line %d)", token, line_nr);
2386 Debug("setup", " old value: '%s'", old_value);
2387 Debug("setup", " new value: '%s'", value);
2391 setHashEntry((SetupFileHash *)setup_file_data, token, value);
2395 insert_ptr = addListEntry((SetupFileList *)insert_ptr, token, value);
2405 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2406 if (token_value_separator_warning)
2407 Debug("setup", "---");
2410 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2411 if (token_already_exists_warning)
2412 Debug("setup", "---");
2415 if (token_count == 0 && include_count == 0)
2416 Warn("configuration file '%s' is empty", filename);
2418 if (top_recursion_level)
2419 freeSetupFileHash(include_filename_hash);
2424 static int compareSetupFileData(const void *object1, const void *object2)
2426 const struct ConfigInfo *entry1 = (struct ConfigInfo *)object1;
2427 const struct ConfigInfo *entry2 = (struct ConfigInfo *)object2;
2429 return strcmp(entry1->token, entry2->token);
2432 static void saveSetupFileHash(SetupFileHash *hash, char *filename)
2434 int item_count = hashtable_count(hash);
2435 int item_size = sizeof(struct ConfigInfo);
2436 struct ConfigInfo *sort_array = checked_malloc(item_count * item_size);
2440 // copy string pointers from hash to array
2441 BEGIN_HASH_ITERATION(hash, itr)
2443 sort_array[i].token = HASH_ITERATION_TOKEN(itr);
2444 sort_array[i].value = HASH_ITERATION_VALUE(itr);
2448 if (i > item_count) // should never happen
2451 END_HASH_ITERATION(hash, itr)
2453 // sort string pointers from hash in array
2454 qsort(sort_array, item_count, item_size, compareSetupFileData);
2456 if (!(file = fopen(filename, MODE_WRITE)))
2458 Warn("cannot write configuration file '%s'", filename);
2463 fprintf(file, "%s\n\n", getFormattedSetupEntry("program.version",
2464 program.version_string));
2465 for (i = 0; i < item_count; i++)
2466 fprintf(file, "%s\n", getFormattedSetupEntry(sort_array[i].token,
2467 sort_array[i].value));
2470 checked_free(sort_array);
2473 SetupFileList *loadSetupFileList(char *filename)
2475 SetupFileList *setup_file_list = newSetupFileList("", "");
2476 SetupFileList *first_valid_list_entry;
2478 if (!loadSetupFileData(setup_file_list, filename, TRUE, FALSE))
2480 freeSetupFileList(setup_file_list);
2485 first_valid_list_entry = setup_file_list->next;
2487 // free empty list header
2488 setup_file_list->next = NULL;
2489 freeSetupFileList(setup_file_list);
2491 return first_valid_list_entry;
2494 SetupFileHash *loadSetupFileHash(char *filename)
2496 SetupFileHash *setup_file_hash = newSetupFileHash();
2498 if (!loadSetupFileData(setup_file_hash, filename, TRUE, TRUE))
2500 freeSetupFileHash(setup_file_hash);
2505 return setup_file_hash;
2509 // ============================================================================
2511 // ============================================================================
2513 #define TOKEN_STR_LAST_LEVEL_SERIES "last_level_series"
2514 #define TOKEN_STR_LAST_PLAYED_LEVEL "last_played_level"
2515 #define TOKEN_STR_HANDICAP_LEVEL "handicap_level"
2516 #define TOKEN_STR_LAST_USER "last_user"
2518 // level directory info
2519 #define LEVELINFO_TOKEN_IDENTIFIER 0
2520 #define LEVELINFO_TOKEN_NAME 1
2521 #define LEVELINFO_TOKEN_NAME_SORTING 2
2522 #define LEVELINFO_TOKEN_AUTHOR 3
2523 #define LEVELINFO_TOKEN_YEAR 4
2524 #define LEVELINFO_TOKEN_PROGRAM_TITLE 5
2525 #define LEVELINFO_TOKEN_PROGRAM_COPYRIGHT 6
2526 #define LEVELINFO_TOKEN_PROGRAM_COMPANY 7
2527 #define LEVELINFO_TOKEN_IMPORTED_FROM 8
2528 #define LEVELINFO_TOKEN_IMPORTED_BY 9
2529 #define LEVELINFO_TOKEN_TESTED_BY 10
2530 #define LEVELINFO_TOKEN_LEVELS 11
2531 #define LEVELINFO_TOKEN_FIRST_LEVEL 12
2532 #define LEVELINFO_TOKEN_SORT_PRIORITY 13
2533 #define LEVELINFO_TOKEN_LATEST_ENGINE 14
2534 #define LEVELINFO_TOKEN_LEVEL_GROUP 15
2535 #define LEVELINFO_TOKEN_READONLY 16
2536 #define LEVELINFO_TOKEN_GRAPHICS_SET_ECS 17
2537 #define LEVELINFO_TOKEN_GRAPHICS_SET_AGA 18
2538 #define LEVELINFO_TOKEN_GRAPHICS_SET 19
2539 #define LEVELINFO_TOKEN_SOUNDS_SET_DEFAULT 20
2540 #define LEVELINFO_TOKEN_SOUNDS_SET_LOWPASS 21
2541 #define LEVELINFO_TOKEN_SOUNDS_SET 22
2542 #define LEVELINFO_TOKEN_MUSIC_SET 23
2543 #define LEVELINFO_TOKEN_FILENAME 24
2544 #define LEVELINFO_TOKEN_FILETYPE 25
2545 #define LEVELINFO_TOKEN_SPECIAL_FLAGS 26
2546 #define LEVELINFO_TOKEN_HANDICAP 27
2547 #define LEVELINFO_TOKEN_SKIP_LEVELS 28
2548 #define LEVELINFO_TOKEN_USE_EMC_TILES 29
2550 #define NUM_LEVELINFO_TOKENS 30
2552 static LevelDirTree ldi;
2554 static struct TokenInfo levelinfo_tokens[] =
2556 // level directory info
2557 { TYPE_STRING, &ldi.identifier, "identifier" },
2558 { TYPE_STRING, &ldi.name, "name" },
2559 { TYPE_STRING, &ldi.name_sorting, "name_sorting" },
2560 { TYPE_STRING, &ldi.author, "author" },
2561 { TYPE_STRING, &ldi.year, "year" },
2562 { TYPE_STRING, &ldi.program_title, "program_title" },
2563 { TYPE_STRING, &ldi.program_copyright, "program_copyright" },
2564 { TYPE_STRING, &ldi.program_company, "program_company" },
2565 { TYPE_STRING, &ldi.imported_from, "imported_from" },
2566 { TYPE_STRING, &ldi.imported_by, "imported_by" },
2567 { TYPE_STRING, &ldi.tested_by, "tested_by" },
2568 { TYPE_INTEGER, &ldi.levels, "levels" },
2569 { TYPE_INTEGER, &ldi.first_level, "first_level" },
2570 { TYPE_INTEGER, &ldi.sort_priority, "sort_priority" },
2571 { TYPE_BOOLEAN, &ldi.latest_engine, "latest_engine" },
2572 { TYPE_BOOLEAN, &ldi.level_group, "level_group" },
2573 { TYPE_BOOLEAN, &ldi.readonly, "readonly" },
2574 { TYPE_STRING, &ldi.graphics_set_ecs, "graphics_set.ecs" },
2575 { TYPE_STRING, &ldi.graphics_set_aga, "graphics_set.aga" },
2576 { TYPE_STRING, &ldi.graphics_set, "graphics_set" },
2577 { TYPE_STRING, &ldi.sounds_set_default,"sounds_set.default" },
2578 { TYPE_STRING, &ldi.sounds_set_lowpass,"sounds_set.lowpass" },
2579 { TYPE_STRING, &ldi.sounds_set, "sounds_set" },
2580 { TYPE_STRING, &ldi.music_set, "music_set" },
2581 { TYPE_STRING, &ldi.level_filename, "filename" },
2582 { TYPE_STRING, &ldi.level_filetype, "filetype" },
2583 { TYPE_STRING, &ldi.special_flags, "special_flags" },
2584 { TYPE_BOOLEAN, &ldi.handicap, "handicap" },
2585 { TYPE_BOOLEAN, &ldi.skip_levels, "skip_levels" },
2586 { TYPE_BOOLEAN, &ldi.use_emc_tiles, "use_emc_tiles" }
2589 static struct TokenInfo artworkinfo_tokens[] =
2591 // artwork directory info
2592 { TYPE_STRING, &ldi.identifier, "identifier" },
2593 { TYPE_STRING, &ldi.subdir, "subdir" },
2594 { TYPE_STRING, &ldi.name, "name" },
2595 { TYPE_STRING, &ldi.name_sorting, "name_sorting" },
2596 { TYPE_STRING, &ldi.author, "author" },
2597 { TYPE_STRING, &ldi.program_title, "program_title" },
2598 { TYPE_STRING, &ldi.program_copyright, "program_copyright" },
2599 { TYPE_STRING, &ldi.program_company, "program_company" },
2600 { TYPE_INTEGER, &ldi.sort_priority, "sort_priority" },
2601 { TYPE_STRING, &ldi.basepath, "basepath" },
2602 { TYPE_STRING, &ldi.fullpath, "fullpath" },
2603 { TYPE_BOOLEAN, &ldi.in_user_dir, "in_user_dir" },
2604 { TYPE_STRING, &ldi.class_desc, "class_desc" },
2609 static char *optional_tokens[] =
2612 "program_copyright",
2618 static void setTreeInfoToDefaults(TreeInfo *ti, int type)
2622 ti->node_top = (ti->type == TREE_TYPE_LEVEL_DIR ? &leveldir_first :
2623 ti->type == TREE_TYPE_GRAPHICS_DIR ? &artwork.gfx_first :
2624 ti->type == TREE_TYPE_SOUNDS_DIR ? &artwork.snd_first :
2625 ti->type == TREE_TYPE_MUSIC_DIR ? &artwork.mus_first :
2628 ti->node_parent = NULL;
2629 ti->node_group = NULL;
2636 ti->fullpath = NULL;
2637 ti->basepath = NULL;
2638 ti->identifier = NULL;
2639 ti->name = getStringCopy(ANONYMOUS_NAME);
2640 ti->name_sorting = NULL;
2641 ti->author = getStringCopy(ANONYMOUS_NAME);
2644 ti->program_title = NULL;
2645 ti->program_copyright = NULL;
2646 ti->program_company = NULL;
2648 ti->sort_priority = LEVELCLASS_UNDEFINED; // default: least priority
2649 ti->latest_engine = FALSE; // default: get from level
2650 ti->parent_link = FALSE;
2651 ti->is_copy = FALSE;
2652 ti->in_user_dir = FALSE;
2653 ti->user_defined = FALSE;
2655 ti->class_desc = NULL;
2657 ti->infotext = getStringCopy(TREE_INFOTEXT(ti->type));
2659 if (ti->type == TREE_TYPE_LEVEL_DIR)
2661 ti->imported_from = NULL;
2662 ti->imported_by = NULL;
2663 ti->tested_by = NULL;
2665 ti->graphics_set_ecs = NULL;
2666 ti->graphics_set_aga = NULL;
2667 ti->graphics_set = NULL;
2668 ti->sounds_set_default = NULL;
2669 ti->sounds_set_lowpass = NULL;
2670 ti->sounds_set = NULL;
2671 ti->music_set = NULL;
2672 ti->graphics_path = getStringCopy(UNDEFINED_FILENAME);
2673 ti->sounds_path = getStringCopy(UNDEFINED_FILENAME);
2674 ti->music_path = getStringCopy(UNDEFINED_FILENAME);
2676 ti->level_filename = NULL;
2677 ti->level_filetype = NULL;
2679 ti->special_flags = NULL;
2682 ti->first_level = 0;
2684 ti->level_group = FALSE;
2685 ti->handicap_level = 0;
2686 ti->readonly = TRUE;
2687 ti->handicap = TRUE;
2688 ti->skip_levels = FALSE;
2690 ti->use_emc_tiles = FALSE;
2694 static void setTreeInfoToDefaultsFromParent(TreeInfo *ti, TreeInfo *parent)
2698 Warn("setTreeInfoToDefaultsFromParent(): parent == NULL");
2700 setTreeInfoToDefaults(ti, TREE_TYPE_UNDEFINED);
2705 // copy all values from the parent structure
2707 ti->type = parent->type;
2709 ti->node_top = parent->node_top;
2710 ti->node_parent = parent;
2711 ti->node_group = NULL;
2718 ti->fullpath = NULL;
2719 ti->basepath = NULL;
2720 ti->identifier = NULL;
2721 ti->name = getStringCopy(ANONYMOUS_NAME);
2722 ti->name_sorting = NULL;
2723 ti->author = getStringCopy(parent->author);
2724 ti->year = getStringCopy(parent->year);
2726 ti->program_title = getStringCopy(parent->program_title);
2727 ti->program_copyright = getStringCopy(parent->program_copyright);
2728 ti->program_company = getStringCopy(parent->program_company);
2730 ti->sort_priority = parent->sort_priority;
2731 ti->latest_engine = parent->latest_engine;
2732 ti->parent_link = FALSE;
2733 ti->is_copy = FALSE;
2734 ti->in_user_dir = parent->in_user_dir;
2735 ti->user_defined = parent->user_defined;
2736 ti->color = parent->color;
2737 ti->class_desc = getStringCopy(parent->class_desc);
2739 ti->infotext = getStringCopy(parent->infotext);
2741 if (ti->type == TREE_TYPE_LEVEL_DIR)
2743 ti->imported_from = getStringCopy(parent->imported_from);
2744 ti->imported_by = getStringCopy(parent->imported_by);
2745 ti->tested_by = getStringCopy(parent->tested_by);
2747 ti->graphics_set_ecs = getStringCopy(parent->graphics_set_ecs);
2748 ti->graphics_set_aga = getStringCopy(parent->graphics_set_aga);
2749 ti->graphics_set = getStringCopy(parent->graphics_set);
2750 ti->sounds_set_default = getStringCopy(parent->sounds_set_default);
2751 ti->sounds_set_lowpass = getStringCopy(parent->sounds_set_lowpass);
2752 ti->sounds_set = getStringCopy(parent->sounds_set);
2753 ti->music_set = getStringCopy(parent->music_set);
2754 ti->graphics_path = getStringCopy(UNDEFINED_FILENAME);
2755 ti->sounds_path = getStringCopy(UNDEFINED_FILENAME);
2756 ti->music_path = getStringCopy(UNDEFINED_FILENAME);
2758 ti->level_filename = getStringCopy(parent->level_filename);
2759 ti->level_filetype = getStringCopy(parent->level_filetype);
2761 ti->special_flags = getStringCopy(parent->special_flags);
2763 ti->levels = parent->levels;
2764 ti->first_level = parent->first_level;
2765 ti->last_level = parent->last_level;
2766 ti->level_group = FALSE;
2767 ti->handicap_level = parent->handicap_level;
2768 ti->readonly = parent->readonly;
2769 ti->handicap = parent->handicap;
2770 ti->skip_levels = parent->skip_levels;
2772 ti->use_emc_tiles = parent->use_emc_tiles;
2776 static TreeInfo *getTreeInfoCopy(TreeInfo *ti)
2778 TreeInfo *ti_copy = newTreeInfo();
2780 // copy all values from the original structure
2782 ti_copy->type = ti->type;
2784 ti_copy->node_top = ti->node_top;
2785 ti_copy->node_parent = ti->node_parent;
2786 ti_copy->node_group = ti->node_group;
2787 ti_copy->next = ti->next;
2789 ti_copy->cl_first = ti->cl_first;
2790 ti_copy->cl_cursor = ti->cl_cursor;
2792 ti_copy->subdir = getStringCopy(ti->subdir);
2793 ti_copy->fullpath = getStringCopy(ti->fullpath);
2794 ti_copy->basepath = getStringCopy(ti->basepath);
2795 ti_copy->identifier = getStringCopy(ti->identifier);
2796 ti_copy->name = getStringCopy(ti->name);
2797 ti_copy->name_sorting = getStringCopy(ti->name_sorting);
2798 ti_copy->author = getStringCopy(ti->author);
2799 ti_copy->year = getStringCopy(ti->year);
2801 ti_copy->program_title = getStringCopy(ti->program_title);
2802 ti_copy->program_copyright = getStringCopy(ti->program_copyright);
2803 ti_copy->program_company = getStringCopy(ti->program_company);
2805 ti_copy->imported_from = getStringCopy(ti->imported_from);
2806 ti_copy->imported_by = getStringCopy(ti->imported_by);
2807 ti_copy->tested_by = getStringCopy(ti->tested_by);
2809 ti_copy->graphics_set_ecs = getStringCopy(ti->graphics_set_ecs);
2810 ti_copy->graphics_set_aga = getStringCopy(ti->graphics_set_aga);
2811 ti_copy->graphics_set = getStringCopy(ti->graphics_set);
2812 ti_copy->sounds_set_default = getStringCopy(ti->sounds_set_default);
2813 ti_copy->sounds_set_lowpass = getStringCopy(ti->sounds_set_lowpass);
2814 ti_copy->sounds_set = getStringCopy(ti->sounds_set);
2815 ti_copy->music_set = getStringCopy(ti->music_set);
2816 ti_copy->graphics_path = getStringCopy(ti->graphics_path);
2817 ti_copy->sounds_path = getStringCopy(ti->sounds_path);
2818 ti_copy->music_path = getStringCopy(ti->music_path);
2820 ti_copy->level_filename = getStringCopy(ti->level_filename);
2821 ti_copy->level_filetype = getStringCopy(ti->level_filetype);
2823 ti_copy->special_flags = getStringCopy(ti->special_flags);
2825 ti_copy->levels = ti->levels;
2826 ti_copy->first_level = ti->first_level;
2827 ti_copy->last_level = ti->last_level;
2828 ti_copy->sort_priority = ti->sort_priority;
2830 ti_copy->latest_engine = ti->latest_engine;
2832 ti_copy->level_group = ti->level_group;
2833 ti_copy->parent_link = ti->parent_link;
2834 ti_copy->is_copy = ti->is_copy;
2835 ti_copy->in_user_dir = ti->in_user_dir;
2836 ti_copy->user_defined = ti->user_defined;
2837 ti_copy->readonly = ti->readonly;
2838 ti_copy->handicap = ti->handicap;
2839 ti_copy->skip_levels = ti->skip_levels;
2841 ti_copy->use_emc_tiles = ti->use_emc_tiles;
2843 ti_copy->color = ti->color;
2844 ti_copy->class_desc = getStringCopy(ti->class_desc);
2845 ti_copy->handicap_level = ti->handicap_level;
2847 ti_copy->infotext = getStringCopy(ti->infotext);
2852 void freeTreeInfo(TreeInfo *ti)
2857 checked_free(ti->subdir);
2858 checked_free(ti->fullpath);
2859 checked_free(ti->basepath);
2860 checked_free(ti->identifier);
2862 checked_free(ti->name);
2863 checked_free(ti->name_sorting);
2864 checked_free(ti->author);
2865 checked_free(ti->year);
2867 checked_free(ti->program_title);
2868 checked_free(ti->program_copyright);
2869 checked_free(ti->program_company);
2871 checked_free(ti->class_desc);
2873 checked_free(ti->infotext);
2875 if (ti->type == TREE_TYPE_LEVEL_DIR)
2877 checked_free(ti->imported_from);
2878 checked_free(ti->imported_by);
2879 checked_free(ti->tested_by);
2881 checked_free(ti->graphics_set_ecs);
2882 checked_free(ti->graphics_set_aga);
2883 checked_free(ti->graphics_set);
2884 checked_free(ti->sounds_set_default);
2885 checked_free(ti->sounds_set_lowpass);
2886 checked_free(ti->sounds_set);
2887 checked_free(ti->music_set);
2889 checked_free(ti->graphics_path);
2890 checked_free(ti->sounds_path);
2891 checked_free(ti->music_path);
2893 checked_free(ti->level_filename);
2894 checked_free(ti->level_filetype);
2896 checked_free(ti->special_flags);
2899 // recursively free child node
2901 freeTreeInfo(ti->node_group);
2903 // recursively free next node
2905 freeTreeInfo(ti->next);
2910 void setSetupInfo(struct TokenInfo *token_info,
2911 int token_nr, char *token_value)
2913 int token_type = token_info[token_nr].type;
2914 void *setup_value = token_info[token_nr].value;
2916 if (token_value == NULL)
2919 // set setup field to corresponding token value
2924 *(boolean *)setup_value = get_boolean_from_string(token_value);
2928 *(int *)setup_value = get_switch3_from_string(token_value);
2932 *(Key *)setup_value = getKeyFromKeyName(token_value);
2936 *(Key *)setup_value = getKeyFromX11KeyName(token_value);
2940 *(int *)setup_value = get_integer_from_string(token_value);
2944 checked_free(*(char **)setup_value);
2945 *(char **)setup_value = getStringCopy(token_value);
2949 *(int *)setup_value = get_player_nr_from_string(token_value);
2957 static int compareTreeInfoEntries(const void *object1, const void *object2)
2959 const TreeInfo *entry1 = *((TreeInfo **)object1);
2960 const TreeInfo *entry2 = *((TreeInfo **)object2);
2961 int tree_sorting1 = TREE_SORTING(entry1);
2962 int tree_sorting2 = TREE_SORTING(entry2);
2964 if (tree_sorting1 != tree_sorting2)
2965 return (tree_sorting1 - tree_sorting2);
2967 return strcasecmp(entry1->name_sorting, entry2->name_sorting);
2970 static TreeInfo *createParentTreeInfoNode(TreeInfo *node_parent)
2974 if (node_parent == NULL)
2977 ti_new = newTreeInfo();
2978 setTreeInfoToDefaults(ti_new, node_parent->type);
2980 ti_new->node_parent = node_parent;
2981 ti_new->parent_link = TRUE;
2983 setString(&ti_new->identifier, node_parent->identifier);
2984 setString(&ti_new->name, BACKLINK_TEXT_PARENT);
2985 setString(&ti_new->name_sorting, ti_new->name);
2987 setString(&ti_new->subdir, STRING_PARENT_DIRECTORY);
2988 setString(&ti_new->fullpath, node_parent->fullpath);
2990 ti_new->sort_priority = LEVELCLASS_PARENT;
2991 ti_new->latest_engine = node_parent->latest_engine;
2993 setString(&ti_new->class_desc, getLevelClassDescription(ti_new));
2995 pushTreeInfo(&node_parent->node_group, ti_new);
3000 static TreeInfo *createTopTreeInfoNode(TreeInfo *node_first)
3002 if (node_first == NULL)
3005 TreeInfo *ti_new = newTreeInfo();
3006 int type = node_first->type;
3008 setTreeInfoToDefaults(ti_new, type);
3010 ti_new->node_parent = NULL;
3011 ti_new->parent_link = FALSE;
3013 setString(&ti_new->identifier, "top_tree_node");
3014 setString(&ti_new->name, TREE_INFOTEXT(type));
3015 setString(&ti_new->name_sorting, ti_new->name);
3017 setString(&ti_new->subdir, STRING_TOP_DIRECTORY);
3018 setString(&ti_new->fullpath, ".");
3020 ti_new->sort_priority = LEVELCLASS_TOP;
3021 ti_new->latest_engine = node_first->latest_engine;
3023 setString(&ti_new->class_desc, TREE_INFOTEXT(type));
3025 ti_new->node_group = node_first;
3026 ti_new->level_group = TRUE;
3028 TreeInfo *ti_new2 = createParentTreeInfoNode(ti_new);
3030 setString(&ti_new2->name, TREE_BACKLINK_TEXT(type));
3031 setString(&ti_new2->name_sorting, ti_new2->name);
3036 static void setTreeInfoParentNodes(TreeInfo *node, TreeInfo *node_parent)
3040 if (node->node_group)
3041 setTreeInfoParentNodes(node->node_group, node);
3043 node->node_parent = node_parent;
3050 // ----------------------------------------------------------------------------
3051 // functions for handling level and custom artwork info cache
3052 // ----------------------------------------------------------------------------
3054 static void LoadArtworkInfoCache(void)
3056 InitCacheDirectory();
3058 if (artworkinfo_cache_old == NULL)
3060 char *filename = getPath2(getCacheDir(), ARTWORKINFO_CACHE_FILE);
3062 // try to load artwork info hash from already existing cache file
3063 artworkinfo_cache_old = loadSetupFileHash(filename);
3065 // try to get program version that artwork info cache was written with
3066 char *version = getHashEntry(artworkinfo_cache_old, "program.version");
3068 // check program version of artwork info cache against current version
3069 if (!strEqual(version, program.version_string))
3071 freeSetupFileHash(artworkinfo_cache_old);
3073 artworkinfo_cache_old = NULL;
3076 // if no artwork info cache file was found, start with empty hash
3077 if (artworkinfo_cache_old == NULL)
3078 artworkinfo_cache_old = newSetupFileHash();
3083 if (artworkinfo_cache_new == NULL)
3084 artworkinfo_cache_new = newSetupFileHash();
3086 update_artworkinfo_cache = FALSE;
3089 static void SaveArtworkInfoCache(void)
3091 if (!update_artworkinfo_cache)
3094 char *filename = getPath2(getCacheDir(), ARTWORKINFO_CACHE_FILE);
3096 InitCacheDirectory();
3098 saveSetupFileHash(artworkinfo_cache_new, filename);
3103 static char *getCacheTokenPrefix(char *prefix1, char *prefix2)
3105 static char *prefix = NULL;
3107 checked_free(prefix);
3109 prefix = getStringCat2WithSeparator(prefix1, prefix2, ".");
3114 // (identical to above function, but separate string buffer needed -- nasty)
3115 static char *getCacheToken(char *prefix, char *suffix)
3117 static char *token = NULL;
3119 checked_free(token);
3121 token = getStringCat2WithSeparator(prefix, suffix, ".");
3126 static char *getFileTimestampString(char *filename)
3128 return getStringCopy(i_to_a(getFileTimestampEpochSeconds(filename)));
3131 static boolean modifiedFileTimestamp(char *filename, char *timestamp_string)
3133 struct stat file_status;
3135 if (timestamp_string == NULL)
3138 if (!fileExists(filename)) // file does not exist
3139 return (atoi(timestamp_string) != 0);
3141 if (stat(filename, &file_status) != 0) // cannot stat file
3144 return (file_status.st_mtime != atoi(timestamp_string));
3147 static TreeInfo *getArtworkInfoCacheEntry(LevelDirTree *level_node, int type)
3149 char *identifier = level_node->subdir;
3150 char *type_string = ARTWORK_DIRECTORY(type);
3151 char *token_prefix = getCacheTokenPrefix(type_string, identifier);
3152 char *token_main = getCacheToken(token_prefix, "CACHED");
3153 char *cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
3154 boolean cached = (cache_entry != NULL && strEqual(cache_entry, "true"));
3155 TreeInfo *artwork_info = NULL;
3157 if (!use_artworkinfo_cache)
3160 if (optional_tokens_hash == NULL)
3164 // create hash from list of optional tokens (for quick access)
3165 optional_tokens_hash = newSetupFileHash();
3166 for (i = 0; optional_tokens[i] != NULL; i++)
3167 setHashEntry(optional_tokens_hash, optional_tokens[i], "");
3174 artwork_info = newTreeInfo();
3175 setTreeInfoToDefaults(artwork_info, type);
3177 // set all structure fields according to the token/value pairs
3178 ldi = *artwork_info;
3179 for (i = 0; artworkinfo_tokens[i].type != -1; i++)
3181 char *token_suffix = artworkinfo_tokens[i].text;
3182 char *token = getCacheToken(token_prefix, token_suffix);
3183 char *value = getHashEntry(artworkinfo_cache_old, token);
3185 (getHashEntry(optional_tokens_hash, token_suffix) != NULL);
3187 setSetupInfo(artworkinfo_tokens, i, value);
3189 // check if cache entry for this item is mandatory, but missing
3190 if (value == NULL && !optional)
3192 Warn("missing cache entry '%s'", token);
3198 *artwork_info = ldi;
3203 char *filename_levelinfo = getPath2(getLevelDirFromTreeInfo(level_node),
3204 LEVELINFO_FILENAME);
3205 char *filename_artworkinfo = getPath2(getSetupArtworkDir(artwork_info),
3206 ARTWORKINFO_FILENAME(type));
3208 // check if corresponding "levelinfo.conf" file has changed
3209 token_main = getCacheToken(token_prefix, "TIMESTAMP_LEVELINFO");
3210 cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
3212 if (modifiedFileTimestamp(filename_levelinfo, cache_entry))
3215 // check if corresponding "<artworkinfo>.conf" file has changed
3216 token_main = getCacheToken(token_prefix, "TIMESTAMP_ARTWORKINFO");
3217 cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
3219 if (modifiedFileTimestamp(filename_artworkinfo, cache_entry))
3222 checked_free(filename_levelinfo);
3223 checked_free(filename_artworkinfo);
3226 if (!cached && artwork_info != NULL)
3228 freeTreeInfo(artwork_info);
3233 return artwork_info;
3236 static void setArtworkInfoCacheEntry(TreeInfo *artwork_info,
3237 LevelDirTree *level_node, int type)
3239 char *identifier = level_node->subdir;
3240 char *type_string = ARTWORK_DIRECTORY(type);
3241 char *token_prefix = getCacheTokenPrefix(type_string, identifier);
3242 char *token_main = getCacheToken(token_prefix, "CACHED");
3243 boolean set_cache_timestamps = TRUE;
3246 setHashEntry(artworkinfo_cache_new, token_main, "true");
3248 if (set_cache_timestamps)
3250 char *filename_levelinfo = getPath2(getLevelDirFromTreeInfo(level_node),
3251 LEVELINFO_FILENAME);
3252 char *filename_artworkinfo = getPath2(getSetupArtworkDir(artwork_info),
3253 ARTWORKINFO_FILENAME(type));
3254 char *timestamp_levelinfo = getFileTimestampString(filename_levelinfo);
3255 char *timestamp_artworkinfo = getFileTimestampString(filename_artworkinfo);
3257 token_main = getCacheToken(token_prefix, "TIMESTAMP_LEVELINFO");
3258 setHashEntry(artworkinfo_cache_new, token_main, timestamp_levelinfo);
3260 token_main = getCacheToken(token_prefix, "TIMESTAMP_ARTWORKINFO");
3261 setHashEntry(artworkinfo_cache_new, token_main, timestamp_artworkinfo);
3263 checked_free(filename_levelinfo);
3264 checked_free(filename_artworkinfo);
3265 checked_free(timestamp_levelinfo);
3266 checked_free(timestamp_artworkinfo);
3269 ldi = *artwork_info;
3270 for (i = 0; artworkinfo_tokens[i].type != -1; i++)
3272 char *token = getCacheToken(token_prefix, artworkinfo_tokens[i].text);
3273 char *value = getSetupValue(artworkinfo_tokens[i].type,
3274 artworkinfo_tokens[i].value);
3276 setHashEntry(artworkinfo_cache_new, token, value);
3281 // ----------------------------------------------------------------------------
3282 // functions for loading level info and custom artwork info
3283 // ----------------------------------------------------------------------------
3285 int GetZipFileTreeType(char *zip_filename)
3287 static char *top_dir_path = NULL;
3288 static char *top_dir_conf_filename[NUM_BASE_TREE_TYPES] = { NULL };
3289 static char *conf_basename[NUM_BASE_TREE_TYPES] =
3291 GRAPHICSINFO_FILENAME,
3292 SOUNDSINFO_FILENAME,
3298 checked_free(top_dir_path);
3299 top_dir_path = NULL;
3301 for (j = 0; j < NUM_BASE_TREE_TYPES; j++)
3303 checked_free(top_dir_conf_filename[j]);
3304 top_dir_conf_filename[j] = NULL;
3307 char **zip_entries = zip_list(zip_filename);
3309 // check if zip file successfully opened
3310 if (zip_entries == NULL || zip_entries[0] == NULL)
3311 return TREE_TYPE_UNDEFINED;
3313 // first zip file entry is expected to be top level directory
3314 char *top_dir = zip_entries[0];
3316 // check if valid top level directory found in zip file
3317 if (!strSuffix(top_dir, "/"))
3318 return TREE_TYPE_UNDEFINED;
3320 // get filenames of valid configuration files in top level directory
3321 for (j = 0; j < NUM_BASE_TREE_TYPES; j++)
3322 top_dir_conf_filename[j] = getStringCat2(top_dir, conf_basename[j]);
3324 int tree_type = TREE_TYPE_UNDEFINED;
3327 while (zip_entries[e] != NULL)
3329 // check if every zip file entry is below top level directory
3330 if (!strPrefix(zip_entries[e], top_dir))
3331 return TREE_TYPE_UNDEFINED;
3333 // check if this zip file entry is a valid configuration filename
3334 for (j = 0; j < NUM_BASE_TREE_TYPES; j++)
3336 if (strEqual(zip_entries[e], top_dir_conf_filename[j]))
3338 // only exactly one valid configuration file allowed
3339 if (tree_type != TREE_TYPE_UNDEFINED)
3340 return TREE_TYPE_UNDEFINED;
3352 static boolean CheckZipFileForDirectory(char *zip_filename, char *directory,
3355 static char *top_dir_path = NULL;
3356 static char *top_dir_conf_filename = NULL;
3358 checked_free(top_dir_path);
3359 checked_free(top_dir_conf_filename);
3361 top_dir_path = NULL;
3362 top_dir_conf_filename = NULL;
3364 char *conf_basename = (tree_type == TREE_TYPE_LEVEL_DIR ? LEVELINFO_FILENAME :
3365 ARTWORKINFO_FILENAME(tree_type));
3367 // check if valid configuration filename determined
3368 if (conf_basename == NULL || strEqual(conf_basename, ""))
3371 char **zip_entries = zip_list(zip_filename);
3373 // check if zip file successfully opened
3374 if (zip_entries == NULL || zip_entries[0] == NULL)
3377 // first zip file entry is expected to be top level directory
3378 char *top_dir = zip_entries[0];
3380 // check if valid top level directory found in zip file
3381 if (!strSuffix(top_dir, "/"))
3384 // get path of extracted top level directory
3385 top_dir_path = getPath2(directory, top_dir);
3387 // remove trailing directory separator from top level directory path
3388 // (required to be able to check for file and directory in next step)
3389 top_dir_path[strlen(top_dir_path) - 1] = '\0';
3391 // check if zip file's top level directory already exists in target directory
3392 if (fileExists(top_dir_path)) // (checks for file and directory)
3395 // get filename of configuration file in top level directory
3396 top_dir_conf_filename = getStringCat2(top_dir, conf_basename);
3398 boolean found_top_dir_conf_filename = FALSE;
3401 while (zip_entries[i] != NULL)
3403 // check if every zip file entry is below top level directory
3404 if (!strPrefix(zip_entries[i], top_dir))
3407 // check if this zip file entry is the configuration filename
3408 if (strEqual(zip_entries[i], top_dir_conf_filename))
3409 found_top_dir_conf_filename = TRUE;
3414 // check if valid configuration filename was found in zip file
3415 if (!found_top_dir_conf_filename)
3421 char *ExtractZipFileIntoDirectory(char *zip_filename, char *directory,
3424 boolean zip_file_valid = CheckZipFileForDirectory(zip_filename, directory,
3427 if (!zip_file_valid)
3429 Warn("zip file '%s' rejected!", zip_filename);
3434 char **zip_entries = zip_extract(zip_filename, directory);
3436 if (zip_entries == NULL)
3438 Warn("zip file '%s' could not be extracted!", zip_filename);
3443 Info("zip file '%s' successfully extracted!", zip_filename);
3445 // first zip file entry contains top level directory
3446 char *top_dir = zip_entries[0];
3448 // remove trailing directory separator from top level directory
3449 top_dir[strlen(top_dir) - 1] = '\0';
3454 static void ProcessZipFilesInDirectory(char *directory, int tree_type)
3457 DirectoryEntry *dir_entry;
3459 if ((dir = openDirectory(directory)) == NULL)
3461 // display error if directory is main "options.graphics_directory" etc.
3462 if (tree_type == TREE_TYPE_LEVEL_DIR ||
3463 directory == OPTIONS_ARTWORK_DIRECTORY(tree_type))
3464 Warn("cannot read directory '%s'", directory);
3469 while ((dir_entry = readDirectory(dir)) != NULL) // loop all entries
3471 // skip non-zip files (and also directories with zip extension)
3472 if (!strSuffixLower(dir_entry->basename, ".zip") || dir_entry->is_directory)
3475 char *zip_filename = getPath2(directory, dir_entry->basename);
3476 char *zip_filename_extracted = getStringCat2(zip_filename, ".extracted");
3477 char *zip_filename_rejected = getStringCat2(zip_filename, ".rejected");
3479 // check if zip file hasn't already been extracted or rejected
3480 if (!fileExists(zip_filename_extracted) &&
3481 !fileExists(zip_filename_rejected))
3483 char *top_dir = ExtractZipFileIntoDirectory(zip_filename, directory,
3485 char *marker_filename = (top_dir != NULL ? zip_filename_extracted :
3486 zip_filename_rejected);
3489 // create empty file to mark zip file as extracted or rejected
3490 if ((marker_file = fopen(marker_filename, MODE_WRITE)))
3491 fclose(marker_file);
3494 free(zip_filename_extracted);
3495 free(zip_filename_rejected);
3499 closeDirectory(dir);
3502 // forward declaration for recursive call by "LoadLevelInfoFromLevelDir()"
3503 static void LoadLevelInfoFromLevelDir(TreeInfo **, TreeInfo *, char *);
3505 static boolean LoadLevelInfoFromLevelConf(TreeInfo **node_first,
3506 TreeInfo *node_parent,
3507 char *level_directory,
3508 char *directory_name)
3510 char *directory_path = getPath2(level_directory, directory_name);
3511 char *filename = getPath2(directory_path, LEVELINFO_FILENAME);
3512 SetupFileHash *setup_file_hash;
3513 LevelDirTree *leveldir_new = NULL;
3516 // unless debugging, silently ignore directories without "levelinfo.conf"
3517 if (!options.debug && !fileExists(filename))
3519 free(directory_path);
3525 setup_file_hash = loadSetupFileHash(filename);
3527 if (setup_file_hash == NULL)
3529 #if DEBUG_NO_CONFIG_FILE
3530 Debug("setup", "ignoring level directory '%s'", directory_path);
3533 free(directory_path);
3539 leveldir_new = newTreeInfo();
3542 setTreeInfoToDefaultsFromParent(leveldir_new, node_parent);
3544 setTreeInfoToDefaults(leveldir_new, TREE_TYPE_LEVEL_DIR);
3546 leveldir_new->subdir = getStringCopy(directory_name);
3548 // set all structure fields according to the token/value pairs
3549 ldi = *leveldir_new;
3550 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
3551 setSetupInfo(levelinfo_tokens, i,
3552 getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
3553 *leveldir_new = ldi;
3555 if (strEqual(leveldir_new->name, ANONYMOUS_NAME))
3556 setString(&leveldir_new->name, leveldir_new->subdir);
3558 if (leveldir_new->identifier == NULL)
3559 leveldir_new->identifier = getStringCopy(leveldir_new->subdir);
3561 if (leveldir_new->name_sorting == NULL)
3562 leveldir_new->name_sorting = getStringCopy(leveldir_new->name);
3564 if (node_parent == NULL) // top level group
3566 leveldir_new->basepath = getStringCopy(level_directory);
3567 leveldir_new->fullpath = getStringCopy(leveldir_new->subdir);
3569 else // sub level group
3571 leveldir_new->basepath = getStringCopy(node_parent->basepath);
3572 leveldir_new->fullpath = getPath2(node_parent->fullpath, directory_name);
3575 leveldir_new->last_level =
3576 leveldir_new->first_level + leveldir_new->levels - 1;
3578 leveldir_new->in_user_dir =
3579 (!strEqual(leveldir_new->basepath, options.level_directory));
3581 // adjust some settings if user's private level directory was detected
3582 if (leveldir_new->sort_priority == LEVELCLASS_UNDEFINED &&
3583 leveldir_new->in_user_dir &&
3584 (strEqual(leveldir_new->subdir, getLoginName()) ||
3585 strEqual(leveldir_new->name, getLoginName()) ||
3586 strEqual(leveldir_new->author, getRealName())))
3588 leveldir_new->sort_priority = LEVELCLASS_PRIVATE_START;
3589 leveldir_new->readonly = FALSE;
3592 leveldir_new->user_defined =
3593 (leveldir_new->in_user_dir && IS_LEVELCLASS_PRIVATE(leveldir_new));
3595 setString(&leveldir_new->class_desc, getLevelClassDescription(leveldir_new));
3597 leveldir_new->handicap_level = // set handicap to default value
3598 (leveldir_new->user_defined || !leveldir_new->handicap ?
3599 leveldir_new->last_level : leveldir_new->first_level);
3601 DrawInitText(leveldir_new->name, 150, FC_YELLOW);
3603 pushTreeInfo(node_first, leveldir_new);
3605 freeSetupFileHash(setup_file_hash);
3607 if (leveldir_new->level_group)
3609 // create node to link back to current level directory
3610 createParentTreeInfoNode(leveldir_new);
3612 // recursively step into sub-directory and look for more level series
3613 LoadLevelInfoFromLevelDir(&leveldir_new->node_group,
3614 leveldir_new, directory_path);
3617 free(directory_path);
3623 static void LoadLevelInfoFromLevelDir(TreeInfo **node_first,
3624 TreeInfo *node_parent,
3625 char *level_directory)
3627 // ---------- 1st stage: process any level set zip files ----------
3629 ProcessZipFilesInDirectory(level_directory, TREE_TYPE_LEVEL_DIR);
3631 // ---------- 2nd stage: check for level set directories ----------
3634 DirectoryEntry *dir_entry;
3635 boolean valid_entry_found = FALSE;
3637 if ((dir = openDirectory(level_directory)) == NULL)
3639 Warn("cannot read level directory '%s'", level_directory);
3644 while ((dir_entry = readDirectory(dir)) != NULL) // loop all entries
3646 char *directory_name = dir_entry->basename;
3647 char *directory_path = getPath2(level_directory, directory_name);
3649 // skip entries for current and parent directory
3650 if (strEqual(directory_name, ".") ||
3651 strEqual(directory_name, ".."))
3653 free(directory_path);
3658 // find out if directory entry is itself a directory
3659 if (!dir_entry->is_directory) // not a directory
3661 free(directory_path);
3666 free(directory_path);
3668 if (strEqual(directory_name, GRAPHICS_DIRECTORY) ||
3669 strEqual(directory_name, SOUNDS_DIRECTORY) ||
3670 strEqual(directory_name, MUSIC_DIRECTORY))
3673 valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
3678 closeDirectory(dir);
3680 // special case: top level directory may directly contain "levelinfo.conf"
3681 if (node_parent == NULL && !valid_entry_found)
3683 // check if this directory directly contains a file "levelinfo.conf"
3684 valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
3685 level_directory, ".");
3688 if (!valid_entry_found)
3689 Warn("cannot find any valid level series in directory '%s'",
3693 boolean AdjustGraphicsForEMC(void)
3695 boolean settings_changed = FALSE;
3697 settings_changed |= adjustTreeGraphicsForEMC(leveldir_first_all);
3698 settings_changed |= adjustTreeGraphicsForEMC(leveldir_first);
3700 return settings_changed;
3703 boolean AdjustSoundsForEMC(void)
3705 boolean settings_changed = FALSE;
3707 settings_changed |= adjustTreeSoundsForEMC(leveldir_first_all);
3708 settings_changed |= adjustTreeSoundsForEMC(leveldir_first);
3710 return settings_changed;
3713 void LoadLevelInfo(void)
3715 InitUserLevelDirectory(getLoginName());
3717 DrawInitText("Loading level series", 120, FC_GREEN);
3719 LoadLevelInfoFromLevelDir(&leveldir_first, NULL, options.level_directory);
3720 LoadLevelInfoFromLevelDir(&leveldir_first, NULL, getUserLevelDir(NULL));
3722 leveldir_first = createTopTreeInfoNode(leveldir_first);
3724 /* after loading all level set information, clone the level directory tree
3725 and remove all level sets without levels (these may still contain artwork
3726 to be offered in the setup menu as "custom artwork", and are therefore
3727 checked for existing artwork in the function "LoadLevelArtworkInfo()") */
3728 leveldir_first_all = leveldir_first;
3729 cloneTree(&leveldir_first, leveldir_first_all, TRUE);
3731 AdjustGraphicsForEMC();
3732 AdjustSoundsForEMC();
3734 // before sorting, the first entries will be from the user directory
3735 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
3737 if (leveldir_first == NULL)
3738 Fail("cannot find any valid level series in any directory");
3740 sortTreeInfo(&leveldir_first);
3742 #if ENABLE_UNUSED_CODE
3743 dumpTreeInfo(leveldir_first, 0);
3747 static boolean LoadArtworkInfoFromArtworkConf(TreeInfo **node_first,
3748 TreeInfo *node_parent,
3749 char *base_directory,
3750 char *directory_name, int type)
3752 char *directory_path = getPath2(base_directory, directory_name);
3753 char *filename = getPath2(directory_path, ARTWORKINFO_FILENAME(type));
3754 SetupFileHash *setup_file_hash = NULL;
3755 TreeInfo *artwork_new = NULL;
3758 if (fileExists(filename))
3759 setup_file_hash = loadSetupFileHash(filename);
3761 if (setup_file_hash == NULL) // no config file -- look for artwork files
3764 DirectoryEntry *dir_entry;
3765 boolean valid_file_found = FALSE;
3767 if ((dir = openDirectory(directory_path)) != NULL)
3769 while ((dir_entry = readDirectory(dir)) != NULL)
3771 if (FileIsArtworkType(dir_entry->filename, type))
3773 valid_file_found = TRUE;
3779 closeDirectory(dir);
3782 if (!valid_file_found)
3784 #if DEBUG_NO_CONFIG_FILE
3785 if (!strEqual(directory_name, "."))
3786 Debug("setup", "ignoring artwork directory '%s'", directory_path);
3789 free(directory_path);
3796 artwork_new = newTreeInfo();
3799 setTreeInfoToDefaultsFromParent(artwork_new, node_parent);
3801 setTreeInfoToDefaults(artwork_new, type);
3803 artwork_new->subdir = getStringCopy(directory_name);
3805 if (setup_file_hash) // (before defining ".color" and ".class_desc")
3807 // set all structure fields according to the token/value pairs
3809 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
3810 setSetupInfo(levelinfo_tokens, i,
3811 getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
3814 if (strEqual(artwork_new->name, ANONYMOUS_NAME))
3815 setString(&artwork_new->name, artwork_new->subdir);
3817 if (artwork_new->identifier == NULL)
3818 artwork_new->identifier = getStringCopy(artwork_new->subdir);
3820 if (artwork_new->name_sorting == NULL)
3821 artwork_new->name_sorting = getStringCopy(artwork_new->name);
3824 if (node_parent == NULL) // top level group
3826 artwork_new->basepath = getStringCopy(base_directory);
3827 artwork_new->fullpath = getStringCopy(artwork_new->subdir);
3829 else // sub level group
3831 artwork_new->basepath = getStringCopy(node_parent->basepath);
3832 artwork_new->fullpath = getPath2(node_parent->fullpath, directory_name);
3835 artwork_new->in_user_dir =
3836 (!strEqual(artwork_new->basepath, OPTIONS_ARTWORK_DIRECTORY(type)));
3838 setString(&artwork_new->class_desc, getLevelClassDescription(artwork_new));
3840 if (setup_file_hash == NULL) // (after determining ".user_defined")
3842 if (strEqual(artwork_new->subdir, "."))
3844 if (artwork_new->user_defined)
3846 setString(&artwork_new->identifier, "private");
3847 artwork_new->sort_priority = ARTWORKCLASS_PRIVATE;
3851 setString(&artwork_new->identifier, "classic");
3852 artwork_new->sort_priority = ARTWORKCLASS_CLASSICS;
3855 setString(&artwork_new->class_desc,
3856 getLevelClassDescription(artwork_new));
3860 setString(&artwork_new->identifier, artwork_new->subdir);
3863 setString(&artwork_new->name, artwork_new->identifier);
3864 setString(&artwork_new->name_sorting, artwork_new->name);
3867 pushTreeInfo(node_first, artwork_new);
3869 freeSetupFileHash(setup_file_hash);
3871 free(directory_path);
3877 static void LoadArtworkInfoFromArtworkDir(TreeInfo **node_first,
3878 TreeInfo *node_parent,
3879 char *base_directory, int type)
3881 // ---------- 1st stage: process any artwork set zip files ----------
3883 ProcessZipFilesInDirectory(base_directory, type);
3885 // ---------- 2nd stage: check for artwork set directories ----------
3888 DirectoryEntry *dir_entry;
3889 boolean valid_entry_found = FALSE;
3891 if ((dir = openDirectory(base_directory)) == NULL)
3893 // display error if directory is main "options.graphics_directory" etc.
3894 if (base_directory == OPTIONS_ARTWORK_DIRECTORY(type))
3895 Warn("cannot read directory '%s'", base_directory);
3900 while ((dir_entry = readDirectory(dir)) != NULL) // loop all entries
3902 char *directory_name = dir_entry->basename;
3903 char *directory_path = getPath2(base_directory, directory_name);
3905 // skip directory entries for current and parent directory
3906 if (strEqual(directory_name, ".") ||
3907 strEqual(directory_name, ".."))
3909 free(directory_path);
3914 // skip directory entries which are not a directory
3915 if (!dir_entry->is_directory) // not a directory
3917 free(directory_path);
3922 free(directory_path);
3924 // check if this directory contains artwork with or without config file
3925 valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
3927 directory_name, type);
3930 closeDirectory(dir);
3932 // check if this directory directly contains artwork itself
3933 valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
3934 base_directory, ".",
3936 if (!valid_entry_found)
3937 Warn("cannot find any valid artwork in directory '%s'", base_directory);
3940 static TreeInfo *getDummyArtworkInfo(int type)
3942 // this is only needed when there is completely no artwork available
3943 TreeInfo *artwork_new = newTreeInfo();
3945 setTreeInfoToDefaults(artwork_new, type);
3947 setString(&artwork_new->subdir, UNDEFINED_FILENAME);
3948 setString(&artwork_new->fullpath, UNDEFINED_FILENAME);
3949 setString(&artwork_new->basepath, UNDEFINED_FILENAME);
3951 setString(&artwork_new->identifier, UNDEFINED_FILENAME);
3952 setString(&artwork_new->name, UNDEFINED_FILENAME);
3953 setString(&artwork_new->name_sorting, UNDEFINED_FILENAME);
3958 void SetCurrentArtwork(int type)
3960 ArtworkDirTree **current_ptr = ARTWORK_CURRENT_PTR(artwork, type);
3961 ArtworkDirTree *first_node = ARTWORK_FIRST_NODE(artwork, type);
3962 char *setup_set = SETUP_ARTWORK_SET(setup, type);
3963 char *default_subdir = ARTWORK_DEFAULT_SUBDIR(type);
3965 // set current artwork to artwork configured in setup menu
3966 *current_ptr = getTreeInfoFromIdentifier(first_node, setup_set);
3968 // if not found, set current artwork to default artwork
3969 if (*current_ptr == NULL)
3970 *current_ptr = getTreeInfoFromIdentifier(first_node, default_subdir);
3972 // if not found, set current artwork to first artwork in tree
3973 if (*current_ptr == NULL)
3974 *current_ptr = getFirstValidTreeInfoEntry(first_node);
3977 void ChangeCurrentArtworkIfNeeded(int type)
3979 char *current_identifier = ARTWORK_CURRENT_IDENTIFIER(artwork, type);
3980 char *setup_set = SETUP_ARTWORK_SET(setup, type);
3982 if (!strEqual(current_identifier, setup_set))
3983 SetCurrentArtwork(type);
3986 void LoadArtworkInfo(void)
3988 LoadArtworkInfoCache();
3990 DrawInitText("Looking for custom artwork", 120, FC_GREEN);
3992 LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
3993 options.graphics_directory,
3994 TREE_TYPE_GRAPHICS_DIR);
3995 LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
3996 getUserGraphicsDir(),
3997 TREE_TYPE_GRAPHICS_DIR);
3999 LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
4000 options.sounds_directory,
4001 TREE_TYPE_SOUNDS_DIR);
4002 LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
4004 TREE_TYPE_SOUNDS_DIR);
4006 LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
4007 options.music_directory,
4008 TREE_TYPE_MUSIC_DIR);
4009 LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
4011 TREE_TYPE_MUSIC_DIR);
4013 if (artwork.gfx_first == NULL)
4014 artwork.gfx_first = getDummyArtworkInfo(TREE_TYPE_GRAPHICS_DIR);
4015 if (artwork.snd_first == NULL)
4016 artwork.snd_first = getDummyArtworkInfo(TREE_TYPE_SOUNDS_DIR);
4017 if (artwork.mus_first == NULL)
4018 artwork.mus_first = getDummyArtworkInfo(TREE_TYPE_MUSIC_DIR);
4020 // before sorting, the first entries will be from the user directory
4021 SetCurrentArtwork(ARTWORK_TYPE_GRAPHICS);
4022 SetCurrentArtwork(ARTWORK_TYPE_SOUNDS);
4023 SetCurrentArtwork(ARTWORK_TYPE_MUSIC);
4025 artwork.gfx_current_identifier = artwork.gfx_current->identifier;
4026 artwork.snd_current_identifier = artwork.snd_current->identifier;
4027 artwork.mus_current_identifier = artwork.mus_current->identifier;
4029 #if ENABLE_UNUSED_CODE
4030 Debug("setup:LoadArtworkInfo", "graphics set == %s",
4031 artwork.gfx_current_identifier);
4032 Debug("setup:LoadArtworkInfo", "sounds set == %s",
4033 artwork.snd_current_identifier);
4034 Debug("setup:LoadArtworkInfo", "music set == %s",
4035 artwork.mus_current_identifier);
4038 sortTreeInfo(&artwork.gfx_first);
4039 sortTreeInfo(&artwork.snd_first);
4040 sortTreeInfo(&artwork.mus_first);
4042 #if ENABLE_UNUSED_CODE
4043 dumpTreeInfo(artwork.gfx_first, 0);
4044 dumpTreeInfo(artwork.snd_first, 0);
4045 dumpTreeInfo(artwork.mus_first, 0);
4049 static void MoveArtworkInfoIntoSubTree(ArtworkDirTree **artwork_node)
4051 ArtworkDirTree *artwork_new = newTreeInfo();
4052 char *top_node_name = "standalone artwork";
4054 setTreeInfoToDefaults(artwork_new, (*artwork_node)->type);
4056 artwork_new->level_group = TRUE;
4058 setString(&artwork_new->identifier, top_node_name);
4059 setString(&artwork_new->name, top_node_name);
4060 setString(&artwork_new->name_sorting, top_node_name);
4062 // create node to link back to current custom artwork directory
4063 createParentTreeInfoNode(artwork_new);
4065 // move existing custom artwork tree into newly created sub-tree
4066 artwork_new->node_group->next = *artwork_node;
4068 // change custom artwork tree to contain only newly created node
4069 *artwork_node = artwork_new;
4072 static void LoadArtworkInfoFromLevelInfoExt(ArtworkDirTree **artwork_node,
4073 ArtworkDirTree *node_parent,
4074 LevelDirTree *level_node,
4075 boolean empty_level_set_mode)
4077 int type = (*artwork_node)->type;
4079 // recursively check all level directories for artwork sub-directories
4083 boolean empty_level_set = (level_node->levels == 0);
4085 // check all tree entries for artwork, but skip parent link entries
4086 if (!level_node->parent_link && empty_level_set == empty_level_set_mode)
4088 TreeInfo *artwork_new = getArtworkInfoCacheEntry(level_node, type);
4089 boolean cached = (artwork_new != NULL);
4093 pushTreeInfo(artwork_node, artwork_new);
4097 TreeInfo *topnode_last = *artwork_node;
4098 char *path = getPath2(getLevelDirFromTreeInfo(level_node),
4099 ARTWORK_DIRECTORY(type));
4101 LoadArtworkInfoFromArtworkDir(artwork_node, NULL, path, type);
4103 if (topnode_last != *artwork_node) // check for newly added node
4105 artwork_new = *artwork_node;
4107 setString(&artwork_new->identifier, level_node->subdir);
4108 setString(&artwork_new->name, level_node->name);
4109 setString(&artwork_new->name_sorting, level_node->name_sorting);
4111 artwork_new->sort_priority = level_node->sort_priority;
4112 artwork_new->in_user_dir = level_node->in_user_dir;
4114 update_artworkinfo_cache = TRUE;
4120 // insert artwork info (from old cache or filesystem) into new cache
4121 if (artwork_new != NULL)
4122 setArtworkInfoCacheEntry(artwork_new, level_node, type);
4125 DrawInitText(level_node->name, 150, FC_YELLOW);
4127 if (level_node->node_group != NULL)
4129 TreeInfo *artwork_new = newTreeInfo();
4132 setTreeInfoToDefaultsFromParent(artwork_new, node_parent);
4134 setTreeInfoToDefaults(artwork_new, type);
4136 artwork_new->level_group = TRUE;
4138 setString(&artwork_new->identifier, level_node->subdir);
4140 if (node_parent == NULL) // check for top tree node
4142 char *top_node_name = (empty_level_set_mode ?
4143 "artwork for certain level sets" :
4144 "artwork included in level sets");
4146 setString(&artwork_new->name, top_node_name);
4147 setString(&artwork_new->name_sorting, top_node_name);
4151 setString(&artwork_new->name, level_node->name);
4152 setString(&artwork_new->name_sorting, level_node->name_sorting);
4155 pushTreeInfo(artwork_node, artwork_new);
4157 // create node to link back to current custom artwork directory
4158 createParentTreeInfoNode(artwork_new);
4160 // recursively step into sub-directory and look for more custom artwork
4161 LoadArtworkInfoFromLevelInfoExt(&artwork_new->node_group, artwork_new,
4162 level_node->node_group,
4163 empty_level_set_mode);
4165 // if sub-tree has no custom artwork at all, remove it
4166 if (artwork_new->node_group->next == NULL)
4167 removeTreeInfo(artwork_node);
4170 level_node = level_node->next;
4174 static void LoadArtworkInfoFromLevelInfo(ArtworkDirTree **artwork_node)
4176 // move peviously loaded artwork tree into separate sub-tree
4177 MoveArtworkInfoIntoSubTree(artwork_node);
4179 // load artwork from level sets into separate sub-trees
4180 LoadArtworkInfoFromLevelInfoExt(artwork_node, NULL, leveldir_first_all, TRUE);
4181 LoadArtworkInfoFromLevelInfoExt(artwork_node, NULL, leveldir_first_all, FALSE);
4183 // add top tree node over all three separate sub-trees
4184 *artwork_node = createTopTreeInfoNode(*artwork_node);
4186 // set all parent links (back links) in complete artwork tree
4187 setTreeInfoParentNodes(*artwork_node, NULL);
4190 void LoadLevelArtworkInfo(void)
4192 print_timestamp_init("LoadLevelArtworkInfo");
4194 DrawInitText("Looking for custom level artwork", 120, FC_GREEN);
4196 print_timestamp_time("DrawTimeText");
4198 LoadArtworkInfoFromLevelInfo(&artwork.gfx_first);
4199 print_timestamp_time("LoadArtworkInfoFromLevelInfo (gfx)");
4200 LoadArtworkInfoFromLevelInfo(&artwork.snd_first);
4201 print_timestamp_time("LoadArtworkInfoFromLevelInfo (snd)");
4202 LoadArtworkInfoFromLevelInfo(&artwork.mus_first);
4203 print_timestamp_time("LoadArtworkInfoFromLevelInfo (mus)");
4205 SaveArtworkInfoCache();
4207 print_timestamp_time("SaveArtworkInfoCache");
4209 // needed for reloading level artwork not known at ealier stage
4210 ChangeCurrentArtworkIfNeeded(ARTWORK_TYPE_GRAPHICS);
4211 ChangeCurrentArtworkIfNeeded(ARTWORK_TYPE_SOUNDS);
4212 ChangeCurrentArtworkIfNeeded(ARTWORK_TYPE_MUSIC);
4214 print_timestamp_time("getTreeInfoFromIdentifier");
4216 sortTreeInfo(&artwork.gfx_first);
4217 sortTreeInfo(&artwork.snd_first);
4218 sortTreeInfo(&artwork.mus_first);
4220 print_timestamp_time("sortTreeInfo");
4222 #if ENABLE_UNUSED_CODE
4223 dumpTreeInfo(artwork.gfx_first, 0);
4224 dumpTreeInfo(artwork.snd_first, 0);
4225 dumpTreeInfo(artwork.mus_first, 0);
4228 print_timestamp_done("LoadLevelArtworkInfo");
4231 static boolean AddTreeSetToTreeInfoExt(TreeInfo *tree_node_old, char *tree_dir,
4232 char *tree_subdir_new, int type)
4234 if (tree_node_old == NULL)
4236 if (type == TREE_TYPE_LEVEL_DIR)
4238 // get level info tree node of personal user level set
4239 tree_node_old = getTreeInfoFromIdentifier(leveldir_first, getLoginName());
4241 // this may happen if "setup.internal.create_user_levelset" is FALSE
4242 // or if file "levelinfo.conf" is missing in personal user level set
4243 if (tree_node_old == NULL)
4244 tree_node_old = leveldir_first->node_group;
4248 // get artwork info tree node of first artwork set
4249 tree_node_old = ARTWORK_FIRST_NODE(artwork, type);
4253 if (tree_dir == NULL)
4254 tree_dir = TREE_USERDIR(type);
4256 if (tree_node_old == NULL ||
4258 tree_subdir_new == NULL) // should not happen
4261 int draw_deactivation_mask = GetDrawDeactivationMask();
4263 // override draw deactivation mask (temporarily disable drawing)
4264 SetDrawDeactivationMask(REDRAW_ALL);
4266 if (type == TREE_TYPE_LEVEL_DIR)
4268 // load new level set config and add it next to first user level set
4269 LoadLevelInfoFromLevelConf(&tree_node_old->next,
4270 tree_node_old->node_parent,
4271 tree_dir, tree_subdir_new);
4275 // load new artwork set config and add it next to first artwork set
4276 LoadArtworkInfoFromArtworkConf(&tree_node_old->next,
4277 tree_node_old->node_parent,
4278 tree_dir, tree_subdir_new, type);
4281 // set draw deactivation mask to previous value
4282 SetDrawDeactivationMask(draw_deactivation_mask);
4284 // get first node of level or artwork info tree
4285 TreeInfo **tree_node_first = TREE_FIRST_NODE_PTR(type);
4287 // get tree info node of newly added level or artwork set
4288 TreeInfo *tree_node_new = getTreeInfoFromIdentifier(*tree_node_first,
4291 if (tree_node_new == NULL) // should not happen
4294 // correct top link and parent node link of newly created tree node
4295 tree_node_new->node_top = tree_node_old->node_top;
4296 tree_node_new->node_parent = tree_node_old->node_parent;
4298 // sort tree info to adjust position of newly added tree set
4299 sortTreeInfo(tree_node_first);
4304 void AddTreeSetToTreeInfo(TreeInfo *tree_node, char *tree_dir,
4305 char *tree_subdir_new, int type)
4307 if (!AddTreeSetToTreeInfoExt(tree_node, tree_dir, tree_subdir_new, type))
4308 Fail("internal tree info structure corrupted -- aborting");
4311 void AddUserLevelSetToLevelInfo(char *level_subdir_new)
4313 AddTreeSetToTreeInfo(NULL, NULL, level_subdir_new, TREE_TYPE_LEVEL_DIR);
4316 char *getArtworkIdentifierForUserLevelSet(int type)
4318 char *classic_artwork_set = getClassicArtworkSet(type);
4320 // check for custom artwork configured in "levelinfo.conf"
4321 char *leveldir_artwork_set =
4322 *LEVELDIR_ARTWORK_SET_PTR(leveldir_current, type);
4323 boolean has_leveldir_artwork_set =
4324 (leveldir_artwork_set != NULL && !strEqual(leveldir_artwork_set,
4325 classic_artwork_set));
4327 // check for custom artwork in sub-directory "graphics" etc.
4328 TreeInfo *artwork_first_node = ARTWORK_FIRST_NODE(artwork, type);
4329 char *leveldir_identifier = leveldir_current->identifier;
4330 boolean has_artwork_subdir =
4331 (getTreeInfoFromIdentifier(artwork_first_node,
4332 leveldir_identifier) != NULL);
4334 return (has_leveldir_artwork_set ? leveldir_artwork_set :
4335 has_artwork_subdir ? leveldir_identifier :
4336 classic_artwork_set);
4339 TreeInfo *getArtworkTreeInfoForUserLevelSet(int type)
4341 char *artwork_set = getArtworkIdentifierForUserLevelSet(type);
4342 TreeInfo *artwork_first_node = ARTWORK_FIRST_NODE(artwork, type);
4343 TreeInfo *ti = getTreeInfoFromIdentifier(artwork_first_node, artwork_set);
4347 ti = getTreeInfoFromIdentifier(artwork_first_node,
4348 ARTWORK_DEFAULT_SUBDIR(type));
4350 Fail("cannot find default graphics -- should not happen");
4356 boolean checkIfCustomArtworkExistsForCurrentLevelSet(void)
4358 char *graphics_set =
4359 getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_GRAPHICS);
4361 getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_SOUNDS);
4363 getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_MUSIC);
4365 return (!strEqual(graphics_set, GFX_CLASSIC_SUBDIR) ||
4366 !strEqual(sounds_set, SND_CLASSIC_SUBDIR) ||
4367 !strEqual(music_set, MUS_CLASSIC_SUBDIR));
4370 boolean UpdateUserLevelSet(char *level_subdir, char *level_name,
4371 char *level_author, int num_levels)
4373 char *filename = getPath2(getUserLevelDir(level_subdir), LEVELINFO_FILENAME);
4374 char *filename_tmp = getStringCat2(filename, ".tmp");
4376 FILE *file_tmp = NULL;
4377 char line[MAX_LINE_LEN];
4378 boolean success = FALSE;
4379 LevelDirTree *leveldir = getTreeInfoFromIdentifier(leveldir_first,
4381 // update values in level directory tree
4383 if (level_name != NULL)
4384 setString(&leveldir->name, level_name);
4386 if (level_author != NULL)
4387 setString(&leveldir->author, level_author);
4389 if (num_levels != -1)
4390 leveldir->levels = num_levels;
4392 // update values that depend on other values
4394 setString(&leveldir->name_sorting, leveldir->name);
4396 leveldir->last_level = leveldir->first_level + leveldir->levels - 1;
4398 // sort order of level sets may have changed
4399 sortTreeInfo(&leveldir_first);
4401 if ((file = fopen(filename, MODE_READ)) &&
4402 (file_tmp = fopen(filename_tmp, MODE_WRITE)))
4404 while (fgets(line, MAX_LINE_LEN, file))
4406 if (strPrefix(line, "name:") && level_name != NULL)
4407 fprintf(file_tmp, "%-32s%s\n", "name:", level_name);
4408 else if (strPrefix(line, "author:") && level_author != NULL)
4409 fprintf(file_tmp, "%-32s%s\n", "author:", level_author);
4410 else if (strPrefix(line, "levels:") && num_levels != -1)
4411 fprintf(file_tmp, "%-32s%d\n", "levels:", num_levels);
4413 fputs(line, file_tmp);
4426 success = (rename(filename_tmp, filename) == 0);
4434 boolean CreateUserLevelSet(char *level_subdir, char *level_name,
4435 char *level_author, int num_levels,
4436 boolean use_artwork_set)
4438 LevelDirTree *level_info;
4443 // create user level sub-directory, if needed
4444 createDirectory(getUserLevelDir(level_subdir), "user level", PERMS_PRIVATE);
4446 filename = getPath2(getUserLevelDir(level_subdir), LEVELINFO_FILENAME);
4448 if (!(file = fopen(filename, MODE_WRITE)))
4450 Warn("cannot write level info file '%s'", filename);
4457 level_info = newTreeInfo();
4459 // always start with reliable default values
4460 setTreeInfoToDefaults(level_info, TREE_TYPE_LEVEL_DIR);
4462 setString(&level_info->name, level_name);
4463 setString(&level_info->author, level_author);
4464 level_info->levels = num_levels;
4465 level_info->first_level = 1;
4466 level_info->sort_priority = LEVELCLASS_PRIVATE_START;
4467 level_info->readonly = FALSE;
4469 if (use_artwork_set)
4471 level_info->graphics_set =
4472 getStringCopy(getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_GRAPHICS));
4473 level_info->sounds_set =
4474 getStringCopy(getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_SOUNDS));
4475 level_info->music_set =
4476 getStringCopy(getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_MUSIC));
4479 token_value_position = TOKEN_VALUE_POSITION_SHORT;
4481 fprintFileHeader(file, LEVELINFO_FILENAME);
4484 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
4486 if (i == LEVELINFO_TOKEN_NAME ||
4487 i == LEVELINFO_TOKEN_AUTHOR ||
4488 i == LEVELINFO_TOKEN_LEVELS ||
4489 i == LEVELINFO_TOKEN_FIRST_LEVEL ||
4490 i == LEVELINFO_TOKEN_SORT_PRIORITY ||
4491 i == LEVELINFO_TOKEN_READONLY ||
4492 (use_artwork_set && (i == LEVELINFO_TOKEN_GRAPHICS_SET ||
4493 i == LEVELINFO_TOKEN_SOUNDS_SET ||
4494 i == LEVELINFO_TOKEN_MUSIC_SET)))
4495 fprintf(file, "%s\n", getSetupLine(levelinfo_tokens, "", i));
4497 // just to make things nicer :)
4498 if (i == LEVELINFO_TOKEN_AUTHOR ||
4499 i == LEVELINFO_TOKEN_FIRST_LEVEL ||
4500 (use_artwork_set && i == LEVELINFO_TOKEN_READONLY))
4501 fprintf(file, "\n");
4504 token_value_position = TOKEN_VALUE_POSITION_DEFAULT;
4508 SetFilePermissions(filename, PERMS_PRIVATE);
4510 freeTreeInfo(level_info);
4516 static void SaveUserLevelInfo(void)
4518 CreateUserLevelSet(getLoginName(), getLoginName(), getRealName(), 100, FALSE);
4521 char *getSetupValue(int type, void *value)
4523 static char value_string[MAX_LINE_LEN];
4531 strcpy(value_string, (*(boolean *)value ? "true" : "false"));
4535 strcpy(value_string, (*(boolean *)value ? "on" : "off"));
4539 strcpy(value_string, (*(int *)value == AUTO ? "auto" :
4540 *(int *)value == FALSE ? "off" : "on"));
4544 strcpy(value_string, (*(boolean *)value ? "yes" : "no"));
4547 case TYPE_YES_NO_AUTO:
4548 strcpy(value_string, (*(int *)value == AUTO ? "auto" :
4549 *(int *)value == FALSE ? "no" : "yes"));
4553 strcpy(value_string, (*(boolean *)value ? "AGA" : "ECS"));
4557 strcpy(value_string, getKeyNameFromKey(*(Key *)value));
4561 strcpy(value_string, getX11KeyNameFromKey(*(Key *)value));
4565 sprintf(value_string, "%d", *(int *)value);
4569 if (*(char **)value == NULL)
4572 strcpy(value_string, *(char **)value);
4576 sprintf(value_string, "player_%d", *(int *)value + 1);
4580 value_string[0] = '\0';
4584 if (type & TYPE_GHOSTED)
4585 strcpy(value_string, "n/a");
4587 return value_string;
4590 char *getSetupLine(struct TokenInfo *token_info, char *prefix, int token_nr)
4594 static char token_string[MAX_LINE_LEN];
4595 int token_type = token_info[token_nr].type;
4596 void *setup_value = token_info[token_nr].value;
4597 char *token_text = token_info[token_nr].text;
4598 char *value_string = getSetupValue(token_type, setup_value);
4600 // build complete token string
4601 sprintf(token_string, "%s%s", prefix, token_text);
4603 // build setup entry line
4604 line = getFormattedSetupEntry(token_string, value_string);
4606 if (token_type == TYPE_KEY_X11)
4608 Key key = *(Key *)setup_value;
4609 char *keyname = getKeyNameFromKey(key);
4611 // add comment, if useful
4612 if (!strEqual(keyname, "(undefined)") &&
4613 !strEqual(keyname, "(unknown)"))
4615 // add at least one whitespace
4617 for (i = strlen(line); i < token_comment_position; i++)
4621 strcat(line, keyname);
4628 static void InitLastPlayedLevels_ParentNode(void)
4630 LevelDirTree **leveldir_top = &leveldir_first->node_group->next;
4631 LevelDirTree *leveldir_new = NULL;
4633 // check if parent node for last played levels already exists
4634 if (strEqual((*leveldir_top)->identifier, TOKEN_STR_LAST_LEVEL_SERIES))
4637 leveldir_new = newTreeInfo();
4639 setTreeInfoToDefaultsFromParent(leveldir_new, leveldir_first);
4641 leveldir_new->level_group = TRUE;
4642 leveldir_new->sort_priority = LEVELCLASS_LAST_PLAYED_LEVEL;
4644 setString(&leveldir_new->identifier, TOKEN_STR_LAST_LEVEL_SERIES);
4645 setString(&leveldir_new->name, "<< (last played level sets)");
4646 setString(&leveldir_new->name_sorting, leveldir_new->name);
4648 pushTreeInfo(leveldir_top, leveldir_new);
4650 // create node to link back to current level directory
4651 createParentTreeInfoNode(leveldir_new);
4654 void UpdateLastPlayedLevels_TreeInfo(void)
4656 char **last_level_series = setup.level_setup.last_level_series;
4657 LevelDirTree *leveldir_last;
4658 TreeInfo **node_new = NULL;
4661 if (last_level_series[0] == NULL)
4664 InitLastPlayedLevels_ParentNode();
4666 leveldir_last = getTreeInfoFromIdentifierExt(leveldir_first,
4667 TOKEN_STR_LAST_LEVEL_SERIES,
4668 TREE_NODE_TYPE_GROUP);
4669 if (leveldir_last == NULL)
4672 node_new = &leveldir_last->node_group->next;
4674 freeTreeInfo(*node_new);
4678 for (i = 0; last_level_series[i] != NULL; i++)
4680 LevelDirTree *node_last = getTreeInfoFromIdentifier(leveldir_first,
4681 last_level_series[i]);
4682 if (node_last == NULL)
4685 *node_new = getTreeInfoCopy(node_last); // copy complete node
4687 (*node_new)->node_top = &leveldir_first; // correct top node link
4688 (*node_new)->node_parent = leveldir_last; // correct parent node link
4690 (*node_new)->is_copy = TRUE; // mark entry as node copy
4692 (*node_new)->node_group = NULL;
4693 (*node_new)->next = NULL;
4695 (*node_new)->cl_first = -1; // force setting tree cursor
4697 node_new = &((*node_new)->next);
4701 static void UpdateLastPlayedLevels_List(void)
4703 char **last_level_series = setup.level_setup.last_level_series;
4704 int pos = MAX_LEVELDIR_HISTORY - 1;
4707 // search for potentially already existing entry in list of level sets
4708 for (i = 0; last_level_series[i] != NULL; i++)
4709 if (strEqual(last_level_series[i], leveldir_current->identifier))
4712 // move list of level sets one entry down (using potentially free entry)
4713 for (i = pos; i > 0; i--)
4714 setString(&last_level_series[i], last_level_series[i - 1]);
4716 // put last played level set at top position
4717 setString(&last_level_series[0], leveldir_current->identifier);
4720 static TreeInfo *StoreOrRestoreLastPlayedLevels(TreeInfo *node, boolean store)
4722 static char *identifier = NULL;
4726 setString(&identifier, (node && node->is_copy ? node->identifier : NULL));
4728 return NULL; // not used
4732 TreeInfo *node_new = getTreeInfoFromIdentifierExt(leveldir_first,
4734 TREE_NODE_TYPE_COPY);
4735 return (node_new != NULL ? node_new : node);
4739 void StoreLastPlayedLevels(TreeInfo *node)
4741 StoreOrRestoreLastPlayedLevels(node, TRUE);
4744 void RestoreLastPlayedLevels(TreeInfo **node)
4746 *node = StoreOrRestoreLastPlayedLevels(*node, FALSE);
4749 void LoadLevelSetup_LastSeries(void)
4751 // --------------------------------------------------------------------------
4752 // ~/.<program>/levelsetup.conf
4753 // --------------------------------------------------------------------------
4755 char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
4756 SetupFileHash *level_setup_hash = NULL;
4760 // always start with reliable default values
4761 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
4763 // start with empty history of last played level sets
4764 setString(&setup.level_setup.last_level_series[0], NULL);
4766 if (!strEqual(DEFAULT_LEVELSET, UNDEFINED_LEVELSET))
4768 leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
4770 if (leveldir_current == NULL)
4771 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
4774 if ((level_setup_hash = loadSetupFileHash(filename)))
4776 char *last_level_series =
4777 getHashEntry(level_setup_hash, TOKEN_STR_LAST_LEVEL_SERIES);
4779 leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
4781 if (leveldir_current == NULL)
4782 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
4784 for (i = 0; i < MAX_LEVELDIR_HISTORY; i++)
4786 char token[strlen(TOKEN_STR_LAST_LEVEL_SERIES) + 10];
4787 LevelDirTree *leveldir_last;
4789 sprintf(token, "%s.%03d", TOKEN_STR_LAST_LEVEL_SERIES, i);
4791 last_level_series = getHashEntry(level_setup_hash, token);
4793 leveldir_last = getTreeInfoFromIdentifier(leveldir_first,
4795 if (leveldir_last != NULL)
4796 setString(&setup.level_setup.last_level_series[pos++],
4800 setString(&setup.level_setup.last_level_series[pos], NULL);
4802 freeSetupFileHash(level_setup_hash);
4806 Debug("setup", "using default setup values");
4812 static void SaveLevelSetup_LastSeries_Ext(boolean deactivate_last_level_series)
4814 // --------------------------------------------------------------------------
4815 // ~/.<program>/levelsetup.conf
4816 // --------------------------------------------------------------------------
4818 // check if the current level directory structure is available at this point
4819 if (leveldir_current == NULL)
4822 char **last_level_series = setup.level_setup.last_level_series;
4823 char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
4827 InitUserDataDirectory();
4829 UpdateLastPlayedLevels_List();
4831 if (!(file = fopen(filename, MODE_WRITE)))
4833 Warn("cannot write setup file '%s'", filename);
4840 fprintFileHeader(file, LEVELSETUP_FILENAME);
4842 if (deactivate_last_level_series)
4843 fprintf(file, "# %s\n# ", "the following level set may have caused a problem and was deactivated");
4845 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_LAST_LEVEL_SERIES,
4846 leveldir_current->identifier));
4848 for (i = 0; last_level_series[i] != NULL; i++)
4850 char token[strlen(TOKEN_STR_LAST_LEVEL_SERIES) + 10];
4852 sprintf(token, "%s.%03d", TOKEN_STR_LAST_LEVEL_SERIES, i);
4854 fprintf(file, "%s\n", getFormattedSetupEntry(token, last_level_series[i]));
4859 SetFilePermissions(filename, PERMS_PRIVATE);
4864 void SaveLevelSetup_LastSeries(void)
4866 SaveLevelSetup_LastSeries_Ext(FALSE);
4869 void SaveLevelSetup_LastSeries_Deactivate(void)
4871 SaveLevelSetup_LastSeries_Ext(TRUE);
4874 static void checkSeriesInfo(void)
4876 static char *level_directory = NULL;
4879 DirectoryEntry *dir_entry;
4882 checked_free(level_directory);
4884 // check for more levels besides the 'levels' field of 'levelinfo.conf'
4886 level_directory = getPath2((leveldir_current->in_user_dir ?
4887 getUserLevelDir(NULL) :
4888 options.level_directory),
4889 leveldir_current->fullpath);
4891 if ((dir = openDirectory(level_directory)) == NULL)
4893 Warn("cannot read level directory '%s'", level_directory);
4899 while ((dir_entry = readDirectory(dir)) != NULL) // loop all entries
4901 if (strlen(dir_entry->basename) > 4 &&
4902 dir_entry->basename[3] == '.' &&
4903 strEqual(&dir_entry->basename[4], LEVELFILE_EXTENSION))
4905 char levelnum_str[4];
4908 strncpy(levelnum_str, dir_entry->basename, 3);
4909 levelnum_str[3] = '\0';
4911 levelnum_value = atoi(levelnum_str);
4913 if (levelnum_value < leveldir_current->first_level)
4915 Warn("additional level %d found", levelnum_value);
4917 leveldir_current->first_level = levelnum_value;
4919 else if (levelnum_value > leveldir_current->last_level)
4921 Warn("additional level %d found", levelnum_value);
4923 leveldir_current->last_level = levelnum_value;
4929 closeDirectory(dir);
4932 void LoadLevelSetup_SeriesInfo(void)
4935 SetupFileHash *level_setup_hash = NULL;
4936 char *level_subdir = leveldir_current->subdir;
4939 // always start with reliable default values
4940 level_nr = leveldir_current->first_level;
4942 for (i = 0; i < MAX_LEVELS; i++)
4944 LevelStats_setPlayed(i, 0);
4945 LevelStats_setSolved(i, 0);
4950 // --------------------------------------------------------------------------
4951 // ~/.<program>/levelsetup/<level series>/levelsetup.conf
4952 // --------------------------------------------------------------------------
4954 level_subdir = leveldir_current->subdir;
4956 filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
4958 if ((level_setup_hash = loadSetupFileHash(filename)))
4962 // get last played level in this level set
4964 token_value = getHashEntry(level_setup_hash, TOKEN_STR_LAST_PLAYED_LEVEL);
4968 level_nr = atoi(token_value);
4970 if (level_nr < leveldir_current->first_level)
4971 level_nr = leveldir_current->first_level;
4972 if (level_nr > leveldir_current->last_level)
4973 level_nr = leveldir_current->last_level;
4976 // get handicap level in this level set
4978 token_value = getHashEntry(level_setup_hash, TOKEN_STR_HANDICAP_LEVEL);
4982 int level_nr = atoi(token_value);
4984 if (level_nr < leveldir_current->first_level)
4985 level_nr = leveldir_current->first_level;
4986 if (level_nr > leveldir_current->last_level + 1)
4987 level_nr = leveldir_current->last_level;
4989 if (leveldir_current->user_defined || !leveldir_current->handicap)
4990 level_nr = leveldir_current->last_level;
4992 leveldir_current->handicap_level = level_nr;
4995 // get number of played and solved levels in this level set
4997 BEGIN_HASH_ITERATION(level_setup_hash, itr)
4999 char *token = HASH_ITERATION_TOKEN(itr);
5000 char *value = HASH_ITERATION_VALUE(itr);
5002 if (strlen(token) == 3 &&
5003 token[0] >= '0' && token[0] <= '9' &&
5004 token[1] >= '0' && token[1] <= '9' &&
5005 token[2] >= '0' && token[2] <= '9')
5007 int level_nr = atoi(token);
5010 LevelStats_setPlayed(level_nr, atoi(value)); // read 1st column
5012 value = strchr(value, ' ');
5015 LevelStats_setSolved(level_nr, atoi(value)); // read 2nd column
5018 END_HASH_ITERATION(hash, itr)
5020 freeSetupFileHash(level_setup_hash);
5024 Debug("setup", "using default setup values");
5030 void SaveLevelSetup_SeriesInfo(void)
5033 char *level_subdir = leveldir_current->subdir;
5034 char *level_nr_str = int2str(level_nr, 0);
5035 char *handicap_level_str = int2str(leveldir_current->handicap_level, 0);
5039 // --------------------------------------------------------------------------
5040 // ~/.<program>/levelsetup/<level series>/levelsetup.conf
5041 // --------------------------------------------------------------------------
5043 InitLevelSetupDirectory(level_subdir);
5045 filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
5047 if (!(file = fopen(filename, MODE_WRITE)))
5049 Warn("cannot write setup file '%s'", filename);
5056 fprintFileHeader(file, LEVELSETUP_FILENAME);
5058 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_PLAYED_LEVEL,
5060 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_HANDICAP_LEVEL,
5061 handicap_level_str));
5063 for (i = leveldir_current->first_level; i <= leveldir_current->last_level;
5066 if (LevelStats_getPlayed(i) > 0 ||
5067 LevelStats_getSolved(i) > 0)
5072 sprintf(token, "%03d", i);
5073 sprintf(value, "%d %d", LevelStats_getPlayed(i), LevelStats_getSolved(i));
5075 fprintf(file, "%s\n", getFormattedSetupEntry(token, value));
5081 SetFilePermissions(filename, PERMS_PRIVATE);
5086 int LevelStats_getPlayed(int nr)
5088 return (nr >= 0 && nr < MAX_LEVELS ? level_stats[nr].played : 0);
5091 int LevelStats_getSolved(int nr)
5093 return (nr >= 0 && nr < MAX_LEVELS ? level_stats[nr].solved : 0);
5096 void LevelStats_setPlayed(int nr, int value)
5098 if (nr >= 0 && nr < MAX_LEVELS)
5099 level_stats[nr].played = value;
5102 void LevelStats_setSolved(int nr, int value)
5104 if (nr >= 0 && nr < MAX_LEVELS)
5105 level_stats[nr].solved = value;
5108 void LevelStats_incPlayed(int nr)
5110 if (nr >= 0 && nr < MAX_LEVELS)
5111 level_stats[nr].played++;
5114 void LevelStats_incSolved(int nr)
5116 if (nr >= 0 && nr < MAX_LEVELS)
5117 level_stats[nr].solved++;
5120 void LoadUserSetup(void)
5122 // --------------------------------------------------------------------------
5123 // ~/.<program>/usersetup.conf
5124 // --------------------------------------------------------------------------
5126 char *filename = getPath2(getMainUserGameDataDir(), USERSETUP_FILENAME);
5127 SetupFileHash *user_setup_hash = NULL;
5129 // always start with reliable default values
5132 if ((user_setup_hash = loadSetupFileHash(filename)))
5136 // get last selected user number
5137 token_value = getHashEntry(user_setup_hash, TOKEN_STR_LAST_USER);
5140 user.nr = MIN(MAX(0, atoi(token_value)), MAX_PLAYER_NAMES - 1);
5142 freeSetupFileHash(user_setup_hash);
5146 Debug("setup", "using default setup values");
5152 void SaveUserSetup(void)
5154 // --------------------------------------------------------------------------
5155 // ~/.<program>/usersetup.conf
5156 // --------------------------------------------------------------------------
5158 char *filename = getPath2(getMainUserGameDataDir(), USERSETUP_FILENAME);
5161 InitMainUserDataDirectory();
5163 if (!(file = fopen(filename, MODE_WRITE)))
5165 Warn("cannot write setup file '%s'", filename);
5172 fprintFileHeader(file, USERSETUP_FILENAME);
5174 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_USER,
5178 SetFilePermissions(filename, PERMS_PRIVATE);