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;
1306 // get next regular tree node, or step up until one is found
1307 while (node->next == NULL && node->node_parent != NULL)
1308 node = node->node_parent;
1310 return getFirstValidTreeInfoEntry(node->next);
1313 // this is a regular tree node
1317 TreeInfo *getFirstValidTreeInfoEntry(TreeInfo *node)
1319 return getValidTreeInfoEntryExt(node, FALSE);
1322 TreeInfo *getNextValidTreeInfoEntry(TreeInfo *node)
1324 return getValidTreeInfoEntryExt(node, TRUE);
1327 TreeInfo *getTreeInfoFirstGroupEntry(TreeInfo *node)
1332 if (node->node_parent == NULL) // top level group
1333 return *node->node_top;
1334 else // sub level group
1335 return node->node_parent->node_group;
1338 int numTreeInfoInGroup(TreeInfo *node)
1340 return numTreeInfo(getTreeInfoFirstGroupEntry(node));
1343 int getPosFromTreeInfo(TreeInfo *node)
1345 TreeInfo *node_cmp = getTreeInfoFirstGroupEntry(node);
1350 if (node_cmp == node)
1354 node_cmp = node_cmp->next;
1360 TreeInfo *getTreeInfoFromPos(TreeInfo *node, int pos)
1362 TreeInfo *node_default = node;
1374 return node_default;
1377 static TreeInfo *getTreeInfoFromIdentifierExt(TreeInfo *node, char *identifier,
1378 int node_type_wanted)
1380 if (identifier == NULL)
1385 if (TREE_NODE_TYPE(node) == node_type_wanted &&
1386 strEqual(identifier, node->identifier))
1389 if (node->node_group)
1391 TreeInfo *node_group = getTreeInfoFromIdentifierExt(node->node_group,
1404 TreeInfo *getTreeInfoFromIdentifier(TreeInfo *node, char *identifier)
1406 return getTreeInfoFromIdentifierExt(node, identifier, TREE_NODE_TYPE_DEFAULT);
1409 static TreeInfo *cloneTreeNode(TreeInfo **node_top, TreeInfo *node_parent,
1410 TreeInfo *node, boolean skip_sets_without_levels)
1417 if (!node->parent_link && !node->level_group &&
1418 skip_sets_without_levels && node->levels == 0)
1419 return cloneTreeNode(node_top, node_parent, node->next,
1420 skip_sets_without_levels);
1422 node_new = getTreeInfoCopy(node); // copy complete node
1424 node_new->node_top = node_top; // correct top node link
1425 node_new->node_parent = node_parent; // correct parent node link
1427 if (node->level_group)
1428 node_new->node_group = cloneTreeNode(node_top, node_new, node->node_group,
1429 skip_sets_without_levels);
1431 node_new->next = cloneTreeNode(node_top, node_parent, node->next,
1432 skip_sets_without_levels);
1437 static void cloneTree(TreeInfo **ti_new, TreeInfo *ti, boolean skip_empty_sets)
1439 TreeInfo *ti_cloned = cloneTreeNode(ti_new, NULL, ti, skip_empty_sets);
1441 *ti_new = ti_cloned;
1444 static boolean adjustTreeGraphicsForEMC(TreeInfo *node)
1446 boolean settings_changed = FALSE;
1450 boolean want_ecs = (setup.prefer_aga_graphics == FALSE);
1451 boolean want_aga = (setup.prefer_aga_graphics == TRUE);
1452 boolean has_only_ecs = (!node->graphics_set && !node->graphics_set_aga);
1453 boolean has_only_aga = (!node->graphics_set && !node->graphics_set_ecs);
1454 char *graphics_set = NULL;
1456 if (node->graphics_set_ecs && (want_ecs || has_only_ecs))
1457 graphics_set = node->graphics_set_ecs;
1459 if (node->graphics_set_aga && (want_aga || has_only_aga))
1460 graphics_set = node->graphics_set_aga;
1462 if (graphics_set && !strEqual(node->graphics_set, graphics_set))
1464 setString(&node->graphics_set, graphics_set);
1465 settings_changed = TRUE;
1468 if (node->node_group != NULL)
1469 settings_changed |= adjustTreeGraphicsForEMC(node->node_group);
1474 return settings_changed;
1477 static boolean adjustTreeSoundsForEMC(TreeInfo *node)
1479 boolean settings_changed = FALSE;
1483 boolean want_default = (setup.prefer_lowpass_sounds == FALSE);
1484 boolean want_lowpass = (setup.prefer_lowpass_sounds == TRUE);
1485 boolean has_only_default = (!node->sounds_set && !node->sounds_set_lowpass);
1486 boolean has_only_lowpass = (!node->sounds_set && !node->sounds_set_default);
1487 char *sounds_set = NULL;
1489 if (node->sounds_set_default && (want_default || has_only_default))
1490 sounds_set = node->sounds_set_default;
1492 if (node->sounds_set_lowpass && (want_lowpass || has_only_lowpass))
1493 sounds_set = node->sounds_set_lowpass;
1495 if (sounds_set && !strEqual(node->sounds_set, sounds_set))
1497 setString(&node->sounds_set, sounds_set);
1498 settings_changed = TRUE;
1501 if (node->node_group != NULL)
1502 settings_changed |= adjustTreeSoundsForEMC(node->node_group);
1507 return settings_changed;
1510 int dumpTreeInfo(TreeInfo *node, int depth)
1512 char bullet_list[] = { '-', '*', 'o' };
1513 int num_leaf_nodes = 0;
1517 Debug("tree", "Dumping TreeInfo:");
1521 char bullet = bullet_list[depth % ARRAY_SIZE(bullet_list)];
1523 for (i = 0; i < depth * 2; i++)
1524 DebugContinued("", " ");
1526 DebugContinued("tree", "%c '%s' ['%s] [PARENT: '%s'] %s\n",
1527 bullet, node->name, node->identifier,
1528 (node->node_parent ? node->node_parent->identifier : "-"),
1529 (node->node_group ? "[GROUP]" : ""));
1531 if (!node->node_group && !node->parent_link)
1535 // use for dumping artwork info tree
1536 Debug("tree", "subdir == '%s' ['%s', '%s'] [%d])",
1537 node->subdir, node->fullpath, node->basepath, node->in_user_dir);
1540 if (node->node_group != NULL)
1541 num_leaf_nodes += dumpTreeInfo(node->node_group, depth + 1);
1547 Debug("tree", "Summary: %d leaf nodes found", num_leaf_nodes);
1549 return num_leaf_nodes;
1552 void sortTreeInfoBySortFunction(TreeInfo **node_first,
1553 int (*compare_function)(const void *,
1556 int num_nodes = numTreeInfo(*node_first);
1557 TreeInfo **sort_array;
1558 TreeInfo *node = *node_first;
1564 // allocate array for sorting structure pointers
1565 sort_array = checked_calloc(num_nodes * sizeof(TreeInfo *));
1567 // writing structure pointers to sorting array
1568 while (i < num_nodes && node) // double boundary check...
1570 sort_array[i] = node;
1576 // sorting the structure pointers in the sorting array
1577 qsort(sort_array, num_nodes, sizeof(TreeInfo *),
1580 // update the linkage of list elements with the sorted node array
1581 for (i = 0; i < num_nodes - 1; i++)
1582 sort_array[i]->next = sort_array[i + 1];
1583 sort_array[num_nodes - 1]->next = NULL;
1585 // update the linkage of the main list anchor pointer
1586 *node_first = sort_array[0];
1590 // now recursively sort the level group structures
1594 if (node->node_group != NULL)
1595 sortTreeInfoBySortFunction(&node->node_group, compare_function);
1601 void sortTreeInfo(TreeInfo **node_first)
1603 sortTreeInfoBySortFunction(node_first, compareTreeInfoEntries);
1607 // ============================================================================
1608 // some stuff from "files.c"
1609 // ============================================================================
1611 #if defined(PLATFORM_WIN32)
1613 #define S_IRGRP S_IRUSR
1616 #define S_IROTH S_IRUSR
1619 #define S_IWGRP S_IWUSR
1622 #define S_IWOTH S_IWUSR
1625 #define S_IXGRP S_IXUSR
1628 #define S_IXOTH S_IXUSR
1631 #define S_IRWXG (S_IRGRP | S_IWGRP | S_IXGRP)
1636 #endif // PLATFORM_WIN32
1638 // file permissions for newly written files
1639 #define MODE_R_ALL (S_IRUSR | S_IRGRP | S_IROTH)
1640 #define MODE_W_ALL (S_IWUSR | S_IWGRP | S_IWOTH)
1641 #define MODE_X_ALL (S_IXUSR | S_IXGRP | S_IXOTH)
1643 #define MODE_W_PRIVATE (S_IWUSR)
1644 #define MODE_W_PUBLIC_FILE (S_IWUSR | S_IWGRP)
1645 #define MODE_W_PUBLIC_DIR (S_IWUSR | S_IWGRP | S_ISGID)
1647 #define DIR_PERMS_PRIVATE (MODE_R_ALL | MODE_X_ALL | MODE_W_PRIVATE)
1648 #define DIR_PERMS_PUBLIC (MODE_R_ALL | MODE_X_ALL | MODE_W_PUBLIC_DIR)
1649 #define DIR_PERMS_PUBLIC_ALL (MODE_R_ALL | MODE_X_ALL | MODE_W_ALL)
1651 #define FILE_PERMS_PRIVATE (MODE_R_ALL | MODE_W_PRIVATE)
1652 #define FILE_PERMS_PUBLIC (MODE_R_ALL | MODE_W_PUBLIC_FILE)
1653 #define FILE_PERMS_PUBLIC_ALL (MODE_R_ALL | MODE_W_ALL)
1656 char *getHomeDir(void)
1658 static char *dir = NULL;
1660 #if defined(PLATFORM_WIN32)
1663 dir = checked_malloc(MAX_PATH + 1);
1665 if (!SUCCEEDED(SHGetFolderPath(NULL, CSIDL_PERSONAL, NULL, 0, dir)))
1668 #elif defined(PLATFORM_EMSCRIPTEN)
1669 dir = "/persistent";
1670 #elif defined(PLATFORM_UNIX)
1673 if ((dir = getenv("HOME")) == NULL)
1675 dir = getUnixHomeDir();
1678 dir = getStringCopy(dir);
1690 char *getPersonalDataDir(void)
1692 static char *personal_data_dir = NULL;
1694 #if defined(PLATFORM_MACOSX)
1695 if (personal_data_dir == NULL)
1696 personal_data_dir = getPath2(getHomeDir(), "Documents");
1698 if (personal_data_dir == NULL)
1699 personal_data_dir = getHomeDir();
1702 return personal_data_dir;
1705 char *getMainUserGameDataDir(void)
1707 static char *main_user_data_dir = NULL;
1709 #if defined(PLATFORM_ANDROID)
1710 if (main_user_data_dir == NULL)
1711 main_user_data_dir = (char *)(SDL_AndroidGetExternalStorageState() &
1712 SDL_ANDROID_EXTERNAL_STORAGE_WRITE ?
1713 SDL_AndroidGetExternalStoragePath() :
1714 SDL_AndroidGetInternalStoragePath());
1716 if (main_user_data_dir == NULL)
1717 main_user_data_dir = getPath2(getPersonalDataDir(),
1718 program.userdata_subdir);
1721 return main_user_data_dir;
1724 char *getUserGameDataDir(void)
1727 return getMainUserGameDataDir();
1729 return getUserDir(user.nr);
1732 char *getSetupDir(void)
1734 return getUserGameDataDir();
1737 static mode_t posix_umask(mode_t mask)
1739 #if defined(PLATFORM_UNIX)
1746 static int posix_mkdir(const char *pathname, mode_t mode)
1748 #if defined(PLATFORM_WIN32)
1749 return mkdir(pathname);
1751 return mkdir(pathname, mode);
1755 static boolean posix_process_running_setgid(void)
1757 #if defined(PLATFORM_UNIX)
1758 return (getgid() != getegid());
1764 void createDirectory(char *dir, char *text, int permission_class)
1766 if (directoryExists(dir))
1769 // leave "other" permissions in umask untouched, but ensure group parts
1770 // of USERDATA_DIR_MODE are not masked
1771 mode_t dir_mode = (permission_class == PERMS_PRIVATE ?
1772 DIR_PERMS_PRIVATE : DIR_PERMS_PUBLIC);
1773 mode_t last_umask = posix_umask(0);
1774 mode_t group_umask = ~(dir_mode & S_IRWXG);
1775 int running_setgid = posix_process_running_setgid();
1777 if (permission_class == PERMS_PUBLIC)
1779 // if we're setgid, protect files against "other"
1780 // else keep umask(0) to make the dir world-writable
1783 posix_umask(last_umask & group_umask);
1785 dir_mode = DIR_PERMS_PUBLIC_ALL;
1788 if (posix_mkdir(dir, dir_mode) != 0)
1789 Warn("cannot create %s directory '%s': %s", text, dir, strerror(errno));
1791 if (permission_class == PERMS_PUBLIC && !running_setgid)
1792 chmod(dir, dir_mode);
1794 posix_umask(last_umask); // restore previous umask
1797 void InitMainUserDataDirectory(void)
1799 createDirectory(getMainUserGameDataDir(), "main user data", PERMS_PRIVATE);
1802 void InitUserDataDirectory(void)
1804 createDirectory(getMainUserGameDataDir(), "main user data", PERMS_PRIVATE);
1808 createDirectory(getUserDir(-1), "users", PERMS_PRIVATE);
1809 createDirectory(getUserDir(user.nr), "user data", PERMS_PRIVATE);
1813 void SetFilePermissions(char *filename, int permission_class)
1815 int running_setgid = posix_process_running_setgid();
1816 int perms = (permission_class == PERMS_PRIVATE ?
1817 FILE_PERMS_PRIVATE : FILE_PERMS_PUBLIC);
1819 if (permission_class == PERMS_PUBLIC && !running_setgid)
1820 perms = FILE_PERMS_PUBLIC_ALL;
1822 chmod(filename, perms);
1825 char *getCookie(char *file_type)
1827 static char cookie[MAX_COOKIE_LEN + 1];
1829 if (strlen(program.cookie_prefix) + 1 +
1830 strlen(file_type) + strlen("_FILE_VERSION_x.x") > MAX_COOKIE_LEN)
1831 return "[COOKIE ERROR]"; // should never happen
1833 sprintf(cookie, "%s_%s_FILE_VERSION_%d.%d",
1834 program.cookie_prefix, file_type,
1835 program.version_super, program.version_major);
1840 void fprintFileHeader(FILE *file, char *basename)
1842 char *prefix = "# ";
1845 fprintf_line_with_prefix(file, prefix, sep1, 77);
1846 fprintf(file, "%s%s\n", prefix, basename);
1847 fprintf_line_with_prefix(file, prefix, sep1, 77);
1848 fprintf(file, "\n");
1851 int getFileVersionFromCookieString(const char *cookie)
1853 const char *ptr_cookie1, *ptr_cookie2;
1854 const char *pattern1 = "_FILE_VERSION_";
1855 const char *pattern2 = "?.?";
1856 const int len_cookie = strlen(cookie);
1857 const int len_pattern1 = strlen(pattern1);
1858 const int len_pattern2 = strlen(pattern2);
1859 const int len_pattern = len_pattern1 + len_pattern2;
1860 int version_super, version_major;
1862 if (len_cookie <= len_pattern)
1865 ptr_cookie1 = &cookie[len_cookie - len_pattern];
1866 ptr_cookie2 = &cookie[len_cookie - len_pattern2];
1868 if (strncmp(ptr_cookie1, pattern1, len_pattern1) != 0)
1871 if (ptr_cookie2[0] < '0' || ptr_cookie2[0] > '9' ||
1872 ptr_cookie2[1] != '.' ||
1873 ptr_cookie2[2] < '0' || ptr_cookie2[2] > '9')
1876 version_super = ptr_cookie2[0] - '0';
1877 version_major = ptr_cookie2[2] - '0';
1879 return VERSION_IDENT(version_super, version_major, 0, 0);
1882 boolean checkCookieString(const char *cookie, const char *template)
1884 const char *pattern = "_FILE_VERSION_?.?";
1885 const int len_cookie = strlen(cookie);
1886 const int len_template = strlen(template);
1887 const int len_pattern = strlen(pattern);
1889 if (len_cookie != len_template)
1892 if (strncmp(cookie, template, len_cookie - len_pattern) != 0)
1899 // ----------------------------------------------------------------------------
1900 // setup file list and hash handling functions
1901 // ----------------------------------------------------------------------------
1903 char *getFormattedSetupEntry(char *token, char *value)
1906 static char entry[MAX_LINE_LEN];
1908 // if value is an empty string, just return token without value
1912 // start with the token and some spaces to format output line
1913 sprintf(entry, "%s:", token);
1914 for (i = strlen(entry); i < token_value_position; i++)
1917 // continue with the token's value
1918 strcat(entry, value);
1923 SetupFileList *newSetupFileList(char *token, char *value)
1925 SetupFileList *new = checked_malloc(sizeof(SetupFileList));
1927 new->token = getStringCopy(token);
1928 new->value = getStringCopy(value);
1935 void freeSetupFileList(SetupFileList *list)
1940 checked_free(list->token);
1941 checked_free(list->value);
1944 freeSetupFileList(list->next);
1949 char *getListEntry(SetupFileList *list, char *token)
1954 if (strEqual(list->token, token))
1957 return getListEntry(list->next, token);
1960 SetupFileList *setListEntry(SetupFileList *list, char *token, char *value)
1965 if (strEqual(list->token, token))
1967 checked_free(list->value);
1969 list->value = getStringCopy(value);
1973 else if (list->next == NULL)
1974 return (list->next = newSetupFileList(token, value));
1976 return setListEntry(list->next, token, value);
1979 SetupFileList *addListEntry(SetupFileList *list, char *token, char *value)
1984 if (list->next == NULL)
1985 return (list->next = newSetupFileList(token, value));
1987 return addListEntry(list->next, token, value);
1990 #if ENABLE_UNUSED_CODE
1992 static void printSetupFileList(SetupFileList *list)
1997 Debug("setup:printSetupFileList", "token: '%s'", list->token);
1998 Debug("setup:printSetupFileList", "value: '%s'", list->value);
2000 printSetupFileList(list->next);
2006 DEFINE_HASHTABLE_INSERT(insert_hash_entry, char, char);
2007 DEFINE_HASHTABLE_SEARCH(search_hash_entry, char, char);
2008 DEFINE_HASHTABLE_CHANGE(change_hash_entry, char, char);
2009 DEFINE_HASHTABLE_REMOVE(remove_hash_entry, char, char);
2011 #define insert_hash_entry hashtable_insert
2012 #define search_hash_entry hashtable_search
2013 #define change_hash_entry hashtable_change
2014 #define remove_hash_entry hashtable_remove
2017 unsigned int get_hash_from_key(void *key)
2022 This algorithm (k=33) was first reported by Dan Bernstein many years ago in
2023 'comp.lang.c'. Another version of this algorithm (now favored by Bernstein)
2024 uses XOR: hash(i) = hash(i - 1) * 33 ^ str[i]; the magic of number 33 (why
2025 it works better than many other constants, prime or not) has never been
2026 adequately explained.
2028 If you just want to have a good hash function, and cannot wait, djb2
2029 is one of the best string hash functions i know. It has excellent
2030 distribution and speed on many different sets of keys and table sizes.
2031 You are not likely to do better with one of the "well known" functions
2032 such as PJW, K&R, etc.
2034 Ozan (oz) Yigit [http://www.cs.yorku.ca/~oz/hash.html]
2037 char *str = (char *)key;
2038 unsigned int hash = 5381;
2041 while ((c = *str++))
2042 hash = ((hash << 5) + hash) + c; // hash * 33 + c
2047 static int keys_are_equal(void *key1, void *key2)
2049 return (strEqual((char *)key1, (char *)key2));
2052 SetupFileHash *newSetupFileHash(void)
2054 SetupFileHash *new_hash =
2055 create_hashtable(16, 0.75, get_hash_from_key, keys_are_equal);
2057 if (new_hash == NULL)
2058 Fail("create_hashtable() failed -- out of memory");
2063 void freeSetupFileHash(SetupFileHash *hash)
2068 hashtable_destroy(hash, 1); // 1 == also free values stored in hash
2071 char *getHashEntry(SetupFileHash *hash, char *token)
2076 return search_hash_entry(hash, token);
2079 void setHashEntry(SetupFileHash *hash, char *token, char *value)
2086 value_copy = getStringCopy(value);
2088 // change value; if it does not exist, insert it as new
2089 if (!change_hash_entry(hash, token, value_copy))
2090 if (!insert_hash_entry(hash, getStringCopy(token), value_copy))
2091 Fail("cannot insert into hash -- aborting");
2094 char *removeHashEntry(SetupFileHash *hash, char *token)
2099 return remove_hash_entry(hash, token);
2102 #if ENABLE_UNUSED_CODE
2104 static void printSetupFileHash(SetupFileHash *hash)
2106 BEGIN_HASH_ITERATION(hash, itr)
2108 Debug("setup:printSetupFileHash", "token: '%s'", HASH_ITERATION_TOKEN(itr));
2109 Debug("setup:printSetupFileHash", "value: '%s'", HASH_ITERATION_VALUE(itr));
2111 END_HASH_ITERATION(hash, itr)
2116 #define ALLOW_TOKEN_VALUE_SEPARATOR_BEING_WHITESPACE 1
2117 #define CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING 0
2118 #define CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH 0
2120 static boolean token_value_separator_found = FALSE;
2121 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2122 static boolean token_value_separator_warning = FALSE;
2124 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2125 static boolean token_already_exists_warning = FALSE;
2128 static boolean getTokenValueFromSetupLineExt(char *line,
2129 char **token_ptr, char **value_ptr,
2130 char *filename, char *line_raw,
2132 boolean separator_required)
2134 static char line_copy[MAX_LINE_LEN + 1], line_raw_copy[MAX_LINE_LEN + 1];
2135 char *token, *value, *line_ptr;
2137 // when externally invoked via ReadTokenValueFromLine(), copy line buffers
2138 if (line_raw == NULL)
2140 strncpy(line_copy, line, MAX_LINE_LEN);
2141 line_copy[MAX_LINE_LEN] = '\0';
2144 strcpy(line_raw_copy, line_copy);
2145 line_raw = line_raw_copy;
2148 // cut trailing comment from input line
2149 for (line_ptr = line; *line_ptr; line_ptr++)
2151 if (*line_ptr == '#')
2158 // cut trailing whitespaces from input line
2159 for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
2160 if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
2163 // ignore empty lines
2167 // cut leading whitespaces from token
2168 for (token = line; *token; token++)
2169 if (*token != ' ' && *token != '\t')
2172 // start with empty value as reliable default
2175 token_value_separator_found = FALSE;
2177 // find end of token to determine start of value
2178 for (line_ptr = token; *line_ptr; line_ptr++)
2180 // first look for an explicit token/value separator, like ':' or '='
2181 if (*line_ptr == ':' || *line_ptr == '=')
2183 *line_ptr = '\0'; // terminate token string
2184 value = line_ptr + 1; // set beginning of value
2186 token_value_separator_found = TRUE;
2192 #if ALLOW_TOKEN_VALUE_SEPARATOR_BEING_WHITESPACE
2193 // fallback: if no token/value separator found, also allow whitespaces
2194 if (!token_value_separator_found && !separator_required)
2196 for (line_ptr = token; *line_ptr; line_ptr++)
2198 if (*line_ptr == ' ' || *line_ptr == '\t')
2200 *line_ptr = '\0'; // terminate token string
2201 value = line_ptr + 1; // set beginning of value
2203 token_value_separator_found = TRUE;
2209 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2210 if (token_value_separator_found)
2212 if (!token_value_separator_warning)
2214 Debug("setup", "---");
2216 if (filename != NULL)
2218 Debug("setup", "missing token/value separator(s) in config file:");
2219 Debug("setup", "- config file: '%s'", filename);
2223 Debug("setup", "missing token/value separator(s):");
2226 token_value_separator_warning = TRUE;
2229 if (filename != NULL)
2230 Debug("setup", "- line %d: '%s'", line_nr, line_raw);
2232 Debug("setup", "- line: '%s'", line_raw);
2238 // cut trailing whitespaces from token
2239 for (line_ptr = &token[strlen(token)]; line_ptr >= token; line_ptr--)
2240 if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
2243 // cut leading whitespaces from value
2244 for (; *value; value++)
2245 if (*value != ' ' && *value != '\t')
2254 boolean getTokenValueFromSetupLine(char *line, char **token, char **value)
2256 // while the internal (old) interface does not require a token/value
2257 // separator (for downwards compatibility with existing files which
2258 // don't use them), it is mandatory for the external (new) interface
2260 return getTokenValueFromSetupLineExt(line, token, value, NULL, NULL, 0, TRUE);
2263 static boolean loadSetupFileData(void *setup_file_data, char *filename,
2264 boolean top_recursion_level, boolean is_hash)
2266 static SetupFileHash *include_filename_hash = NULL;
2267 char line[MAX_LINE_LEN], line_raw[MAX_LINE_LEN], previous_line[MAX_LINE_LEN];
2268 char *token, *value, *line_ptr;
2269 void *insert_ptr = NULL;
2270 boolean read_continued_line = FALSE;
2272 int line_nr = 0, token_count = 0, include_count = 0;
2274 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2275 token_value_separator_warning = FALSE;
2278 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2279 token_already_exists_warning = FALSE;
2282 if (!(file = openFile(filename, MODE_READ)))
2284 #if DEBUG_NO_CONFIG_FILE
2285 Debug("setup", "cannot open configuration file '%s'", filename);
2291 // use "insert pointer" to store list end for constant insertion complexity
2293 insert_ptr = setup_file_data;
2295 // on top invocation, create hash to mark included files (to prevent loops)
2296 if (top_recursion_level)
2297 include_filename_hash = newSetupFileHash();
2299 // mark this file as already included (to prevent including it again)
2300 setHashEntry(include_filename_hash, getBaseNamePtr(filename), "true");
2302 while (!checkEndOfFile(file))
2304 // read next line of input file
2305 if (!getStringFromFile(file, line, MAX_LINE_LEN))
2308 // check if line was completely read and is terminated by line break
2309 if (strlen(line) > 0 && line[strlen(line) - 1] == '\n')
2312 // cut trailing line break (this can be newline and/or carriage return)
2313 for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
2314 if ((*line_ptr == '\n' || *line_ptr == '\r') && *(line_ptr + 1) == '\0')
2317 // copy raw input line for later use (mainly debugging output)
2318 strcpy(line_raw, line);
2320 if (read_continued_line)
2322 // append new line to existing line, if there is enough space
2323 if (strlen(previous_line) + strlen(line_ptr) < MAX_LINE_LEN)
2324 strcat(previous_line, line_ptr);
2326 strcpy(line, previous_line); // copy storage buffer to line
2328 read_continued_line = FALSE;
2331 // if the last character is '\', continue at next line
2332 if (strlen(line) > 0 && line[strlen(line) - 1] == '\\')
2334 line[strlen(line) - 1] = '\0'; // cut off trailing backslash
2335 strcpy(previous_line, line); // copy line to storage buffer
2337 read_continued_line = TRUE;
2342 if (!getTokenValueFromSetupLineExt(line, &token, &value, filename,
2343 line_raw, line_nr, FALSE))
2348 if (strEqual(token, "include"))
2350 if (getHashEntry(include_filename_hash, value) == NULL)
2352 char *basepath = getBasePath(filename);
2353 char *basename = getBaseName(value);
2354 char *filename_include = getPath2(basepath, basename);
2356 loadSetupFileData(setup_file_data, filename_include, FALSE, is_hash);
2360 free(filename_include);
2366 Warn("ignoring already processed file '%s'", value);
2373 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2375 getHashEntry((SetupFileHash *)setup_file_data, token);
2377 if (old_value != NULL)
2379 if (!token_already_exists_warning)
2381 Debug("setup", "---");
2382 Debug("setup", "duplicate token(s) found in config file:");
2383 Debug("setup", "- config file: '%s'", filename);
2385 token_already_exists_warning = TRUE;
2388 Debug("setup", "- token: '%s' (in line %d)", token, line_nr);
2389 Debug("setup", " old value: '%s'", old_value);
2390 Debug("setup", " new value: '%s'", value);
2394 setHashEntry((SetupFileHash *)setup_file_data, token, value);
2398 insert_ptr = addListEntry((SetupFileList *)insert_ptr, token, value);
2408 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2409 if (token_value_separator_warning)
2410 Debug("setup", "---");
2413 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2414 if (token_already_exists_warning)
2415 Debug("setup", "---");
2418 if (token_count == 0 && include_count == 0)
2419 Warn("configuration file '%s' is empty", filename);
2421 if (top_recursion_level)
2422 freeSetupFileHash(include_filename_hash);
2427 static int compareSetupFileData(const void *object1, const void *object2)
2429 const struct ConfigInfo *entry1 = (struct ConfigInfo *)object1;
2430 const struct ConfigInfo *entry2 = (struct ConfigInfo *)object2;
2432 return strcmp(entry1->token, entry2->token);
2435 static void saveSetupFileHash(SetupFileHash *hash, char *filename)
2437 int item_count = hashtable_count(hash);
2438 int item_size = sizeof(struct ConfigInfo);
2439 struct ConfigInfo *sort_array = checked_malloc(item_count * item_size);
2443 // copy string pointers from hash to array
2444 BEGIN_HASH_ITERATION(hash, itr)
2446 sort_array[i].token = HASH_ITERATION_TOKEN(itr);
2447 sort_array[i].value = HASH_ITERATION_VALUE(itr);
2451 if (i > item_count) // should never happen
2454 END_HASH_ITERATION(hash, itr)
2456 // sort string pointers from hash in array
2457 qsort(sort_array, item_count, item_size, compareSetupFileData);
2459 if (!(file = fopen(filename, MODE_WRITE)))
2461 Warn("cannot write configuration file '%s'", filename);
2466 fprintf(file, "%s\n\n", getFormattedSetupEntry("program.version",
2467 program.version_string));
2468 for (i = 0; i < item_count; i++)
2469 fprintf(file, "%s\n", getFormattedSetupEntry(sort_array[i].token,
2470 sort_array[i].value));
2473 checked_free(sort_array);
2476 SetupFileList *loadSetupFileList(char *filename)
2478 SetupFileList *setup_file_list = newSetupFileList("", "");
2479 SetupFileList *first_valid_list_entry;
2481 if (!loadSetupFileData(setup_file_list, filename, TRUE, FALSE))
2483 freeSetupFileList(setup_file_list);
2488 first_valid_list_entry = setup_file_list->next;
2490 // free empty list header
2491 setup_file_list->next = NULL;
2492 freeSetupFileList(setup_file_list);
2494 return first_valid_list_entry;
2497 SetupFileHash *loadSetupFileHash(char *filename)
2499 SetupFileHash *setup_file_hash = newSetupFileHash();
2501 if (!loadSetupFileData(setup_file_hash, filename, TRUE, TRUE))
2503 freeSetupFileHash(setup_file_hash);
2508 return setup_file_hash;
2512 // ============================================================================
2514 // ============================================================================
2516 #define TOKEN_STR_LAST_LEVEL_SERIES "last_level_series"
2517 #define TOKEN_STR_LAST_PLAYED_LEVEL "last_played_level"
2518 #define TOKEN_STR_HANDICAP_LEVEL "handicap_level"
2519 #define TOKEN_STR_LAST_USER "last_user"
2521 // level directory info
2522 #define LEVELINFO_TOKEN_IDENTIFIER 0
2523 #define LEVELINFO_TOKEN_NAME 1
2524 #define LEVELINFO_TOKEN_NAME_SORTING 2
2525 #define LEVELINFO_TOKEN_AUTHOR 3
2526 #define LEVELINFO_TOKEN_YEAR 4
2527 #define LEVELINFO_TOKEN_PROGRAM_TITLE 5
2528 #define LEVELINFO_TOKEN_PROGRAM_COPYRIGHT 6
2529 #define LEVELINFO_TOKEN_PROGRAM_COMPANY 7
2530 #define LEVELINFO_TOKEN_IMPORTED_FROM 8
2531 #define LEVELINFO_TOKEN_IMPORTED_BY 9
2532 #define LEVELINFO_TOKEN_TESTED_BY 10
2533 #define LEVELINFO_TOKEN_LEVELS 11
2534 #define LEVELINFO_TOKEN_FIRST_LEVEL 12
2535 #define LEVELINFO_TOKEN_SORT_PRIORITY 13
2536 #define LEVELINFO_TOKEN_LATEST_ENGINE 14
2537 #define LEVELINFO_TOKEN_LEVEL_GROUP 15
2538 #define LEVELINFO_TOKEN_READONLY 16
2539 #define LEVELINFO_TOKEN_GRAPHICS_SET_ECS 17
2540 #define LEVELINFO_TOKEN_GRAPHICS_SET_AGA 18
2541 #define LEVELINFO_TOKEN_GRAPHICS_SET 19
2542 #define LEVELINFO_TOKEN_SOUNDS_SET_DEFAULT 20
2543 #define LEVELINFO_TOKEN_SOUNDS_SET_LOWPASS 21
2544 #define LEVELINFO_TOKEN_SOUNDS_SET 22
2545 #define LEVELINFO_TOKEN_MUSIC_SET 23
2546 #define LEVELINFO_TOKEN_FILENAME 24
2547 #define LEVELINFO_TOKEN_FILETYPE 25
2548 #define LEVELINFO_TOKEN_SPECIAL_FLAGS 26
2549 #define LEVELINFO_TOKEN_HANDICAP 27
2550 #define LEVELINFO_TOKEN_SKIP_LEVELS 28
2551 #define LEVELINFO_TOKEN_USE_EMC_TILES 29
2553 #define NUM_LEVELINFO_TOKENS 30
2555 static LevelDirTree ldi;
2557 static struct TokenInfo levelinfo_tokens[] =
2559 // level directory info
2560 { TYPE_STRING, &ldi.identifier, "identifier" },
2561 { TYPE_STRING, &ldi.name, "name" },
2562 { TYPE_STRING, &ldi.name_sorting, "name_sorting" },
2563 { TYPE_STRING, &ldi.author, "author" },
2564 { TYPE_STRING, &ldi.year, "year" },
2565 { TYPE_STRING, &ldi.program_title, "program_title" },
2566 { TYPE_STRING, &ldi.program_copyright, "program_copyright" },
2567 { TYPE_STRING, &ldi.program_company, "program_company" },
2568 { TYPE_STRING, &ldi.imported_from, "imported_from" },
2569 { TYPE_STRING, &ldi.imported_by, "imported_by" },
2570 { TYPE_STRING, &ldi.tested_by, "tested_by" },
2571 { TYPE_INTEGER, &ldi.levels, "levels" },
2572 { TYPE_INTEGER, &ldi.first_level, "first_level" },
2573 { TYPE_INTEGER, &ldi.sort_priority, "sort_priority" },
2574 { TYPE_BOOLEAN, &ldi.latest_engine, "latest_engine" },
2575 { TYPE_BOOLEAN, &ldi.level_group, "level_group" },
2576 { TYPE_BOOLEAN, &ldi.readonly, "readonly" },
2577 { TYPE_STRING, &ldi.graphics_set_ecs, "graphics_set.ecs" },
2578 { TYPE_STRING, &ldi.graphics_set_aga, "graphics_set.aga" },
2579 { TYPE_STRING, &ldi.graphics_set, "graphics_set" },
2580 { TYPE_STRING, &ldi.sounds_set_default,"sounds_set.default" },
2581 { TYPE_STRING, &ldi.sounds_set_lowpass,"sounds_set.lowpass" },
2582 { TYPE_STRING, &ldi.sounds_set, "sounds_set" },
2583 { TYPE_STRING, &ldi.music_set, "music_set" },
2584 { TYPE_STRING, &ldi.level_filename, "filename" },
2585 { TYPE_STRING, &ldi.level_filetype, "filetype" },
2586 { TYPE_STRING, &ldi.special_flags, "special_flags" },
2587 { TYPE_BOOLEAN, &ldi.handicap, "handicap" },
2588 { TYPE_BOOLEAN, &ldi.skip_levels, "skip_levels" },
2589 { TYPE_BOOLEAN, &ldi.use_emc_tiles, "use_emc_tiles" }
2592 static struct TokenInfo artworkinfo_tokens[] =
2594 // artwork directory info
2595 { TYPE_STRING, &ldi.identifier, "identifier" },
2596 { TYPE_STRING, &ldi.subdir, "subdir" },
2597 { TYPE_STRING, &ldi.name, "name" },
2598 { TYPE_STRING, &ldi.name_sorting, "name_sorting" },
2599 { TYPE_STRING, &ldi.author, "author" },
2600 { TYPE_STRING, &ldi.program_title, "program_title" },
2601 { TYPE_STRING, &ldi.program_copyright, "program_copyright" },
2602 { TYPE_STRING, &ldi.program_company, "program_company" },
2603 { TYPE_INTEGER, &ldi.sort_priority, "sort_priority" },
2604 { TYPE_STRING, &ldi.basepath, "basepath" },
2605 { TYPE_STRING, &ldi.fullpath, "fullpath" },
2606 { TYPE_BOOLEAN, &ldi.in_user_dir, "in_user_dir" },
2607 { TYPE_STRING, &ldi.class_desc, "class_desc" },
2612 static char *optional_tokens[] =
2615 "program_copyright",
2621 static void setTreeInfoToDefaults(TreeInfo *ti, int type)
2625 ti->node_top = (ti->type == TREE_TYPE_LEVEL_DIR ? &leveldir_first :
2626 ti->type == TREE_TYPE_GRAPHICS_DIR ? &artwork.gfx_first :
2627 ti->type == TREE_TYPE_SOUNDS_DIR ? &artwork.snd_first :
2628 ti->type == TREE_TYPE_MUSIC_DIR ? &artwork.mus_first :
2631 ti->node_parent = NULL;
2632 ti->node_group = NULL;
2639 ti->fullpath = NULL;
2640 ti->basepath = NULL;
2641 ti->identifier = NULL;
2642 ti->name = getStringCopy(ANONYMOUS_NAME);
2643 ti->name_sorting = NULL;
2644 ti->author = getStringCopy(ANONYMOUS_NAME);
2647 ti->program_title = NULL;
2648 ti->program_copyright = NULL;
2649 ti->program_company = NULL;
2651 ti->sort_priority = LEVELCLASS_UNDEFINED; // default: least priority
2652 ti->latest_engine = FALSE; // default: get from level
2653 ti->parent_link = FALSE;
2654 ti->is_copy = FALSE;
2655 ti->in_user_dir = FALSE;
2656 ti->user_defined = FALSE;
2658 ti->class_desc = NULL;
2660 ti->infotext = getStringCopy(TREE_INFOTEXT(ti->type));
2662 if (ti->type == TREE_TYPE_LEVEL_DIR)
2664 ti->imported_from = NULL;
2665 ti->imported_by = NULL;
2666 ti->tested_by = NULL;
2668 ti->graphics_set_ecs = NULL;
2669 ti->graphics_set_aga = NULL;
2670 ti->graphics_set = NULL;
2671 ti->sounds_set_default = NULL;
2672 ti->sounds_set_lowpass = NULL;
2673 ti->sounds_set = NULL;
2674 ti->music_set = NULL;
2675 ti->graphics_path = getStringCopy(UNDEFINED_FILENAME);
2676 ti->sounds_path = getStringCopy(UNDEFINED_FILENAME);
2677 ti->music_path = getStringCopy(UNDEFINED_FILENAME);
2679 ti->level_filename = NULL;
2680 ti->level_filetype = NULL;
2682 ti->special_flags = NULL;
2685 ti->first_level = 0;
2687 ti->level_group = FALSE;
2688 ti->handicap_level = 0;
2689 ti->readonly = TRUE;
2690 ti->handicap = TRUE;
2691 ti->skip_levels = FALSE;
2693 ti->use_emc_tiles = FALSE;
2697 static void setTreeInfoToDefaultsFromParent(TreeInfo *ti, TreeInfo *parent)
2701 Warn("setTreeInfoToDefaultsFromParent(): parent == NULL");
2703 setTreeInfoToDefaults(ti, TREE_TYPE_UNDEFINED);
2708 // copy all values from the parent structure
2710 ti->type = parent->type;
2712 ti->node_top = parent->node_top;
2713 ti->node_parent = parent;
2714 ti->node_group = NULL;
2721 ti->fullpath = NULL;
2722 ti->basepath = NULL;
2723 ti->identifier = NULL;
2724 ti->name = getStringCopy(ANONYMOUS_NAME);
2725 ti->name_sorting = NULL;
2726 ti->author = getStringCopy(parent->author);
2727 ti->year = getStringCopy(parent->year);
2729 ti->program_title = getStringCopy(parent->program_title);
2730 ti->program_copyright = getStringCopy(parent->program_copyright);
2731 ti->program_company = getStringCopy(parent->program_company);
2733 ti->sort_priority = parent->sort_priority;
2734 ti->latest_engine = parent->latest_engine;
2735 ti->parent_link = FALSE;
2736 ti->is_copy = FALSE;
2737 ti->in_user_dir = parent->in_user_dir;
2738 ti->user_defined = parent->user_defined;
2739 ti->color = parent->color;
2740 ti->class_desc = getStringCopy(parent->class_desc);
2742 ti->infotext = getStringCopy(parent->infotext);
2744 if (ti->type == TREE_TYPE_LEVEL_DIR)
2746 ti->imported_from = getStringCopy(parent->imported_from);
2747 ti->imported_by = getStringCopy(parent->imported_by);
2748 ti->tested_by = getStringCopy(parent->tested_by);
2750 ti->graphics_set_ecs = getStringCopy(parent->graphics_set_ecs);
2751 ti->graphics_set_aga = getStringCopy(parent->graphics_set_aga);
2752 ti->graphics_set = getStringCopy(parent->graphics_set);
2753 ti->sounds_set_default = getStringCopy(parent->sounds_set_default);
2754 ti->sounds_set_lowpass = getStringCopy(parent->sounds_set_lowpass);
2755 ti->sounds_set = getStringCopy(parent->sounds_set);
2756 ti->music_set = getStringCopy(parent->music_set);
2757 ti->graphics_path = getStringCopy(UNDEFINED_FILENAME);
2758 ti->sounds_path = getStringCopy(UNDEFINED_FILENAME);
2759 ti->music_path = getStringCopy(UNDEFINED_FILENAME);
2761 ti->level_filename = getStringCopy(parent->level_filename);
2762 ti->level_filetype = getStringCopy(parent->level_filetype);
2764 ti->special_flags = getStringCopy(parent->special_flags);
2766 ti->levels = parent->levels;
2767 ti->first_level = parent->first_level;
2768 ti->last_level = parent->last_level;
2769 ti->level_group = FALSE;
2770 ti->handicap_level = parent->handicap_level;
2771 ti->readonly = parent->readonly;
2772 ti->handicap = parent->handicap;
2773 ti->skip_levels = parent->skip_levels;
2775 ti->use_emc_tiles = parent->use_emc_tiles;
2779 static TreeInfo *getTreeInfoCopy(TreeInfo *ti)
2781 TreeInfo *ti_copy = newTreeInfo();
2783 // copy all values from the original structure
2785 ti_copy->type = ti->type;
2787 ti_copy->node_top = ti->node_top;
2788 ti_copy->node_parent = ti->node_parent;
2789 ti_copy->node_group = ti->node_group;
2790 ti_copy->next = ti->next;
2792 ti_copy->cl_first = ti->cl_first;
2793 ti_copy->cl_cursor = ti->cl_cursor;
2795 ti_copy->subdir = getStringCopy(ti->subdir);
2796 ti_copy->fullpath = getStringCopy(ti->fullpath);
2797 ti_copy->basepath = getStringCopy(ti->basepath);
2798 ti_copy->identifier = getStringCopy(ti->identifier);
2799 ti_copy->name = getStringCopy(ti->name);
2800 ti_copy->name_sorting = getStringCopy(ti->name_sorting);
2801 ti_copy->author = getStringCopy(ti->author);
2802 ti_copy->year = getStringCopy(ti->year);
2804 ti_copy->program_title = getStringCopy(ti->program_title);
2805 ti_copy->program_copyright = getStringCopy(ti->program_copyright);
2806 ti_copy->program_company = getStringCopy(ti->program_company);
2808 ti_copy->imported_from = getStringCopy(ti->imported_from);
2809 ti_copy->imported_by = getStringCopy(ti->imported_by);
2810 ti_copy->tested_by = getStringCopy(ti->tested_by);
2812 ti_copy->graphics_set_ecs = getStringCopy(ti->graphics_set_ecs);
2813 ti_copy->graphics_set_aga = getStringCopy(ti->graphics_set_aga);
2814 ti_copy->graphics_set = getStringCopy(ti->graphics_set);
2815 ti_copy->sounds_set_default = getStringCopy(ti->sounds_set_default);
2816 ti_copy->sounds_set_lowpass = getStringCopy(ti->sounds_set_lowpass);
2817 ti_copy->sounds_set = getStringCopy(ti->sounds_set);
2818 ti_copy->music_set = getStringCopy(ti->music_set);
2819 ti_copy->graphics_path = getStringCopy(ti->graphics_path);
2820 ti_copy->sounds_path = getStringCopy(ti->sounds_path);
2821 ti_copy->music_path = getStringCopy(ti->music_path);
2823 ti_copy->level_filename = getStringCopy(ti->level_filename);
2824 ti_copy->level_filetype = getStringCopy(ti->level_filetype);
2826 ti_copy->special_flags = getStringCopy(ti->special_flags);
2828 ti_copy->levels = ti->levels;
2829 ti_copy->first_level = ti->first_level;
2830 ti_copy->last_level = ti->last_level;
2831 ti_copy->sort_priority = ti->sort_priority;
2833 ti_copy->latest_engine = ti->latest_engine;
2835 ti_copy->level_group = ti->level_group;
2836 ti_copy->parent_link = ti->parent_link;
2837 ti_copy->is_copy = ti->is_copy;
2838 ti_copy->in_user_dir = ti->in_user_dir;
2839 ti_copy->user_defined = ti->user_defined;
2840 ti_copy->readonly = ti->readonly;
2841 ti_copy->handicap = ti->handicap;
2842 ti_copy->skip_levels = ti->skip_levels;
2844 ti_copy->use_emc_tiles = ti->use_emc_tiles;
2846 ti_copy->color = ti->color;
2847 ti_copy->class_desc = getStringCopy(ti->class_desc);
2848 ti_copy->handicap_level = ti->handicap_level;
2850 ti_copy->infotext = getStringCopy(ti->infotext);
2855 void freeTreeInfo(TreeInfo *ti)
2860 checked_free(ti->subdir);
2861 checked_free(ti->fullpath);
2862 checked_free(ti->basepath);
2863 checked_free(ti->identifier);
2865 checked_free(ti->name);
2866 checked_free(ti->name_sorting);
2867 checked_free(ti->author);
2868 checked_free(ti->year);
2870 checked_free(ti->program_title);
2871 checked_free(ti->program_copyright);
2872 checked_free(ti->program_company);
2874 checked_free(ti->class_desc);
2876 checked_free(ti->infotext);
2878 if (ti->type == TREE_TYPE_LEVEL_DIR)
2880 checked_free(ti->imported_from);
2881 checked_free(ti->imported_by);
2882 checked_free(ti->tested_by);
2884 checked_free(ti->graphics_set_ecs);
2885 checked_free(ti->graphics_set_aga);
2886 checked_free(ti->graphics_set);
2887 checked_free(ti->sounds_set_default);
2888 checked_free(ti->sounds_set_lowpass);
2889 checked_free(ti->sounds_set);
2890 checked_free(ti->music_set);
2892 checked_free(ti->graphics_path);
2893 checked_free(ti->sounds_path);
2894 checked_free(ti->music_path);
2896 checked_free(ti->level_filename);
2897 checked_free(ti->level_filetype);
2899 checked_free(ti->special_flags);
2902 // recursively free child node
2904 freeTreeInfo(ti->node_group);
2906 // recursively free next node
2908 freeTreeInfo(ti->next);
2913 void setSetupInfo(struct TokenInfo *token_info,
2914 int token_nr, char *token_value)
2916 int token_type = token_info[token_nr].type;
2917 void *setup_value = token_info[token_nr].value;
2919 if (token_value == NULL)
2922 // set setup field to corresponding token value
2927 *(boolean *)setup_value = get_boolean_from_string(token_value);
2931 *(int *)setup_value = get_switch3_from_string(token_value);
2935 *(Key *)setup_value = getKeyFromKeyName(token_value);
2939 *(Key *)setup_value = getKeyFromX11KeyName(token_value);
2943 *(int *)setup_value = get_integer_from_string(token_value);
2947 checked_free(*(char **)setup_value);
2948 *(char **)setup_value = getStringCopy(token_value);
2952 *(int *)setup_value = get_player_nr_from_string(token_value);
2960 static int compareTreeInfoEntries(const void *object1, const void *object2)
2962 const TreeInfo *entry1 = *((TreeInfo **)object1);
2963 const TreeInfo *entry2 = *((TreeInfo **)object2);
2964 int tree_sorting1 = TREE_SORTING(entry1);
2965 int tree_sorting2 = TREE_SORTING(entry2);
2967 if (tree_sorting1 != tree_sorting2)
2968 return (tree_sorting1 - tree_sorting2);
2970 return strcasecmp(entry1->name_sorting, entry2->name_sorting);
2973 static TreeInfo *createParentTreeInfoNode(TreeInfo *node_parent)
2977 if (node_parent == NULL)
2980 ti_new = newTreeInfo();
2981 setTreeInfoToDefaults(ti_new, node_parent->type);
2983 ti_new->node_parent = node_parent;
2984 ti_new->parent_link = TRUE;
2986 setString(&ti_new->identifier, node_parent->identifier);
2987 setString(&ti_new->name, BACKLINK_TEXT_PARENT);
2988 setString(&ti_new->name_sorting, ti_new->name);
2990 setString(&ti_new->subdir, STRING_PARENT_DIRECTORY);
2991 setString(&ti_new->fullpath, node_parent->fullpath);
2993 ti_new->sort_priority = LEVELCLASS_PARENT;
2994 ti_new->latest_engine = node_parent->latest_engine;
2996 setString(&ti_new->class_desc, getLevelClassDescription(ti_new));
2998 pushTreeInfo(&node_parent->node_group, ti_new);
3003 static TreeInfo *createTopTreeInfoNode(TreeInfo *node_first)
3005 if (node_first == NULL)
3008 TreeInfo *ti_new = newTreeInfo();
3009 int type = node_first->type;
3011 setTreeInfoToDefaults(ti_new, type);
3013 ti_new->node_parent = NULL;
3014 ti_new->parent_link = FALSE;
3016 setString(&ti_new->identifier, "top_tree_node");
3017 setString(&ti_new->name, TREE_INFOTEXT(type));
3018 setString(&ti_new->name_sorting, ti_new->name);
3020 setString(&ti_new->subdir, STRING_TOP_DIRECTORY);
3021 setString(&ti_new->fullpath, ".");
3023 ti_new->sort_priority = LEVELCLASS_TOP;
3024 ti_new->latest_engine = node_first->latest_engine;
3026 setString(&ti_new->class_desc, TREE_INFOTEXT(type));
3028 ti_new->node_group = node_first;
3029 ti_new->level_group = TRUE;
3031 TreeInfo *ti_new2 = createParentTreeInfoNode(ti_new);
3033 setString(&ti_new2->name, TREE_BACKLINK_TEXT(type));
3034 setString(&ti_new2->name_sorting, ti_new2->name);
3039 static void setTreeInfoParentNodes(TreeInfo *node, TreeInfo *node_parent)
3043 if (node->node_group)
3044 setTreeInfoParentNodes(node->node_group, node);
3046 node->node_parent = node_parent;
3053 // ----------------------------------------------------------------------------
3054 // functions for handling level and custom artwork info cache
3055 // ----------------------------------------------------------------------------
3057 static void LoadArtworkInfoCache(void)
3059 InitCacheDirectory();
3061 if (artworkinfo_cache_old == NULL)
3063 char *filename = getPath2(getCacheDir(), ARTWORKINFO_CACHE_FILE);
3065 // try to load artwork info hash from already existing cache file
3066 artworkinfo_cache_old = loadSetupFileHash(filename);
3068 // try to get program version that artwork info cache was written with
3069 char *version = getHashEntry(artworkinfo_cache_old, "program.version");
3071 // check program version of artwork info cache against current version
3072 if (!strEqual(version, program.version_string))
3074 freeSetupFileHash(artworkinfo_cache_old);
3076 artworkinfo_cache_old = NULL;
3079 // if no artwork info cache file was found, start with empty hash
3080 if (artworkinfo_cache_old == NULL)
3081 artworkinfo_cache_old = newSetupFileHash();
3086 if (artworkinfo_cache_new == NULL)
3087 artworkinfo_cache_new = newSetupFileHash();
3089 update_artworkinfo_cache = FALSE;
3092 static void SaveArtworkInfoCache(void)
3094 if (!update_artworkinfo_cache)
3097 char *filename = getPath2(getCacheDir(), ARTWORKINFO_CACHE_FILE);
3099 InitCacheDirectory();
3101 saveSetupFileHash(artworkinfo_cache_new, filename);
3106 static char *getCacheTokenPrefix(char *prefix1, char *prefix2)
3108 static char *prefix = NULL;
3110 checked_free(prefix);
3112 prefix = getStringCat2WithSeparator(prefix1, prefix2, ".");
3117 // (identical to above function, but separate string buffer needed -- nasty)
3118 static char *getCacheToken(char *prefix, char *suffix)
3120 static char *token = NULL;
3122 checked_free(token);
3124 token = getStringCat2WithSeparator(prefix, suffix, ".");
3129 static char *getFileTimestampString(char *filename)
3131 return getStringCopy(i_to_a(getFileTimestampEpochSeconds(filename)));
3134 static boolean modifiedFileTimestamp(char *filename, char *timestamp_string)
3136 struct stat file_status;
3138 if (timestamp_string == NULL)
3141 if (!fileExists(filename)) // file does not exist
3142 return (atoi(timestamp_string) != 0);
3144 if (stat(filename, &file_status) != 0) // cannot stat file
3147 return (file_status.st_mtime != atoi(timestamp_string));
3150 static TreeInfo *getArtworkInfoCacheEntry(LevelDirTree *level_node, int type)
3152 char *identifier = level_node->subdir;
3153 char *type_string = ARTWORK_DIRECTORY(type);
3154 char *token_prefix = getCacheTokenPrefix(type_string, identifier);
3155 char *token_main = getCacheToken(token_prefix, "CACHED");
3156 char *cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
3157 boolean cached = (cache_entry != NULL && strEqual(cache_entry, "true"));
3158 TreeInfo *artwork_info = NULL;
3160 if (!use_artworkinfo_cache)
3163 if (optional_tokens_hash == NULL)
3167 // create hash from list of optional tokens (for quick access)
3168 optional_tokens_hash = newSetupFileHash();
3169 for (i = 0; optional_tokens[i] != NULL; i++)
3170 setHashEntry(optional_tokens_hash, optional_tokens[i], "");
3177 artwork_info = newTreeInfo();
3178 setTreeInfoToDefaults(artwork_info, type);
3180 // set all structure fields according to the token/value pairs
3181 ldi = *artwork_info;
3182 for (i = 0; artworkinfo_tokens[i].type != -1; i++)
3184 char *token_suffix = artworkinfo_tokens[i].text;
3185 char *token = getCacheToken(token_prefix, token_suffix);
3186 char *value = getHashEntry(artworkinfo_cache_old, token);
3188 (getHashEntry(optional_tokens_hash, token_suffix) != NULL);
3190 setSetupInfo(artworkinfo_tokens, i, value);
3192 // check if cache entry for this item is mandatory, but missing
3193 if (value == NULL && !optional)
3195 Warn("missing cache entry '%s'", token);
3201 *artwork_info = ldi;
3206 char *filename_levelinfo = getPath2(getLevelDirFromTreeInfo(level_node),
3207 LEVELINFO_FILENAME);
3208 char *filename_artworkinfo = getPath2(getSetupArtworkDir(artwork_info),
3209 ARTWORKINFO_FILENAME(type));
3211 // check if corresponding "levelinfo.conf" file has changed
3212 token_main = getCacheToken(token_prefix, "TIMESTAMP_LEVELINFO");
3213 cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
3215 if (modifiedFileTimestamp(filename_levelinfo, cache_entry))
3218 // check if corresponding "<artworkinfo>.conf" file has changed
3219 token_main = getCacheToken(token_prefix, "TIMESTAMP_ARTWORKINFO");
3220 cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
3222 if (modifiedFileTimestamp(filename_artworkinfo, cache_entry))
3225 checked_free(filename_levelinfo);
3226 checked_free(filename_artworkinfo);
3229 if (!cached && artwork_info != NULL)
3231 freeTreeInfo(artwork_info);
3236 return artwork_info;
3239 static void setArtworkInfoCacheEntry(TreeInfo *artwork_info,
3240 LevelDirTree *level_node, int type)
3242 char *identifier = level_node->subdir;
3243 char *type_string = ARTWORK_DIRECTORY(type);
3244 char *token_prefix = getCacheTokenPrefix(type_string, identifier);
3245 char *token_main = getCacheToken(token_prefix, "CACHED");
3246 boolean set_cache_timestamps = TRUE;
3249 setHashEntry(artworkinfo_cache_new, token_main, "true");
3251 if (set_cache_timestamps)
3253 char *filename_levelinfo = getPath2(getLevelDirFromTreeInfo(level_node),
3254 LEVELINFO_FILENAME);
3255 char *filename_artworkinfo = getPath2(getSetupArtworkDir(artwork_info),
3256 ARTWORKINFO_FILENAME(type));
3257 char *timestamp_levelinfo = getFileTimestampString(filename_levelinfo);
3258 char *timestamp_artworkinfo = getFileTimestampString(filename_artworkinfo);
3260 token_main = getCacheToken(token_prefix, "TIMESTAMP_LEVELINFO");
3261 setHashEntry(artworkinfo_cache_new, token_main, timestamp_levelinfo);
3263 token_main = getCacheToken(token_prefix, "TIMESTAMP_ARTWORKINFO");
3264 setHashEntry(artworkinfo_cache_new, token_main, timestamp_artworkinfo);
3266 checked_free(filename_levelinfo);
3267 checked_free(filename_artworkinfo);
3268 checked_free(timestamp_levelinfo);
3269 checked_free(timestamp_artworkinfo);
3272 ldi = *artwork_info;
3273 for (i = 0; artworkinfo_tokens[i].type != -1; i++)
3275 char *token = getCacheToken(token_prefix, artworkinfo_tokens[i].text);
3276 char *value = getSetupValue(artworkinfo_tokens[i].type,
3277 artworkinfo_tokens[i].value);
3279 setHashEntry(artworkinfo_cache_new, token, value);
3284 // ----------------------------------------------------------------------------
3285 // functions for loading level info and custom artwork info
3286 // ----------------------------------------------------------------------------
3288 int GetZipFileTreeType(char *zip_filename)
3290 static char *top_dir_path = NULL;
3291 static char *top_dir_conf_filename[NUM_BASE_TREE_TYPES] = { NULL };
3292 static char *conf_basename[NUM_BASE_TREE_TYPES] =
3294 GRAPHICSINFO_FILENAME,
3295 SOUNDSINFO_FILENAME,
3301 checked_free(top_dir_path);
3302 top_dir_path = NULL;
3304 for (j = 0; j < NUM_BASE_TREE_TYPES; j++)
3306 checked_free(top_dir_conf_filename[j]);
3307 top_dir_conf_filename[j] = NULL;
3310 char **zip_entries = zip_list(zip_filename);
3312 // check if zip file successfully opened
3313 if (zip_entries == NULL || zip_entries[0] == NULL)
3314 return TREE_TYPE_UNDEFINED;
3316 // first zip file entry is expected to be top level directory
3317 char *top_dir = zip_entries[0];
3319 // check if valid top level directory found in zip file
3320 if (!strSuffix(top_dir, "/"))
3321 return TREE_TYPE_UNDEFINED;
3323 // get filenames of valid configuration files in top level directory
3324 for (j = 0; j < NUM_BASE_TREE_TYPES; j++)
3325 top_dir_conf_filename[j] = getStringCat2(top_dir, conf_basename[j]);
3327 int tree_type = TREE_TYPE_UNDEFINED;
3330 while (zip_entries[e] != NULL)
3332 // check if every zip file entry is below top level directory
3333 if (!strPrefix(zip_entries[e], top_dir))
3334 return TREE_TYPE_UNDEFINED;
3336 // check if this zip file entry is a valid configuration filename
3337 for (j = 0; j < NUM_BASE_TREE_TYPES; j++)
3339 if (strEqual(zip_entries[e], top_dir_conf_filename[j]))
3341 // only exactly one valid configuration file allowed
3342 if (tree_type != TREE_TYPE_UNDEFINED)
3343 return TREE_TYPE_UNDEFINED;
3355 static boolean CheckZipFileForDirectory(char *zip_filename, char *directory,
3358 static char *top_dir_path = NULL;
3359 static char *top_dir_conf_filename = NULL;
3361 checked_free(top_dir_path);
3362 checked_free(top_dir_conf_filename);
3364 top_dir_path = NULL;
3365 top_dir_conf_filename = NULL;
3367 char *conf_basename = (tree_type == TREE_TYPE_LEVEL_DIR ? LEVELINFO_FILENAME :
3368 ARTWORKINFO_FILENAME(tree_type));
3370 // check if valid configuration filename determined
3371 if (conf_basename == NULL || strEqual(conf_basename, ""))
3374 char **zip_entries = zip_list(zip_filename);
3376 // check if zip file successfully opened
3377 if (zip_entries == NULL || zip_entries[0] == NULL)
3380 // first zip file entry is expected to be top level directory
3381 char *top_dir = zip_entries[0];
3383 // check if valid top level directory found in zip file
3384 if (!strSuffix(top_dir, "/"))
3387 // get path of extracted top level directory
3388 top_dir_path = getPath2(directory, top_dir);
3390 // remove trailing directory separator from top level directory path
3391 // (required to be able to check for file and directory in next step)
3392 top_dir_path[strlen(top_dir_path) - 1] = '\0';
3394 // check if zip file's top level directory already exists in target directory
3395 if (fileExists(top_dir_path)) // (checks for file and directory)
3398 // get filename of configuration file in top level directory
3399 top_dir_conf_filename = getStringCat2(top_dir, conf_basename);
3401 boolean found_top_dir_conf_filename = FALSE;
3404 while (zip_entries[i] != NULL)
3406 // check if every zip file entry is below top level directory
3407 if (!strPrefix(zip_entries[i], top_dir))
3410 // check if this zip file entry is the configuration filename
3411 if (strEqual(zip_entries[i], top_dir_conf_filename))
3412 found_top_dir_conf_filename = TRUE;
3417 // check if valid configuration filename was found in zip file
3418 if (!found_top_dir_conf_filename)
3424 char *ExtractZipFileIntoDirectory(char *zip_filename, char *directory,
3427 boolean zip_file_valid = CheckZipFileForDirectory(zip_filename, directory,
3430 if (!zip_file_valid)
3432 Warn("zip file '%s' rejected!", zip_filename);
3437 char **zip_entries = zip_extract(zip_filename, directory);
3439 if (zip_entries == NULL)
3441 Warn("zip file '%s' could not be extracted!", zip_filename);
3446 Info("zip file '%s' successfully extracted!", zip_filename);
3448 // first zip file entry contains top level directory
3449 char *top_dir = zip_entries[0];
3451 // remove trailing directory separator from top level directory
3452 top_dir[strlen(top_dir) - 1] = '\0';
3457 static void ProcessZipFilesInDirectory(char *directory, int tree_type)
3460 DirectoryEntry *dir_entry;
3462 if ((dir = openDirectory(directory)) == NULL)
3464 // display error if directory is main "options.graphics_directory" etc.
3465 if (tree_type == TREE_TYPE_LEVEL_DIR ||
3466 directory == OPTIONS_ARTWORK_DIRECTORY(tree_type))
3467 Warn("cannot read directory '%s'", directory);
3472 while ((dir_entry = readDirectory(dir)) != NULL) // loop all entries
3474 // skip non-zip files (and also directories with zip extension)
3475 if (!strSuffixLower(dir_entry->basename, ".zip") || dir_entry->is_directory)
3478 char *zip_filename = getPath2(directory, dir_entry->basename);
3479 char *zip_filename_extracted = getStringCat2(zip_filename, ".extracted");
3480 char *zip_filename_rejected = getStringCat2(zip_filename, ".rejected");
3482 // check if zip file hasn't already been extracted or rejected
3483 if (!fileExists(zip_filename_extracted) &&
3484 !fileExists(zip_filename_rejected))
3486 char *top_dir = ExtractZipFileIntoDirectory(zip_filename, directory,
3488 char *marker_filename = (top_dir != NULL ? zip_filename_extracted :
3489 zip_filename_rejected);
3492 // create empty file to mark zip file as extracted or rejected
3493 if ((marker_file = fopen(marker_filename, MODE_WRITE)))
3494 fclose(marker_file);
3497 free(zip_filename_extracted);
3498 free(zip_filename_rejected);
3502 closeDirectory(dir);
3505 // forward declaration for recursive call by "LoadLevelInfoFromLevelDir()"
3506 static void LoadLevelInfoFromLevelDir(TreeInfo **, TreeInfo *, char *);
3508 static boolean LoadLevelInfoFromLevelConf(TreeInfo **node_first,
3509 TreeInfo *node_parent,
3510 char *level_directory,
3511 char *directory_name)
3513 char *directory_path = getPath2(level_directory, directory_name);
3514 char *filename = getPath2(directory_path, LEVELINFO_FILENAME);
3515 SetupFileHash *setup_file_hash;
3516 LevelDirTree *leveldir_new = NULL;
3519 // unless debugging, silently ignore directories without "levelinfo.conf"
3520 if (!options.debug && !fileExists(filename))
3522 free(directory_path);
3528 setup_file_hash = loadSetupFileHash(filename);
3530 if (setup_file_hash == NULL)
3532 #if DEBUG_NO_CONFIG_FILE
3533 Debug("setup", "ignoring level directory '%s'", directory_path);
3536 free(directory_path);
3542 leveldir_new = newTreeInfo();
3545 setTreeInfoToDefaultsFromParent(leveldir_new, node_parent);
3547 setTreeInfoToDefaults(leveldir_new, TREE_TYPE_LEVEL_DIR);
3549 leveldir_new->subdir = getStringCopy(directory_name);
3551 // set all structure fields according to the token/value pairs
3552 ldi = *leveldir_new;
3553 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
3554 setSetupInfo(levelinfo_tokens, i,
3555 getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
3556 *leveldir_new = ldi;
3558 if (strEqual(leveldir_new->name, ANONYMOUS_NAME))
3559 setString(&leveldir_new->name, leveldir_new->subdir);
3561 if (leveldir_new->identifier == NULL)
3562 leveldir_new->identifier = getStringCopy(leveldir_new->subdir);
3564 if (leveldir_new->name_sorting == NULL)
3565 leveldir_new->name_sorting = getStringCopy(leveldir_new->name);
3567 if (node_parent == NULL) // top level group
3569 leveldir_new->basepath = getStringCopy(level_directory);
3570 leveldir_new->fullpath = getStringCopy(leveldir_new->subdir);
3572 else // sub level group
3574 leveldir_new->basepath = getStringCopy(node_parent->basepath);
3575 leveldir_new->fullpath = getPath2(node_parent->fullpath, directory_name);
3578 leveldir_new->last_level =
3579 leveldir_new->first_level + leveldir_new->levels - 1;
3581 leveldir_new->in_user_dir =
3582 (!strEqual(leveldir_new->basepath, options.level_directory));
3584 // adjust some settings if user's private level directory was detected
3585 if (leveldir_new->sort_priority == LEVELCLASS_UNDEFINED &&
3586 leveldir_new->in_user_dir &&
3587 (strEqual(leveldir_new->subdir, getLoginName()) ||
3588 strEqual(leveldir_new->name, getLoginName()) ||
3589 strEqual(leveldir_new->author, getRealName())))
3591 leveldir_new->sort_priority = LEVELCLASS_PRIVATE_START;
3592 leveldir_new->readonly = FALSE;
3595 leveldir_new->user_defined =
3596 (leveldir_new->in_user_dir && IS_LEVELCLASS_PRIVATE(leveldir_new));
3598 setString(&leveldir_new->class_desc, getLevelClassDescription(leveldir_new));
3600 leveldir_new->handicap_level = // set handicap to default value
3601 (leveldir_new->user_defined || !leveldir_new->handicap ?
3602 leveldir_new->last_level : leveldir_new->first_level);
3604 DrawInitText(leveldir_new->name, 150, FC_YELLOW);
3606 pushTreeInfo(node_first, leveldir_new);
3608 freeSetupFileHash(setup_file_hash);
3610 if (leveldir_new->level_group)
3612 // create node to link back to current level directory
3613 createParentTreeInfoNode(leveldir_new);
3615 // recursively step into sub-directory and look for more level series
3616 LoadLevelInfoFromLevelDir(&leveldir_new->node_group,
3617 leveldir_new, directory_path);
3620 free(directory_path);
3626 static void LoadLevelInfoFromLevelDir(TreeInfo **node_first,
3627 TreeInfo *node_parent,
3628 char *level_directory)
3630 // ---------- 1st stage: process any level set zip files ----------
3632 ProcessZipFilesInDirectory(level_directory, TREE_TYPE_LEVEL_DIR);
3634 // ---------- 2nd stage: check for level set directories ----------
3637 DirectoryEntry *dir_entry;
3638 boolean valid_entry_found = FALSE;
3640 if ((dir = openDirectory(level_directory)) == NULL)
3642 Warn("cannot read level directory '%s'", level_directory);
3647 while ((dir_entry = readDirectory(dir)) != NULL) // loop all entries
3649 char *directory_name = dir_entry->basename;
3650 char *directory_path = getPath2(level_directory, directory_name);
3652 // skip entries for current and parent directory
3653 if (strEqual(directory_name, ".") ||
3654 strEqual(directory_name, ".."))
3656 free(directory_path);
3661 // find out if directory entry is itself a directory
3662 if (!dir_entry->is_directory) // not a directory
3664 free(directory_path);
3669 free(directory_path);
3671 if (strEqual(directory_name, GRAPHICS_DIRECTORY) ||
3672 strEqual(directory_name, SOUNDS_DIRECTORY) ||
3673 strEqual(directory_name, MUSIC_DIRECTORY))
3676 valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
3681 closeDirectory(dir);
3683 // special case: top level directory may directly contain "levelinfo.conf"
3684 if (node_parent == NULL && !valid_entry_found)
3686 // check if this directory directly contains a file "levelinfo.conf"
3687 valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
3688 level_directory, ".");
3691 if (!valid_entry_found)
3692 Warn("cannot find any valid level series in directory '%s'",
3696 boolean AdjustGraphicsForEMC(void)
3698 boolean settings_changed = FALSE;
3700 settings_changed |= adjustTreeGraphicsForEMC(leveldir_first_all);
3701 settings_changed |= adjustTreeGraphicsForEMC(leveldir_first);
3703 return settings_changed;
3706 boolean AdjustSoundsForEMC(void)
3708 boolean settings_changed = FALSE;
3710 settings_changed |= adjustTreeSoundsForEMC(leveldir_first_all);
3711 settings_changed |= adjustTreeSoundsForEMC(leveldir_first);
3713 return settings_changed;
3716 void LoadLevelInfo(void)
3718 InitUserLevelDirectory(getLoginName());
3720 DrawInitText("Loading level series", 120, FC_GREEN);
3722 LoadLevelInfoFromLevelDir(&leveldir_first, NULL, options.level_directory);
3723 LoadLevelInfoFromLevelDir(&leveldir_first, NULL, getUserLevelDir(NULL));
3725 leveldir_first = createTopTreeInfoNode(leveldir_first);
3727 /* after loading all level set information, clone the level directory tree
3728 and remove all level sets without levels (these may still contain artwork
3729 to be offered in the setup menu as "custom artwork", and are therefore
3730 checked for existing artwork in the function "LoadLevelArtworkInfo()") */
3731 leveldir_first_all = leveldir_first;
3732 cloneTree(&leveldir_first, leveldir_first_all, TRUE);
3734 AdjustGraphicsForEMC();
3735 AdjustSoundsForEMC();
3737 // before sorting, the first entries will be from the user directory
3738 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
3740 if (leveldir_first == NULL)
3741 Fail("cannot find any valid level series in any directory");
3743 sortTreeInfo(&leveldir_first);
3745 #if ENABLE_UNUSED_CODE
3746 dumpTreeInfo(leveldir_first, 0);
3750 static boolean LoadArtworkInfoFromArtworkConf(TreeInfo **node_first,
3751 TreeInfo *node_parent,
3752 char *base_directory,
3753 char *directory_name, int type)
3755 char *directory_path = getPath2(base_directory, directory_name);
3756 char *filename = getPath2(directory_path, ARTWORKINFO_FILENAME(type));
3757 SetupFileHash *setup_file_hash = NULL;
3758 TreeInfo *artwork_new = NULL;
3761 if (fileExists(filename))
3762 setup_file_hash = loadSetupFileHash(filename);
3764 if (setup_file_hash == NULL) // no config file -- look for artwork files
3767 DirectoryEntry *dir_entry;
3768 boolean valid_file_found = FALSE;
3770 if ((dir = openDirectory(directory_path)) != NULL)
3772 while ((dir_entry = readDirectory(dir)) != NULL)
3774 if (FileIsArtworkType(dir_entry->filename, type))
3776 valid_file_found = TRUE;
3782 closeDirectory(dir);
3785 if (!valid_file_found)
3787 #if DEBUG_NO_CONFIG_FILE
3788 if (!strEqual(directory_name, "."))
3789 Debug("setup", "ignoring artwork directory '%s'", directory_path);
3792 free(directory_path);
3799 artwork_new = newTreeInfo();
3802 setTreeInfoToDefaultsFromParent(artwork_new, node_parent);
3804 setTreeInfoToDefaults(artwork_new, type);
3806 artwork_new->subdir = getStringCopy(directory_name);
3808 if (setup_file_hash) // (before defining ".color" and ".class_desc")
3810 // set all structure fields according to the token/value pairs
3812 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
3813 setSetupInfo(levelinfo_tokens, i,
3814 getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
3817 if (strEqual(artwork_new->name, ANONYMOUS_NAME))
3818 setString(&artwork_new->name, artwork_new->subdir);
3820 if (artwork_new->identifier == NULL)
3821 artwork_new->identifier = getStringCopy(artwork_new->subdir);
3823 if (artwork_new->name_sorting == NULL)
3824 artwork_new->name_sorting = getStringCopy(artwork_new->name);
3827 if (node_parent == NULL) // top level group
3829 artwork_new->basepath = getStringCopy(base_directory);
3830 artwork_new->fullpath = getStringCopy(artwork_new->subdir);
3832 else // sub level group
3834 artwork_new->basepath = getStringCopy(node_parent->basepath);
3835 artwork_new->fullpath = getPath2(node_parent->fullpath, directory_name);
3838 artwork_new->in_user_dir =
3839 (!strEqual(artwork_new->basepath, OPTIONS_ARTWORK_DIRECTORY(type)));
3841 setString(&artwork_new->class_desc, getLevelClassDescription(artwork_new));
3843 if (setup_file_hash == NULL) // (after determining ".user_defined")
3845 if (strEqual(artwork_new->subdir, "."))
3847 if (artwork_new->user_defined)
3849 setString(&artwork_new->identifier, "private");
3850 artwork_new->sort_priority = ARTWORKCLASS_PRIVATE;
3854 setString(&artwork_new->identifier, "classic");
3855 artwork_new->sort_priority = ARTWORKCLASS_CLASSICS;
3858 setString(&artwork_new->class_desc,
3859 getLevelClassDescription(artwork_new));
3863 setString(&artwork_new->identifier, artwork_new->subdir);
3866 setString(&artwork_new->name, artwork_new->identifier);
3867 setString(&artwork_new->name_sorting, artwork_new->name);
3870 pushTreeInfo(node_first, artwork_new);
3872 freeSetupFileHash(setup_file_hash);
3874 free(directory_path);
3880 static void LoadArtworkInfoFromArtworkDir(TreeInfo **node_first,
3881 TreeInfo *node_parent,
3882 char *base_directory, int type)
3884 // ---------- 1st stage: process any artwork set zip files ----------
3886 ProcessZipFilesInDirectory(base_directory, type);
3888 // ---------- 2nd stage: check for artwork set directories ----------
3891 DirectoryEntry *dir_entry;
3892 boolean valid_entry_found = FALSE;
3894 if ((dir = openDirectory(base_directory)) == NULL)
3896 // display error if directory is main "options.graphics_directory" etc.
3897 if (base_directory == OPTIONS_ARTWORK_DIRECTORY(type))
3898 Warn("cannot read directory '%s'", base_directory);
3903 while ((dir_entry = readDirectory(dir)) != NULL) // loop all entries
3905 char *directory_name = dir_entry->basename;
3906 char *directory_path = getPath2(base_directory, directory_name);
3908 // skip directory entries for current and parent directory
3909 if (strEqual(directory_name, ".") ||
3910 strEqual(directory_name, ".."))
3912 free(directory_path);
3917 // skip directory entries which are not a directory
3918 if (!dir_entry->is_directory) // not a directory
3920 free(directory_path);
3925 free(directory_path);
3927 // check if this directory contains artwork with or without config file
3928 valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
3930 directory_name, type);
3933 closeDirectory(dir);
3935 // check if this directory directly contains artwork itself
3936 valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
3937 base_directory, ".",
3939 if (!valid_entry_found)
3940 Warn("cannot find any valid artwork in directory '%s'", base_directory);
3943 static TreeInfo *getDummyArtworkInfo(int type)
3945 // this is only needed when there is completely no artwork available
3946 TreeInfo *artwork_new = newTreeInfo();
3948 setTreeInfoToDefaults(artwork_new, type);
3950 setString(&artwork_new->subdir, UNDEFINED_FILENAME);
3951 setString(&artwork_new->fullpath, UNDEFINED_FILENAME);
3952 setString(&artwork_new->basepath, UNDEFINED_FILENAME);
3954 setString(&artwork_new->identifier, UNDEFINED_FILENAME);
3955 setString(&artwork_new->name, UNDEFINED_FILENAME);
3956 setString(&artwork_new->name_sorting, UNDEFINED_FILENAME);
3961 void SetCurrentArtwork(int type)
3963 ArtworkDirTree **current_ptr = ARTWORK_CURRENT_PTR(artwork, type);
3964 ArtworkDirTree *first_node = ARTWORK_FIRST_NODE(artwork, type);
3965 char *setup_set = SETUP_ARTWORK_SET(setup, type);
3966 char *default_subdir = ARTWORK_DEFAULT_SUBDIR(type);
3968 // set current artwork to artwork configured in setup menu
3969 *current_ptr = getTreeInfoFromIdentifier(first_node, setup_set);
3971 // if not found, set current artwork to default artwork
3972 if (*current_ptr == NULL)
3973 *current_ptr = getTreeInfoFromIdentifier(first_node, default_subdir);
3975 // if not found, set current artwork to first artwork in tree
3976 if (*current_ptr == NULL)
3977 *current_ptr = getFirstValidTreeInfoEntry(first_node);
3980 void ChangeCurrentArtworkIfNeeded(int type)
3982 char *current_identifier = ARTWORK_CURRENT_IDENTIFIER(artwork, type);
3983 char *setup_set = SETUP_ARTWORK_SET(setup, type);
3985 if (!strEqual(current_identifier, setup_set))
3986 SetCurrentArtwork(type);
3989 void LoadArtworkInfo(void)
3991 LoadArtworkInfoCache();
3993 DrawInitText("Looking for custom artwork", 120, FC_GREEN);
3995 LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
3996 options.graphics_directory,
3997 TREE_TYPE_GRAPHICS_DIR);
3998 LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
3999 getUserGraphicsDir(),
4000 TREE_TYPE_GRAPHICS_DIR);
4002 LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
4003 options.sounds_directory,
4004 TREE_TYPE_SOUNDS_DIR);
4005 LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
4007 TREE_TYPE_SOUNDS_DIR);
4009 LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
4010 options.music_directory,
4011 TREE_TYPE_MUSIC_DIR);
4012 LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
4014 TREE_TYPE_MUSIC_DIR);
4016 if (artwork.gfx_first == NULL)
4017 artwork.gfx_first = getDummyArtworkInfo(TREE_TYPE_GRAPHICS_DIR);
4018 if (artwork.snd_first == NULL)
4019 artwork.snd_first = getDummyArtworkInfo(TREE_TYPE_SOUNDS_DIR);
4020 if (artwork.mus_first == NULL)
4021 artwork.mus_first = getDummyArtworkInfo(TREE_TYPE_MUSIC_DIR);
4023 // before sorting, the first entries will be from the user directory
4024 SetCurrentArtwork(ARTWORK_TYPE_GRAPHICS);
4025 SetCurrentArtwork(ARTWORK_TYPE_SOUNDS);
4026 SetCurrentArtwork(ARTWORK_TYPE_MUSIC);
4028 artwork.gfx_current_identifier = artwork.gfx_current->identifier;
4029 artwork.snd_current_identifier = artwork.snd_current->identifier;
4030 artwork.mus_current_identifier = artwork.mus_current->identifier;
4032 #if ENABLE_UNUSED_CODE
4033 Debug("setup:LoadArtworkInfo", "graphics set == %s",
4034 artwork.gfx_current_identifier);
4035 Debug("setup:LoadArtworkInfo", "sounds set == %s",
4036 artwork.snd_current_identifier);
4037 Debug("setup:LoadArtworkInfo", "music set == %s",
4038 artwork.mus_current_identifier);
4041 sortTreeInfo(&artwork.gfx_first);
4042 sortTreeInfo(&artwork.snd_first);
4043 sortTreeInfo(&artwork.mus_first);
4045 #if ENABLE_UNUSED_CODE
4046 dumpTreeInfo(artwork.gfx_first, 0);
4047 dumpTreeInfo(artwork.snd_first, 0);
4048 dumpTreeInfo(artwork.mus_first, 0);
4052 static void MoveArtworkInfoIntoSubTree(ArtworkDirTree **artwork_node)
4054 ArtworkDirTree *artwork_new = newTreeInfo();
4055 char *top_node_name = "standalone artwork";
4057 setTreeInfoToDefaults(artwork_new, (*artwork_node)->type);
4059 artwork_new->level_group = TRUE;
4061 setString(&artwork_new->identifier, top_node_name);
4062 setString(&artwork_new->name, top_node_name);
4063 setString(&artwork_new->name_sorting, top_node_name);
4065 // create node to link back to current custom artwork directory
4066 createParentTreeInfoNode(artwork_new);
4068 // move existing custom artwork tree into newly created sub-tree
4069 artwork_new->node_group->next = *artwork_node;
4071 // change custom artwork tree to contain only newly created node
4072 *artwork_node = artwork_new;
4075 static void LoadArtworkInfoFromLevelInfoExt(ArtworkDirTree **artwork_node,
4076 ArtworkDirTree *node_parent,
4077 LevelDirTree *level_node,
4078 boolean empty_level_set_mode)
4080 int type = (*artwork_node)->type;
4082 // recursively check all level directories for artwork sub-directories
4086 boolean empty_level_set = (level_node->levels == 0);
4088 // check all tree entries for artwork, but skip parent link entries
4089 if (!level_node->parent_link && empty_level_set == empty_level_set_mode)
4091 TreeInfo *artwork_new = getArtworkInfoCacheEntry(level_node, type);
4092 boolean cached = (artwork_new != NULL);
4096 pushTreeInfo(artwork_node, artwork_new);
4100 TreeInfo *topnode_last = *artwork_node;
4101 char *path = getPath2(getLevelDirFromTreeInfo(level_node),
4102 ARTWORK_DIRECTORY(type));
4104 LoadArtworkInfoFromArtworkDir(artwork_node, NULL, path, type);
4106 if (topnode_last != *artwork_node) // check for newly added node
4108 artwork_new = *artwork_node;
4110 setString(&artwork_new->identifier, level_node->subdir);
4111 setString(&artwork_new->name, level_node->name);
4112 setString(&artwork_new->name_sorting, level_node->name_sorting);
4114 artwork_new->sort_priority = level_node->sort_priority;
4115 artwork_new->in_user_dir = level_node->in_user_dir;
4117 update_artworkinfo_cache = TRUE;
4123 // insert artwork info (from old cache or filesystem) into new cache
4124 if (artwork_new != NULL)
4125 setArtworkInfoCacheEntry(artwork_new, level_node, type);
4128 DrawInitText(level_node->name, 150, FC_YELLOW);
4130 if (level_node->node_group != NULL)
4132 TreeInfo *artwork_new = newTreeInfo();
4135 setTreeInfoToDefaultsFromParent(artwork_new, node_parent);
4137 setTreeInfoToDefaults(artwork_new, type);
4139 artwork_new->level_group = TRUE;
4141 setString(&artwork_new->identifier, level_node->subdir);
4143 if (node_parent == NULL) // check for top tree node
4145 char *top_node_name = (empty_level_set_mode ?
4146 "artwork for certain level sets" :
4147 "artwork included in level sets");
4149 setString(&artwork_new->name, top_node_name);
4150 setString(&artwork_new->name_sorting, top_node_name);
4154 setString(&artwork_new->name, level_node->name);
4155 setString(&artwork_new->name_sorting, level_node->name_sorting);
4158 pushTreeInfo(artwork_node, artwork_new);
4160 // create node to link back to current custom artwork directory
4161 createParentTreeInfoNode(artwork_new);
4163 // recursively step into sub-directory and look for more custom artwork
4164 LoadArtworkInfoFromLevelInfoExt(&artwork_new->node_group, artwork_new,
4165 level_node->node_group,
4166 empty_level_set_mode);
4168 // if sub-tree has no custom artwork at all, remove it
4169 if (artwork_new->node_group->next == NULL)
4170 removeTreeInfo(artwork_node);
4173 level_node = level_node->next;
4177 static void LoadArtworkInfoFromLevelInfo(ArtworkDirTree **artwork_node)
4179 // move peviously loaded artwork tree into separate sub-tree
4180 MoveArtworkInfoIntoSubTree(artwork_node);
4182 // load artwork from level sets into separate sub-trees
4183 LoadArtworkInfoFromLevelInfoExt(artwork_node, NULL, leveldir_first_all, TRUE);
4184 LoadArtworkInfoFromLevelInfoExt(artwork_node, NULL, leveldir_first_all, FALSE);
4186 // add top tree node over all three separate sub-trees
4187 *artwork_node = createTopTreeInfoNode(*artwork_node);
4189 // set all parent links (back links) in complete artwork tree
4190 setTreeInfoParentNodes(*artwork_node, NULL);
4193 void LoadLevelArtworkInfo(void)
4195 print_timestamp_init("LoadLevelArtworkInfo");
4197 DrawInitText("Looking for custom level artwork", 120, FC_GREEN);
4199 print_timestamp_time("DrawTimeText");
4201 LoadArtworkInfoFromLevelInfo(&artwork.gfx_first);
4202 print_timestamp_time("LoadArtworkInfoFromLevelInfo (gfx)");
4203 LoadArtworkInfoFromLevelInfo(&artwork.snd_first);
4204 print_timestamp_time("LoadArtworkInfoFromLevelInfo (snd)");
4205 LoadArtworkInfoFromLevelInfo(&artwork.mus_first);
4206 print_timestamp_time("LoadArtworkInfoFromLevelInfo (mus)");
4208 SaveArtworkInfoCache();
4210 print_timestamp_time("SaveArtworkInfoCache");
4212 // needed for reloading level artwork not known at ealier stage
4213 ChangeCurrentArtworkIfNeeded(ARTWORK_TYPE_GRAPHICS);
4214 ChangeCurrentArtworkIfNeeded(ARTWORK_TYPE_SOUNDS);
4215 ChangeCurrentArtworkIfNeeded(ARTWORK_TYPE_MUSIC);
4217 print_timestamp_time("getTreeInfoFromIdentifier");
4219 sortTreeInfo(&artwork.gfx_first);
4220 sortTreeInfo(&artwork.snd_first);
4221 sortTreeInfo(&artwork.mus_first);
4223 print_timestamp_time("sortTreeInfo");
4225 #if ENABLE_UNUSED_CODE
4226 dumpTreeInfo(artwork.gfx_first, 0);
4227 dumpTreeInfo(artwork.snd_first, 0);
4228 dumpTreeInfo(artwork.mus_first, 0);
4231 print_timestamp_done("LoadLevelArtworkInfo");
4234 static boolean AddTreeSetToTreeInfoExt(TreeInfo *tree_node_old, char *tree_dir,
4235 char *tree_subdir_new, int type)
4237 if (tree_node_old == NULL)
4239 if (type == TREE_TYPE_LEVEL_DIR)
4241 // get level info tree node of personal user level set
4242 tree_node_old = getTreeInfoFromIdentifier(leveldir_first, getLoginName());
4244 // this may happen if "setup.internal.create_user_levelset" is FALSE
4245 // or if file "levelinfo.conf" is missing in personal user level set
4246 if (tree_node_old == NULL)
4247 tree_node_old = leveldir_first->node_group;
4251 // get artwork info tree node of first artwork set
4252 tree_node_old = ARTWORK_FIRST_NODE(artwork, type);
4256 if (tree_dir == NULL)
4257 tree_dir = TREE_USERDIR(type);
4259 if (tree_node_old == NULL ||
4261 tree_subdir_new == NULL) // should not happen
4264 int draw_deactivation_mask = GetDrawDeactivationMask();
4266 // override draw deactivation mask (temporarily disable drawing)
4267 SetDrawDeactivationMask(REDRAW_ALL);
4269 if (type == TREE_TYPE_LEVEL_DIR)
4271 // load new level set config and add it next to first user level set
4272 LoadLevelInfoFromLevelConf(&tree_node_old->next,
4273 tree_node_old->node_parent,
4274 tree_dir, tree_subdir_new);
4278 // load new artwork set config and add it next to first artwork set
4279 LoadArtworkInfoFromArtworkConf(&tree_node_old->next,
4280 tree_node_old->node_parent,
4281 tree_dir, tree_subdir_new, type);
4284 // set draw deactivation mask to previous value
4285 SetDrawDeactivationMask(draw_deactivation_mask);
4287 // get first node of level or artwork info tree
4288 TreeInfo **tree_node_first = TREE_FIRST_NODE_PTR(type);
4290 // get tree info node of newly added level or artwork set
4291 TreeInfo *tree_node_new = getTreeInfoFromIdentifier(*tree_node_first,
4294 if (tree_node_new == NULL) // should not happen
4297 // correct top link and parent node link of newly created tree node
4298 tree_node_new->node_top = tree_node_old->node_top;
4299 tree_node_new->node_parent = tree_node_old->node_parent;
4301 // sort tree info to adjust position of newly added tree set
4302 sortTreeInfo(tree_node_first);
4307 void AddTreeSetToTreeInfo(TreeInfo *tree_node, char *tree_dir,
4308 char *tree_subdir_new, int type)
4310 if (!AddTreeSetToTreeInfoExt(tree_node, tree_dir, tree_subdir_new, type))
4311 Fail("internal tree info structure corrupted -- aborting");
4314 void AddUserLevelSetToLevelInfo(char *level_subdir_new)
4316 AddTreeSetToTreeInfo(NULL, NULL, level_subdir_new, TREE_TYPE_LEVEL_DIR);
4319 char *getArtworkIdentifierForUserLevelSet(int type)
4321 char *classic_artwork_set = getClassicArtworkSet(type);
4323 // check for custom artwork configured in "levelinfo.conf"
4324 char *leveldir_artwork_set =
4325 *LEVELDIR_ARTWORK_SET_PTR(leveldir_current, type);
4326 boolean has_leveldir_artwork_set =
4327 (leveldir_artwork_set != NULL && !strEqual(leveldir_artwork_set,
4328 classic_artwork_set));
4330 // check for custom artwork in sub-directory "graphics" etc.
4331 TreeInfo *artwork_first_node = ARTWORK_FIRST_NODE(artwork, type);
4332 char *leveldir_identifier = leveldir_current->identifier;
4333 boolean has_artwork_subdir =
4334 (getTreeInfoFromIdentifier(artwork_first_node,
4335 leveldir_identifier) != NULL);
4337 return (has_leveldir_artwork_set ? leveldir_artwork_set :
4338 has_artwork_subdir ? leveldir_identifier :
4339 classic_artwork_set);
4342 TreeInfo *getArtworkTreeInfoForUserLevelSet(int type)
4344 char *artwork_set = getArtworkIdentifierForUserLevelSet(type);
4345 TreeInfo *artwork_first_node = ARTWORK_FIRST_NODE(artwork, type);
4346 TreeInfo *ti = getTreeInfoFromIdentifier(artwork_first_node, artwork_set);
4350 ti = getTreeInfoFromIdentifier(artwork_first_node,
4351 ARTWORK_DEFAULT_SUBDIR(type));
4353 Fail("cannot find default graphics -- should not happen");
4359 boolean checkIfCustomArtworkExistsForCurrentLevelSet(void)
4361 char *graphics_set =
4362 getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_GRAPHICS);
4364 getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_SOUNDS);
4366 getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_MUSIC);
4368 return (!strEqual(graphics_set, GFX_CLASSIC_SUBDIR) ||
4369 !strEqual(sounds_set, SND_CLASSIC_SUBDIR) ||
4370 !strEqual(music_set, MUS_CLASSIC_SUBDIR));
4373 boolean UpdateUserLevelSet(char *level_subdir, char *level_name,
4374 char *level_author, int num_levels)
4376 char *filename = getPath2(getUserLevelDir(level_subdir), LEVELINFO_FILENAME);
4377 char *filename_tmp = getStringCat2(filename, ".tmp");
4379 FILE *file_tmp = NULL;
4380 char line[MAX_LINE_LEN];
4381 boolean success = FALSE;
4382 LevelDirTree *leveldir = getTreeInfoFromIdentifier(leveldir_first,
4384 // update values in level directory tree
4386 if (level_name != NULL)
4387 setString(&leveldir->name, level_name);
4389 if (level_author != NULL)
4390 setString(&leveldir->author, level_author);
4392 if (num_levels != -1)
4393 leveldir->levels = num_levels;
4395 // update values that depend on other values
4397 setString(&leveldir->name_sorting, leveldir->name);
4399 leveldir->last_level = leveldir->first_level + leveldir->levels - 1;
4401 // sort order of level sets may have changed
4402 sortTreeInfo(&leveldir_first);
4404 if ((file = fopen(filename, MODE_READ)) &&
4405 (file_tmp = fopen(filename_tmp, MODE_WRITE)))
4407 while (fgets(line, MAX_LINE_LEN, file))
4409 if (strPrefix(line, "name:") && level_name != NULL)
4410 fprintf(file_tmp, "%-32s%s\n", "name:", level_name);
4411 else if (strPrefix(line, "author:") && level_author != NULL)
4412 fprintf(file_tmp, "%-32s%s\n", "author:", level_author);
4413 else if (strPrefix(line, "levels:") && num_levels != -1)
4414 fprintf(file_tmp, "%-32s%d\n", "levels:", num_levels);
4416 fputs(line, file_tmp);
4429 success = (rename(filename_tmp, filename) == 0);
4437 boolean CreateUserLevelSet(char *level_subdir, char *level_name,
4438 char *level_author, int num_levels,
4439 boolean use_artwork_set)
4441 LevelDirTree *level_info;
4446 // create user level sub-directory, if needed
4447 createDirectory(getUserLevelDir(level_subdir), "user level", PERMS_PRIVATE);
4449 filename = getPath2(getUserLevelDir(level_subdir), LEVELINFO_FILENAME);
4451 if (!(file = fopen(filename, MODE_WRITE)))
4453 Warn("cannot write level info file '%s'", filename);
4460 level_info = newTreeInfo();
4462 // always start with reliable default values
4463 setTreeInfoToDefaults(level_info, TREE_TYPE_LEVEL_DIR);
4465 setString(&level_info->name, level_name);
4466 setString(&level_info->author, level_author);
4467 level_info->levels = num_levels;
4468 level_info->first_level = 1;
4469 level_info->sort_priority = LEVELCLASS_PRIVATE_START;
4470 level_info->readonly = FALSE;
4472 if (use_artwork_set)
4474 level_info->graphics_set =
4475 getStringCopy(getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_GRAPHICS));
4476 level_info->sounds_set =
4477 getStringCopy(getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_SOUNDS));
4478 level_info->music_set =
4479 getStringCopy(getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_MUSIC));
4482 token_value_position = TOKEN_VALUE_POSITION_SHORT;
4484 fprintFileHeader(file, LEVELINFO_FILENAME);
4487 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
4489 if (i == LEVELINFO_TOKEN_NAME ||
4490 i == LEVELINFO_TOKEN_AUTHOR ||
4491 i == LEVELINFO_TOKEN_LEVELS ||
4492 i == LEVELINFO_TOKEN_FIRST_LEVEL ||
4493 i == LEVELINFO_TOKEN_SORT_PRIORITY ||
4494 i == LEVELINFO_TOKEN_READONLY ||
4495 (use_artwork_set && (i == LEVELINFO_TOKEN_GRAPHICS_SET ||
4496 i == LEVELINFO_TOKEN_SOUNDS_SET ||
4497 i == LEVELINFO_TOKEN_MUSIC_SET)))
4498 fprintf(file, "%s\n", getSetupLine(levelinfo_tokens, "", i));
4500 // just to make things nicer :)
4501 if (i == LEVELINFO_TOKEN_AUTHOR ||
4502 i == LEVELINFO_TOKEN_FIRST_LEVEL ||
4503 (use_artwork_set && i == LEVELINFO_TOKEN_READONLY))
4504 fprintf(file, "\n");
4507 token_value_position = TOKEN_VALUE_POSITION_DEFAULT;
4511 SetFilePermissions(filename, PERMS_PRIVATE);
4513 freeTreeInfo(level_info);
4519 static void SaveUserLevelInfo(void)
4521 CreateUserLevelSet(getLoginName(), getLoginName(), getRealName(), 100, FALSE);
4524 char *getSetupValue(int type, void *value)
4526 static char value_string[MAX_LINE_LEN];
4534 strcpy(value_string, (*(boolean *)value ? "true" : "false"));
4538 strcpy(value_string, (*(boolean *)value ? "on" : "off"));
4542 strcpy(value_string, (*(int *)value == AUTO ? "auto" :
4543 *(int *)value == FALSE ? "off" : "on"));
4547 strcpy(value_string, (*(boolean *)value ? "yes" : "no"));
4550 case TYPE_YES_NO_AUTO:
4551 strcpy(value_string, (*(int *)value == AUTO ? "auto" :
4552 *(int *)value == FALSE ? "no" : "yes"));
4556 strcpy(value_string, (*(boolean *)value ? "AGA" : "ECS"));
4560 strcpy(value_string, getKeyNameFromKey(*(Key *)value));
4564 strcpy(value_string, getX11KeyNameFromKey(*(Key *)value));
4568 sprintf(value_string, "%d", *(int *)value);
4572 if (*(char **)value == NULL)
4575 strcpy(value_string, *(char **)value);
4579 sprintf(value_string, "player_%d", *(int *)value + 1);
4583 value_string[0] = '\0';
4587 if (type & TYPE_GHOSTED)
4588 strcpy(value_string, "n/a");
4590 return value_string;
4593 char *getSetupLine(struct TokenInfo *token_info, char *prefix, int token_nr)
4597 static char token_string[MAX_LINE_LEN];
4598 int token_type = token_info[token_nr].type;
4599 void *setup_value = token_info[token_nr].value;
4600 char *token_text = token_info[token_nr].text;
4601 char *value_string = getSetupValue(token_type, setup_value);
4603 // build complete token string
4604 sprintf(token_string, "%s%s", prefix, token_text);
4606 // build setup entry line
4607 line = getFormattedSetupEntry(token_string, value_string);
4609 if (token_type == TYPE_KEY_X11)
4611 Key key = *(Key *)setup_value;
4612 char *keyname = getKeyNameFromKey(key);
4614 // add comment, if useful
4615 if (!strEqual(keyname, "(undefined)") &&
4616 !strEqual(keyname, "(unknown)"))
4618 // add at least one whitespace
4620 for (i = strlen(line); i < token_comment_position; i++)
4624 strcat(line, keyname);
4631 static void InitLastPlayedLevels_ParentNode(void)
4633 LevelDirTree **leveldir_top = &leveldir_first->node_group->next;
4634 LevelDirTree *leveldir_new = NULL;
4636 // check if parent node for last played levels already exists
4637 if (strEqual((*leveldir_top)->identifier, TOKEN_STR_LAST_LEVEL_SERIES))
4640 leveldir_new = newTreeInfo();
4642 setTreeInfoToDefaultsFromParent(leveldir_new, leveldir_first);
4644 leveldir_new->level_group = TRUE;
4645 leveldir_new->sort_priority = LEVELCLASS_LAST_PLAYED_LEVEL;
4647 setString(&leveldir_new->identifier, TOKEN_STR_LAST_LEVEL_SERIES);
4648 setString(&leveldir_new->name, "<< (last played level sets)");
4649 setString(&leveldir_new->name_sorting, leveldir_new->name);
4651 pushTreeInfo(leveldir_top, leveldir_new);
4653 // create node to link back to current level directory
4654 createParentTreeInfoNode(leveldir_new);
4657 void UpdateLastPlayedLevels_TreeInfo(void)
4659 char **last_level_series = setup.level_setup.last_level_series;
4660 LevelDirTree *leveldir_last;
4661 TreeInfo **node_new = NULL;
4664 if (last_level_series[0] == NULL)
4667 InitLastPlayedLevels_ParentNode();
4669 leveldir_last = getTreeInfoFromIdentifierExt(leveldir_first,
4670 TOKEN_STR_LAST_LEVEL_SERIES,
4671 TREE_NODE_TYPE_GROUP);
4672 if (leveldir_last == NULL)
4675 node_new = &leveldir_last->node_group->next;
4677 freeTreeInfo(*node_new);
4681 for (i = 0; last_level_series[i] != NULL; i++)
4683 LevelDirTree *node_last = getTreeInfoFromIdentifier(leveldir_first,
4684 last_level_series[i]);
4685 if (node_last == NULL)
4688 *node_new = getTreeInfoCopy(node_last); // copy complete node
4690 (*node_new)->node_top = &leveldir_first; // correct top node link
4691 (*node_new)->node_parent = leveldir_last; // correct parent node link
4693 (*node_new)->is_copy = TRUE; // mark entry as node copy
4695 (*node_new)->node_group = NULL;
4696 (*node_new)->next = NULL;
4698 (*node_new)->cl_first = -1; // force setting tree cursor
4700 node_new = &((*node_new)->next);
4704 static void UpdateLastPlayedLevels_List(void)
4706 char **last_level_series = setup.level_setup.last_level_series;
4707 int pos = MAX_LEVELDIR_HISTORY - 1;
4710 // search for potentially already existing entry in list of level sets
4711 for (i = 0; last_level_series[i] != NULL; i++)
4712 if (strEqual(last_level_series[i], leveldir_current->identifier))
4715 // move list of level sets one entry down (using potentially free entry)
4716 for (i = pos; i > 0; i--)
4717 setString(&last_level_series[i], last_level_series[i - 1]);
4719 // put last played level set at top position
4720 setString(&last_level_series[0], leveldir_current->identifier);
4723 static TreeInfo *StoreOrRestoreLastPlayedLevels(TreeInfo *node, boolean store)
4725 static char *identifier = NULL;
4729 setString(&identifier, (node && node->is_copy ? node->identifier : NULL));
4731 return NULL; // not used
4735 TreeInfo *node_new = getTreeInfoFromIdentifierExt(leveldir_first,
4737 TREE_NODE_TYPE_COPY);
4738 return (node_new != NULL ? node_new : node);
4742 void StoreLastPlayedLevels(TreeInfo *node)
4744 StoreOrRestoreLastPlayedLevels(node, TRUE);
4747 void RestoreLastPlayedLevels(TreeInfo **node)
4749 *node = StoreOrRestoreLastPlayedLevels(*node, FALSE);
4752 void LoadLevelSetup_LastSeries(void)
4754 // --------------------------------------------------------------------------
4755 // ~/.<program>/levelsetup.conf
4756 // --------------------------------------------------------------------------
4758 char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
4759 SetupFileHash *level_setup_hash = NULL;
4763 // always start with reliable default values
4764 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
4766 // start with empty history of last played level sets
4767 setString(&setup.level_setup.last_level_series[0], NULL);
4769 if (!strEqual(DEFAULT_LEVELSET, UNDEFINED_LEVELSET))
4771 leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
4773 if (leveldir_current == NULL)
4774 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
4777 if ((level_setup_hash = loadSetupFileHash(filename)))
4779 char *last_level_series =
4780 getHashEntry(level_setup_hash, TOKEN_STR_LAST_LEVEL_SERIES);
4782 leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
4784 if (leveldir_current == NULL)
4785 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
4787 for (i = 0; i < MAX_LEVELDIR_HISTORY; i++)
4789 char token[strlen(TOKEN_STR_LAST_LEVEL_SERIES) + 10];
4790 LevelDirTree *leveldir_last;
4792 sprintf(token, "%s.%03d", TOKEN_STR_LAST_LEVEL_SERIES, i);
4794 last_level_series = getHashEntry(level_setup_hash, token);
4796 leveldir_last = getTreeInfoFromIdentifier(leveldir_first,
4798 if (leveldir_last != NULL)
4799 setString(&setup.level_setup.last_level_series[pos++],
4803 setString(&setup.level_setup.last_level_series[pos], NULL);
4805 freeSetupFileHash(level_setup_hash);
4809 Debug("setup", "using default setup values");
4815 static void SaveLevelSetup_LastSeries_Ext(boolean deactivate_last_level_series)
4817 // --------------------------------------------------------------------------
4818 // ~/.<program>/levelsetup.conf
4819 // --------------------------------------------------------------------------
4821 // check if the current level directory structure is available at this point
4822 if (leveldir_current == NULL)
4825 char **last_level_series = setup.level_setup.last_level_series;
4826 char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
4830 InitUserDataDirectory();
4832 UpdateLastPlayedLevels_List();
4834 if (!(file = fopen(filename, MODE_WRITE)))
4836 Warn("cannot write setup file '%s'", filename);
4843 fprintFileHeader(file, LEVELSETUP_FILENAME);
4845 if (deactivate_last_level_series)
4846 fprintf(file, "# %s\n# ", "the following level set may have caused a problem and was deactivated");
4848 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_LAST_LEVEL_SERIES,
4849 leveldir_current->identifier));
4851 for (i = 0; last_level_series[i] != NULL; i++)
4853 char token[strlen(TOKEN_STR_LAST_LEVEL_SERIES) + 10];
4855 sprintf(token, "%s.%03d", TOKEN_STR_LAST_LEVEL_SERIES, i);
4857 fprintf(file, "%s\n", getFormattedSetupEntry(token, last_level_series[i]));
4862 SetFilePermissions(filename, PERMS_PRIVATE);
4867 void SaveLevelSetup_LastSeries(void)
4869 SaveLevelSetup_LastSeries_Ext(FALSE);
4872 void SaveLevelSetup_LastSeries_Deactivate(void)
4874 SaveLevelSetup_LastSeries_Ext(TRUE);
4877 static void checkSeriesInfo(void)
4879 static char *level_directory = NULL;
4882 DirectoryEntry *dir_entry;
4885 checked_free(level_directory);
4887 // check for more levels besides the 'levels' field of 'levelinfo.conf'
4889 level_directory = getPath2((leveldir_current->in_user_dir ?
4890 getUserLevelDir(NULL) :
4891 options.level_directory),
4892 leveldir_current->fullpath);
4894 if ((dir = openDirectory(level_directory)) == NULL)
4896 Warn("cannot read level directory '%s'", level_directory);
4902 while ((dir_entry = readDirectory(dir)) != NULL) // loop all entries
4904 if (strlen(dir_entry->basename) > 4 &&
4905 dir_entry->basename[3] == '.' &&
4906 strEqual(&dir_entry->basename[4], LEVELFILE_EXTENSION))
4908 char levelnum_str[4];
4911 strncpy(levelnum_str, dir_entry->basename, 3);
4912 levelnum_str[3] = '\0';
4914 levelnum_value = atoi(levelnum_str);
4916 if (levelnum_value < leveldir_current->first_level)
4918 Warn("additional level %d found", levelnum_value);
4920 leveldir_current->first_level = levelnum_value;
4922 else if (levelnum_value > leveldir_current->last_level)
4924 Warn("additional level %d found", levelnum_value);
4926 leveldir_current->last_level = levelnum_value;
4932 closeDirectory(dir);
4935 void LoadLevelSetup_SeriesInfo(void)
4938 SetupFileHash *level_setup_hash = NULL;
4939 char *level_subdir = leveldir_current->subdir;
4942 // always start with reliable default values
4943 level_nr = leveldir_current->first_level;
4945 for (i = 0; i < MAX_LEVELS; i++)
4947 LevelStats_setPlayed(i, 0);
4948 LevelStats_setSolved(i, 0);
4953 // --------------------------------------------------------------------------
4954 // ~/.<program>/levelsetup/<level series>/levelsetup.conf
4955 // --------------------------------------------------------------------------
4957 level_subdir = leveldir_current->subdir;
4959 filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
4961 if ((level_setup_hash = loadSetupFileHash(filename)))
4965 // get last played level in this level set
4967 token_value = getHashEntry(level_setup_hash, TOKEN_STR_LAST_PLAYED_LEVEL);
4971 level_nr = atoi(token_value);
4973 if (level_nr < leveldir_current->first_level)
4974 level_nr = leveldir_current->first_level;
4975 if (level_nr > leveldir_current->last_level)
4976 level_nr = leveldir_current->last_level;
4979 // get handicap level in this level set
4981 token_value = getHashEntry(level_setup_hash, TOKEN_STR_HANDICAP_LEVEL);
4985 int level_nr = atoi(token_value);
4987 if (level_nr < leveldir_current->first_level)
4988 level_nr = leveldir_current->first_level;
4989 if (level_nr > leveldir_current->last_level + 1)
4990 level_nr = leveldir_current->last_level;
4992 if (leveldir_current->user_defined || !leveldir_current->handicap)
4993 level_nr = leveldir_current->last_level;
4995 leveldir_current->handicap_level = level_nr;
4998 // get number of played and solved levels in this level set
5000 BEGIN_HASH_ITERATION(level_setup_hash, itr)
5002 char *token = HASH_ITERATION_TOKEN(itr);
5003 char *value = HASH_ITERATION_VALUE(itr);
5005 if (strlen(token) == 3 &&
5006 token[0] >= '0' && token[0] <= '9' &&
5007 token[1] >= '0' && token[1] <= '9' &&
5008 token[2] >= '0' && token[2] <= '9')
5010 int level_nr = atoi(token);
5013 LevelStats_setPlayed(level_nr, atoi(value)); // read 1st column
5015 value = strchr(value, ' ');
5018 LevelStats_setSolved(level_nr, atoi(value)); // read 2nd column
5021 END_HASH_ITERATION(hash, itr)
5023 freeSetupFileHash(level_setup_hash);
5027 Debug("setup", "using default setup values");
5033 void SaveLevelSetup_SeriesInfo(void)
5036 char *level_subdir = leveldir_current->subdir;
5037 char *level_nr_str = int2str(level_nr, 0);
5038 char *handicap_level_str = int2str(leveldir_current->handicap_level, 0);
5042 // --------------------------------------------------------------------------
5043 // ~/.<program>/levelsetup/<level series>/levelsetup.conf
5044 // --------------------------------------------------------------------------
5046 InitLevelSetupDirectory(level_subdir);
5048 filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
5050 if (!(file = fopen(filename, MODE_WRITE)))
5052 Warn("cannot write setup file '%s'", filename);
5059 fprintFileHeader(file, LEVELSETUP_FILENAME);
5061 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_PLAYED_LEVEL,
5063 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_HANDICAP_LEVEL,
5064 handicap_level_str));
5066 for (i = leveldir_current->first_level; i <= leveldir_current->last_level;
5069 if (LevelStats_getPlayed(i) > 0 ||
5070 LevelStats_getSolved(i) > 0)
5075 sprintf(token, "%03d", i);
5076 sprintf(value, "%d %d", LevelStats_getPlayed(i), LevelStats_getSolved(i));
5078 fprintf(file, "%s\n", getFormattedSetupEntry(token, value));
5084 SetFilePermissions(filename, PERMS_PRIVATE);
5089 int LevelStats_getPlayed(int nr)
5091 return (nr >= 0 && nr < MAX_LEVELS ? level_stats[nr].played : 0);
5094 int LevelStats_getSolved(int nr)
5096 return (nr >= 0 && nr < MAX_LEVELS ? level_stats[nr].solved : 0);
5099 void LevelStats_setPlayed(int nr, int value)
5101 if (nr >= 0 && nr < MAX_LEVELS)
5102 level_stats[nr].played = value;
5105 void LevelStats_setSolved(int nr, int value)
5107 if (nr >= 0 && nr < MAX_LEVELS)
5108 level_stats[nr].solved = value;
5111 void LevelStats_incPlayed(int nr)
5113 if (nr >= 0 && nr < MAX_LEVELS)
5114 level_stats[nr].played++;
5117 void LevelStats_incSolved(int nr)
5119 if (nr >= 0 && nr < MAX_LEVELS)
5120 level_stats[nr].solved++;
5123 void LoadUserSetup(void)
5125 // --------------------------------------------------------------------------
5126 // ~/.<program>/usersetup.conf
5127 // --------------------------------------------------------------------------
5129 char *filename = getPath2(getMainUserGameDataDir(), USERSETUP_FILENAME);
5130 SetupFileHash *user_setup_hash = NULL;
5132 // always start with reliable default values
5135 if ((user_setup_hash = loadSetupFileHash(filename)))
5139 // get last selected user number
5140 token_value = getHashEntry(user_setup_hash, TOKEN_STR_LAST_USER);
5143 user.nr = MIN(MAX(0, atoi(token_value)), MAX_PLAYER_NAMES - 1);
5145 freeSetupFileHash(user_setup_hash);
5149 Debug("setup", "using default setup values");
5155 void SaveUserSetup(void)
5157 // --------------------------------------------------------------------------
5158 // ~/.<program>/usersetup.conf
5159 // --------------------------------------------------------------------------
5161 char *filename = getPath2(getMainUserGameDataDir(), USERSETUP_FILENAME);
5164 InitMainUserDataDirectory();
5166 if (!(file = fopen(filename, MODE_WRITE)))
5168 Warn("cannot write setup file '%s'", filename);
5175 fprintFileHeader(file, USERSETUP_FILENAME);
5177 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_USER,
5181 SetFilePermissions(filename, PERMS_PRIVATE);