1 // ============================================================================
2 // Artsoft Retro-Game Library
3 // ----------------------------------------------------------------------------
4 // (c) 1995-2014 by Artsoft Entertainment
7 // https://www.artsoft.org/
8 // ----------------------------------------------------------------------------
10 // ============================================================================
12 #include <sys/types.h>
26 #include "zip/miniunz.h"
29 #define ENABLE_UNUSED_CODE FALSE // for currently unused functions
30 #define DEBUG_NO_CONFIG_FILE FALSE // for extra-verbose debug output
32 #define NUM_LEVELCLASS_DESC 8
34 static char *levelclass_desc[NUM_LEVELCLASS_DESC] =
46 #define TOKEN_VALUE_POSITION_SHORT 32
47 #define TOKEN_VALUE_POSITION_DEFAULT 40
48 #define TOKEN_COMMENT_POSITION_DEFAULT 60
50 #define MAX_COOKIE_LEN 256
52 #define TREE_NODE_TYPE_DEFAULT 0
53 #define TREE_NODE_TYPE_PARENT 1
54 #define TREE_NODE_TYPE_GROUP 2
55 #define TREE_NODE_TYPE_COPY 3
57 #define TREE_NODE_TYPE(ti) (ti->node_group ? TREE_NODE_TYPE_GROUP : \
58 ti->parent_link ? TREE_NODE_TYPE_PARENT : \
59 ti->is_copy ? TREE_NODE_TYPE_COPY : \
60 TREE_NODE_TYPE_DEFAULT)
63 static void setTreeInfoToDefaults(TreeInfo *, int);
64 static TreeInfo *getTreeInfoCopy(TreeInfo *ti);
65 static int compareTreeInfoEntries(const void *, const void *);
67 static int token_value_position = TOKEN_VALUE_POSITION_DEFAULT;
68 static int token_comment_position = TOKEN_COMMENT_POSITION_DEFAULT;
70 static SetupFileHash *artworkinfo_cache_old = NULL;
71 static SetupFileHash *artworkinfo_cache_new = NULL;
72 static SetupFileHash *optional_tokens_hash = NULL;
73 static boolean use_artworkinfo_cache = TRUE;
74 static boolean update_artworkinfo_cache = FALSE;
77 // ----------------------------------------------------------------------------
79 // ----------------------------------------------------------------------------
81 static char *getLevelClassDescription(TreeInfo *ti)
83 int position = ti->sort_priority / 100;
85 if (position >= 0 && position < NUM_LEVELCLASS_DESC)
86 return levelclass_desc[position];
88 return "Unknown Level Class";
91 static char *getCacheDir(void)
93 static char *cache_dir = NULL;
95 if (cache_dir == NULL)
96 cache_dir = getPath2(getMainUserGameDataDir(), CACHE_DIRECTORY);
101 static char *getScoreDir(char *level_subdir)
103 static char *score_dir = NULL;
104 static char *score_level_dir = NULL;
105 char *score_subdir = SCORES_DIRECTORY;
107 if (score_dir == NULL)
108 score_dir = getPath2(getMainUserGameDataDir(), score_subdir);
110 if (level_subdir != NULL)
112 checked_free(score_level_dir);
114 score_level_dir = getPath2(score_dir, level_subdir);
116 return score_level_dir;
122 static char *getScoreCacheDir(char *level_subdir)
124 static char *score_dir = NULL;
125 static char *score_level_dir = NULL;
126 char *score_subdir = SCORES_DIRECTORY;
128 if (score_dir == NULL)
129 score_dir = getPath2(getCacheDir(), score_subdir);
131 if (level_subdir != NULL)
133 checked_free(score_level_dir);
135 score_level_dir = getPath2(score_dir, level_subdir);
137 return score_level_dir;
143 static char *getScoreTapeDir(char *level_subdir, int nr)
145 static char *score_tape_dir = NULL;
146 char tape_subdir[MAX_FILENAME_LEN];
148 checked_free(score_tape_dir);
150 sprintf(tape_subdir, "%03d", nr);
151 score_tape_dir = getPath2(getScoreDir(level_subdir), tape_subdir);
153 return score_tape_dir;
156 static char *getUserSubdir(int nr)
158 static char user_subdir[16] = { 0 };
160 sprintf(user_subdir, "%03d", nr);
165 static char *getUserDir(int nr)
167 static char *user_dir = NULL;
168 char *main_data_dir = getMainUserGameDataDir();
169 char *users_subdir = USERS_DIRECTORY;
170 char *user_subdir = getUserSubdir(nr);
172 checked_free(user_dir);
175 user_dir = getPath3(main_data_dir, users_subdir, user_subdir);
177 user_dir = getPath2(main_data_dir, users_subdir);
182 static char *getLevelSetupDir(char *level_subdir)
184 static char *levelsetup_dir = NULL;
185 char *data_dir = getUserGameDataDir();
186 char *levelsetup_subdir = LEVELSETUP_DIRECTORY;
188 checked_free(levelsetup_dir);
190 if (level_subdir != NULL)
191 levelsetup_dir = getPath3(data_dir, levelsetup_subdir, level_subdir);
193 levelsetup_dir = getPath2(data_dir, levelsetup_subdir);
195 return levelsetup_dir;
198 static char *getNetworkDir(void)
200 static char *network_dir = NULL;
202 if (network_dir == NULL)
203 network_dir = getPath2(getMainUserGameDataDir(), NETWORK_DIRECTORY);
208 char *getLevelDirFromTreeInfo(TreeInfo *node)
210 static char *level_dir = NULL;
213 return options.level_directory;
215 checked_free(level_dir);
217 level_dir = getPath2((node->in_user_dir ? getUserLevelDir(NULL) :
218 options.level_directory), node->fullpath);
223 char *getUserLevelDir(char *level_subdir)
225 static char *userlevel_dir = NULL;
226 char *data_dir = getMainUserGameDataDir();
227 char *userlevel_subdir = LEVELS_DIRECTORY;
229 checked_free(userlevel_dir);
231 if (level_subdir != NULL)
232 userlevel_dir = getPath3(data_dir, userlevel_subdir, level_subdir);
234 userlevel_dir = getPath2(data_dir, userlevel_subdir);
236 return userlevel_dir;
239 char *getNetworkLevelDir(char *level_subdir)
241 static char *network_level_dir = NULL;
242 char *data_dir = getNetworkDir();
243 char *networklevel_subdir = LEVELS_DIRECTORY;
245 checked_free(network_level_dir);
247 if (level_subdir != NULL)
248 network_level_dir = getPath3(data_dir, networklevel_subdir, level_subdir);
250 network_level_dir = getPath2(data_dir, networklevel_subdir);
252 return network_level_dir;
255 char *getCurrentLevelDir(void)
257 return getLevelDirFromTreeInfo(leveldir_current);
260 char *getNewUserLevelSubdir(void)
262 static char *new_level_subdir = NULL;
263 char *subdir_prefix = getLoginName();
264 char subdir_suffix[10];
265 int max_suffix_number = 1000;
268 while (++i < max_suffix_number)
270 sprintf(subdir_suffix, "_%d", i);
272 checked_free(new_level_subdir);
273 new_level_subdir = getStringCat2(subdir_prefix, subdir_suffix);
275 if (!directoryExists(getUserLevelDir(new_level_subdir)))
279 return new_level_subdir;
282 char *getTapeDir(char *level_subdir)
284 static char *tape_dir = NULL;
285 char *data_dir = getUserGameDataDir();
286 char *tape_subdir = TAPES_DIRECTORY;
288 checked_free(tape_dir);
290 if (level_subdir != NULL)
291 tape_dir = getPath3(data_dir, tape_subdir, level_subdir);
293 tape_dir = getPath2(data_dir, tape_subdir);
298 static char *getSolutionTapeDir(void)
300 static char *tape_dir = NULL;
301 char *data_dir = getCurrentLevelDir();
302 char *tape_subdir = TAPES_DIRECTORY;
304 checked_free(tape_dir);
306 tape_dir = getPath2(data_dir, tape_subdir);
311 static char *getDefaultGraphicsDir(char *graphics_subdir)
313 static char *graphics_dir = NULL;
315 if (graphics_subdir == NULL)
316 return options.graphics_directory;
318 checked_free(graphics_dir);
320 graphics_dir = getPath2(options.graphics_directory, graphics_subdir);
325 static char *getDefaultSoundsDir(char *sounds_subdir)
327 static char *sounds_dir = NULL;
329 if (sounds_subdir == NULL)
330 return options.sounds_directory;
332 checked_free(sounds_dir);
334 sounds_dir = getPath2(options.sounds_directory, sounds_subdir);
339 static char *getDefaultMusicDir(char *music_subdir)
341 static char *music_dir = NULL;
343 if (music_subdir == NULL)
344 return options.music_directory;
346 checked_free(music_dir);
348 music_dir = getPath2(options.music_directory, music_subdir);
353 static char *getClassicArtworkSet(int type)
355 return (type == TREE_TYPE_GRAPHICS_DIR ? GFX_CLASSIC_SUBDIR :
356 type == TREE_TYPE_SOUNDS_DIR ? SND_CLASSIC_SUBDIR :
357 type == TREE_TYPE_MUSIC_DIR ? MUS_CLASSIC_SUBDIR : "");
360 static char *getClassicArtworkDir(int type)
362 return (type == TREE_TYPE_GRAPHICS_DIR ?
363 getDefaultGraphicsDir(GFX_CLASSIC_SUBDIR) :
364 type == TREE_TYPE_SOUNDS_DIR ?
365 getDefaultSoundsDir(SND_CLASSIC_SUBDIR) :
366 type == TREE_TYPE_MUSIC_DIR ?
367 getDefaultMusicDir(MUS_CLASSIC_SUBDIR) : "");
370 char *getUserGraphicsDir(void)
372 static char *usergraphics_dir = NULL;
374 if (usergraphics_dir == NULL)
375 usergraphics_dir = getPath2(getMainUserGameDataDir(), GRAPHICS_DIRECTORY);
377 return usergraphics_dir;
380 char *getUserSoundsDir(void)
382 static char *usersounds_dir = NULL;
384 if (usersounds_dir == NULL)
385 usersounds_dir = getPath2(getMainUserGameDataDir(), SOUNDS_DIRECTORY);
387 return usersounds_dir;
390 char *getUserMusicDir(void)
392 static char *usermusic_dir = NULL;
394 if (usermusic_dir == NULL)
395 usermusic_dir = getPath2(getMainUserGameDataDir(), MUSIC_DIRECTORY);
397 return usermusic_dir;
400 static char *getSetupArtworkDir(TreeInfo *ti)
402 static char *artwork_dir = NULL;
407 checked_free(artwork_dir);
409 artwork_dir = getPath2(ti->basepath, ti->fullpath);
414 char *setLevelArtworkDir(TreeInfo *ti)
416 char **artwork_path_ptr, **artwork_set_ptr;
417 TreeInfo *level_artwork;
419 if (ti == NULL || leveldir_current == NULL)
422 artwork_path_ptr = LEVELDIR_ARTWORK_PATH_PTR(leveldir_current, ti->type);
423 artwork_set_ptr = LEVELDIR_ARTWORK_SET_PTR( leveldir_current, ti->type);
425 checked_free(*artwork_path_ptr);
427 if ((level_artwork = getTreeInfoFromIdentifier(ti, *artwork_set_ptr)))
429 *artwork_path_ptr = getStringCopy(getSetupArtworkDir(level_artwork));
434 No (or non-existing) artwork configured in "levelinfo.conf". This would
435 normally result in using the artwork configured in the setup menu. But
436 if an artwork subdirectory exists (which might contain custom artwork
437 or an artwork configuration file), this level artwork must be treated
438 as relative to the default "classic" artwork, not to the artwork that
439 is currently configured in the setup menu.
441 Update: For "special" versions of R'n'D (like "R'n'D jue"), do not use
442 the "default" artwork (which would be "jue0" for "R'n'D jue"), but use
443 the real "classic" artwork from the original R'n'D (like "gfx_classic").
446 char *dir = getPath2(getCurrentLevelDir(), ARTWORK_DIRECTORY(ti->type));
448 checked_free(*artwork_set_ptr);
450 if (directoryExists(dir))
452 *artwork_path_ptr = getStringCopy(getClassicArtworkDir(ti->type));
453 *artwork_set_ptr = getStringCopy(getClassicArtworkSet(ti->type));
457 *artwork_path_ptr = getStringCopy(UNDEFINED_FILENAME);
458 *artwork_set_ptr = NULL;
464 return *artwork_set_ptr;
467 static char *getLevelArtworkSet(int type)
469 if (leveldir_current == NULL)
472 return LEVELDIR_ARTWORK_SET(leveldir_current, type);
475 static char *getLevelArtworkDir(int type)
477 if (leveldir_current == NULL)
478 return UNDEFINED_FILENAME;
480 return LEVELDIR_ARTWORK_PATH(leveldir_current, type);
483 char *getProgramMainDataPath(char *command_filename, char *base_path)
485 // check if the program's main data base directory is configured
486 if (!strEqual(base_path, "."))
487 return getStringCopy(base_path);
489 /* if the program is configured to start from current directory (default),
490 determine program package directory from program binary (some versions
491 of KDE/Konqueror and Mac OS X (especially "Mavericks") apparently do not
492 set the current working directory to the program package directory) */
493 char *main_data_path = getBasePath(command_filename);
495 #if defined(PLATFORM_MACOSX)
496 if (strSuffix(main_data_path, MAC_APP_BINARY_SUBDIR))
498 char *main_data_path_old = main_data_path;
500 // cut relative path to Mac OS X application binary directory from path
501 main_data_path[strlen(main_data_path) -
502 strlen(MAC_APP_BINARY_SUBDIR)] = '\0';
504 // cut trailing path separator from path (but not if path is root directory)
505 if (strSuffix(main_data_path, "/") && !strEqual(main_data_path, "/"))
506 main_data_path[strlen(main_data_path) - 1] = '\0';
508 // replace empty path with current directory
509 if (strEqual(main_data_path, ""))
510 main_data_path = ".";
512 // add relative path to Mac OS X application resources directory to path
513 main_data_path = getPath2(main_data_path, MAC_APP_FILES_SUBDIR);
515 free(main_data_path_old);
519 return main_data_path;
522 char *getProgramConfigFilename(char *command_filename)
524 static char *config_filename_1 = NULL;
525 static char *config_filename_2 = NULL;
526 static char *config_filename_3 = NULL;
527 static boolean initialized = FALSE;
531 char *command_filename_1 = getStringCopy(command_filename);
533 // strip trailing executable suffix from command filename
534 if (strSuffix(command_filename_1, ".exe"))
535 command_filename_1[strlen(command_filename_1) - 4] = '\0';
537 char *base_path = getProgramMainDataPath(command_filename, BASE_PATH);
538 char *conf_directory = getPath2(base_path, CONF_DIRECTORY);
540 char *command_basepath = getBasePath(command_filename);
541 char *command_basename = getBaseNameNoSuffix(command_filename);
542 char *command_filename_2 = getPath2(command_basepath, command_basename);
544 config_filename_1 = getStringCat2(command_filename_1, ".conf");
545 config_filename_2 = getStringCat2(command_filename_2, ".conf");
546 config_filename_3 = getPath2(conf_directory, SETUP_FILENAME);
548 checked_free(base_path);
549 checked_free(conf_directory);
551 checked_free(command_basepath);
552 checked_free(command_basename);
554 checked_free(command_filename_1);
555 checked_free(command_filename_2);
560 // 1st try: look for config file that exactly matches the binary filename
561 if (fileExists(config_filename_1))
562 return config_filename_1;
564 // 2nd try: look for config file that matches binary filename without suffix
565 if (fileExists(config_filename_2))
566 return config_filename_2;
568 // 3rd try: return setup config filename in global program config directory
569 return config_filename_3;
572 char *getTapeFilename(int nr)
574 static char *filename = NULL;
575 char basename[MAX_FILENAME_LEN];
577 checked_free(filename);
579 sprintf(basename, "%03d.%s", nr, TAPEFILE_EXTENSION);
580 filename = getPath2(getTapeDir(leveldir_current->subdir), basename);
585 char *getTemporaryTapeFilename(void)
587 static char *filename = NULL;
588 char basename[MAX_FILENAME_LEN];
590 checked_free(filename);
592 sprintf(basename, "tmp.%s", TAPEFILE_EXTENSION);
593 filename = getPath2(getTapeDir(NULL), basename);
598 char *getDefaultSolutionTapeFilename(int nr)
600 static char *filename = NULL;
601 char basename[MAX_FILENAME_LEN];
603 checked_free(filename);
605 sprintf(basename, "%03d.%s", nr, TAPEFILE_EXTENSION);
606 filename = getPath2(getSolutionTapeDir(), basename);
611 char *getSokobanSolutionTapeFilename(int nr)
613 static char *filename = NULL;
614 char basename[MAX_FILENAME_LEN];
616 checked_free(filename);
618 sprintf(basename, "%03d.sln", nr);
619 filename = getPath2(getSolutionTapeDir(), basename);
624 char *getSolutionTapeFilename(int nr)
626 char *filename = getDefaultSolutionTapeFilename(nr);
628 if (!fileExists(filename))
630 char *filename2 = getSokobanSolutionTapeFilename(nr);
632 if (fileExists(filename2))
639 char *getScoreFilename(int nr)
641 static char *filename = NULL;
642 char basename[MAX_FILENAME_LEN];
644 checked_free(filename);
646 sprintf(basename, "%03d.%s", nr, SCOREFILE_EXTENSION);
648 // used instead of "leveldir_current->subdir" (for network games)
649 filename = getPath2(getScoreDir(levelset.identifier), basename);
654 char *getScoreCacheFilename(int nr)
656 static char *filename = NULL;
657 char basename[MAX_FILENAME_LEN];
659 checked_free(filename);
661 sprintf(basename, "%03d.%s", nr, SCOREFILE_EXTENSION);
663 // used instead of "leveldir_current->subdir" (for network games)
664 filename = getPath2(getScoreCacheDir(levelset.identifier), basename);
669 char *getScoreTapeBasename(char *name)
671 static char basename[MAX_FILENAME_LEN];
672 char basename_raw[MAX_FILENAME_LEN];
675 sprintf(timestamp, "%s", getCurrentTimestamp());
676 sprintf(basename_raw, "%s-%s", timestamp, name);
677 sprintf(basename, "%s-%08x", timestamp, get_hash_from_key(basename_raw));
682 char *getScoreTapeFilename(char *basename_no_ext, int nr)
684 static char *filename = NULL;
685 char basename[MAX_FILENAME_LEN];
687 checked_free(filename);
689 sprintf(basename, "%s.%s", basename_no_ext, TAPEFILE_EXTENSION);
691 // used instead of "leveldir_current->subdir" (for network games)
692 filename = getPath2(getScoreTapeDir(levelset.identifier, nr), basename);
697 char *getSetupFilename(void)
699 static char *filename = NULL;
701 checked_free(filename);
703 filename = getPath2(getSetupDir(), SETUP_FILENAME);
708 char *getDefaultSetupFilename(void)
710 return program.config_filename;
713 char *getEditorSetupFilename(void)
715 static char *filename = NULL;
717 checked_free(filename);
718 filename = getPath2(getCurrentLevelDir(), EDITORSETUP_FILENAME);
720 if (fileExists(filename))
723 checked_free(filename);
724 filename = getPath2(getSetupDir(), EDITORSETUP_FILENAME);
729 char *getHelpAnimFilename(void)
731 static char *filename = NULL;
733 checked_free(filename);
735 filename = getPath2(getCurrentLevelDir(), HELPANIM_FILENAME);
740 char *getHelpTextFilename(void)
742 static char *filename = NULL;
744 checked_free(filename);
746 filename = getPath2(getCurrentLevelDir(), HELPTEXT_FILENAME);
751 char *getLevelSetInfoFilename(void)
753 static char *filename = NULL;
768 for (i = 0; basenames[i] != NULL; i++)
770 checked_free(filename);
771 filename = getPath2(getCurrentLevelDir(), basenames[i]);
773 if (fileExists(filename))
780 static char *getLevelSetTitleMessageBasename(int nr, boolean initial)
782 static char basename[32];
784 sprintf(basename, "%s_%d.txt",
785 (initial ? "titlemessage_initial" : "titlemessage"), nr + 1);
790 char *getLevelSetTitleMessageFilename(int nr, boolean initial)
792 static char *filename = NULL;
794 boolean skip_setup_artwork = FALSE;
796 checked_free(filename);
798 basename = getLevelSetTitleMessageBasename(nr, initial);
800 if (!gfx.override_level_graphics)
802 // 1st try: look for special artwork in current level series directory
803 filename = getPath3(getCurrentLevelDir(), GRAPHICS_DIRECTORY, basename);
804 if (fileExists(filename))
809 // 2nd try: look for message file in current level set directory
810 filename = getPath2(getCurrentLevelDir(), basename);
811 if (fileExists(filename))
816 // check if there is special artwork configured in level series config
817 if (getLevelArtworkSet(ARTWORK_TYPE_GRAPHICS) != NULL)
819 // 3rd try: look for special artwork configured in level series config
820 filename = getPath2(getLevelArtworkDir(ARTWORK_TYPE_GRAPHICS), basename);
821 if (fileExists(filename))
826 // take missing artwork configured in level set config from default
827 skip_setup_artwork = TRUE;
831 if (!skip_setup_artwork)
833 // 4th try: look for special artwork in configured artwork directory
834 filename = getPath2(getSetupArtworkDir(artwork.gfx_current), basename);
835 if (fileExists(filename))
841 // 5th try: look for default artwork in new default artwork directory
842 filename = getPath2(getDefaultGraphicsDir(GFX_DEFAULT_SUBDIR), basename);
843 if (fileExists(filename))
848 // 6th try: look for default artwork in old default artwork directory
849 filename = getPath2(options.graphics_directory, basename);
850 if (fileExists(filename))
853 return NULL; // cannot find specified artwork file anywhere
856 static char *getCorrectedArtworkBasename(char *basename)
861 char *getCustomImageFilename(char *basename)
863 static char *filename = NULL;
864 boolean skip_setup_artwork = FALSE;
866 checked_free(filename);
868 basename = getCorrectedArtworkBasename(basename);
870 if (!gfx.override_level_graphics)
872 // 1st try: look for special artwork in current level series directory
873 filename = getImg3(getCurrentLevelDir(), GRAPHICS_DIRECTORY, basename);
874 if (fileExists(filename))
879 // check if there is special artwork configured in level series config
880 if (getLevelArtworkSet(ARTWORK_TYPE_GRAPHICS) != NULL)
882 // 2nd try: look for special artwork configured in level series config
883 filename = getImg2(getLevelArtworkDir(ARTWORK_TYPE_GRAPHICS), basename);
884 if (fileExists(filename))
889 // take missing artwork configured in level set config from default
890 skip_setup_artwork = TRUE;
894 if (!skip_setup_artwork)
896 // 3rd try: look for special artwork in configured artwork directory
897 filename = getImg2(getSetupArtworkDir(artwork.gfx_current), basename);
898 if (fileExists(filename))
904 // 4th try: look for default artwork in new default artwork directory
905 filename = getImg2(getDefaultGraphicsDir(GFX_DEFAULT_SUBDIR), basename);
906 if (fileExists(filename))
911 // 5th try: look for default artwork in old default artwork directory
912 filename = getImg2(options.graphics_directory, basename);
913 if (fileExists(filename))
916 if (!strEqual(GFX_FALLBACK_FILENAME, UNDEFINED_FILENAME))
920 Warn("cannot find artwork file '%s' (using fallback)", basename);
922 // 6th try: look for fallback artwork in old default artwork directory
923 // (needed to prevent errors when trying to access unused artwork files)
924 filename = getImg2(options.graphics_directory, GFX_FALLBACK_FILENAME);
925 if (fileExists(filename))
929 return NULL; // cannot find specified artwork file anywhere
932 char *getCustomSoundFilename(char *basename)
934 static char *filename = NULL;
935 boolean skip_setup_artwork = FALSE;
937 checked_free(filename);
939 basename = getCorrectedArtworkBasename(basename);
941 if (!gfx.override_level_sounds)
943 // 1st try: look for special artwork in current level series directory
944 filename = getPath3(getCurrentLevelDir(), SOUNDS_DIRECTORY, basename);
945 if (fileExists(filename))
950 // check if there is special artwork configured in level series config
951 if (getLevelArtworkSet(ARTWORK_TYPE_SOUNDS) != NULL)
953 // 2nd try: look for special artwork configured in level series config
954 filename = getPath2(getLevelArtworkDir(TREE_TYPE_SOUNDS_DIR), basename);
955 if (fileExists(filename))
960 // take missing artwork configured in level set config from default
961 skip_setup_artwork = TRUE;
965 if (!skip_setup_artwork)
967 // 3rd try: look for special artwork in configured artwork directory
968 filename = getPath2(getSetupArtworkDir(artwork.snd_current), basename);
969 if (fileExists(filename))
975 // 4th try: look for default artwork in new default artwork directory
976 filename = getPath2(getDefaultSoundsDir(SND_DEFAULT_SUBDIR), basename);
977 if (fileExists(filename))
982 // 5th try: look for default artwork in old default artwork directory
983 filename = getPath2(options.sounds_directory, basename);
984 if (fileExists(filename))
987 if (!strEqual(SND_FALLBACK_FILENAME, UNDEFINED_FILENAME))
991 Warn("cannot find artwork file '%s' (using fallback)", basename);
993 // 6th try: look for fallback artwork in old default artwork directory
994 // (needed to prevent errors when trying to access unused artwork files)
995 filename = getPath2(options.sounds_directory, SND_FALLBACK_FILENAME);
996 if (fileExists(filename))
1000 return NULL; // cannot find specified artwork file anywhere
1003 char *getCustomMusicFilename(char *basename)
1005 static char *filename = NULL;
1006 boolean skip_setup_artwork = FALSE;
1008 checked_free(filename);
1010 basename = getCorrectedArtworkBasename(basename);
1012 if (!gfx.override_level_music)
1014 // 1st try: look for special artwork in current level series directory
1015 filename = getPath3(getCurrentLevelDir(), MUSIC_DIRECTORY, basename);
1016 if (fileExists(filename))
1021 // check if there is special artwork configured in level series config
1022 if (getLevelArtworkSet(ARTWORK_TYPE_MUSIC) != NULL)
1024 // 2nd try: look for special artwork configured in level series config
1025 filename = getPath2(getLevelArtworkDir(TREE_TYPE_MUSIC_DIR), basename);
1026 if (fileExists(filename))
1031 // take missing artwork configured in level set config from default
1032 skip_setup_artwork = TRUE;
1036 if (!skip_setup_artwork)
1038 // 3rd try: look for special artwork in configured artwork directory
1039 filename = getPath2(getSetupArtworkDir(artwork.mus_current), basename);
1040 if (fileExists(filename))
1046 // 4th try: look for default artwork in new default artwork directory
1047 filename = getPath2(getDefaultMusicDir(MUS_DEFAULT_SUBDIR), basename);
1048 if (fileExists(filename))
1053 // 5th try: look for default artwork in old default artwork directory
1054 filename = getPath2(options.music_directory, basename);
1055 if (fileExists(filename))
1058 if (!strEqual(MUS_FALLBACK_FILENAME, UNDEFINED_FILENAME))
1062 Warn("cannot find artwork file '%s' (using fallback)", basename);
1064 // 6th try: look for fallback artwork in old default artwork directory
1065 // (needed to prevent errors when trying to access unused artwork files)
1066 filename = getPath2(options.music_directory, MUS_FALLBACK_FILENAME);
1067 if (fileExists(filename))
1071 return NULL; // cannot find specified artwork file anywhere
1074 char *getCustomArtworkFilename(char *basename, int type)
1076 if (type == ARTWORK_TYPE_GRAPHICS)
1077 return getCustomImageFilename(basename);
1078 else if (type == ARTWORK_TYPE_SOUNDS)
1079 return getCustomSoundFilename(basename);
1080 else if (type == ARTWORK_TYPE_MUSIC)
1081 return getCustomMusicFilename(basename);
1083 return UNDEFINED_FILENAME;
1086 char *getCustomArtworkConfigFilename(int type)
1088 return getCustomArtworkFilename(ARTWORKINFO_FILENAME(type), type);
1091 char *getCustomArtworkLevelConfigFilename(int type)
1093 static char *filename = NULL;
1095 checked_free(filename);
1097 filename = getPath2(getLevelArtworkDir(type), ARTWORKINFO_FILENAME(type));
1102 char *getCustomMusicDirectory(void)
1104 static char *directory = NULL;
1105 boolean skip_setup_artwork = FALSE;
1107 checked_free(directory);
1109 if (!gfx.override_level_music)
1111 // 1st try: look for special artwork in current level series directory
1112 directory = getPath2(getCurrentLevelDir(), MUSIC_DIRECTORY);
1113 if (directoryExists(directory))
1118 // check if there is special artwork configured in level series config
1119 if (getLevelArtworkSet(ARTWORK_TYPE_MUSIC) != NULL)
1121 // 2nd try: look for special artwork configured in level series config
1122 directory = getStringCopy(getLevelArtworkDir(TREE_TYPE_MUSIC_DIR));
1123 if (directoryExists(directory))
1128 // take missing artwork configured in level set config from default
1129 skip_setup_artwork = TRUE;
1133 if (!skip_setup_artwork)
1135 // 3rd try: look for special artwork in configured artwork directory
1136 directory = getStringCopy(getSetupArtworkDir(artwork.mus_current));
1137 if (directoryExists(directory))
1143 // 4th try: look for default artwork in new default artwork directory
1144 directory = getStringCopy(getDefaultMusicDir(MUS_DEFAULT_SUBDIR));
1145 if (directoryExists(directory))
1150 // 5th try: look for default artwork in old default artwork directory
1151 directory = getStringCopy(options.music_directory);
1152 if (directoryExists(directory))
1155 return NULL; // cannot find specified artwork file anywhere
1158 void InitTapeDirectory(char *level_subdir)
1160 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
1161 createDirectory(getTapeDir(NULL), "main tape", PERMS_PRIVATE);
1162 createDirectory(getTapeDir(level_subdir), "level tape", PERMS_PRIVATE);
1165 void InitScoreDirectory(char *level_subdir)
1167 createDirectory(getMainUserGameDataDir(), "main user data", PERMS_PRIVATE);
1168 createDirectory(getScoreDir(NULL), "main score", PERMS_PRIVATE);
1169 createDirectory(getScoreDir(level_subdir), "level score", PERMS_PRIVATE);
1172 void InitScoreCacheDirectory(char *level_subdir)
1174 createDirectory(getMainUserGameDataDir(), "main user data", PERMS_PRIVATE);
1175 createDirectory(getCacheDir(), "cache data", PERMS_PRIVATE);
1176 createDirectory(getScoreCacheDir(NULL), "main score", PERMS_PRIVATE);
1177 createDirectory(getScoreCacheDir(level_subdir), "level score", PERMS_PRIVATE);
1180 void InitScoreTapeDirectory(char *level_subdir, int nr)
1182 InitScoreDirectory(level_subdir);
1184 createDirectory(getScoreTapeDir(level_subdir, nr), "score tape", PERMS_PRIVATE);
1187 static void SaveUserLevelInfo(void);
1189 void InitUserLevelDirectory(char *level_subdir)
1191 if (!directoryExists(getUserLevelDir(level_subdir)))
1193 createDirectory(getMainUserGameDataDir(), "main user data", PERMS_PRIVATE);
1194 createDirectory(getUserLevelDir(NULL), "main user level", PERMS_PRIVATE);
1195 createDirectory(getUserLevelDir(level_subdir), "user level", PERMS_PRIVATE);
1197 if (setup.internal.create_user_levelset)
1198 SaveUserLevelInfo();
1202 void InitNetworkLevelDirectory(char *level_subdir)
1204 if (!directoryExists(getNetworkLevelDir(level_subdir)))
1206 createDirectory(getMainUserGameDataDir(), "main user data", PERMS_PRIVATE);
1207 createDirectory(getNetworkDir(), "network data", PERMS_PRIVATE);
1208 createDirectory(getNetworkLevelDir(NULL), "main network level", PERMS_PRIVATE);
1209 createDirectory(getNetworkLevelDir(level_subdir), "network level", PERMS_PRIVATE);
1213 void InitLevelSetupDirectory(char *level_subdir)
1215 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
1216 createDirectory(getLevelSetupDir(NULL), "main level setup", PERMS_PRIVATE);
1217 createDirectory(getLevelSetupDir(level_subdir), "level setup", PERMS_PRIVATE);
1220 static void InitCacheDirectory(void)
1222 createDirectory(getMainUserGameDataDir(), "main user data", PERMS_PRIVATE);
1223 createDirectory(getCacheDir(), "cache data", PERMS_PRIVATE);
1227 // ----------------------------------------------------------------------------
1228 // some functions to handle lists of level and artwork directories
1229 // ----------------------------------------------------------------------------
1231 TreeInfo *newTreeInfo(void)
1233 return checked_calloc(sizeof(TreeInfo));
1236 TreeInfo *newTreeInfo_setDefaults(int type)
1238 TreeInfo *ti = newTreeInfo();
1240 setTreeInfoToDefaults(ti, type);
1245 void pushTreeInfo(TreeInfo **node_first, TreeInfo *node_new)
1247 node_new->next = *node_first;
1248 *node_first = node_new;
1251 void removeTreeInfo(TreeInfo **node_first)
1253 TreeInfo *node_old = *node_first;
1255 *node_first = node_old->next;
1256 node_old->next = NULL;
1258 freeTreeInfo(node_old);
1261 int numTreeInfo(TreeInfo *node)
1274 boolean validLevelSeries(TreeInfo *node)
1276 // in a number of cases, tree node is no valid level set
1277 if (node == NULL || node->node_group || node->parent_link || node->is_copy)
1283 TreeInfo *getValidLevelSeries(TreeInfo *node, TreeInfo *default_node)
1285 if (validLevelSeries(node))
1287 else if (node->is_copy)
1288 return getTreeInfoFromIdentifier(leveldir_first, node->identifier);
1290 return getFirstValidTreeInfoEntry(default_node);
1293 static TreeInfo *getValidTreeInfoEntryExt(TreeInfo *node, boolean get_next_node)
1298 if (node->node_group) // enter node group (step down into tree)
1299 return getFirstValidTreeInfoEntry(node->node_group);
1301 if (node->parent_link) // skip first node (back link) of node group
1302 get_next_node = TRUE;
1304 if (!get_next_node) // get current regular tree node
1307 // get next regular tree node, or step up until one is found
1308 while (node->next == NULL && node->node_parent != NULL)
1309 node = node->node_parent;
1311 return getFirstValidTreeInfoEntry(node->next);
1314 TreeInfo *getFirstValidTreeInfoEntry(TreeInfo *node)
1316 return getValidTreeInfoEntryExt(node, FALSE);
1319 TreeInfo *getNextValidTreeInfoEntry(TreeInfo *node)
1321 return getValidTreeInfoEntryExt(node, TRUE);
1324 TreeInfo *getTreeInfoFirstGroupEntry(TreeInfo *node)
1329 if (node->node_parent == NULL) // top level group
1330 return *node->node_top;
1331 else // sub level group
1332 return node->node_parent->node_group;
1335 int numTreeInfoInGroup(TreeInfo *node)
1337 return numTreeInfo(getTreeInfoFirstGroupEntry(node));
1340 int getPosFromTreeInfo(TreeInfo *node)
1342 TreeInfo *node_cmp = getTreeInfoFirstGroupEntry(node);
1347 if (node_cmp == node)
1351 node_cmp = node_cmp->next;
1357 TreeInfo *getTreeInfoFromPos(TreeInfo *node, int pos)
1359 TreeInfo *node_default = node;
1371 return node_default;
1374 static TreeInfo *getTreeInfoFromIdentifierExt(TreeInfo *node, char *identifier,
1375 int node_type_wanted)
1377 if (identifier == NULL)
1382 if (TREE_NODE_TYPE(node) == node_type_wanted &&
1383 strEqual(identifier, node->identifier))
1386 if (node->node_group)
1388 TreeInfo *node_group = getTreeInfoFromIdentifierExt(node->node_group,
1401 TreeInfo *getTreeInfoFromIdentifier(TreeInfo *node, char *identifier)
1403 return getTreeInfoFromIdentifierExt(node, identifier, TREE_NODE_TYPE_DEFAULT);
1406 static TreeInfo *cloneTreeNode(TreeInfo **node_top, TreeInfo *node_parent,
1407 TreeInfo *node, boolean skip_sets_without_levels)
1414 if (!node->parent_link && !node->level_group &&
1415 skip_sets_without_levels && node->levels == 0)
1416 return cloneTreeNode(node_top, node_parent, node->next,
1417 skip_sets_without_levels);
1419 node_new = getTreeInfoCopy(node); // copy complete node
1421 node_new->node_top = node_top; // correct top node link
1422 node_new->node_parent = node_parent; // correct parent node link
1424 if (node->level_group)
1425 node_new->node_group = cloneTreeNode(node_top, node_new, node->node_group,
1426 skip_sets_without_levels);
1428 node_new->next = cloneTreeNode(node_top, node_parent, node->next,
1429 skip_sets_without_levels);
1434 static void cloneTree(TreeInfo **ti_new, TreeInfo *ti, boolean skip_empty_sets)
1436 TreeInfo *ti_cloned = cloneTreeNode(ti_new, NULL, ti, skip_empty_sets);
1438 *ti_new = ti_cloned;
1441 static boolean adjustTreeGraphicsForEMC(TreeInfo *node)
1443 boolean settings_changed = FALSE;
1447 boolean want_ecs = (setup.prefer_aga_graphics == FALSE);
1448 boolean want_aga = (setup.prefer_aga_graphics == TRUE);
1449 boolean has_only_ecs = (!node->graphics_set && !node->graphics_set_aga);
1450 boolean has_only_aga = (!node->graphics_set && !node->graphics_set_ecs);
1451 char *graphics_set = NULL;
1453 if (node->graphics_set_ecs && (want_ecs || has_only_ecs))
1454 graphics_set = node->graphics_set_ecs;
1456 if (node->graphics_set_aga && (want_aga || has_only_aga))
1457 graphics_set = node->graphics_set_aga;
1459 if (graphics_set && !strEqual(node->graphics_set, graphics_set))
1461 setString(&node->graphics_set, graphics_set);
1462 settings_changed = TRUE;
1465 if (node->node_group != NULL)
1466 settings_changed |= adjustTreeGraphicsForEMC(node->node_group);
1471 return settings_changed;
1474 static boolean adjustTreeSoundsForEMC(TreeInfo *node)
1476 boolean settings_changed = FALSE;
1480 boolean want_default = (setup.prefer_lowpass_sounds == FALSE);
1481 boolean want_lowpass = (setup.prefer_lowpass_sounds == TRUE);
1482 boolean has_only_default = (!node->sounds_set && !node->sounds_set_lowpass);
1483 boolean has_only_lowpass = (!node->sounds_set && !node->sounds_set_default);
1484 char *sounds_set = NULL;
1486 if (node->sounds_set_default && (want_default || has_only_default))
1487 sounds_set = node->sounds_set_default;
1489 if (node->sounds_set_lowpass && (want_lowpass || has_only_lowpass))
1490 sounds_set = node->sounds_set_lowpass;
1492 if (sounds_set && !strEqual(node->sounds_set, sounds_set))
1494 setString(&node->sounds_set, sounds_set);
1495 settings_changed = TRUE;
1498 if (node->node_group != NULL)
1499 settings_changed |= adjustTreeSoundsForEMC(node->node_group);
1504 return settings_changed;
1507 int dumpTreeInfo(TreeInfo *node, int depth)
1509 char bullet_list[] = { '-', '*', 'o' };
1510 int num_leaf_nodes = 0;
1514 Debug("tree", "Dumping TreeInfo:");
1518 char bullet = bullet_list[depth % ARRAY_SIZE(bullet_list)];
1520 for (i = 0; i < depth * 2; i++)
1521 DebugContinued("", " ");
1523 DebugContinued("tree", "%c '%s' ['%s] [PARENT: '%s'] %s\n",
1524 bullet, node->name, node->identifier,
1525 (node->node_parent ? node->node_parent->identifier : "-"),
1526 (node->node_group ? "[GROUP]" :
1527 node->is_copy ? "[COPY]" : ""));
1529 if (!node->node_group && !node->parent_link)
1533 // use for dumping artwork info tree
1534 Debug("tree", "subdir == '%s' ['%s', '%s'] [%d])",
1535 node->subdir, node->fullpath, node->basepath, node->in_user_dir);
1538 if (node->node_group != NULL)
1539 num_leaf_nodes += dumpTreeInfo(node->node_group, depth + 1);
1545 Debug("tree", "Summary: %d leaf nodes found", num_leaf_nodes);
1547 return num_leaf_nodes;
1550 void sortTreeInfoBySortFunction(TreeInfo **node_first,
1551 int (*compare_function)(const void *,
1554 int num_nodes = numTreeInfo(*node_first);
1555 TreeInfo **sort_array;
1556 TreeInfo *node = *node_first;
1562 // allocate array for sorting structure pointers
1563 sort_array = checked_calloc(num_nodes * sizeof(TreeInfo *));
1565 // writing structure pointers to sorting array
1566 while (i < num_nodes && node) // double boundary check...
1568 sort_array[i] = node;
1574 // sorting the structure pointers in the sorting array
1575 qsort(sort_array, num_nodes, sizeof(TreeInfo *),
1578 // update the linkage of list elements with the sorted node array
1579 for (i = 0; i < num_nodes - 1; i++)
1580 sort_array[i]->next = sort_array[i + 1];
1581 sort_array[num_nodes - 1]->next = NULL;
1583 // update the linkage of the main list anchor pointer
1584 *node_first = sort_array[0];
1588 // now recursively sort the level group structures
1592 if (node->node_group != NULL)
1593 sortTreeInfoBySortFunction(&node->node_group, compare_function);
1599 void sortTreeInfo(TreeInfo **node_first)
1601 sortTreeInfoBySortFunction(node_first, compareTreeInfoEntries);
1605 // ============================================================================
1606 // some stuff from "files.c"
1607 // ============================================================================
1609 #if defined(PLATFORM_WIN32)
1611 #define S_IRGRP S_IRUSR
1614 #define S_IROTH S_IRUSR
1617 #define S_IWGRP S_IWUSR
1620 #define S_IWOTH S_IWUSR
1623 #define S_IXGRP S_IXUSR
1626 #define S_IXOTH S_IXUSR
1629 #define S_IRWXG (S_IRGRP | S_IWGRP | S_IXGRP)
1634 #endif // PLATFORM_WIN32
1636 // file permissions for newly written files
1637 #define MODE_R_ALL (S_IRUSR | S_IRGRP | S_IROTH)
1638 #define MODE_W_ALL (S_IWUSR | S_IWGRP | S_IWOTH)
1639 #define MODE_X_ALL (S_IXUSR | S_IXGRP | S_IXOTH)
1641 #define MODE_W_PRIVATE (S_IWUSR)
1642 #define MODE_W_PUBLIC_FILE (S_IWUSR | S_IWGRP)
1643 #define MODE_W_PUBLIC_DIR (S_IWUSR | S_IWGRP | S_ISGID)
1645 #define DIR_PERMS_PRIVATE (MODE_R_ALL | MODE_X_ALL | MODE_W_PRIVATE)
1646 #define DIR_PERMS_PUBLIC (MODE_R_ALL | MODE_X_ALL | MODE_W_PUBLIC_DIR)
1647 #define DIR_PERMS_PUBLIC_ALL (MODE_R_ALL | MODE_X_ALL | MODE_W_ALL)
1649 #define FILE_PERMS_PRIVATE (MODE_R_ALL | MODE_W_PRIVATE)
1650 #define FILE_PERMS_PUBLIC (MODE_R_ALL | MODE_W_PUBLIC_FILE)
1651 #define FILE_PERMS_PUBLIC_ALL (MODE_R_ALL | MODE_W_ALL)
1654 char *getHomeDir(void)
1656 static char *dir = NULL;
1658 #if defined(PLATFORM_WIN32)
1661 dir = checked_malloc(MAX_PATH + 1);
1663 if (!SUCCEEDED(SHGetFolderPath(NULL, CSIDL_PERSONAL, NULL, 0, dir)))
1666 #elif defined(PLATFORM_EMSCRIPTEN)
1667 dir = "/persistent";
1668 #elif defined(PLATFORM_UNIX)
1671 if ((dir = getenv("HOME")) == NULL)
1673 dir = getUnixHomeDir();
1676 dir = getStringCopy(dir);
1688 char *getPersonalDataDir(void)
1690 static char *personal_data_dir = NULL;
1692 #if defined(PLATFORM_MACOSX)
1693 if (personal_data_dir == NULL)
1694 personal_data_dir = getPath2(getHomeDir(), "Documents");
1696 if (personal_data_dir == NULL)
1697 personal_data_dir = getHomeDir();
1700 return personal_data_dir;
1703 char *getMainUserGameDataDir(void)
1705 static char *main_user_data_dir = NULL;
1707 #if defined(PLATFORM_ANDROID)
1708 if (main_user_data_dir == NULL)
1709 main_user_data_dir = (char *)(SDL_AndroidGetExternalStorageState() &
1710 SDL_ANDROID_EXTERNAL_STORAGE_WRITE ?
1711 SDL_AndroidGetExternalStoragePath() :
1712 SDL_AndroidGetInternalStoragePath());
1714 if (main_user_data_dir == NULL)
1715 main_user_data_dir = getPath2(getPersonalDataDir(),
1716 program.userdata_subdir);
1719 return main_user_data_dir;
1722 char *getUserGameDataDir(void)
1725 return getMainUserGameDataDir();
1727 return getUserDir(user.nr);
1730 char *getSetupDir(void)
1732 return getUserGameDataDir();
1735 static mode_t posix_umask(mode_t mask)
1737 #if defined(PLATFORM_UNIX)
1744 static int posix_mkdir(const char *pathname, mode_t mode)
1746 #if defined(PLATFORM_WIN32)
1747 return mkdir(pathname);
1749 return mkdir(pathname, mode);
1753 static boolean posix_process_running_setgid(void)
1755 #if defined(PLATFORM_UNIX)
1756 return (getgid() != getegid());
1762 void createDirectory(char *dir, char *text, int permission_class)
1764 if (directoryExists(dir))
1767 // leave "other" permissions in umask untouched, but ensure group parts
1768 // of USERDATA_DIR_MODE are not masked
1769 mode_t dir_mode = (permission_class == PERMS_PRIVATE ?
1770 DIR_PERMS_PRIVATE : DIR_PERMS_PUBLIC);
1771 mode_t last_umask = posix_umask(0);
1772 mode_t group_umask = ~(dir_mode & S_IRWXG);
1773 int running_setgid = posix_process_running_setgid();
1775 if (permission_class == PERMS_PUBLIC)
1777 // if we're setgid, protect files against "other"
1778 // else keep umask(0) to make the dir world-writable
1781 posix_umask(last_umask & group_umask);
1783 dir_mode = DIR_PERMS_PUBLIC_ALL;
1786 if (posix_mkdir(dir, dir_mode) != 0)
1787 Warn("cannot create %s directory '%s': %s", text, dir, strerror(errno));
1789 if (permission_class == PERMS_PUBLIC && !running_setgid)
1790 chmod(dir, dir_mode);
1792 posix_umask(last_umask); // restore previous umask
1795 void InitMainUserDataDirectory(void)
1797 createDirectory(getMainUserGameDataDir(), "main user data", PERMS_PRIVATE);
1800 void InitUserDataDirectory(void)
1802 createDirectory(getMainUserGameDataDir(), "main user data", PERMS_PRIVATE);
1806 createDirectory(getUserDir(-1), "users", PERMS_PRIVATE);
1807 createDirectory(getUserDir(user.nr), "user data", PERMS_PRIVATE);
1811 void SetFilePermissions(char *filename, int permission_class)
1813 int running_setgid = posix_process_running_setgid();
1814 int perms = (permission_class == PERMS_PRIVATE ?
1815 FILE_PERMS_PRIVATE : FILE_PERMS_PUBLIC);
1817 if (permission_class == PERMS_PUBLIC && !running_setgid)
1818 perms = FILE_PERMS_PUBLIC_ALL;
1820 chmod(filename, perms);
1823 char *getCookie(char *file_type)
1825 static char cookie[MAX_COOKIE_LEN + 1];
1827 if (strlen(program.cookie_prefix) + 1 +
1828 strlen(file_type) + strlen("_FILE_VERSION_x.x") > MAX_COOKIE_LEN)
1829 return "[COOKIE ERROR]"; // should never happen
1831 sprintf(cookie, "%s_%s_FILE_VERSION_%d.%d",
1832 program.cookie_prefix, file_type,
1833 program.version_super, program.version_major);
1838 void fprintFileHeader(FILE *file, char *basename)
1840 char *prefix = "# ";
1843 fprintf_line_with_prefix(file, prefix, sep1, 77);
1844 fprintf(file, "%s%s\n", prefix, basename);
1845 fprintf_line_with_prefix(file, prefix, sep1, 77);
1846 fprintf(file, "\n");
1849 int getFileVersionFromCookieString(const char *cookie)
1851 const char *ptr_cookie1, *ptr_cookie2;
1852 const char *pattern1 = "_FILE_VERSION_";
1853 const char *pattern2 = "?.?";
1854 const int len_cookie = strlen(cookie);
1855 const int len_pattern1 = strlen(pattern1);
1856 const int len_pattern2 = strlen(pattern2);
1857 const int len_pattern = len_pattern1 + len_pattern2;
1858 int version_super, version_major;
1860 if (len_cookie <= len_pattern)
1863 ptr_cookie1 = &cookie[len_cookie - len_pattern];
1864 ptr_cookie2 = &cookie[len_cookie - len_pattern2];
1866 if (strncmp(ptr_cookie1, pattern1, len_pattern1) != 0)
1869 if (ptr_cookie2[0] < '0' || ptr_cookie2[0] > '9' ||
1870 ptr_cookie2[1] != '.' ||
1871 ptr_cookie2[2] < '0' || ptr_cookie2[2] > '9')
1874 version_super = ptr_cookie2[0] - '0';
1875 version_major = ptr_cookie2[2] - '0';
1877 return VERSION_IDENT(version_super, version_major, 0, 0);
1880 boolean checkCookieString(const char *cookie, const char *template)
1882 const char *pattern = "_FILE_VERSION_?.?";
1883 const int len_cookie = strlen(cookie);
1884 const int len_template = strlen(template);
1885 const int len_pattern = strlen(pattern);
1887 if (len_cookie != len_template)
1890 if (strncmp(cookie, template, len_cookie - len_pattern) != 0)
1897 // ----------------------------------------------------------------------------
1898 // setup file list and hash handling functions
1899 // ----------------------------------------------------------------------------
1901 char *getFormattedSetupEntry(char *token, char *value)
1904 static char entry[MAX_LINE_LEN];
1906 // if value is an empty string, just return token without value
1910 // start with the token and some spaces to format output line
1911 sprintf(entry, "%s:", token);
1912 for (i = strlen(entry); i < token_value_position; i++)
1915 // continue with the token's value
1916 strcat(entry, value);
1921 SetupFileList *newSetupFileList(char *token, char *value)
1923 SetupFileList *new = checked_malloc(sizeof(SetupFileList));
1925 new->token = getStringCopy(token);
1926 new->value = getStringCopy(value);
1933 void freeSetupFileList(SetupFileList *list)
1938 checked_free(list->token);
1939 checked_free(list->value);
1942 freeSetupFileList(list->next);
1947 char *getListEntry(SetupFileList *list, char *token)
1952 if (strEqual(list->token, token))
1955 return getListEntry(list->next, token);
1958 SetupFileList *setListEntry(SetupFileList *list, char *token, char *value)
1963 if (strEqual(list->token, token))
1965 checked_free(list->value);
1967 list->value = getStringCopy(value);
1971 else if (list->next == NULL)
1972 return (list->next = newSetupFileList(token, value));
1974 return setListEntry(list->next, token, value);
1977 SetupFileList *addListEntry(SetupFileList *list, char *token, char *value)
1982 if (list->next == NULL)
1983 return (list->next = newSetupFileList(token, value));
1985 return addListEntry(list->next, token, value);
1988 #if ENABLE_UNUSED_CODE
1990 static void printSetupFileList(SetupFileList *list)
1995 Debug("setup:printSetupFileList", "token: '%s'", list->token);
1996 Debug("setup:printSetupFileList", "value: '%s'", list->value);
1998 printSetupFileList(list->next);
2004 DEFINE_HASHTABLE_INSERT(insert_hash_entry, char, char);
2005 DEFINE_HASHTABLE_SEARCH(search_hash_entry, char, char);
2006 DEFINE_HASHTABLE_CHANGE(change_hash_entry, char, char);
2007 DEFINE_HASHTABLE_REMOVE(remove_hash_entry, char, char);
2009 #define insert_hash_entry hashtable_insert
2010 #define search_hash_entry hashtable_search
2011 #define change_hash_entry hashtable_change
2012 #define remove_hash_entry hashtable_remove
2015 unsigned int get_hash_from_key(void *key)
2020 This algorithm (k=33) was first reported by Dan Bernstein many years ago in
2021 'comp.lang.c'. Another version of this algorithm (now favored by Bernstein)
2022 uses XOR: hash(i) = hash(i - 1) * 33 ^ str[i]; the magic of number 33 (why
2023 it works better than many other constants, prime or not) has never been
2024 adequately explained.
2026 If you just want to have a good hash function, and cannot wait, djb2
2027 is one of the best string hash functions i know. It has excellent
2028 distribution and speed on many different sets of keys and table sizes.
2029 You are not likely to do better with one of the "well known" functions
2030 such as PJW, K&R, etc.
2032 Ozan (oz) Yigit [http://www.cs.yorku.ca/~oz/hash.html]
2035 char *str = (char *)key;
2036 unsigned int hash = 5381;
2039 while ((c = *str++))
2040 hash = ((hash << 5) + hash) + c; // hash * 33 + c
2045 static int keys_are_equal(void *key1, void *key2)
2047 return (strEqual((char *)key1, (char *)key2));
2050 SetupFileHash *newSetupFileHash(void)
2052 SetupFileHash *new_hash =
2053 create_hashtable(16, 0.75, get_hash_from_key, keys_are_equal);
2055 if (new_hash == NULL)
2056 Fail("create_hashtable() failed -- out of memory");
2061 void freeSetupFileHash(SetupFileHash *hash)
2066 hashtable_destroy(hash, 1); // 1 == also free values stored in hash
2069 char *getHashEntry(SetupFileHash *hash, char *token)
2074 return search_hash_entry(hash, token);
2077 void setHashEntry(SetupFileHash *hash, char *token, char *value)
2084 value_copy = getStringCopy(value);
2086 // change value; if it does not exist, insert it as new
2087 if (!change_hash_entry(hash, token, value_copy))
2088 if (!insert_hash_entry(hash, getStringCopy(token), value_copy))
2089 Fail("cannot insert into hash -- aborting");
2092 char *removeHashEntry(SetupFileHash *hash, char *token)
2097 return remove_hash_entry(hash, token);
2100 #if ENABLE_UNUSED_CODE
2102 static void printSetupFileHash(SetupFileHash *hash)
2104 BEGIN_HASH_ITERATION(hash, itr)
2106 Debug("setup:printSetupFileHash", "token: '%s'", HASH_ITERATION_TOKEN(itr));
2107 Debug("setup:printSetupFileHash", "value: '%s'", HASH_ITERATION_VALUE(itr));
2109 END_HASH_ITERATION(hash, itr)
2114 #define ALLOW_TOKEN_VALUE_SEPARATOR_BEING_WHITESPACE 1
2115 #define CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING 0
2116 #define CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH 0
2118 static boolean token_value_separator_found = FALSE;
2119 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2120 static boolean token_value_separator_warning = FALSE;
2122 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2123 static boolean token_already_exists_warning = FALSE;
2126 static boolean getTokenValueFromSetupLineExt(char *line,
2127 char **token_ptr, char **value_ptr,
2128 char *filename, char *line_raw,
2130 boolean separator_required)
2132 static char line_copy[MAX_LINE_LEN + 1], line_raw_copy[MAX_LINE_LEN + 1];
2133 char *token, *value, *line_ptr;
2135 // when externally invoked via ReadTokenValueFromLine(), copy line buffers
2136 if (line_raw == NULL)
2138 strncpy(line_copy, line, MAX_LINE_LEN);
2139 line_copy[MAX_LINE_LEN] = '\0';
2142 strcpy(line_raw_copy, line_copy);
2143 line_raw = line_raw_copy;
2146 // cut trailing comment from input line
2147 for (line_ptr = line; *line_ptr; line_ptr++)
2149 if (*line_ptr == '#')
2156 // cut trailing whitespaces from input line
2157 for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
2158 if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
2161 // ignore empty lines
2165 // cut leading whitespaces from token
2166 for (token = line; *token; token++)
2167 if (*token != ' ' && *token != '\t')
2170 // start with empty value as reliable default
2173 token_value_separator_found = FALSE;
2175 // find end of token to determine start of value
2176 for (line_ptr = token; *line_ptr; line_ptr++)
2178 // first look for an explicit token/value separator, like ':' or '='
2179 if (*line_ptr == ':' || *line_ptr == '=')
2181 *line_ptr = '\0'; // terminate token string
2182 value = line_ptr + 1; // set beginning of value
2184 token_value_separator_found = TRUE;
2190 #if ALLOW_TOKEN_VALUE_SEPARATOR_BEING_WHITESPACE
2191 // fallback: if no token/value separator found, also allow whitespaces
2192 if (!token_value_separator_found && !separator_required)
2194 for (line_ptr = token; *line_ptr; line_ptr++)
2196 if (*line_ptr == ' ' || *line_ptr == '\t')
2198 *line_ptr = '\0'; // terminate token string
2199 value = line_ptr + 1; // set beginning of value
2201 token_value_separator_found = TRUE;
2207 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2208 if (token_value_separator_found)
2210 if (!token_value_separator_warning)
2212 Debug("setup", "---");
2214 if (filename != NULL)
2216 Debug("setup", "missing token/value separator(s) in config file:");
2217 Debug("setup", "- config file: '%s'", filename);
2221 Debug("setup", "missing token/value separator(s):");
2224 token_value_separator_warning = TRUE;
2227 if (filename != NULL)
2228 Debug("setup", "- line %d: '%s'", line_nr, line_raw);
2230 Debug("setup", "- line: '%s'", line_raw);
2236 // cut trailing whitespaces from token
2237 for (line_ptr = &token[strlen(token)]; line_ptr >= token; line_ptr--)
2238 if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
2241 // cut leading whitespaces from value
2242 for (; *value; value++)
2243 if (*value != ' ' && *value != '\t')
2252 boolean getTokenValueFromSetupLine(char *line, char **token, char **value)
2254 // while the internal (old) interface does not require a token/value
2255 // separator (for downwards compatibility with existing files which
2256 // don't use them), it is mandatory for the external (new) interface
2258 return getTokenValueFromSetupLineExt(line, token, value, NULL, NULL, 0, TRUE);
2261 static boolean loadSetupFileData(void *setup_file_data, char *filename,
2262 boolean top_recursion_level, boolean is_hash)
2264 static SetupFileHash *include_filename_hash = NULL;
2265 char line[MAX_LINE_LEN], line_raw[MAX_LINE_LEN], previous_line[MAX_LINE_LEN];
2266 char *token, *value, *line_ptr;
2267 void *insert_ptr = NULL;
2268 boolean read_continued_line = FALSE;
2270 int line_nr = 0, token_count = 0, include_count = 0;
2272 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2273 token_value_separator_warning = FALSE;
2276 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2277 token_already_exists_warning = FALSE;
2280 if (!(file = openFile(filename, MODE_READ)))
2282 #if DEBUG_NO_CONFIG_FILE
2283 Debug("setup", "cannot open configuration file '%s'", filename);
2289 // use "insert pointer" to store list end for constant insertion complexity
2291 insert_ptr = setup_file_data;
2293 // on top invocation, create hash to mark included files (to prevent loops)
2294 if (top_recursion_level)
2295 include_filename_hash = newSetupFileHash();
2297 // mark this file as already included (to prevent including it again)
2298 setHashEntry(include_filename_hash, getBaseNamePtr(filename), "true");
2300 while (!checkEndOfFile(file))
2302 // read next line of input file
2303 if (!getStringFromFile(file, line, MAX_LINE_LEN))
2306 // check if line was completely read and is terminated by line break
2307 if (strlen(line) > 0 && line[strlen(line) - 1] == '\n')
2310 // cut trailing line break (this can be newline and/or carriage return)
2311 for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
2312 if ((*line_ptr == '\n' || *line_ptr == '\r') && *(line_ptr + 1) == '\0')
2315 // copy raw input line for later use (mainly debugging output)
2316 strcpy(line_raw, line);
2318 if (read_continued_line)
2320 // append new line to existing line, if there is enough space
2321 if (strlen(previous_line) + strlen(line_ptr) < MAX_LINE_LEN)
2322 strcat(previous_line, line_ptr);
2324 strcpy(line, previous_line); // copy storage buffer to line
2326 read_continued_line = FALSE;
2329 // if the last character is '\', continue at next line
2330 if (strlen(line) > 0 && line[strlen(line) - 1] == '\\')
2332 line[strlen(line) - 1] = '\0'; // cut off trailing backslash
2333 strcpy(previous_line, line); // copy line to storage buffer
2335 read_continued_line = TRUE;
2340 if (!getTokenValueFromSetupLineExt(line, &token, &value, filename,
2341 line_raw, line_nr, FALSE))
2346 if (strEqual(token, "include"))
2348 if (getHashEntry(include_filename_hash, value) == NULL)
2350 char *basepath = getBasePath(filename);
2351 char *basename = getBaseName(value);
2352 char *filename_include = getPath2(basepath, basename);
2354 loadSetupFileData(setup_file_data, filename_include, FALSE, is_hash);
2358 free(filename_include);
2364 Warn("ignoring already processed file '%s'", value);
2371 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2373 getHashEntry((SetupFileHash *)setup_file_data, token);
2375 if (old_value != NULL)
2377 if (!token_already_exists_warning)
2379 Debug("setup", "---");
2380 Debug("setup", "duplicate token(s) found in config file:");
2381 Debug("setup", "- config file: '%s'", filename);
2383 token_already_exists_warning = TRUE;
2386 Debug("setup", "- token: '%s' (in line %d)", token, line_nr);
2387 Debug("setup", " old value: '%s'", old_value);
2388 Debug("setup", " new value: '%s'", value);
2392 setHashEntry((SetupFileHash *)setup_file_data, token, value);
2396 insert_ptr = addListEntry((SetupFileList *)insert_ptr, token, value);
2406 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2407 if (token_value_separator_warning)
2408 Debug("setup", "---");
2411 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2412 if (token_already_exists_warning)
2413 Debug("setup", "---");
2416 if (token_count == 0 && include_count == 0)
2417 Warn("configuration file '%s' is empty", filename);
2419 if (top_recursion_level)
2420 freeSetupFileHash(include_filename_hash);
2425 static int compareSetupFileData(const void *object1, const void *object2)
2427 const struct ConfigInfo *entry1 = (struct ConfigInfo *)object1;
2428 const struct ConfigInfo *entry2 = (struct ConfigInfo *)object2;
2430 return strcmp(entry1->token, entry2->token);
2433 static void saveSetupFileHash(SetupFileHash *hash, char *filename)
2435 int item_count = hashtable_count(hash);
2436 int item_size = sizeof(struct ConfigInfo);
2437 struct ConfigInfo *sort_array = checked_malloc(item_count * item_size);
2441 // copy string pointers from hash to array
2442 BEGIN_HASH_ITERATION(hash, itr)
2444 sort_array[i].token = HASH_ITERATION_TOKEN(itr);
2445 sort_array[i].value = HASH_ITERATION_VALUE(itr);
2449 if (i > item_count) // should never happen
2452 END_HASH_ITERATION(hash, itr)
2454 // sort string pointers from hash in array
2455 qsort(sort_array, item_count, item_size, compareSetupFileData);
2457 if (!(file = fopen(filename, MODE_WRITE)))
2459 Warn("cannot write configuration file '%s'", filename);
2464 fprintf(file, "%s\n\n", getFormattedSetupEntry("program.version",
2465 program.version_string));
2466 for (i = 0; i < item_count; i++)
2467 fprintf(file, "%s\n", getFormattedSetupEntry(sort_array[i].token,
2468 sort_array[i].value));
2471 checked_free(sort_array);
2474 SetupFileList *loadSetupFileList(char *filename)
2476 SetupFileList *setup_file_list = newSetupFileList("", "");
2477 SetupFileList *first_valid_list_entry;
2479 if (!loadSetupFileData(setup_file_list, filename, TRUE, FALSE))
2481 freeSetupFileList(setup_file_list);
2486 first_valid_list_entry = setup_file_list->next;
2488 // free empty list header
2489 setup_file_list->next = NULL;
2490 freeSetupFileList(setup_file_list);
2492 return first_valid_list_entry;
2495 SetupFileHash *loadSetupFileHash(char *filename)
2497 SetupFileHash *setup_file_hash = newSetupFileHash();
2499 if (!loadSetupFileData(setup_file_hash, filename, TRUE, TRUE))
2501 freeSetupFileHash(setup_file_hash);
2506 return setup_file_hash;
2510 // ============================================================================
2512 // ============================================================================
2514 #define TOKEN_STR_LAST_LEVEL_SERIES "last_level_series"
2515 #define TOKEN_STR_LAST_PLAYED_LEVEL "last_played_level"
2516 #define TOKEN_STR_HANDICAP_LEVEL "handicap_level"
2517 #define TOKEN_STR_LAST_USER "last_user"
2519 // level directory info
2520 #define LEVELINFO_TOKEN_IDENTIFIER 0
2521 #define LEVELINFO_TOKEN_NAME 1
2522 #define LEVELINFO_TOKEN_NAME_SORTING 2
2523 #define LEVELINFO_TOKEN_AUTHOR 3
2524 #define LEVELINFO_TOKEN_YEAR 4
2525 #define LEVELINFO_TOKEN_PROGRAM_TITLE 5
2526 #define LEVELINFO_TOKEN_PROGRAM_COPYRIGHT 6
2527 #define LEVELINFO_TOKEN_PROGRAM_COMPANY 7
2528 #define LEVELINFO_TOKEN_IMPORTED_FROM 8
2529 #define LEVELINFO_TOKEN_IMPORTED_BY 9
2530 #define LEVELINFO_TOKEN_TESTED_BY 10
2531 #define LEVELINFO_TOKEN_LEVELS 11
2532 #define LEVELINFO_TOKEN_FIRST_LEVEL 12
2533 #define LEVELINFO_TOKEN_SORT_PRIORITY 13
2534 #define LEVELINFO_TOKEN_LATEST_ENGINE 14
2535 #define LEVELINFO_TOKEN_LEVEL_GROUP 15
2536 #define LEVELINFO_TOKEN_READONLY 16
2537 #define LEVELINFO_TOKEN_GRAPHICS_SET_ECS 17
2538 #define LEVELINFO_TOKEN_GRAPHICS_SET_AGA 18
2539 #define LEVELINFO_TOKEN_GRAPHICS_SET 19
2540 #define LEVELINFO_TOKEN_SOUNDS_SET_DEFAULT 20
2541 #define LEVELINFO_TOKEN_SOUNDS_SET_LOWPASS 21
2542 #define LEVELINFO_TOKEN_SOUNDS_SET 22
2543 #define LEVELINFO_TOKEN_MUSIC_SET 23
2544 #define LEVELINFO_TOKEN_FILENAME 24
2545 #define LEVELINFO_TOKEN_FILETYPE 25
2546 #define LEVELINFO_TOKEN_SPECIAL_FLAGS 26
2547 #define LEVELINFO_TOKEN_HANDICAP 27
2548 #define LEVELINFO_TOKEN_SKIP_LEVELS 28
2549 #define LEVELINFO_TOKEN_USE_EMC_TILES 29
2551 #define NUM_LEVELINFO_TOKENS 30
2553 static LevelDirTree ldi;
2555 static struct TokenInfo levelinfo_tokens[] =
2557 // level directory info
2558 { TYPE_STRING, &ldi.identifier, "identifier" },
2559 { TYPE_STRING, &ldi.name, "name" },
2560 { TYPE_STRING, &ldi.name_sorting, "name_sorting" },
2561 { TYPE_STRING, &ldi.author, "author" },
2562 { TYPE_STRING, &ldi.year, "year" },
2563 { TYPE_STRING, &ldi.program_title, "program_title" },
2564 { TYPE_STRING, &ldi.program_copyright, "program_copyright" },
2565 { TYPE_STRING, &ldi.program_company, "program_company" },
2566 { TYPE_STRING, &ldi.imported_from, "imported_from" },
2567 { TYPE_STRING, &ldi.imported_by, "imported_by" },
2568 { TYPE_STRING, &ldi.tested_by, "tested_by" },
2569 { TYPE_INTEGER, &ldi.levels, "levels" },
2570 { TYPE_INTEGER, &ldi.first_level, "first_level" },
2571 { TYPE_INTEGER, &ldi.sort_priority, "sort_priority" },
2572 { TYPE_BOOLEAN, &ldi.latest_engine, "latest_engine" },
2573 { TYPE_BOOLEAN, &ldi.level_group, "level_group" },
2574 { TYPE_BOOLEAN, &ldi.readonly, "readonly" },
2575 { TYPE_STRING, &ldi.graphics_set_ecs, "graphics_set.ecs" },
2576 { TYPE_STRING, &ldi.graphics_set_aga, "graphics_set.aga" },
2577 { TYPE_STRING, &ldi.graphics_set, "graphics_set" },
2578 { TYPE_STRING, &ldi.sounds_set_default,"sounds_set.default" },
2579 { TYPE_STRING, &ldi.sounds_set_lowpass,"sounds_set.lowpass" },
2580 { TYPE_STRING, &ldi.sounds_set, "sounds_set" },
2581 { TYPE_STRING, &ldi.music_set, "music_set" },
2582 { TYPE_STRING, &ldi.level_filename, "filename" },
2583 { TYPE_STRING, &ldi.level_filetype, "filetype" },
2584 { TYPE_STRING, &ldi.special_flags, "special_flags" },
2585 { TYPE_BOOLEAN, &ldi.handicap, "handicap" },
2586 { TYPE_BOOLEAN, &ldi.skip_levels, "skip_levels" },
2587 { TYPE_BOOLEAN, &ldi.use_emc_tiles, "use_emc_tiles" }
2590 static struct TokenInfo artworkinfo_tokens[] =
2592 // artwork directory info
2593 { TYPE_STRING, &ldi.identifier, "identifier" },
2594 { TYPE_STRING, &ldi.subdir, "subdir" },
2595 { TYPE_STRING, &ldi.name, "name" },
2596 { TYPE_STRING, &ldi.name_sorting, "name_sorting" },
2597 { TYPE_STRING, &ldi.author, "author" },
2598 { TYPE_STRING, &ldi.program_title, "program_title" },
2599 { TYPE_STRING, &ldi.program_copyright, "program_copyright" },
2600 { TYPE_STRING, &ldi.program_company, "program_company" },
2601 { TYPE_INTEGER, &ldi.sort_priority, "sort_priority" },
2602 { TYPE_STRING, &ldi.basepath, "basepath" },
2603 { TYPE_STRING, &ldi.fullpath, "fullpath" },
2604 { TYPE_BOOLEAN, &ldi.in_user_dir, "in_user_dir" },
2605 { TYPE_STRING, &ldi.class_desc, "class_desc" },
2610 static char *optional_tokens[] =
2613 "program_copyright",
2619 static void setTreeInfoToDefaults(TreeInfo *ti, int type)
2623 ti->node_top = (ti->type == TREE_TYPE_LEVEL_DIR ? &leveldir_first :
2624 ti->type == TREE_TYPE_GRAPHICS_DIR ? &artwork.gfx_first :
2625 ti->type == TREE_TYPE_SOUNDS_DIR ? &artwork.snd_first :
2626 ti->type == TREE_TYPE_MUSIC_DIR ? &artwork.mus_first :
2629 ti->node_parent = NULL;
2630 ti->node_group = NULL;
2637 ti->fullpath = NULL;
2638 ti->basepath = NULL;
2639 ti->identifier = NULL;
2640 ti->name = getStringCopy(ANONYMOUS_NAME);
2641 ti->name_sorting = NULL;
2642 ti->author = getStringCopy(ANONYMOUS_NAME);
2645 ti->program_title = NULL;
2646 ti->program_copyright = NULL;
2647 ti->program_company = NULL;
2649 ti->sort_priority = LEVELCLASS_UNDEFINED; // default: least priority
2650 ti->latest_engine = FALSE; // default: get from level
2651 ti->parent_link = FALSE;
2652 ti->is_copy = FALSE;
2653 ti->in_user_dir = FALSE;
2654 ti->user_defined = FALSE;
2656 ti->class_desc = NULL;
2658 ti->infotext = getStringCopy(TREE_INFOTEXT(ti->type));
2660 if (ti->type == TREE_TYPE_LEVEL_DIR)
2662 ti->imported_from = NULL;
2663 ti->imported_by = NULL;
2664 ti->tested_by = NULL;
2666 ti->graphics_set_ecs = NULL;
2667 ti->graphics_set_aga = NULL;
2668 ti->graphics_set = NULL;
2669 ti->sounds_set_default = NULL;
2670 ti->sounds_set_lowpass = NULL;
2671 ti->sounds_set = NULL;
2672 ti->music_set = NULL;
2673 ti->graphics_path = getStringCopy(UNDEFINED_FILENAME);
2674 ti->sounds_path = getStringCopy(UNDEFINED_FILENAME);
2675 ti->music_path = getStringCopy(UNDEFINED_FILENAME);
2677 ti->level_filename = NULL;
2678 ti->level_filetype = NULL;
2680 ti->special_flags = NULL;
2683 ti->first_level = 0;
2685 ti->level_group = FALSE;
2686 ti->handicap_level = 0;
2687 ti->readonly = TRUE;
2688 ti->handicap = TRUE;
2689 ti->skip_levels = FALSE;
2691 ti->use_emc_tiles = FALSE;
2695 static void setTreeInfoToDefaultsFromParent(TreeInfo *ti, TreeInfo *parent)
2699 Warn("setTreeInfoToDefaultsFromParent(): parent == NULL");
2701 setTreeInfoToDefaults(ti, TREE_TYPE_UNDEFINED);
2706 // copy all values from the parent structure
2708 ti->type = parent->type;
2710 ti->node_top = parent->node_top;
2711 ti->node_parent = parent;
2712 ti->node_group = NULL;
2719 ti->fullpath = NULL;
2720 ti->basepath = NULL;
2721 ti->identifier = NULL;
2722 ti->name = getStringCopy(ANONYMOUS_NAME);
2723 ti->name_sorting = NULL;
2724 ti->author = getStringCopy(parent->author);
2725 ti->year = getStringCopy(parent->year);
2727 ti->program_title = getStringCopy(parent->program_title);
2728 ti->program_copyright = getStringCopy(parent->program_copyright);
2729 ti->program_company = getStringCopy(parent->program_company);
2731 ti->sort_priority = parent->sort_priority;
2732 ti->latest_engine = parent->latest_engine;
2733 ti->parent_link = FALSE;
2734 ti->is_copy = FALSE;
2735 ti->in_user_dir = parent->in_user_dir;
2736 ti->user_defined = parent->user_defined;
2737 ti->color = parent->color;
2738 ti->class_desc = getStringCopy(parent->class_desc);
2740 ti->infotext = getStringCopy(parent->infotext);
2742 if (ti->type == TREE_TYPE_LEVEL_DIR)
2744 ti->imported_from = getStringCopy(parent->imported_from);
2745 ti->imported_by = getStringCopy(parent->imported_by);
2746 ti->tested_by = getStringCopy(parent->tested_by);
2748 ti->graphics_set_ecs = getStringCopy(parent->graphics_set_ecs);
2749 ti->graphics_set_aga = getStringCopy(parent->graphics_set_aga);
2750 ti->graphics_set = getStringCopy(parent->graphics_set);
2751 ti->sounds_set_default = getStringCopy(parent->sounds_set_default);
2752 ti->sounds_set_lowpass = getStringCopy(parent->sounds_set_lowpass);
2753 ti->sounds_set = getStringCopy(parent->sounds_set);
2754 ti->music_set = getStringCopy(parent->music_set);
2755 ti->graphics_path = getStringCopy(UNDEFINED_FILENAME);
2756 ti->sounds_path = getStringCopy(UNDEFINED_FILENAME);
2757 ti->music_path = getStringCopy(UNDEFINED_FILENAME);
2759 ti->level_filename = getStringCopy(parent->level_filename);
2760 ti->level_filetype = getStringCopy(parent->level_filetype);
2762 ti->special_flags = getStringCopy(parent->special_flags);
2764 ti->levels = parent->levels;
2765 ti->first_level = parent->first_level;
2766 ti->last_level = parent->last_level;
2767 ti->level_group = FALSE;
2768 ti->handicap_level = parent->handicap_level;
2769 ti->readonly = parent->readonly;
2770 ti->handicap = parent->handicap;
2771 ti->skip_levels = parent->skip_levels;
2773 ti->use_emc_tiles = parent->use_emc_tiles;
2777 static TreeInfo *getTreeInfoCopy(TreeInfo *ti)
2779 TreeInfo *ti_copy = newTreeInfo();
2781 // copy all values from the original structure
2783 ti_copy->type = ti->type;
2785 ti_copy->node_top = ti->node_top;
2786 ti_copy->node_parent = ti->node_parent;
2787 ti_copy->node_group = ti->node_group;
2788 ti_copy->next = ti->next;
2790 ti_copy->cl_first = ti->cl_first;
2791 ti_copy->cl_cursor = ti->cl_cursor;
2793 ti_copy->subdir = getStringCopy(ti->subdir);
2794 ti_copy->fullpath = getStringCopy(ti->fullpath);
2795 ti_copy->basepath = getStringCopy(ti->basepath);
2796 ti_copy->identifier = getStringCopy(ti->identifier);
2797 ti_copy->name = getStringCopy(ti->name);
2798 ti_copy->name_sorting = getStringCopy(ti->name_sorting);
2799 ti_copy->author = getStringCopy(ti->author);
2800 ti_copy->year = getStringCopy(ti->year);
2802 ti_copy->program_title = getStringCopy(ti->program_title);
2803 ti_copy->program_copyright = getStringCopy(ti->program_copyright);
2804 ti_copy->program_company = getStringCopy(ti->program_company);
2806 ti_copy->imported_from = getStringCopy(ti->imported_from);
2807 ti_copy->imported_by = getStringCopy(ti->imported_by);
2808 ti_copy->tested_by = getStringCopy(ti->tested_by);
2810 ti_copy->graphics_set_ecs = getStringCopy(ti->graphics_set_ecs);
2811 ti_copy->graphics_set_aga = getStringCopy(ti->graphics_set_aga);
2812 ti_copy->graphics_set = getStringCopy(ti->graphics_set);
2813 ti_copy->sounds_set_default = getStringCopy(ti->sounds_set_default);
2814 ti_copy->sounds_set_lowpass = getStringCopy(ti->sounds_set_lowpass);
2815 ti_copy->sounds_set = getStringCopy(ti->sounds_set);
2816 ti_copy->music_set = getStringCopy(ti->music_set);
2817 ti_copy->graphics_path = getStringCopy(ti->graphics_path);
2818 ti_copy->sounds_path = getStringCopy(ti->sounds_path);
2819 ti_copy->music_path = getStringCopy(ti->music_path);
2821 ti_copy->level_filename = getStringCopy(ti->level_filename);
2822 ti_copy->level_filetype = getStringCopy(ti->level_filetype);
2824 ti_copy->special_flags = getStringCopy(ti->special_flags);
2826 ti_copy->levels = ti->levels;
2827 ti_copy->first_level = ti->first_level;
2828 ti_copy->last_level = ti->last_level;
2829 ti_copy->sort_priority = ti->sort_priority;
2831 ti_copy->latest_engine = ti->latest_engine;
2833 ti_copy->level_group = ti->level_group;
2834 ti_copy->parent_link = ti->parent_link;
2835 ti_copy->is_copy = ti->is_copy;
2836 ti_copy->in_user_dir = ti->in_user_dir;
2837 ti_copy->user_defined = ti->user_defined;
2838 ti_copy->readonly = ti->readonly;
2839 ti_copy->handicap = ti->handicap;
2840 ti_copy->skip_levels = ti->skip_levels;
2842 ti_copy->use_emc_tiles = ti->use_emc_tiles;
2844 ti_copy->color = ti->color;
2845 ti_copy->class_desc = getStringCopy(ti->class_desc);
2846 ti_copy->handicap_level = ti->handicap_level;
2848 ti_copy->infotext = getStringCopy(ti->infotext);
2853 void freeTreeInfo(TreeInfo *ti)
2858 checked_free(ti->subdir);
2859 checked_free(ti->fullpath);
2860 checked_free(ti->basepath);
2861 checked_free(ti->identifier);
2863 checked_free(ti->name);
2864 checked_free(ti->name_sorting);
2865 checked_free(ti->author);
2866 checked_free(ti->year);
2868 checked_free(ti->program_title);
2869 checked_free(ti->program_copyright);
2870 checked_free(ti->program_company);
2872 checked_free(ti->class_desc);
2874 checked_free(ti->infotext);
2876 if (ti->type == TREE_TYPE_LEVEL_DIR)
2878 checked_free(ti->imported_from);
2879 checked_free(ti->imported_by);
2880 checked_free(ti->tested_by);
2882 checked_free(ti->graphics_set_ecs);
2883 checked_free(ti->graphics_set_aga);
2884 checked_free(ti->graphics_set);
2885 checked_free(ti->sounds_set_default);
2886 checked_free(ti->sounds_set_lowpass);
2887 checked_free(ti->sounds_set);
2888 checked_free(ti->music_set);
2890 checked_free(ti->graphics_path);
2891 checked_free(ti->sounds_path);
2892 checked_free(ti->music_path);
2894 checked_free(ti->level_filename);
2895 checked_free(ti->level_filetype);
2897 checked_free(ti->special_flags);
2900 // recursively free child node
2902 freeTreeInfo(ti->node_group);
2904 // recursively free next node
2906 freeTreeInfo(ti->next);
2911 void setSetupInfo(struct TokenInfo *token_info,
2912 int token_nr, char *token_value)
2914 int token_type = token_info[token_nr].type;
2915 void *setup_value = token_info[token_nr].value;
2917 if (token_value == NULL)
2920 // set setup field to corresponding token value
2925 *(boolean *)setup_value = get_boolean_from_string(token_value);
2929 *(int *)setup_value = get_switch3_from_string(token_value);
2933 *(Key *)setup_value = getKeyFromKeyName(token_value);
2937 *(Key *)setup_value = getKeyFromX11KeyName(token_value);
2941 *(int *)setup_value = get_integer_from_string(token_value);
2945 checked_free(*(char **)setup_value);
2946 *(char **)setup_value = getStringCopy(token_value);
2950 *(int *)setup_value = get_player_nr_from_string(token_value);
2958 static int compareTreeInfoEntries(const void *object1, const void *object2)
2960 const TreeInfo *entry1 = *((TreeInfo **)object1);
2961 const TreeInfo *entry2 = *((TreeInfo **)object2);
2962 int tree_sorting1 = TREE_SORTING(entry1);
2963 int tree_sorting2 = TREE_SORTING(entry2);
2965 if (tree_sorting1 != tree_sorting2)
2966 return (tree_sorting1 - tree_sorting2);
2968 return strcasecmp(entry1->name_sorting, entry2->name_sorting);
2971 static TreeInfo *createParentTreeInfoNode(TreeInfo *node_parent)
2975 if (node_parent == NULL)
2978 ti_new = newTreeInfo();
2979 setTreeInfoToDefaults(ti_new, node_parent->type);
2981 ti_new->node_parent = node_parent;
2982 ti_new->parent_link = TRUE;
2984 setString(&ti_new->identifier, node_parent->identifier);
2985 setString(&ti_new->name, BACKLINK_TEXT_PARENT);
2986 setString(&ti_new->name_sorting, ti_new->name);
2988 setString(&ti_new->subdir, STRING_PARENT_DIRECTORY);
2989 setString(&ti_new->fullpath, node_parent->fullpath);
2991 ti_new->sort_priority = LEVELCLASS_PARENT;
2992 ti_new->latest_engine = node_parent->latest_engine;
2994 setString(&ti_new->class_desc, getLevelClassDescription(ti_new));
2996 pushTreeInfo(&node_parent->node_group, ti_new);
3001 static TreeInfo *createTopTreeInfoNode(TreeInfo *node_first)
3003 if (node_first == NULL)
3006 TreeInfo *ti_new = newTreeInfo();
3007 int type = node_first->type;
3009 setTreeInfoToDefaults(ti_new, type);
3011 ti_new->node_parent = NULL;
3012 ti_new->parent_link = FALSE;
3014 setString(&ti_new->identifier, "top_tree_node");
3015 setString(&ti_new->name, TREE_INFOTEXT(type));
3016 setString(&ti_new->name_sorting, ti_new->name);
3018 setString(&ti_new->subdir, STRING_TOP_DIRECTORY);
3019 setString(&ti_new->fullpath, ".");
3021 ti_new->sort_priority = LEVELCLASS_TOP;
3022 ti_new->latest_engine = node_first->latest_engine;
3024 setString(&ti_new->class_desc, TREE_INFOTEXT(type));
3026 ti_new->node_group = node_first;
3027 ti_new->level_group = TRUE;
3029 TreeInfo *ti_new2 = createParentTreeInfoNode(ti_new);
3031 setString(&ti_new2->name, TREE_BACKLINK_TEXT(type));
3032 setString(&ti_new2->name_sorting, ti_new2->name);
3037 static void setTreeInfoParentNodes(TreeInfo *node, TreeInfo *node_parent)
3041 if (node->node_group)
3042 setTreeInfoParentNodes(node->node_group, node);
3044 node->node_parent = node_parent;
3051 // ----------------------------------------------------------------------------
3052 // functions for handling level and custom artwork info cache
3053 // ----------------------------------------------------------------------------
3055 static void LoadArtworkInfoCache(void)
3057 InitCacheDirectory();
3059 if (artworkinfo_cache_old == NULL)
3061 char *filename = getPath2(getCacheDir(), ARTWORKINFO_CACHE_FILE);
3063 // try to load artwork info hash from already existing cache file
3064 artworkinfo_cache_old = loadSetupFileHash(filename);
3066 // try to get program version that artwork info cache was written with
3067 char *version = getHashEntry(artworkinfo_cache_old, "program.version");
3069 // check program version of artwork info cache against current version
3070 if (!strEqual(version, program.version_string))
3072 freeSetupFileHash(artworkinfo_cache_old);
3074 artworkinfo_cache_old = NULL;
3077 // if no artwork info cache file was found, start with empty hash
3078 if (artworkinfo_cache_old == NULL)
3079 artworkinfo_cache_old = newSetupFileHash();
3084 if (artworkinfo_cache_new == NULL)
3085 artworkinfo_cache_new = newSetupFileHash();
3087 update_artworkinfo_cache = FALSE;
3090 static void SaveArtworkInfoCache(void)
3092 if (!update_artworkinfo_cache)
3095 char *filename = getPath2(getCacheDir(), ARTWORKINFO_CACHE_FILE);
3097 InitCacheDirectory();
3099 saveSetupFileHash(artworkinfo_cache_new, filename);
3104 static char *getCacheTokenPrefix(char *prefix1, char *prefix2)
3106 static char *prefix = NULL;
3108 checked_free(prefix);
3110 prefix = getStringCat2WithSeparator(prefix1, prefix2, ".");
3115 // (identical to above function, but separate string buffer needed -- nasty)
3116 static char *getCacheToken(char *prefix, char *suffix)
3118 static char *token = NULL;
3120 checked_free(token);
3122 token = getStringCat2WithSeparator(prefix, suffix, ".");
3127 static char *getFileTimestampString(char *filename)
3129 return getStringCopy(i_to_a(getFileTimestampEpochSeconds(filename)));
3132 static boolean modifiedFileTimestamp(char *filename, char *timestamp_string)
3134 struct stat file_status;
3136 if (timestamp_string == NULL)
3139 if (!fileExists(filename)) // file does not exist
3140 return (atoi(timestamp_string) != 0);
3142 if (stat(filename, &file_status) != 0) // cannot stat file
3145 return (file_status.st_mtime != atoi(timestamp_string));
3148 static TreeInfo *getArtworkInfoCacheEntry(LevelDirTree *level_node, int type)
3150 char *identifier = level_node->subdir;
3151 char *type_string = ARTWORK_DIRECTORY(type);
3152 char *token_prefix = getCacheTokenPrefix(type_string, identifier);
3153 char *token_main = getCacheToken(token_prefix, "CACHED");
3154 char *cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
3155 boolean cached = (cache_entry != NULL && strEqual(cache_entry, "true"));
3156 TreeInfo *artwork_info = NULL;
3158 if (!use_artworkinfo_cache)
3161 if (optional_tokens_hash == NULL)
3165 // create hash from list of optional tokens (for quick access)
3166 optional_tokens_hash = newSetupFileHash();
3167 for (i = 0; optional_tokens[i] != NULL; i++)
3168 setHashEntry(optional_tokens_hash, optional_tokens[i], "");
3175 artwork_info = newTreeInfo();
3176 setTreeInfoToDefaults(artwork_info, type);
3178 // set all structure fields according to the token/value pairs
3179 ldi = *artwork_info;
3180 for (i = 0; artworkinfo_tokens[i].type != -1; i++)
3182 char *token_suffix = artworkinfo_tokens[i].text;
3183 char *token = getCacheToken(token_prefix, token_suffix);
3184 char *value = getHashEntry(artworkinfo_cache_old, token);
3186 (getHashEntry(optional_tokens_hash, token_suffix) != NULL);
3188 setSetupInfo(artworkinfo_tokens, i, value);
3190 // check if cache entry for this item is mandatory, but missing
3191 if (value == NULL && !optional)
3193 Warn("missing cache entry '%s'", token);
3199 *artwork_info = ldi;
3204 char *filename_levelinfo = getPath2(getLevelDirFromTreeInfo(level_node),
3205 LEVELINFO_FILENAME);
3206 char *filename_artworkinfo = getPath2(getSetupArtworkDir(artwork_info),
3207 ARTWORKINFO_FILENAME(type));
3209 // check if corresponding "levelinfo.conf" file has changed
3210 token_main = getCacheToken(token_prefix, "TIMESTAMP_LEVELINFO");
3211 cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
3213 if (modifiedFileTimestamp(filename_levelinfo, cache_entry))
3216 // check if corresponding "<artworkinfo>.conf" file has changed
3217 token_main = getCacheToken(token_prefix, "TIMESTAMP_ARTWORKINFO");
3218 cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
3220 if (modifiedFileTimestamp(filename_artworkinfo, cache_entry))
3223 checked_free(filename_levelinfo);
3224 checked_free(filename_artworkinfo);
3227 if (!cached && artwork_info != NULL)
3229 freeTreeInfo(artwork_info);
3234 return artwork_info;
3237 static void setArtworkInfoCacheEntry(TreeInfo *artwork_info,
3238 LevelDirTree *level_node, int type)
3240 char *identifier = level_node->subdir;
3241 char *type_string = ARTWORK_DIRECTORY(type);
3242 char *token_prefix = getCacheTokenPrefix(type_string, identifier);
3243 char *token_main = getCacheToken(token_prefix, "CACHED");
3244 boolean set_cache_timestamps = TRUE;
3247 setHashEntry(artworkinfo_cache_new, token_main, "true");
3249 if (set_cache_timestamps)
3251 char *filename_levelinfo = getPath2(getLevelDirFromTreeInfo(level_node),
3252 LEVELINFO_FILENAME);
3253 char *filename_artworkinfo = getPath2(getSetupArtworkDir(artwork_info),
3254 ARTWORKINFO_FILENAME(type));
3255 char *timestamp_levelinfo = getFileTimestampString(filename_levelinfo);
3256 char *timestamp_artworkinfo = getFileTimestampString(filename_artworkinfo);
3258 token_main = getCacheToken(token_prefix, "TIMESTAMP_LEVELINFO");
3259 setHashEntry(artworkinfo_cache_new, token_main, timestamp_levelinfo);
3261 token_main = getCacheToken(token_prefix, "TIMESTAMP_ARTWORKINFO");
3262 setHashEntry(artworkinfo_cache_new, token_main, timestamp_artworkinfo);
3264 checked_free(filename_levelinfo);
3265 checked_free(filename_artworkinfo);
3266 checked_free(timestamp_levelinfo);
3267 checked_free(timestamp_artworkinfo);
3270 ldi = *artwork_info;
3271 for (i = 0; artworkinfo_tokens[i].type != -1; i++)
3273 char *token = getCacheToken(token_prefix, artworkinfo_tokens[i].text);
3274 char *value = getSetupValue(artworkinfo_tokens[i].type,
3275 artworkinfo_tokens[i].value);
3277 setHashEntry(artworkinfo_cache_new, token, value);
3282 // ----------------------------------------------------------------------------
3283 // functions for loading level info and custom artwork info
3284 // ----------------------------------------------------------------------------
3286 int GetZipFileTreeType(char *zip_filename)
3288 static char *top_dir_path = NULL;
3289 static char *top_dir_conf_filename[NUM_BASE_TREE_TYPES] = { NULL };
3290 static char *conf_basename[NUM_BASE_TREE_TYPES] =
3292 GRAPHICSINFO_FILENAME,
3293 SOUNDSINFO_FILENAME,
3299 checked_free(top_dir_path);
3300 top_dir_path = NULL;
3302 for (j = 0; j < NUM_BASE_TREE_TYPES; j++)
3304 checked_free(top_dir_conf_filename[j]);
3305 top_dir_conf_filename[j] = NULL;
3308 char **zip_entries = zip_list(zip_filename);
3310 // check if zip file successfully opened
3311 if (zip_entries == NULL || zip_entries[0] == NULL)
3312 return TREE_TYPE_UNDEFINED;
3314 // first zip file entry is expected to be top level directory
3315 char *top_dir = zip_entries[0];
3317 // check if valid top level directory found in zip file
3318 if (!strSuffix(top_dir, "/"))
3319 return TREE_TYPE_UNDEFINED;
3321 // get filenames of valid configuration files in top level directory
3322 for (j = 0; j < NUM_BASE_TREE_TYPES; j++)
3323 top_dir_conf_filename[j] = getStringCat2(top_dir, conf_basename[j]);
3325 int tree_type = TREE_TYPE_UNDEFINED;
3328 while (zip_entries[e] != NULL)
3330 // check if every zip file entry is below top level directory
3331 if (!strPrefix(zip_entries[e], top_dir))
3332 return TREE_TYPE_UNDEFINED;
3334 // check if this zip file entry is a valid configuration filename
3335 for (j = 0; j < NUM_BASE_TREE_TYPES; j++)
3337 if (strEqual(zip_entries[e], top_dir_conf_filename[j]))
3339 // only exactly one valid configuration file allowed
3340 if (tree_type != TREE_TYPE_UNDEFINED)
3341 return TREE_TYPE_UNDEFINED;
3353 static boolean CheckZipFileForDirectory(char *zip_filename, char *directory,
3356 static char *top_dir_path = NULL;
3357 static char *top_dir_conf_filename = NULL;
3359 checked_free(top_dir_path);
3360 checked_free(top_dir_conf_filename);
3362 top_dir_path = NULL;
3363 top_dir_conf_filename = NULL;
3365 char *conf_basename = (tree_type == TREE_TYPE_LEVEL_DIR ? LEVELINFO_FILENAME :
3366 ARTWORKINFO_FILENAME(tree_type));
3368 // check if valid configuration filename determined
3369 if (conf_basename == NULL || strEqual(conf_basename, ""))
3372 char **zip_entries = zip_list(zip_filename);
3374 // check if zip file successfully opened
3375 if (zip_entries == NULL || zip_entries[0] == NULL)
3378 // first zip file entry is expected to be top level directory
3379 char *top_dir = zip_entries[0];
3381 // check if valid top level directory found in zip file
3382 if (!strSuffix(top_dir, "/"))
3385 // get path of extracted top level directory
3386 top_dir_path = getPath2(directory, top_dir);
3388 // remove trailing directory separator from top level directory path
3389 // (required to be able to check for file and directory in next step)
3390 top_dir_path[strlen(top_dir_path) - 1] = '\0';
3392 // check if zip file's top level directory already exists in target directory
3393 if (fileExists(top_dir_path)) // (checks for file and directory)
3396 // get filename of configuration file in top level directory
3397 top_dir_conf_filename = getStringCat2(top_dir, conf_basename);
3399 boolean found_top_dir_conf_filename = FALSE;
3402 while (zip_entries[i] != NULL)
3404 // check if every zip file entry is below top level directory
3405 if (!strPrefix(zip_entries[i], top_dir))
3408 // check if this zip file entry is the configuration filename
3409 if (strEqual(zip_entries[i], top_dir_conf_filename))
3410 found_top_dir_conf_filename = TRUE;
3415 // check if valid configuration filename was found in zip file
3416 if (!found_top_dir_conf_filename)
3422 char *ExtractZipFileIntoDirectory(char *zip_filename, char *directory,
3425 boolean zip_file_valid = CheckZipFileForDirectory(zip_filename, directory,
3428 if (!zip_file_valid)
3430 Warn("zip file '%s' rejected!", zip_filename);
3435 char **zip_entries = zip_extract(zip_filename, directory);
3437 if (zip_entries == NULL)
3439 Warn("zip file '%s' could not be extracted!", zip_filename);
3444 Info("zip file '%s' successfully extracted!", zip_filename);
3446 // first zip file entry contains top level directory
3447 char *top_dir = zip_entries[0];
3449 // remove trailing directory separator from top level directory
3450 top_dir[strlen(top_dir) - 1] = '\0';
3455 static void ProcessZipFilesInDirectory(char *directory, int tree_type)
3458 DirectoryEntry *dir_entry;
3460 if ((dir = openDirectory(directory)) == NULL)
3462 // display error if directory is main "options.graphics_directory" etc.
3463 if (tree_type == TREE_TYPE_LEVEL_DIR ||
3464 directory == OPTIONS_ARTWORK_DIRECTORY(tree_type))
3465 Warn("cannot read directory '%s'", directory);
3470 while ((dir_entry = readDirectory(dir)) != NULL) // loop all entries
3472 // skip non-zip files (and also directories with zip extension)
3473 if (!strSuffixLower(dir_entry->basename, ".zip") || dir_entry->is_directory)
3476 char *zip_filename = getPath2(directory, dir_entry->basename);
3477 char *zip_filename_extracted = getStringCat2(zip_filename, ".extracted");
3478 char *zip_filename_rejected = getStringCat2(zip_filename, ".rejected");
3480 // check if zip file hasn't already been extracted or rejected
3481 if (!fileExists(zip_filename_extracted) &&
3482 !fileExists(zip_filename_rejected))
3484 char *top_dir = ExtractZipFileIntoDirectory(zip_filename, directory,
3486 char *marker_filename = (top_dir != NULL ? zip_filename_extracted :
3487 zip_filename_rejected);
3490 // create empty file to mark zip file as extracted or rejected
3491 if ((marker_file = fopen(marker_filename, MODE_WRITE)))
3492 fclose(marker_file);
3495 free(zip_filename_extracted);
3496 free(zip_filename_rejected);
3500 closeDirectory(dir);
3503 // forward declaration for recursive call by "LoadLevelInfoFromLevelDir()"
3504 static void LoadLevelInfoFromLevelDir(TreeInfo **, TreeInfo *, char *);
3506 static boolean LoadLevelInfoFromLevelConf(TreeInfo **node_first,
3507 TreeInfo *node_parent,
3508 char *level_directory,
3509 char *directory_name)
3511 char *directory_path = getPath2(level_directory, directory_name);
3512 char *filename = getPath2(directory_path, LEVELINFO_FILENAME);
3513 SetupFileHash *setup_file_hash;
3514 LevelDirTree *leveldir_new = NULL;
3517 // unless debugging, silently ignore directories without "levelinfo.conf"
3518 if (!options.debug && !fileExists(filename))
3520 free(directory_path);
3526 setup_file_hash = loadSetupFileHash(filename);
3528 if (setup_file_hash == NULL)
3530 #if DEBUG_NO_CONFIG_FILE
3531 Debug("setup", "ignoring level directory '%s'", directory_path);
3534 free(directory_path);
3540 leveldir_new = newTreeInfo();
3543 setTreeInfoToDefaultsFromParent(leveldir_new, node_parent);
3545 setTreeInfoToDefaults(leveldir_new, TREE_TYPE_LEVEL_DIR);
3547 leveldir_new->subdir = getStringCopy(directory_name);
3549 // set all structure fields according to the token/value pairs
3550 ldi = *leveldir_new;
3551 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
3552 setSetupInfo(levelinfo_tokens, i,
3553 getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
3554 *leveldir_new = ldi;
3556 if (strEqual(leveldir_new->name, ANONYMOUS_NAME))
3557 setString(&leveldir_new->name, leveldir_new->subdir);
3559 if (leveldir_new->identifier == NULL)
3560 leveldir_new->identifier = getStringCopy(leveldir_new->subdir);
3562 if (leveldir_new->name_sorting == NULL)
3563 leveldir_new->name_sorting = getStringCopy(leveldir_new->name);
3565 if (node_parent == NULL) // top level group
3567 leveldir_new->basepath = getStringCopy(level_directory);
3568 leveldir_new->fullpath = getStringCopy(leveldir_new->subdir);
3570 else // sub level group
3572 leveldir_new->basepath = getStringCopy(node_parent->basepath);
3573 leveldir_new->fullpath = getPath2(node_parent->fullpath, directory_name);
3576 leveldir_new->last_level =
3577 leveldir_new->first_level + leveldir_new->levels - 1;
3579 leveldir_new->in_user_dir =
3580 (!strEqual(leveldir_new->basepath, options.level_directory));
3582 // adjust some settings if user's private level directory was detected
3583 if (leveldir_new->sort_priority == LEVELCLASS_UNDEFINED &&
3584 leveldir_new->in_user_dir &&
3585 (strEqual(leveldir_new->subdir, getLoginName()) ||
3586 strEqual(leveldir_new->name, getLoginName()) ||
3587 strEqual(leveldir_new->author, getRealName())))
3589 leveldir_new->sort_priority = LEVELCLASS_PRIVATE_START;
3590 leveldir_new->readonly = FALSE;
3593 leveldir_new->user_defined =
3594 (leveldir_new->in_user_dir && IS_LEVELCLASS_PRIVATE(leveldir_new));
3596 setString(&leveldir_new->class_desc, getLevelClassDescription(leveldir_new));
3598 leveldir_new->handicap_level = // set handicap to default value
3599 (leveldir_new->user_defined || !leveldir_new->handicap ?
3600 leveldir_new->last_level : leveldir_new->first_level);
3602 DrawInitTextItem(leveldir_new->name);
3604 pushTreeInfo(node_first, leveldir_new);
3606 freeSetupFileHash(setup_file_hash);
3608 if (leveldir_new->level_group)
3610 // create node to link back to current level directory
3611 createParentTreeInfoNode(leveldir_new);
3613 // recursively step into sub-directory and look for more level series
3614 LoadLevelInfoFromLevelDir(&leveldir_new->node_group,
3615 leveldir_new, directory_path);
3618 free(directory_path);
3624 static void LoadLevelInfoFromLevelDir(TreeInfo **node_first,
3625 TreeInfo *node_parent,
3626 char *level_directory)
3628 // ---------- 1st stage: process any level set zip files ----------
3630 ProcessZipFilesInDirectory(level_directory, TREE_TYPE_LEVEL_DIR);
3632 // ---------- 2nd stage: check for level set directories ----------
3635 DirectoryEntry *dir_entry;
3636 boolean valid_entry_found = FALSE;
3638 if ((dir = openDirectory(level_directory)) == NULL)
3640 Warn("cannot read level directory '%s'", level_directory);
3645 while ((dir_entry = readDirectory(dir)) != NULL) // loop all entries
3647 char *directory_name = dir_entry->basename;
3648 char *directory_path = getPath2(level_directory, directory_name);
3650 // skip entries for current and parent directory
3651 if (strEqual(directory_name, ".") ||
3652 strEqual(directory_name, ".."))
3654 free(directory_path);
3659 // find out if directory entry is itself a directory
3660 if (!dir_entry->is_directory) // not a directory
3662 free(directory_path);
3667 free(directory_path);
3669 if (strEqual(directory_name, GRAPHICS_DIRECTORY) ||
3670 strEqual(directory_name, SOUNDS_DIRECTORY) ||
3671 strEqual(directory_name, MUSIC_DIRECTORY))
3674 valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
3679 closeDirectory(dir);
3681 // special case: top level directory may directly contain "levelinfo.conf"
3682 if (node_parent == NULL && !valid_entry_found)
3684 // check if this directory directly contains a file "levelinfo.conf"
3685 valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
3686 level_directory, ".");
3689 if (!valid_entry_found)
3690 Warn("cannot find any valid level series in directory '%s'",
3694 boolean AdjustGraphicsForEMC(void)
3696 boolean settings_changed = FALSE;
3698 settings_changed |= adjustTreeGraphicsForEMC(leveldir_first_all);
3699 settings_changed |= adjustTreeGraphicsForEMC(leveldir_first);
3701 return settings_changed;
3704 boolean AdjustSoundsForEMC(void)
3706 boolean settings_changed = FALSE;
3708 settings_changed |= adjustTreeSoundsForEMC(leveldir_first_all);
3709 settings_changed |= adjustTreeSoundsForEMC(leveldir_first);
3711 return settings_changed;
3714 void LoadLevelInfo(void)
3716 InitUserLevelDirectory(getLoginName());
3718 DrawInitTextHead("Loading level series");
3720 LoadLevelInfoFromLevelDir(&leveldir_first, NULL, options.level_directory);
3721 LoadLevelInfoFromLevelDir(&leveldir_first, NULL, getUserLevelDir(NULL));
3723 leveldir_first = createTopTreeInfoNode(leveldir_first);
3725 /* after loading all level set information, clone the level directory tree
3726 and remove all level sets without levels (these may still contain artwork
3727 to be offered in the setup menu as "custom artwork", and are therefore
3728 checked for existing artwork in the function "LoadLevelArtworkInfo()") */
3729 leveldir_first_all = leveldir_first;
3730 cloneTree(&leveldir_first, leveldir_first_all, TRUE);
3732 AdjustGraphicsForEMC();
3733 AdjustSoundsForEMC();
3735 // before sorting, the first entries will be from the user directory
3736 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
3738 if (leveldir_first == NULL)
3739 Fail("cannot find any valid level series in any directory");
3741 sortTreeInfo(&leveldir_first);
3743 #if ENABLE_UNUSED_CODE
3744 dumpTreeInfo(leveldir_first, 0);
3748 static boolean LoadArtworkInfoFromArtworkConf(TreeInfo **node_first,
3749 TreeInfo *node_parent,
3750 char *base_directory,
3751 char *directory_name, int type)
3753 char *directory_path = getPath2(base_directory, directory_name);
3754 char *filename = getPath2(directory_path, ARTWORKINFO_FILENAME(type));
3755 SetupFileHash *setup_file_hash = NULL;
3756 TreeInfo *artwork_new = NULL;
3759 if (fileExists(filename))
3760 setup_file_hash = loadSetupFileHash(filename);
3762 if (setup_file_hash == NULL) // no config file -- look for artwork files
3765 DirectoryEntry *dir_entry;
3766 boolean valid_file_found = FALSE;
3768 if ((dir = openDirectory(directory_path)) != NULL)
3770 while ((dir_entry = readDirectory(dir)) != NULL)
3772 if (FileIsArtworkType(dir_entry->filename, type))
3774 valid_file_found = TRUE;
3780 closeDirectory(dir);
3783 if (!valid_file_found)
3785 #if DEBUG_NO_CONFIG_FILE
3786 if (!strEqual(directory_name, "."))
3787 Debug("setup", "ignoring artwork directory '%s'", directory_path);
3790 free(directory_path);
3797 artwork_new = newTreeInfo();
3800 setTreeInfoToDefaultsFromParent(artwork_new, node_parent);
3802 setTreeInfoToDefaults(artwork_new, type);
3804 artwork_new->subdir = getStringCopy(directory_name);
3806 if (setup_file_hash) // (before defining ".color" and ".class_desc")
3808 // set all structure fields according to the token/value pairs
3810 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
3811 setSetupInfo(levelinfo_tokens, i,
3812 getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
3815 if (strEqual(artwork_new->name, ANONYMOUS_NAME))
3816 setString(&artwork_new->name, artwork_new->subdir);
3818 if (artwork_new->identifier == NULL)
3819 artwork_new->identifier = getStringCopy(artwork_new->subdir);
3821 if (artwork_new->name_sorting == NULL)
3822 artwork_new->name_sorting = getStringCopy(artwork_new->name);
3825 if (node_parent == NULL) // top level group
3827 artwork_new->basepath = getStringCopy(base_directory);
3828 artwork_new->fullpath = getStringCopy(artwork_new->subdir);
3830 else // sub level group
3832 artwork_new->basepath = getStringCopy(node_parent->basepath);
3833 artwork_new->fullpath = getPath2(node_parent->fullpath, directory_name);
3836 artwork_new->in_user_dir =
3837 (!strEqual(artwork_new->basepath, OPTIONS_ARTWORK_DIRECTORY(type)));
3839 setString(&artwork_new->class_desc, getLevelClassDescription(artwork_new));
3841 if (setup_file_hash == NULL) // (after determining ".user_defined")
3843 if (strEqual(artwork_new->subdir, "."))
3845 if (artwork_new->user_defined)
3847 setString(&artwork_new->identifier, "private");
3848 artwork_new->sort_priority = ARTWORKCLASS_PRIVATE;
3852 setString(&artwork_new->identifier, "classic");
3853 artwork_new->sort_priority = ARTWORKCLASS_CLASSICS;
3856 setString(&artwork_new->class_desc,
3857 getLevelClassDescription(artwork_new));
3861 setString(&artwork_new->identifier, artwork_new->subdir);
3864 setString(&artwork_new->name, artwork_new->identifier);
3865 setString(&artwork_new->name_sorting, artwork_new->name);
3868 pushTreeInfo(node_first, artwork_new);
3870 freeSetupFileHash(setup_file_hash);
3872 free(directory_path);
3878 static void LoadArtworkInfoFromArtworkDir(TreeInfo **node_first,
3879 TreeInfo *node_parent,
3880 char *base_directory, int type)
3882 // ---------- 1st stage: process any artwork set zip files ----------
3884 ProcessZipFilesInDirectory(base_directory, type);
3886 // ---------- 2nd stage: check for artwork set directories ----------
3889 DirectoryEntry *dir_entry;
3890 boolean valid_entry_found = FALSE;
3892 if ((dir = openDirectory(base_directory)) == NULL)
3894 // display error if directory is main "options.graphics_directory" etc.
3895 if (base_directory == OPTIONS_ARTWORK_DIRECTORY(type))
3896 Warn("cannot read directory '%s'", base_directory);
3901 while ((dir_entry = readDirectory(dir)) != NULL) // loop all entries
3903 char *directory_name = dir_entry->basename;
3904 char *directory_path = getPath2(base_directory, directory_name);
3906 // skip directory entries for current and parent directory
3907 if (strEqual(directory_name, ".") ||
3908 strEqual(directory_name, ".."))
3910 free(directory_path);
3915 // skip directory entries which are not a directory
3916 if (!dir_entry->is_directory) // not a directory
3918 free(directory_path);
3923 free(directory_path);
3925 // check if this directory contains artwork with or without config file
3926 valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
3928 directory_name, type);
3931 closeDirectory(dir);
3933 // check if this directory directly contains artwork itself
3934 valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
3935 base_directory, ".",
3937 if (!valid_entry_found)
3938 Warn("cannot find any valid artwork in directory '%s'", base_directory);
3941 static TreeInfo *getDummyArtworkInfo(int type)
3943 // this is only needed when there is completely no artwork available
3944 TreeInfo *artwork_new = newTreeInfo();
3946 setTreeInfoToDefaults(artwork_new, type);
3948 setString(&artwork_new->subdir, UNDEFINED_FILENAME);
3949 setString(&artwork_new->fullpath, UNDEFINED_FILENAME);
3950 setString(&artwork_new->basepath, UNDEFINED_FILENAME);
3952 setString(&artwork_new->identifier, UNDEFINED_FILENAME);
3953 setString(&artwork_new->name, UNDEFINED_FILENAME);
3954 setString(&artwork_new->name_sorting, UNDEFINED_FILENAME);
3959 void SetCurrentArtwork(int type)
3961 ArtworkDirTree **current_ptr = ARTWORK_CURRENT_PTR(artwork, type);
3962 ArtworkDirTree *first_node = ARTWORK_FIRST_NODE(artwork, type);
3963 char *setup_set = SETUP_ARTWORK_SET(setup, type);
3964 char *default_subdir = ARTWORK_DEFAULT_SUBDIR(type);
3966 // set current artwork to artwork configured in setup menu
3967 *current_ptr = getTreeInfoFromIdentifier(first_node, setup_set);
3969 // if not found, set current artwork to default artwork
3970 if (*current_ptr == NULL)
3971 *current_ptr = getTreeInfoFromIdentifier(first_node, default_subdir);
3973 // if not found, set current artwork to first artwork in tree
3974 if (*current_ptr == NULL)
3975 *current_ptr = getFirstValidTreeInfoEntry(first_node);
3978 void ChangeCurrentArtworkIfNeeded(int type)
3980 char *current_identifier = ARTWORK_CURRENT_IDENTIFIER(artwork, type);
3981 char *setup_set = SETUP_ARTWORK_SET(setup, type);
3983 if (!strEqual(current_identifier, setup_set))
3984 SetCurrentArtwork(type);
3987 void LoadArtworkInfo(void)
3989 LoadArtworkInfoCache();
3991 DrawInitTextHead("Looking for custom artwork");
3993 LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
3994 options.graphics_directory,
3995 TREE_TYPE_GRAPHICS_DIR);
3996 LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
3997 getUserGraphicsDir(),
3998 TREE_TYPE_GRAPHICS_DIR);
4000 LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
4001 options.sounds_directory,
4002 TREE_TYPE_SOUNDS_DIR);
4003 LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
4005 TREE_TYPE_SOUNDS_DIR);
4007 LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
4008 options.music_directory,
4009 TREE_TYPE_MUSIC_DIR);
4010 LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
4012 TREE_TYPE_MUSIC_DIR);
4014 if (artwork.gfx_first == NULL)
4015 artwork.gfx_first = getDummyArtworkInfo(TREE_TYPE_GRAPHICS_DIR);
4016 if (artwork.snd_first == NULL)
4017 artwork.snd_first = getDummyArtworkInfo(TREE_TYPE_SOUNDS_DIR);
4018 if (artwork.mus_first == NULL)
4019 artwork.mus_first = getDummyArtworkInfo(TREE_TYPE_MUSIC_DIR);
4021 // before sorting, the first entries will be from the user directory
4022 SetCurrentArtwork(ARTWORK_TYPE_GRAPHICS);
4023 SetCurrentArtwork(ARTWORK_TYPE_SOUNDS);
4024 SetCurrentArtwork(ARTWORK_TYPE_MUSIC);
4026 artwork.gfx_current_identifier = artwork.gfx_current->identifier;
4027 artwork.snd_current_identifier = artwork.snd_current->identifier;
4028 artwork.mus_current_identifier = artwork.mus_current->identifier;
4030 #if ENABLE_UNUSED_CODE
4031 Debug("setup:LoadArtworkInfo", "graphics set == %s",
4032 artwork.gfx_current_identifier);
4033 Debug("setup:LoadArtworkInfo", "sounds set == %s",
4034 artwork.snd_current_identifier);
4035 Debug("setup:LoadArtworkInfo", "music set == %s",
4036 artwork.mus_current_identifier);
4039 sortTreeInfo(&artwork.gfx_first);
4040 sortTreeInfo(&artwork.snd_first);
4041 sortTreeInfo(&artwork.mus_first);
4043 #if ENABLE_UNUSED_CODE
4044 dumpTreeInfo(artwork.gfx_first, 0);
4045 dumpTreeInfo(artwork.snd_first, 0);
4046 dumpTreeInfo(artwork.mus_first, 0);
4050 static void MoveArtworkInfoIntoSubTree(ArtworkDirTree **artwork_node)
4052 ArtworkDirTree *artwork_new = newTreeInfo();
4053 char *top_node_name = "standalone artwork";
4055 setTreeInfoToDefaults(artwork_new, (*artwork_node)->type);
4057 artwork_new->level_group = TRUE;
4059 setString(&artwork_new->identifier, top_node_name);
4060 setString(&artwork_new->name, top_node_name);
4061 setString(&artwork_new->name_sorting, top_node_name);
4063 // create node to link back to current custom artwork directory
4064 createParentTreeInfoNode(artwork_new);
4066 // move existing custom artwork tree into newly created sub-tree
4067 artwork_new->node_group->next = *artwork_node;
4069 // change custom artwork tree to contain only newly created node
4070 *artwork_node = artwork_new;
4073 static void LoadArtworkInfoFromLevelInfoExt(ArtworkDirTree **artwork_node,
4074 ArtworkDirTree *node_parent,
4075 LevelDirTree *level_node,
4076 boolean empty_level_set_mode)
4078 int type = (*artwork_node)->type;
4080 // recursively check all level directories for artwork sub-directories
4084 boolean empty_level_set = (level_node->levels == 0);
4086 // check all tree entries for artwork, but skip parent link entries
4087 if (!level_node->parent_link && empty_level_set == empty_level_set_mode)
4089 TreeInfo *artwork_new = getArtworkInfoCacheEntry(level_node, type);
4090 boolean cached = (artwork_new != NULL);
4094 pushTreeInfo(artwork_node, artwork_new);
4098 TreeInfo *topnode_last = *artwork_node;
4099 char *path = getPath2(getLevelDirFromTreeInfo(level_node),
4100 ARTWORK_DIRECTORY(type));
4102 LoadArtworkInfoFromArtworkDir(artwork_node, NULL, path, type);
4104 if (topnode_last != *artwork_node) // check for newly added node
4106 artwork_new = *artwork_node;
4108 setString(&artwork_new->identifier, level_node->subdir);
4109 setString(&artwork_new->name, level_node->name);
4110 setString(&artwork_new->name_sorting, level_node->name_sorting);
4112 artwork_new->sort_priority = level_node->sort_priority;
4113 artwork_new->in_user_dir = level_node->in_user_dir;
4115 update_artworkinfo_cache = TRUE;
4121 // insert artwork info (from old cache or filesystem) into new cache
4122 if (artwork_new != NULL)
4123 setArtworkInfoCacheEntry(artwork_new, level_node, type);
4126 DrawInitTextItem(level_node->name);
4128 if (level_node->node_group != NULL)
4130 TreeInfo *artwork_new = newTreeInfo();
4133 setTreeInfoToDefaultsFromParent(artwork_new, node_parent);
4135 setTreeInfoToDefaults(artwork_new, type);
4137 artwork_new->level_group = TRUE;
4139 setString(&artwork_new->identifier, level_node->subdir);
4141 if (node_parent == NULL) // check for top tree node
4143 char *top_node_name = (empty_level_set_mode ?
4144 "artwork for certain level sets" :
4145 "artwork included in level sets");
4147 setString(&artwork_new->name, top_node_name);
4148 setString(&artwork_new->name_sorting, top_node_name);
4152 setString(&artwork_new->name, level_node->name);
4153 setString(&artwork_new->name_sorting, level_node->name_sorting);
4156 pushTreeInfo(artwork_node, artwork_new);
4158 // create node to link back to current custom artwork directory
4159 createParentTreeInfoNode(artwork_new);
4161 // recursively step into sub-directory and look for more custom artwork
4162 LoadArtworkInfoFromLevelInfoExt(&artwork_new->node_group, artwork_new,
4163 level_node->node_group,
4164 empty_level_set_mode);
4166 // if sub-tree has no custom artwork at all, remove it
4167 if (artwork_new->node_group->next == NULL)
4168 removeTreeInfo(artwork_node);
4171 level_node = level_node->next;
4175 static void LoadArtworkInfoFromLevelInfo(ArtworkDirTree **artwork_node)
4177 // move peviously loaded artwork tree into separate sub-tree
4178 MoveArtworkInfoIntoSubTree(artwork_node);
4180 // load artwork from level sets into separate sub-trees
4181 LoadArtworkInfoFromLevelInfoExt(artwork_node, NULL, leveldir_first_all, TRUE);
4182 LoadArtworkInfoFromLevelInfoExt(artwork_node, NULL, leveldir_first_all, FALSE);
4184 // add top tree node over all three separate sub-trees
4185 *artwork_node = createTopTreeInfoNode(*artwork_node);
4187 // set all parent links (back links) in complete artwork tree
4188 setTreeInfoParentNodes(*artwork_node, NULL);
4191 void LoadLevelArtworkInfo(void)
4193 print_timestamp_init("LoadLevelArtworkInfo");
4195 DrawInitTextHead("Looking for custom level artwork");
4197 print_timestamp_time("DrawTimeText");
4199 LoadArtworkInfoFromLevelInfo(&artwork.gfx_first);
4200 print_timestamp_time("LoadArtworkInfoFromLevelInfo (gfx)");
4201 LoadArtworkInfoFromLevelInfo(&artwork.snd_first);
4202 print_timestamp_time("LoadArtworkInfoFromLevelInfo (snd)");
4203 LoadArtworkInfoFromLevelInfo(&artwork.mus_first);
4204 print_timestamp_time("LoadArtworkInfoFromLevelInfo (mus)");
4206 SaveArtworkInfoCache();
4208 print_timestamp_time("SaveArtworkInfoCache");
4210 // needed for reloading level artwork not known at ealier stage
4211 ChangeCurrentArtworkIfNeeded(ARTWORK_TYPE_GRAPHICS);
4212 ChangeCurrentArtworkIfNeeded(ARTWORK_TYPE_SOUNDS);
4213 ChangeCurrentArtworkIfNeeded(ARTWORK_TYPE_MUSIC);
4215 print_timestamp_time("getTreeInfoFromIdentifier");
4217 sortTreeInfo(&artwork.gfx_first);
4218 sortTreeInfo(&artwork.snd_first);
4219 sortTreeInfo(&artwork.mus_first);
4221 print_timestamp_time("sortTreeInfo");
4223 #if ENABLE_UNUSED_CODE
4224 dumpTreeInfo(artwork.gfx_first, 0);
4225 dumpTreeInfo(artwork.snd_first, 0);
4226 dumpTreeInfo(artwork.mus_first, 0);
4229 print_timestamp_done("LoadLevelArtworkInfo");
4232 static boolean AddTreeSetToTreeInfoExt(TreeInfo *tree_node_old, char *tree_dir,
4233 char *tree_subdir_new, int type)
4235 if (tree_node_old == NULL)
4237 if (type == TREE_TYPE_LEVEL_DIR)
4239 // get level info tree node of personal user level set
4240 tree_node_old = getTreeInfoFromIdentifier(leveldir_first, getLoginName());
4242 // this may happen if "setup.internal.create_user_levelset" is FALSE
4243 // or if file "levelinfo.conf" is missing in personal user level set
4244 if (tree_node_old == NULL)
4245 tree_node_old = leveldir_first->node_group;
4249 // get artwork info tree node of first artwork set
4250 tree_node_old = ARTWORK_FIRST_NODE(artwork, type);
4254 if (tree_dir == NULL)
4255 tree_dir = TREE_USERDIR(type);
4257 if (tree_node_old == NULL ||
4259 tree_subdir_new == NULL) // should not happen
4262 int draw_deactivation_mask = GetDrawDeactivationMask();
4264 // override draw deactivation mask (temporarily disable drawing)
4265 SetDrawDeactivationMask(REDRAW_ALL);
4267 if (type == TREE_TYPE_LEVEL_DIR)
4269 // load new level set config and add it next to first user level set
4270 LoadLevelInfoFromLevelConf(&tree_node_old->next,
4271 tree_node_old->node_parent,
4272 tree_dir, tree_subdir_new);
4276 // load new artwork set config and add it next to first artwork set
4277 LoadArtworkInfoFromArtworkConf(&tree_node_old->next,
4278 tree_node_old->node_parent,
4279 tree_dir, tree_subdir_new, type);
4282 // set draw deactivation mask to previous value
4283 SetDrawDeactivationMask(draw_deactivation_mask);
4285 // get first node of level or artwork info tree
4286 TreeInfo **tree_node_first = TREE_FIRST_NODE_PTR(type);
4288 // get tree info node of newly added level or artwork set
4289 TreeInfo *tree_node_new = getTreeInfoFromIdentifier(*tree_node_first,
4292 if (tree_node_new == NULL) // should not happen
4295 // correct top link and parent node link of newly created tree node
4296 tree_node_new->node_top = tree_node_old->node_top;
4297 tree_node_new->node_parent = tree_node_old->node_parent;
4299 // sort tree info to adjust position of newly added tree set
4300 sortTreeInfo(tree_node_first);
4305 void AddTreeSetToTreeInfo(TreeInfo *tree_node, char *tree_dir,
4306 char *tree_subdir_new, int type)
4308 if (!AddTreeSetToTreeInfoExt(tree_node, tree_dir, tree_subdir_new, type))
4309 Fail("internal tree info structure corrupted -- aborting");
4312 void AddUserLevelSetToLevelInfo(char *level_subdir_new)
4314 AddTreeSetToTreeInfo(NULL, NULL, level_subdir_new, TREE_TYPE_LEVEL_DIR);
4317 char *getArtworkIdentifierForUserLevelSet(int type)
4319 char *classic_artwork_set = getClassicArtworkSet(type);
4321 // check for custom artwork configured in "levelinfo.conf"
4322 char *leveldir_artwork_set =
4323 *LEVELDIR_ARTWORK_SET_PTR(leveldir_current, type);
4324 boolean has_leveldir_artwork_set =
4325 (leveldir_artwork_set != NULL && !strEqual(leveldir_artwork_set,
4326 classic_artwork_set));
4328 // check for custom artwork in sub-directory "graphics" etc.
4329 TreeInfo *artwork_first_node = ARTWORK_FIRST_NODE(artwork, type);
4330 char *leveldir_identifier = leveldir_current->identifier;
4331 boolean has_artwork_subdir =
4332 (getTreeInfoFromIdentifier(artwork_first_node,
4333 leveldir_identifier) != NULL);
4335 return (has_leveldir_artwork_set ? leveldir_artwork_set :
4336 has_artwork_subdir ? leveldir_identifier :
4337 classic_artwork_set);
4340 TreeInfo *getArtworkTreeInfoForUserLevelSet(int type)
4342 char *artwork_set = getArtworkIdentifierForUserLevelSet(type);
4343 TreeInfo *artwork_first_node = ARTWORK_FIRST_NODE(artwork, type);
4344 TreeInfo *ti = getTreeInfoFromIdentifier(artwork_first_node, artwork_set);
4348 ti = getTreeInfoFromIdentifier(artwork_first_node,
4349 ARTWORK_DEFAULT_SUBDIR(type));
4351 Fail("cannot find default graphics -- should not happen");
4357 boolean checkIfCustomArtworkExistsForCurrentLevelSet(void)
4359 char *graphics_set =
4360 getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_GRAPHICS);
4362 getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_SOUNDS);
4364 getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_MUSIC);
4366 return (!strEqual(graphics_set, GFX_CLASSIC_SUBDIR) ||
4367 !strEqual(sounds_set, SND_CLASSIC_SUBDIR) ||
4368 !strEqual(music_set, MUS_CLASSIC_SUBDIR));
4371 boolean UpdateUserLevelSet(char *level_subdir, char *level_name,
4372 char *level_author, int num_levels)
4374 char *filename = getPath2(getUserLevelDir(level_subdir), LEVELINFO_FILENAME);
4375 char *filename_tmp = getStringCat2(filename, ".tmp");
4377 FILE *file_tmp = NULL;
4378 char line[MAX_LINE_LEN];
4379 boolean success = FALSE;
4380 LevelDirTree *leveldir = getTreeInfoFromIdentifier(leveldir_first,
4382 // update values in level directory tree
4384 if (level_name != NULL)
4385 setString(&leveldir->name, level_name);
4387 if (level_author != NULL)
4388 setString(&leveldir->author, level_author);
4390 if (num_levels != -1)
4391 leveldir->levels = num_levels;
4393 // update values that depend on other values
4395 setString(&leveldir->name_sorting, leveldir->name);
4397 leveldir->last_level = leveldir->first_level + leveldir->levels - 1;
4399 // sort order of level sets may have changed
4400 sortTreeInfo(&leveldir_first);
4402 if ((file = fopen(filename, MODE_READ)) &&
4403 (file_tmp = fopen(filename_tmp, MODE_WRITE)))
4405 while (fgets(line, MAX_LINE_LEN, file))
4407 if (strPrefix(line, "name:") && level_name != NULL)
4408 fprintf(file_tmp, "%-32s%s\n", "name:", level_name);
4409 else if (strPrefix(line, "author:") && level_author != NULL)
4410 fprintf(file_tmp, "%-32s%s\n", "author:", level_author);
4411 else if (strPrefix(line, "levels:") && num_levels != -1)
4412 fprintf(file_tmp, "%-32s%d\n", "levels:", num_levels);
4414 fputs(line, file_tmp);
4427 success = (rename(filename_tmp, filename) == 0);
4435 boolean CreateUserLevelSet(char *level_subdir, char *level_name,
4436 char *level_author, int num_levels,
4437 boolean use_artwork_set)
4439 LevelDirTree *level_info;
4444 // create user level sub-directory, if needed
4445 createDirectory(getUserLevelDir(level_subdir), "user level", PERMS_PRIVATE);
4447 filename = getPath2(getUserLevelDir(level_subdir), LEVELINFO_FILENAME);
4449 if (!(file = fopen(filename, MODE_WRITE)))
4451 Warn("cannot write level info file '%s'", filename);
4458 level_info = newTreeInfo();
4460 // always start with reliable default values
4461 setTreeInfoToDefaults(level_info, TREE_TYPE_LEVEL_DIR);
4463 setString(&level_info->name, level_name);
4464 setString(&level_info->author, level_author);
4465 level_info->levels = num_levels;
4466 level_info->first_level = 1;
4467 level_info->sort_priority = LEVELCLASS_PRIVATE_START;
4468 level_info->readonly = FALSE;
4470 if (use_artwork_set)
4472 level_info->graphics_set =
4473 getStringCopy(getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_GRAPHICS));
4474 level_info->sounds_set =
4475 getStringCopy(getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_SOUNDS));
4476 level_info->music_set =
4477 getStringCopy(getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_MUSIC));
4480 token_value_position = TOKEN_VALUE_POSITION_SHORT;
4482 fprintFileHeader(file, LEVELINFO_FILENAME);
4485 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
4487 if (i == LEVELINFO_TOKEN_NAME ||
4488 i == LEVELINFO_TOKEN_AUTHOR ||
4489 i == LEVELINFO_TOKEN_LEVELS ||
4490 i == LEVELINFO_TOKEN_FIRST_LEVEL ||
4491 i == LEVELINFO_TOKEN_SORT_PRIORITY ||
4492 i == LEVELINFO_TOKEN_READONLY ||
4493 (use_artwork_set && (i == LEVELINFO_TOKEN_GRAPHICS_SET ||
4494 i == LEVELINFO_TOKEN_SOUNDS_SET ||
4495 i == LEVELINFO_TOKEN_MUSIC_SET)))
4496 fprintf(file, "%s\n", getSetupLine(levelinfo_tokens, "", i));
4498 // just to make things nicer :)
4499 if (i == LEVELINFO_TOKEN_AUTHOR ||
4500 i == LEVELINFO_TOKEN_FIRST_LEVEL ||
4501 (use_artwork_set && i == LEVELINFO_TOKEN_READONLY))
4502 fprintf(file, "\n");
4505 token_value_position = TOKEN_VALUE_POSITION_DEFAULT;
4509 SetFilePermissions(filename, PERMS_PRIVATE);
4511 freeTreeInfo(level_info);
4517 static void SaveUserLevelInfo(void)
4519 CreateUserLevelSet(getLoginName(), getLoginName(), getRealName(), 100, FALSE);
4522 char *getSetupValue(int type, void *value)
4524 static char value_string[MAX_LINE_LEN];
4532 strcpy(value_string, (*(boolean *)value ? "true" : "false"));
4536 strcpy(value_string, (*(boolean *)value ? "on" : "off"));
4540 strcpy(value_string, (*(int *)value == AUTO ? "auto" :
4541 *(int *)value == FALSE ? "off" : "on"));
4545 strcpy(value_string, (*(boolean *)value ? "yes" : "no"));
4548 case TYPE_YES_NO_AUTO:
4549 strcpy(value_string, (*(int *)value == AUTO ? "auto" :
4550 *(int *)value == FALSE ? "no" : "yes"));
4554 strcpy(value_string, (*(boolean *)value ? "AGA" : "ECS"));
4558 strcpy(value_string, getKeyNameFromKey(*(Key *)value));
4562 strcpy(value_string, getX11KeyNameFromKey(*(Key *)value));
4566 sprintf(value_string, "%d", *(int *)value);
4570 if (*(char **)value == NULL)
4573 strcpy(value_string, *(char **)value);
4577 sprintf(value_string, "player_%d", *(int *)value + 1);
4581 value_string[0] = '\0';
4585 if (type & TYPE_GHOSTED)
4586 strcpy(value_string, "n/a");
4588 return value_string;
4591 char *getSetupLine(struct TokenInfo *token_info, char *prefix, int token_nr)
4595 static char token_string[MAX_LINE_LEN];
4596 int token_type = token_info[token_nr].type;
4597 void *setup_value = token_info[token_nr].value;
4598 char *token_text = token_info[token_nr].text;
4599 char *value_string = getSetupValue(token_type, setup_value);
4601 // build complete token string
4602 sprintf(token_string, "%s%s", prefix, token_text);
4604 // build setup entry line
4605 line = getFormattedSetupEntry(token_string, value_string);
4607 if (token_type == TYPE_KEY_X11)
4609 Key key = *(Key *)setup_value;
4610 char *keyname = getKeyNameFromKey(key);
4612 // add comment, if useful
4613 if (!strEqual(keyname, "(undefined)") &&
4614 !strEqual(keyname, "(unknown)"))
4616 // add at least one whitespace
4618 for (i = strlen(line); i < token_comment_position; i++)
4622 strcat(line, keyname);
4629 static void InitLastPlayedLevels_ParentNode(void)
4631 LevelDirTree **leveldir_top = &leveldir_first->node_group->next;
4632 LevelDirTree *leveldir_new = NULL;
4634 // check if parent node for last played levels already exists
4635 if (strEqual((*leveldir_top)->identifier, TOKEN_STR_LAST_LEVEL_SERIES))
4638 leveldir_new = newTreeInfo();
4640 setTreeInfoToDefaultsFromParent(leveldir_new, leveldir_first);
4642 leveldir_new->level_group = TRUE;
4643 leveldir_new->sort_priority = LEVELCLASS_LAST_PLAYED_LEVEL;
4645 setString(&leveldir_new->identifier, TOKEN_STR_LAST_LEVEL_SERIES);
4646 setString(&leveldir_new->name, "<< (last played level sets)");
4647 setString(&leveldir_new->name_sorting, leveldir_new->name);
4649 pushTreeInfo(leveldir_top, leveldir_new);
4651 // create node to link back to current level directory
4652 createParentTreeInfoNode(leveldir_new);
4655 void UpdateLastPlayedLevels_TreeInfo(void)
4657 char **last_level_series = setup.level_setup.last_level_series;
4658 LevelDirTree *leveldir_last;
4659 TreeInfo **node_new = NULL;
4662 if (last_level_series[0] == NULL)
4665 InitLastPlayedLevels_ParentNode();
4667 leveldir_last = getTreeInfoFromIdentifierExt(leveldir_first,
4668 TOKEN_STR_LAST_LEVEL_SERIES,
4669 TREE_NODE_TYPE_GROUP);
4670 if (leveldir_last == NULL)
4673 node_new = &leveldir_last->node_group->next;
4675 freeTreeInfo(*node_new);
4679 for (i = 0; last_level_series[i] != NULL; i++)
4681 LevelDirTree *node_last = getTreeInfoFromIdentifier(leveldir_first,
4682 last_level_series[i]);
4683 if (node_last == NULL)
4686 *node_new = getTreeInfoCopy(node_last); // copy complete node
4688 (*node_new)->node_top = &leveldir_first; // correct top node link
4689 (*node_new)->node_parent = leveldir_last; // correct parent node link
4691 (*node_new)->is_copy = TRUE; // mark entry as node copy
4693 (*node_new)->node_group = NULL;
4694 (*node_new)->next = NULL;
4696 (*node_new)->cl_first = -1; // force setting tree cursor
4698 node_new = &((*node_new)->next);
4702 static void UpdateLastPlayedLevels_List(void)
4704 char **last_level_series = setup.level_setup.last_level_series;
4705 int pos = MAX_LEVELDIR_HISTORY - 1;
4708 // search for potentially already existing entry in list of level sets
4709 for (i = 0; last_level_series[i] != NULL; i++)
4710 if (strEqual(last_level_series[i], leveldir_current->identifier))
4713 // move list of level sets one entry down (using potentially free entry)
4714 for (i = pos; i > 0; i--)
4715 setString(&last_level_series[i], last_level_series[i - 1]);
4717 // put last played level set at top position
4718 setString(&last_level_series[0], leveldir_current->identifier);
4721 static TreeInfo *StoreOrRestoreLastPlayedLevels(TreeInfo *node, boolean store)
4723 static char *identifier = NULL;
4727 setString(&identifier, (node && node->is_copy ? node->identifier : NULL));
4729 return NULL; // not used
4733 TreeInfo *node_new = getTreeInfoFromIdentifierExt(leveldir_first,
4735 TREE_NODE_TYPE_COPY);
4736 return (node_new != NULL ? node_new : node);
4740 void StoreLastPlayedLevels(TreeInfo *node)
4742 StoreOrRestoreLastPlayedLevels(node, TRUE);
4745 void RestoreLastPlayedLevels(TreeInfo **node)
4747 *node = StoreOrRestoreLastPlayedLevels(*node, FALSE);
4750 void LoadLevelSetup_LastSeries(void)
4752 // --------------------------------------------------------------------------
4753 // ~/.<program>/levelsetup.conf
4754 // --------------------------------------------------------------------------
4756 char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
4757 SetupFileHash *level_setup_hash = NULL;
4761 // always start with reliable default values
4762 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
4764 // start with empty history of last played level sets
4765 setString(&setup.level_setup.last_level_series[0], NULL);
4767 if (!strEqual(DEFAULT_LEVELSET, UNDEFINED_LEVELSET))
4769 leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
4771 if (leveldir_current == NULL)
4772 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
4775 if ((level_setup_hash = loadSetupFileHash(filename)))
4777 char *last_level_series =
4778 getHashEntry(level_setup_hash, TOKEN_STR_LAST_LEVEL_SERIES);
4780 leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
4782 if (leveldir_current == NULL)
4783 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
4785 for (i = 0; i < MAX_LEVELDIR_HISTORY; i++)
4787 char token[strlen(TOKEN_STR_LAST_LEVEL_SERIES) + 10];
4788 LevelDirTree *leveldir_last;
4790 sprintf(token, "%s.%03d", TOKEN_STR_LAST_LEVEL_SERIES, i);
4792 last_level_series = getHashEntry(level_setup_hash, token);
4794 leveldir_last = getTreeInfoFromIdentifier(leveldir_first,
4796 if (leveldir_last != NULL)
4797 setString(&setup.level_setup.last_level_series[pos++],
4801 setString(&setup.level_setup.last_level_series[pos], NULL);
4803 freeSetupFileHash(level_setup_hash);
4807 Debug("setup", "using default setup values");
4813 static void SaveLevelSetup_LastSeries_Ext(boolean deactivate_last_level_series)
4815 // --------------------------------------------------------------------------
4816 // ~/.<program>/levelsetup.conf
4817 // --------------------------------------------------------------------------
4819 // check if the current level directory structure is available at this point
4820 if (leveldir_current == NULL)
4823 char **last_level_series = setup.level_setup.last_level_series;
4824 char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
4828 InitUserDataDirectory();
4830 UpdateLastPlayedLevels_List();
4832 if (!(file = fopen(filename, MODE_WRITE)))
4834 Warn("cannot write setup file '%s'", filename);
4841 fprintFileHeader(file, LEVELSETUP_FILENAME);
4843 if (deactivate_last_level_series)
4844 fprintf(file, "# %s\n# ", "the following level set may have caused a problem and was deactivated");
4846 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_LAST_LEVEL_SERIES,
4847 leveldir_current->identifier));
4849 for (i = 0; last_level_series[i] != NULL; i++)
4851 char token[strlen(TOKEN_STR_LAST_LEVEL_SERIES) + 10];
4853 sprintf(token, "%s.%03d", TOKEN_STR_LAST_LEVEL_SERIES, i);
4855 fprintf(file, "%s\n", getFormattedSetupEntry(token, last_level_series[i]));
4860 SetFilePermissions(filename, PERMS_PRIVATE);
4865 void SaveLevelSetup_LastSeries(void)
4867 SaveLevelSetup_LastSeries_Ext(FALSE);
4870 void SaveLevelSetup_LastSeries_Deactivate(void)
4872 SaveLevelSetup_LastSeries_Ext(TRUE);
4875 static void checkSeriesInfo(void)
4877 static char *level_directory = NULL;
4880 DirectoryEntry *dir_entry;
4883 checked_free(level_directory);
4885 // check for more levels besides the 'levels' field of 'levelinfo.conf'
4887 level_directory = getPath2((leveldir_current->in_user_dir ?
4888 getUserLevelDir(NULL) :
4889 options.level_directory),
4890 leveldir_current->fullpath);
4892 if ((dir = openDirectory(level_directory)) == NULL)
4894 Warn("cannot read level directory '%s'", level_directory);
4900 while ((dir_entry = readDirectory(dir)) != NULL) // loop all entries
4902 if (strlen(dir_entry->basename) > 4 &&
4903 dir_entry->basename[3] == '.' &&
4904 strEqual(&dir_entry->basename[4], LEVELFILE_EXTENSION))
4906 char levelnum_str[4];
4909 strncpy(levelnum_str, dir_entry->basename, 3);
4910 levelnum_str[3] = '\0';
4912 levelnum_value = atoi(levelnum_str);
4914 if (levelnum_value < leveldir_current->first_level)
4916 Warn("additional level %d found", levelnum_value);
4918 leveldir_current->first_level = levelnum_value;
4920 else if (levelnum_value > leveldir_current->last_level)
4922 Warn("additional level %d found", levelnum_value);
4924 leveldir_current->last_level = levelnum_value;
4930 closeDirectory(dir);
4933 void LoadLevelSetup_SeriesInfo(void)
4936 SetupFileHash *level_setup_hash = NULL;
4937 char *level_subdir = leveldir_current->subdir;
4940 // always start with reliable default values
4941 level_nr = leveldir_current->first_level;
4943 for (i = 0; i < MAX_LEVELS; i++)
4945 LevelStats_setPlayed(i, 0);
4946 LevelStats_setSolved(i, 0);
4951 // --------------------------------------------------------------------------
4952 // ~/.<program>/levelsetup/<level series>/levelsetup.conf
4953 // --------------------------------------------------------------------------
4955 level_subdir = leveldir_current->subdir;
4957 filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
4959 if ((level_setup_hash = loadSetupFileHash(filename)))
4963 // get last played level in this level set
4965 token_value = getHashEntry(level_setup_hash, TOKEN_STR_LAST_PLAYED_LEVEL);
4969 level_nr = atoi(token_value);
4971 if (level_nr < leveldir_current->first_level)
4972 level_nr = leveldir_current->first_level;
4973 if (level_nr > leveldir_current->last_level)
4974 level_nr = leveldir_current->last_level;
4977 // get handicap level in this level set
4979 token_value = getHashEntry(level_setup_hash, TOKEN_STR_HANDICAP_LEVEL);
4983 int level_nr = atoi(token_value);
4985 if (level_nr < leveldir_current->first_level)
4986 level_nr = leveldir_current->first_level;
4987 if (level_nr > leveldir_current->last_level + 1)
4988 level_nr = leveldir_current->last_level;
4990 if (leveldir_current->user_defined || !leveldir_current->handicap)
4991 level_nr = leveldir_current->last_level;
4993 leveldir_current->handicap_level = level_nr;
4996 // get number of played and solved levels in this level set
4998 BEGIN_HASH_ITERATION(level_setup_hash, itr)
5000 char *token = HASH_ITERATION_TOKEN(itr);
5001 char *value = HASH_ITERATION_VALUE(itr);
5003 if (strlen(token) == 3 &&
5004 token[0] >= '0' && token[0] <= '9' &&
5005 token[1] >= '0' && token[1] <= '9' &&
5006 token[2] >= '0' && token[2] <= '9')
5008 int level_nr = atoi(token);
5011 LevelStats_setPlayed(level_nr, atoi(value)); // read 1st column
5013 value = strchr(value, ' ');
5016 LevelStats_setSolved(level_nr, atoi(value)); // read 2nd column
5019 END_HASH_ITERATION(hash, itr)
5021 freeSetupFileHash(level_setup_hash);
5025 Debug("setup", "using default setup values");
5031 void SaveLevelSetup_SeriesInfo(void)
5034 char *level_subdir = leveldir_current->subdir;
5035 char *level_nr_str = int2str(level_nr, 0);
5036 char *handicap_level_str = int2str(leveldir_current->handicap_level, 0);
5040 // --------------------------------------------------------------------------
5041 // ~/.<program>/levelsetup/<level series>/levelsetup.conf
5042 // --------------------------------------------------------------------------
5044 InitLevelSetupDirectory(level_subdir);
5046 filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
5048 if (!(file = fopen(filename, MODE_WRITE)))
5050 Warn("cannot write setup file '%s'", filename);
5057 fprintFileHeader(file, LEVELSETUP_FILENAME);
5059 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_PLAYED_LEVEL,
5061 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_HANDICAP_LEVEL,
5062 handicap_level_str));
5064 for (i = leveldir_current->first_level; i <= leveldir_current->last_level;
5067 if (LevelStats_getPlayed(i) > 0 ||
5068 LevelStats_getSolved(i) > 0)
5073 sprintf(token, "%03d", i);
5074 sprintf(value, "%d %d", LevelStats_getPlayed(i), LevelStats_getSolved(i));
5076 fprintf(file, "%s\n", getFormattedSetupEntry(token, value));
5082 SetFilePermissions(filename, PERMS_PRIVATE);
5087 int LevelStats_getPlayed(int nr)
5089 return (nr >= 0 && nr < MAX_LEVELS ? level_stats[nr].played : 0);
5092 int LevelStats_getSolved(int nr)
5094 return (nr >= 0 && nr < MAX_LEVELS ? level_stats[nr].solved : 0);
5097 void LevelStats_setPlayed(int nr, int value)
5099 if (nr >= 0 && nr < MAX_LEVELS)
5100 level_stats[nr].played = value;
5103 void LevelStats_setSolved(int nr, int value)
5105 if (nr >= 0 && nr < MAX_LEVELS)
5106 level_stats[nr].solved = value;
5109 void LevelStats_incPlayed(int nr)
5111 if (nr >= 0 && nr < MAX_LEVELS)
5112 level_stats[nr].played++;
5115 void LevelStats_incSolved(int nr)
5117 if (nr >= 0 && nr < MAX_LEVELS)
5118 level_stats[nr].solved++;
5121 void LoadUserSetup(void)
5123 // --------------------------------------------------------------------------
5124 // ~/.<program>/usersetup.conf
5125 // --------------------------------------------------------------------------
5127 char *filename = getPath2(getMainUserGameDataDir(), USERSETUP_FILENAME);
5128 SetupFileHash *user_setup_hash = NULL;
5130 // always start with reliable default values
5133 if ((user_setup_hash = loadSetupFileHash(filename)))
5137 // get last selected user number
5138 token_value = getHashEntry(user_setup_hash, TOKEN_STR_LAST_USER);
5141 user.nr = MIN(MAX(0, atoi(token_value)), MAX_PLAYER_NAMES - 1);
5143 freeSetupFileHash(user_setup_hash);
5147 Debug("setup", "using default setup values");
5153 void SaveUserSetup(void)
5155 // --------------------------------------------------------------------------
5156 // ~/.<program>/usersetup.conf
5157 // --------------------------------------------------------------------------
5159 char *filename = getPath2(getMainUserGameDataDir(), USERSETUP_FILENAME);
5162 InitMainUserDataDirectory();
5164 if (!(file = fopen(filename, MODE_WRITE)))
5166 Warn("cannot write setup file '%s'", filename);
5173 fprintFileHeader(file, USERSETUP_FILENAME);
5175 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_USER,
5179 SetFilePermissions(filename, PERMS_PRIVATE);