1 // ============================================================================
2 // Artsoft Retro-Game Library
3 // ----------------------------------------------------------------------------
4 // (c) 1995-2014 by Artsoft Entertainment
7 // https://www.artsoft.org/
8 // ----------------------------------------------------------------------------
10 // ============================================================================
12 #include <sys/types.h>
26 #include "zip/miniunz.h"
29 #define ENABLE_UNUSED_CODE FALSE // for currently unused functions
30 #define DEBUG_NO_CONFIG_FILE FALSE // for extra-verbose debug output
32 #define NUM_LEVELCLASS_DESC 8
34 static char *levelclass_desc[NUM_LEVELCLASS_DESC] =
46 #define TOKEN_VALUE_POSITION_SHORT 32
47 #define TOKEN_VALUE_POSITION_DEFAULT 40
48 #define TOKEN_COMMENT_POSITION_DEFAULT 60
50 #define MAX_COOKIE_LEN 256
52 #define TREE_NODE_TYPE_DEFAULT 0
53 #define TREE_NODE_TYPE_PARENT 1
54 #define TREE_NODE_TYPE_GROUP 2
55 #define TREE_NODE_TYPE_COPY 3
57 #define TREE_NODE_TYPE(ti) (ti->node_group ? TREE_NODE_TYPE_GROUP : \
58 ti->parent_link ? TREE_NODE_TYPE_PARENT : \
59 ti->is_copy ? TREE_NODE_TYPE_COPY : \
60 TREE_NODE_TYPE_DEFAULT)
63 static void setTreeInfoToDefaults(TreeInfo *, int);
64 static TreeInfo *getTreeInfoCopy(TreeInfo *ti);
65 static int compareTreeInfoEntries(const void *, const void *);
67 static int token_value_position = TOKEN_VALUE_POSITION_DEFAULT;
68 static int token_comment_position = TOKEN_COMMENT_POSITION_DEFAULT;
70 static SetupFileHash *artworkinfo_cache_old = NULL;
71 static SetupFileHash *artworkinfo_cache_new = NULL;
72 static SetupFileHash *optional_tokens_hash = NULL;
73 static boolean use_artworkinfo_cache = TRUE;
74 static boolean update_artworkinfo_cache = FALSE;
77 // ----------------------------------------------------------------------------
79 // ----------------------------------------------------------------------------
81 static char *getLevelClassDescription(TreeInfo *ti)
83 int position = ti->sort_priority / 100;
85 if (position >= 0 && position < NUM_LEVELCLASS_DESC)
86 return levelclass_desc[position];
88 return "Unknown Level Class";
91 static char *getCacheDir(void)
93 static char *cache_dir = NULL;
95 if (cache_dir == NULL)
96 cache_dir = getPath2(getMainUserGameDataDir(), CACHE_DIRECTORY);
101 static char *getScoreDir(char *level_subdir)
103 static char *score_dir = NULL;
104 static char *score_level_dir = NULL;
105 char *score_subdir = SCORES_DIRECTORY;
107 if (score_dir == NULL)
108 score_dir = getPath2(getMainUserGameDataDir(), score_subdir);
110 if (level_subdir != NULL)
112 checked_free(score_level_dir);
114 score_level_dir = getPath2(score_dir, level_subdir);
116 return score_level_dir;
122 static char *getScoreCacheDir(char *level_subdir)
124 static char *score_dir = NULL;
125 static char *score_level_dir = NULL;
126 char *score_subdir = SCORES_DIRECTORY;
128 if (score_dir == NULL)
129 score_dir = getPath2(getCacheDir(), score_subdir);
131 if (level_subdir != NULL)
133 checked_free(score_level_dir);
135 score_level_dir = getPath2(score_dir, level_subdir);
137 return score_level_dir;
143 static char *getScoreTapeDir(char *level_subdir, int nr)
145 static char *score_tape_dir = NULL;
146 char tape_subdir[MAX_FILENAME_LEN];
148 checked_free(score_tape_dir);
150 sprintf(tape_subdir, "%03d", nr);
151 score_tape_dir = getPath2(getScoreDir(level_subdir), tape_subdir);
153 return score_tape_dir;
156 static char *getUserSubdir(int nr)
158 static char user_subdir[16] = { 0 };
160 sprintf(user_subdir, "%03d", nr);
165 static char *getUserDir(int nr)
167 static char *user_dir = NULL;
168 char *main_data_dir = getMainUserGameDataDir();
169 char *users_subdir = USERS_DIRECTORY;
170 char *user_subdir = getUserSubdir(nr);
172 checked_free(user_dir);
175 user_dir = getPath3(main_data_dir, users_subdir, user_subdir);
177 user_dir = getPath2(main_data_dir, users_subdir);
182 static char *getLevelSetupDir(char *level_subdir)
184 static char *levelsetup_dir = NULL;
185 char *data_dir = getUserGameDataDir();
186 char *levelsetup_subdir = LEVELSETUP_DIRECTORY;
188 checked_free(levelsetup_dir);
190 if (level_subdir != NULL)
191 levelsetup_dir = getPath3(data_dir, levelsetup_subdir, level_subdir);
193 levelsetup_dir = getPath2(data_dir, levelsetup_subdir);
195 return levelsetup_dir;
198 static char *getNetworkDir(void)
200 static char *network_dir = NULL;
202 if (network_dir == NULL)
203 network_dir = getPath2(getMainUserGameDataDir(), NETWORK_DIRECTORY);
208 char *getLevelDirFromTreeInfo(TreeInfo *node)
210 static char *level_dir = NULL;
213 return options.level_directory;
215 checked_free(level_dir);
217 level_dir = getPath2((node->in_user_dir ? getUserLevelDir(NULL) :
218 options.level_directory), node->fullpath);
223 char *getUserLevelDir(char *level_subdir)
225 static char *userlevel_dir = NULL;
226 char *data_dir = getMainUserGameDataDir();
227 char *userlevel_subdir = LEVELS_DIRECTORY;
229 checked_free(userlevel_dir);
231 if (level_subdir != NULL)
232 userlevel_dir = getPath3(data_dir, userlevel_subdir, level_subdir);
234 userlevel_dir = getPath2(data_dir, userlevel_subdir);
236 return userlevel_dir;
239 char *getNetworkLevelDir(char *level_subdir)
241 static char *network_level_dir = NULL;
242 char *data_dir = getNetworkDir();
243 char *networklevel_subdir = LEVELS_DIRECTORY;
245 checked_free(network_level_dir);
247 if (level_subdir != NULL)
248 network_level_dir = getPath3(data_dir, networklevel_subdir, level_subdir);
250 network_level_dir = getPath2(data_dir, networklevel_subdir);
252 return network_level_dir;
255 char *getCurrentLevelDir(void)
257 return getLevelDirFromTreeInfo(leveldir_current);
260 char *getNewUserLevelSubdir(void)
262 static char *new_level_subdir = NULL;
263 char *subdir_prefix = getLoginName();
264 char subdir_suffix[10];
265 int max_suffix_number = 1000;
268 while (++i < max_suffix_number)
270 sprintf(subdir_suffix, "_%d", i);
272 checked_free(new_level_subdir);
273 new_level_subdir = getStringCat2(subdir_prefix, subdir_suffix);
275 if (!directoryExists(getUserLevelDir(new_level_subdir)))
279 return new_level_subdir;
282 static char *getTapeDir(char *level_subdir)
284 static char *tape_dir = NULL;
285 char *data_dir = getUserGameDataDir();
286 char *tape_subdir = TAPES_DIRECTORY;
288 checked_free(tape_dir);
290 if (level_subdir != NULL)
291 tape_dir = getPath3(data_dir, tape_subdir, level_subdir);
293 tape_dir = getPath2(data_dir, tape_subdir);
298 static char *getSolutionTapeDir(void)
300 static char *tape_dir = NULL;
301 char *data_dir = getCurrentLevelDir();
302 char *tape_subdir = TAPES_DIRECTORY;
304 checked_free(tape_dir);
306 tape_dir = getPath2(data_dir, tape_subdir);
311 static char *getDefaultGraphicsDir(char *graphics_subdir)
313 static char *graphics_dir = NULL;
315 if (graphics_subdir == NULL)
316 return options.graphics_directory;
318 checked_free(graphics_dir);
320 graphics_dir = getPath2(options.graphics_directory, graphics_subdir);
325 static char *getDefaultSoundsDir(char *sounds_subdir)
327 static char *sounds_dir = NULL;
329 if (sounds_subdir == NULL)
330 return options.sounds_directory;
332 checked_free(sounds_dir);
334 sounds_dir = getPath2(options.sounds_directory, sounds_subdir);
339 static char *getDefaultMusicDir(char *music_subdir)
341 static char *music_dir = NULL;
343 if (music_subdir == NULL)
344 return options.music_directory;
346 checked_free(music_dir);
348 music_dir = getPath2(options.music_directory, music_subdir);
353 static char *getClassicArtworkSet(int type)
355 return (type == TREE_TYPE_GRAPHICS_DIR ? GFX_CLASSIC_SUBDIR :
356 type == TREE_TYPE_SOUNDS_DIR ? SND_CLASSIC_SUBDIR :
357 type == TREE_TYPE_MUSIC_DIR ? MUS_CLASSIC_SUBDIR : "");
360 static char *getClassicArtworkDir(int type)
362 return (type == TREE_TYPE_GRAPHICS_DIR ?
363 getDefaultGraphicsDir(GFX_CLASSIC_SUBDIR) :
364 type == TREE_TYPE_SOUNDS_DIR ?
365 getDefaultSoundsDir(SND_CLASSIC_SUBDIR) :
366 type == TREE_TYPE_MUSIC_DIR ?
367 getDefaultMusicDir(MUS_CLASSIC_SUBDIR) : "");
370 char *getUserGraphicsDir(void)
372 static char *usergraphics_dir = NULL;
374 if (usergraphics_dir == NULL)
375 usergraphics_dir = getPath2(getMainUserGameDataDir(), GRAPHICS_DIRECTORY);
377 return usergraphics_dir;
380 char *getUserSoundsDir(void)
382 static char *usersounds_dir = NULL;
384 if (usersounds_dir == NULL)
385 usersounds_dir = getPath2(getMainUserGameDataDir(), SOUNDS_DIRECTORY);
387 return usersounds_dir;
390 char *getUserMusicDir(void)
392 static char *usermusic_dir = NULL;
394 if (usermusic_dir == NULL)
395 usermusic_dir = getPath2(getMainUserGameDataDir(), MUSIC_DIRECTORY);
397 return usermusic_dir;
400 static char *getSetupArtworkDir(TreeInfo *ti)
402 static char *artwork_dir = NULL;
407 checked_free(artwork_dir);
409 artwork_dir = getPath2(ti->basepath, ti->fullpath);
414 char *setLevelArtworkDir(TreeInfo *ti)
416 char **artwork_path_ptr, **artwork_set_ptr;
417 TreeInfo *level_artwork;
419 if (ti == NULL || leveldir_current == NULL)
422 artwork_path_ptr = LEVELDIR_ARTWORK_PATH_PTR(leveldir_current, ti->type);
423 artwork_set_ptr = LEVELDIR_ARTWORK_SET_PTR( leveldir_current, ti->type);
425 checked_free(*artwork_path_ptr);
427 if ((level_artwork = getTreeInfoFromIdentifier(ti, *artwork_set_ptr)))
429 *artwork_path_ptr = getStringCopy(getSetupArtworkDir(level_artwork));
434 No (or non-existing) artwork configured in "levelinfo.conf". This would
435 normally result in using the artwork configured in the setup menu. But
436 if an artwork subdirectory exists (which might contain custom artwork
437 or an artwork configuration file), this level artwork must be treated
438 as relative to the default "classic" artwork, not to the artwork that
439 is currently configured in the setup menu.
441 Update: For "special" versions of R'n'D (like "R'n'D jue"), do not use
442 the "default" artwork (which would be "jue0" for "R'n'D jue"), but use
443 the real "classic" artwork from the original R'n'D (like "gfx_classic").
446 char *dir = getPath2(getCurrentLevelDir(), ARTWORK_DIRECTORY(ti->type));
448 checked_free(*artwork_set_ptr);
450 if (directoryExists(dir))
452 *artwork_path_ptr = getStringCopy(getClassicArtworkDir(ti->type));
453 *artwork_set_ptr = getStringCopy(getClassicArtworkSet(ti->type));
457 *artwork_path_ptr = getStringCopy(UNDEFINED_FILENAME);
458 *artwork_set_ptr = NULL;
464 return *artwork_set_ptr;
467 static char *getLevelArtworkSet(int type)
469 if (leveldir_current == NULL)
472 return LEVELDIR_ARTWORK_SET(leveldir_current, type);
475 static char *getLevelArtworkDir(int type)
477 if (leveldir_current == NULL)
478 return UNDEFINED_FILENAME;
480 return LEVELDIR_ARTWORK_PATH(leveldir_current, type);
483 char *getProgramMainDataPath(char *command_filename, char *base_path)
485 // check if the program's main data base directory is configured
486 if (!strEqual(base_path, "."))
487 return getStringCopy(base_path);
489 /* if the program is configured to start from current directory (default),
490 determine program package directory from program binary (some versions
491 of KDE/Konqueror and Mac OS X (especially "Mavericks") apparently do not
492 set the current working directory to the program package directory) */
493 char *main_data_path = getBasePath(command_filename);
495 #if defined(PLATFORM_MACOSX)
496 if (strSuffix(main_data_path, MAC_APP_BINARY_SUBDIR))
498 char *main_data_path_old = main_data_path;
500 // cut relative path to Mac OS X application binary directory from path
501 main_data_path[strlen(main_data_path) -
502 strlen(MAC_APP_BINARY_SUBDIR)] = '\0';
504 // cut trailing path separator from path (but not if path is root directory)
505 if (strSuffix(main_data_path, "/") && !strEqual(main_data_path, "/"))
506 main_data_path[strlen(main_data_path) - 1] = '\0';
508 // replace empty path with current directory
509 if (strEqual(main_data_path, ""))
510 main_data_path = ".";
512 // add relative path to Mac OS X application resources directory to path
513 main_data_path = getPath2(main_data_path, MAC_APP_FILES_SUBDIR);
515 free(main_data_path_old);
519 return main_data_path;
522 char *getProgramConfigFilename(char *command_filename)
524 static char *config_filename_1 = NULL;
525 static char *config_filename_2 = NULL;
526 static char *config_filename_3 = NULL;
527 static boolean initialized = FALSE;
531 char *command_filename_1 = getStringCopy(command_filename);
533 // strip trailing executable suffix from command filename
534 if (strSuffix(command_filename_1, ".exe"))
535 command_filename_1[strlen(command_filename_1) - 4] = '\0';
537 char *ro_base_path = getProgramMainDataPath(command_filename, RO_BASE_PATH);
538 char *conf_directory = getPath2(ro_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(ro_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 *getSolutionTapeFilename(int nr)
587 static char *filename = NULL;
588 char basename[MAX_FILENAME_LEN];
590 checked_free(filename);
592 sprintf(basename, "%03d.%s", nr, TAPEFILE_EXTENSION);
593 filename = getPath2(getSolutionTapeDir(), basename);
595 if (!fileExists(filename))
597 static char *filename_sln = NULL;
599 checked_free(filename_sln);
601 sprintf(basename, "%03d.sln", nr);
602 filename_sln = getPath2(getSolutionTapeDir(), basename);
604 if (fileExists(filename_sln))
611 char *getScoreFilename(int nr)
613 static char *filename = NULL;
614 char basename[MAX_FILENAME_LEN];
616 checked_free(filename);
618 sprintf(basename, "%03d.%s", nr, SCOREFILE_EXTENSION);
620 // used instead of "leveldir_current->subdir" (for network games)
621 filename = getPath2(getScoreDir(levelset.identifier), basename);
626 char *getScoreCacheFilename(int nr)
628 static char *filename = NULL;
629 char basename[MAX_FILENAME_LEN];
631 checked_free(filename);
633 sprintf(basename, "%03d.%s", nr, SCOREFILE_EXTENSION);
635 // used instead of "leveldir_current->subdir" (for network games)
636 filename = getPath2(getScoreCacheDir(levelset.identifier), basename);
641 char *getScoreTapeBasename(char *name)
643 static char basename[MAX_FILENAME_LEN];
644 char basename_raw[MAX_FILENAME_LEN];
647 sprintf(timestamp, "%s", getCurrentTimestamp());
648 sprintf(basename_raw, "%s-%s", timestamp, name);
649 sprintf(basename, "%s-%08x", timestamp, get_hash_from_key(basename_raw));
654 char *getScoreTapeFilename(char *basename_no_ext, int nr)
656 static char *filename = NULL;
657 char basename[MAX_FILENAME_LEN];
659 checked_free(filename);
661 sprintf(basename, "%s.%s", basename_no_ext, TAPEFILE_EXTENSION);
663 // used instead of "leveldir_current->subdir" (for network games)
664 filename = getPath2(getScoreTapeDir(levelset.identifier, nr), basename);
669 char *getSetupFilename(void)
671 static char *filename = NULL;
673 checked_free(filename);
675 filename = getPath2(getSetupDir(), SETUP_FILENAME);
680 char *getDefaultSetupFilename(void)
682 return program.config_filename;
685 char *getEditorSetupFilename(void)
687 static char *filename = NULL;
689 checked_free(filename);
690 filename = getPath2(getCurrentLevelDir(), EDITORSETUP_FILENAME);
692 if (fileExists(filename))
695 checked_free(filename);
696 filename = getPath2(getSetupDir(), EDITORSETUP_FILENAME);
701 char *getHelpAnimFilename(void)
703 static char *filename = NULL;
705 checked_free(filename);
707 filename = getPath2(getCurrentLevelDir(), HELPANIM_FILENAME);
712 char *getHelpTextFilename(void)
714 static char *filename = NULL;
716 checked_free(filename);
718 filename = getPath2(getCurrentLevelDir(), HELPTEXT_FILENAME);
723 char *getLevelSetInfoFilename(void)
725 static char *filename = NULL;
740 for (i = 0; basenames[i] != NULL; i++)
742 checked_free(filename);
743 filename = getPath2(getCurrentLevelDir(), basenames[i]);
745 if (fileExists(filename))
752 static char *getLevelSetTitleMessageBasename(int nr, boolean initial)
754 static char basename[32];
756 sprintf(basename, "%s_%d.txt",
757 (initial ? "titlemessage_initial" : "titlemessage"), nr + 1);
762 char *getLevelSetTitleMessageFilename(int nr, boolean initial)
764 static char *filename = NULL;
766 boolean skip_setup_artwork = FALSE;
768 checked_free(filename);
770 basename = getLevelSetTitleMessageBasename(nr, initial);
772 if (!gfx.override_level_graphics)
774 // 1st try: look for special artwork in current level series directory
775 filename = getPath3(getCurrentLevelDir(), GRAPHICS_DIRECTORY, basename);
776 if (fileExists(filename))
781 // 2nd try: look for message file in current level set directory
782 filename = getPath2(getCurrentLevelDir(), basename);
783 if (fileExists(filename))
788 // check if there is special artwork configured in level series config
789 if (getLevelArtworkSet(ARTWORK_TYPE_GRAPHICS) != NULL)
791 // 3rd try: look for special artwork configured in level series config
792 filename = getPath2(getLevelArtworkDir(ARTWORK_TYPE_GRAPHICS), basename);
793 if (fileExists(filename))
798 // take missing artwork configured in level set config from default
799 skip_setup_artwork = TRUE;
803 if (!skip_setup_artwork)
805 // 4th try: look for special artwork in configured artwork directory
806 filename = getPath2(getSetupArtworkDir(artwork.gfx_current), basename);
807 if (fileExists(filename))
813 // 5th try: look for default artwork in new default artwork directory
814 filename = getPath2(getDefaultGraphicsDir(GFX_DEFAULT_SUBDIR), basename);
815 if (fileExists(filename))
820 // 6th try: look for default artwork in old default artwork directory
821 filename = getPath2(options.graphics_directory, basename);
822 if (fileExists(filename))
825 return NULL; // cannot find specified artwork file anywhere
828 static char *getCorrectedArtworkBasename(char *basename)
833 char *getCustomImageFilename(char *basename)
835 static char *filename = NULL;
836 boolean skip_setup_artwork = FALSE;
838 checked_free(filename);
840 basename = getCorrectedArtworkBasename(basename);
842 if (!gfx.override_level_graphics)
844 // 1st try: look for special artwork in current level series directory
845 filename = getImg3(getCurrentLevelDir(), GRAPHICS_DIRECTORY, basename);
846 if (fileExists(filename))
851 // check if there is special artwork configured in level series config
852 if (getLevelArtworkSet(ARTWORK_TYPE_GRAPHICS) != NULL)
854 // 2nd try: look for special artwork configured in level series config
855 filename = getImg2(getLevelArtworkDir(ARTWORK_TYPE_GRAPHICS), basename);
856 if (fileExists(filename))
861 // take missing artwork configured in level set config from default
862 skip_setup_artwork = TRUE;
866 if (!skip_setup_artwork)
868 // 3rd try: look for special artwork in configured artwork directory
869 filename = getImg2(getSetupArtworkDir(artwork.gfx_current), basename);
870 if (fileExists(filename))
876 // 4th try: look for default artwork in new default artwork directory
877 filename = getImg2(getDefaultGraphicsDir(GFX_DEFAULT_SUBDIR), basename);
878 if (fileExists(filename))
883 // 5th try: look for default artwork in old default artwork directory
884 filename = getImg2(options.graphics_directory, basename);
885 if (fileExists(filename))
888 if (!strEqual(GFX_FALLBACK_FILENAME, UNDEFINED_FILENAME))
892 Warn("cannot find artwork file '%s' (using fallback)", basename);
894 // 6th try: look for fallback artwork in old default artwork directory
895 // (needed to prevent errors when trying to access unused artwork files)
896 filename = getImg2(options.graphics_directory, GFX_FALLBACK_FILENAME);
897 if (fileExists(filename))
901 return NULL; // cannot find specified artwork file anywhere
904 char *getCustomSoundFilename(char *basename)
906 static char *filename = NULL;
907 boolean skip_setup_artwork = FALSE;
909 checked_free(filename);
911 basename = getCorrectedArtworkBasename(basename);
913 if (!gfx.override_level_sounds)
915 // 1st try: look for special artwork in current level series directory
916 filename = getPath3(getCurrentLevelDir(), SOUNDS_DIRECTORY, basename);
917 if (fileExists(filename))
922 // check if there is special artwork configured in level series config
923 if (getLevelArtworkSet(ARTWORK_TYPE_SOUNDS) != NULL)
925 // 2nd try: look for special artwork configured in level series config
926 filename = getPath2(getLevelArtworkDir(TREE_TYPE_SOUNDS_DIR), basename);
927 if (fileExists(filename))
932 // take missing artwork configured in level set config from default
933 skip_setup_artwork = TRUE;
937 if (!skip_setup_artwork)
939 // 3rd try: look for special artwork in configured artwork directory
940 filename = getPath2(getSetupArtworkDir(artwork.snd_current), basename);
941 if (fileExists(filename))
947 // 4th try: look for default artwork in new default artwork directory
948 filename = getPath2(getDefaultSoundsDir(SND_DEFAULT_SUBDIR), basename);
949 if (fileExists(filename))
954 // 5th try: look for default artwork in old default artwork directory
955 filename = getPath2(options.sounds_directory, basename);
956 if (fileExists(filename))
959 if (!strEqual(SND_FALLBACK_FILENAME, UNDEFINED_FILENAME))
963 Warn("cannot find artwork file '%s' (using fallback)", basename);
965 // 6th try: look for fallback artwork in old default artwork directory
966 // (needed to prevent errors when trying to access unused artwork files)
967 filename = getPath2(options.sounds_directory, SND_FALLBACK_FILENAME);
968 if (fileExists(filename))
972 return NULL; // cannot find specified artwork file anywhere
975 char *getCustomMusicFilename(char *basename)
977 static char *filename = NULL;
978 boolean skip_setup_artwork = FALSE;
980 checked_free(filename);
982 basename = getCorrectedArtworkBasename(basename);
984 if (!gfx.override_level_music)
986 // 1st try: look for special artwork in current level series directory
987 filename = getPath3(getCurrentLevelDir(), MUSIC_DIRECTORY, basename);
988 if (fileExists(filename))
993 // check if there is special artwork configured in level series config
994 if (getLevelArtworkSet(ARTWORK_TYPE_MUSIC) != NULL)
996 // 2nd try: look for special artwork configured in level series config
997 filename = getPath2(getLevelArtworkDir(TREE_TYPE_MUSIC_DIR), basename);
998 if (fileExists(filename))
1003 // take missing artwork configured in level set config from default
1004 skip_setup_artwork = TRUE;
1008 if (!skip_setup_artwork)
1010 // 3rd try: look for special artwork in configured artwork directory
1011 filename = getPath2(getSetupArtworkDir(artwork.mus_current), basename);
1012 if (fileExists(filename))
1018 // 4th try: look for default artwork in new default artwork directory
1019 filename = getPath2(getDefaultMusicDir(MUS_DEFAULT_SUBDIR), basename);
1020 if (fileExists(filename))
1025 // 5th try: look for default artwork in old default artwork directory
1026 filename = getPath2(options.music_directory, basename);
1027 if (fileExists(filename))
1030 if (!strEqual(MUS_FALLBACK_FILENAME, UNDEFINED_FILENAME))
1034 Warn("cannot find artwork file '%s' (using fallback)", basename);
1036 // 6th try: look for fallback artwork in old default artwork directory
1037 // (needed to prevent errors when trying to access unused artwork files)
1038 filename = getPath2(options.music_directory, MUS_FALLBACK_FILENAME);
1039 if (fileExists(filename))
1043 return NULL; // cannot find specified artwork file anywhere
1046 char *getCustomArtworkFilename(char *basename, int type)
1048 if (type == ARTWORK_TYPE_GRAPHICS)
1049 return getCustomImageFilename(basename);
1050 else if (type == ARTWORK_TYPE_SOUNDS)
1051 return getCustomSoundFilename(basename);
1052 else if (type == ARTWORK_TYPE_MUSIC)
1053 return getCustomMusicFilename(basename);
1055 return UNDEFINED_FILENAME;
1058 char *getCustomArtworkConfigFilename(int type)
1060 return getCustomArtworkFilename(ARTWORKINFO_FILENAME(type), type);
1063 char *getCustomArtworkLevelConfigFilename(int type)
1065 static char *filename = NULL;
1067 checked_free(filename);
1069 filename = getPath2(getLevelArtworkDir(type), ARTWORKINFO_FILENAME(type));
1074 char *getCustomMusicDirectory(void)
1076 static char *directory = NULL;
1077 boolean skip_setup_artwork = FALSE;
1079 checked_free(directory);
1081 if (!gfx.override_level_music)
1083 // 1st try: look for special artwork in current level series directory
1084 directory = getPath2(getCurrentLevelDir(), MUSIC_DIRECTORY);
1085 if (directoryExists(directory))
1090 // check if there is special artwork configured in level series config
1091 if (getLevelArtworkSet(ARTWORK_TYPE_MUSIC) != NULL)
1093 // 2nd try: look for special artwork configured in level series config
1094 directory = getStringCopy(getLevelArtworkDir(TREE_TYPE_MUSIC_DIR));
1095 if (directoryExists(directory))
1100 // take missing artwork configured in level set config from default
1101 skip_setup_artwork = TRUE;
1105 if (!skip_setup_artwork)
1107 // 3rd try: look for special artwork in configured artwork directory
1108 directory = getStringCopy(getSetupArtworkDir(artwork.mus_current));
1109 if (directoryExists(directory))
1115 // 4th try: look for default artwork in new default artwork directory
1116 directory = getStringCopy(getDefaultMusicDir(MUS_DEFAULT_SUBDIR));
1117 if (directoryExists(directory))
1122 // 5th try: look for default artwork in old default artwork directory
1123 directory = getStringCopy(options.music_directory);
1124 if (directoryExists(directory))
1127 return NULL; // cannot find specified artwork file anywhere
1130 void InitTapeDirectory(char *level_subdir)
1132 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
1133 createDirectory(getTapeDir(NULL), "main tape", PERMS_PRIVATE);
1134 createDirectory(getTapeDir(level_subdir), "level tape", PERMS_PRIVATE);
1137 void InitScoreDirectory(char *level_subdir)
1139 createDirectory(getMainUserGameDataDir(), "main user data", PERMS_PRIVATE);
1140 createDirectory(getScoreDir(NULL), "main score", PERMS_PRIVATE);
1141 createDirectory(getScoreDir(level_subdir), "level score", PERMS_PRIVATE);
1144 void InitScoreCacheDirectory(char *level_subdir)
1146 createDirectory(getMainUserGameDataDir(), "main user data", PERMS_PRIVATE);
1147 createDirectory(getCacheDir(), "cache data", PERMS_PRIVATE);
1148 createDirectory(getScoreCacheDir(NULL), "main score", PERMS_PRIVATE);
1149 createDirectory(getScoreCacheDir(level_subdir), "level score", PERMS_PRIVATE);
1152 void InitScoreTapeDirectory(char *level_subdir, int nr)
1154 InitScoreDirectory(level_subdir);
1156 createDirectory(getScoreTapeDir(level_subdir, nr), "score tape", PERMS_PRIVATE);
1159 static void SaveUserLevelInfo(void);
1161 void InitUserLevelDirectory(char *level_subdir)
1163 if (!directoryExists(getUserLevelDir(level_subdir)))
1165 createDirectory(getMainUserGameDataDir(), "main user data", PERMS_PRIVATE);
1166 createDirectory(getUserLevelDir(NULL), "main user level", PERMS_PRIVATE);
1167 createDirectory(getUserLevelDir(level_subdir), "user level", PERMS_PRIVATE);
1169 if (setup.internal.create_user_levelset)
1170 SaveUserLevelInfo();
1174 void InitNetworkLevelDirectory(char *level_subdir)
1176 if (!directoryExists(getNetworkLevelDir(level_subdir)))
1178 createDirectory(getMainUserGameDataDir(), "main user data", PERMS_PRIVATE);
1179 createDirectory(getNetworkDir(), "network data", PERMS_PRIVATE);
1180 createDirectory(getNetworkLevelDir(NULL), "main network level", PERMS_PRIVATE);
1181 createDirectory(getNetworkLevelDir(level_subdir), "network level", PERMS_PRIVATE);
1185 void InitLevelSetupDirectory(char *level_subdir)
1187 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
1188 createDirectory(getLevelSetupDir(NULL), "main level setup", PERMS_PRIVATE);
1189 createDirectory(getLevelSetupDir(level_subdir), "level setup", PERMS_PRIVATE);
1192 static void InitCacheDirectory(void)
1194 createDirectory(getMainUserGameDataDir(), "main user data", PERMS_PRIVATE);
1195 createDirectory(getCacheDir(), "cache data", PERMS_PRIVATE);
1199 // ----------------------------------------------------------------------------
1200 // some functions to handle lists of level and artwork directories
1201 // ----------------------------------------------------------------------------
1203 TreeInfo *newTreeInfo(void)
1205 return checked_calloc(sizeof(TreeInfo));
1208 TreeInfo *newTreeInfo_setDefaults(int type)
1210 TreeInfo *ti = newTreeInfo();
1212 setTreeInfoToDefaults(ti, type);
1217 void pushTreeInfo(TreeInfo **node_first, TreeInfo *node_new)
1219 node_new->next = *node_first;
1220 *node_first = node_new;
1223 void removeTreeInfo(TreeInfo **node_first)
1225 TreeInfo *node_old = *node_first;
1227 *node_first = node_old->next;
1228 node_old->next = NULL;
1230 freeTreeInfo(node_old);
1233 int numTreeInfo(TreeInfo *node)
1246 boolean validLevelSeries(TreeInfo *node)
1248 // in a number of cases, tree node is no valid level set
1249 if (node == NULL || node->node_group || node->parent_link || node->is_copy)
1255 TreeInfo *getValidLevelSeries(TreeInfo *node, TreeInfo *default_node)
1257 if (validLevelSeries(node))
1259 else if (node->is_copy)
1260 return getTreeInfoFromIdentifier(leveldir_first, node->identifier);
1262 return getFirstValidTreeInfoEntry(default_node);
1265 TreeInfo *getFirstValidTreeInfoEntry(TreeInfo *node)
1270 if (node->node_group) // enter level group (step down into tree)
1271 return getFirstValidTreeInfoEntry(node->node_group);
1272 else if (node->parent_link) // skip start entry of level group
1274 if (node->next) // get first real level series entry
1275 return getFirstValidTreeInfoEntry(node->next);
1276 else // leave empty level group and go on
1277 return getFirstValidTreeInfoEntry(node->node_parent->next);
1279 else // this seems to be a regular level series
1283 TreeInfo *getTreeInfoFirstGroupEntry(TreeInfo *node)
1288 if (node->node_parent == NULL) // top level group
1289 return *node->node_top;
1290 else // sub level group
1291 return node->node_parent->node_group;
1294 int numTreeInfoInGroup(TreeInfo *node)
1296 return numTreeInfo(getTreeInfoFirstGroupEntry(node));
1299 int getPosFromTreeInfo(TreeInfo *node)
1301 TreeInfo *node_cmp = getTreeInfoFirstGroupEntry(node);
1306 if (node_cmp == node)
1310 node_cmp = node_cmp->next;
1316 TreeInfo *getTreeInfoFromPos(TreeInfo *node, int pos)
1318 TreeInfo *node_default = node;
1330 return node_default;
1333 static TreeInfo *getTreeInfoFromIdentifierExt(TreeInfo *node, char *identifier,
1334 int node_type_wanted)
1336 if (identifier == NULL)
1341 if (TREE_NODE_TYPE(node) == node_type_wanted &&
1342 strEqual(identifier, node->identifier))
1345 if (node->node_group)
1347 TreeInfo *node_group = getTreeInfoFromIdentifierExt(node->node_group,
1360 TreeInfo *getTreeInfoFromIdentifier(TreeInfo *node, char *identifier)
1362 return getTreeInfoFromIdentifierExt(node, identifier, TREE_NODE_TYPE_DEFAULT);
1365 static TreeInfo *cloneTreeNode(TreeInfo **node_top, TreeInfo *node_parent,
1366 TreeInfo *node, boolean skip_sets_without_levels)
1373 if (!node->parent_link && !node->level_group &&
1374 skip_sets_without_levels && node->levels == 0)
1375 return cloneTreeNode(node_top, node_parent, node->next,
1376 skip_sets_without_levels);
1378 node_new = getTreeInfoCopy(node); // copy complete node
1380 node_new->node_top = node_top; // correct top node link
1381 node_new->node_parent = node_parent; // correct parent node link
1383 if (node->level_group)
1384 node_new->node_group = cloneTreeNode(node_top, node_new, node->node_group,
1385 skip_sets_without_levels);
1387 node_new->next = cloneTreeNode(node_top, node_parent, node->next,
1388 skip_sets_without_levels);
1393 static void cloneTree(TreeInfo **ti_new, TreeInfo *ti, boolean skip_empty_sets)
1395 TreeInfo *ti_cloned = cloneTreeNode(ti_new, NULL, ti, skip_empty_sets);
1397 *ti_new = ti_cloned;
1400 static boolean adjustTreeGraphicsForEMC(TreeInfo *node)
1402 boolean settings_changed = FALSE;
1406 boolean want_ecs = (setup.prefer_aga_graphics == FALSE);
1407 boolean want_aga = (setup.prefer_aga_graphics == TRUE);
1408 boolean has_only_ecs = (!node->graphics_set && !node->graphics_set_aga);
1409 boolean has_only_aga = (!node->graphics_set && !node->graphics_set_ecs);
1410 char *graphics_set = NULL;
1412 if (node->graphics_set_ecs && (want_ecs || has_only_ecs))
1413 graphics_set = node->graphics_set_ecs;
1415 if (node->graphics_set_aga && (want_aga || has_only_aga))
1416 graphics_set = node->graphics_set_aga;
1418 if (graphics_set && !strEqual(node->graphics_set, graphics_set))
1420 setString(&node->graphics_set, graphics_set);
1421 settings_changed = TRUE;
1424 if (node->node_group != NULL)
1425 settings_changed |= adjustTreeGraphicsForEMC(node->node_group);
1430 return settings_changed;
1433 static boolean adjustTreeSoundsForEMC(TreeInfo *node)
1435 boolean settings_changed = FALSE;
1439 boolean want_default = (setup.prefer_lowpass_sounds == FALSE);
1440 boolean want_lowpass = (setup.prefer_lowpass_sounds == TRUE);
1441 boolean has_only_default = (!node->sounds_set && !node->sounds_set_lowpass);
1442 boolean has_only_lowpass = (!node->sounds_set && !node->sounds_set_default);
1443 char *sounds_set = NULL;
1445 if (node->sounds_set_default && (want_default || has_only_default))
1446 sounds_set = node->sounds_set_default;
1448 if (node->sounds_set_lowpass && (want_lowpass || has_only_lowpass))
1449 sounds_set = node->sounds_set_lowpass;
1451 if (sounds_set && !strEqual(node->sounds_set, sounds_set))
1453 setString(&node->sounds_set, sounds_set);
1454 settings_changed = TRUE;
1457 if (node->node_group != NULL)
1458 settings_changed |= adjustTreeSoundsForEMC(node->node_group);
1463 return settings_changed;
1466 void dumpTreeInfo(TreeInfo *node, int depth)
1468 char bullet_list[] = { '-', '*', 'o' };
1472 Debug("tree", "Dumping TreeInfo:");
1476 char bullet = bullet_list[depth % ARRAY_SIZE(bullet_list)];
1478 for (i = 0; i < depth * 2; i++)
1479 DebugContinued("", " ");
1481 DebugContinued("tree", "%c '%s' ['%s] [PARENT: '%s'] %s\n",
1482 bullet, node->name, node->identifier,
1483 (node->node_parent ? node->node_parent->identifier : "-"),
1484 (node->node_group ? "[GROUP]" : ""));
1487 // use for dumping artwork info tree
1488 Debug("tree", "subdir == '%s' ['%s', '%s'] [%d])",
1489 node->subdir, node->fullpath, node->basepath, node->in_user_dir);
1492 if (node->node_group != NULL)
1493 dumpTreeInfo(node->node_group, depth + 1);
1499 void sortTreeInfoBySortFunction(TreeInfo **node_first,
1500 int (*compare_function)(const void *,
1503 int num_nodes = numTreeInfo(*node_first);
1504 TreeInfo **sort_array;
1505 TreeInfo *node = *node_first;
1511 // allocate array for sorting structure pointers
1512 sort_array = checked_calloc(num_nodes * sizeof(TreeInfo *));
1514 // writing structure pointers to sorting array
1515 while (i < num_nodes && node) // double boundary check...
1517 sort_array[i] = node;
1523 // sorting the structure pointers in the sorting array
1524 qsort(sort_array, num_nodes, sizeof(TreeInfo *),
1527 // update the linkage of list elements with the sorted node array
1528 for (i = 0; i < num_nodes - 1; i++)
1529 sort_array[i]->next = sort_array[i + 1];
1530 sort_array[num_nodes - 1]->next = NULL;
1532 // update the linkage of the main list anchor pointer
1533 *node_first = sort_array[0];
1537 // now recursively sort the level group structures
1541 if (node->node_group != NULL)
1542 sortTreeInfoBySortFunction(&node->node_group, compare_function);
1548 void sortTreeInfo(TreeInfo **node_first)
1550 sortTreeInfoBySortFunction(node_first, compareTreeInfoEntries);
1554 // ============================================================================
1555 // some stuff from "files.c"
1556 // ============================================================================
1558 #if defined(PLATFORM_WIN32)
1560 #define S_IRGRP S_IRUSR
1563 #define S_IROTH S_IRUSR
1566 #define S_IWGRP S_IWUSR
1569 #define S_IWOTH S_IWUSR
1572 #define S_IXGRP S_IXUSR
1575 #define S_IXOTH S_IXUSR
1578 #define S_IRWXG (S_IRGRP | S_IWGRP | S_IXGRP)
1583 #endif // PLATFORM_WIN32
1585 // file permissions for newly written files
1586 #define MODE_R_ALL (S_IRUSR | S_IRGRP | S_IROTH)
1587 #define MODE_W_ALL (S_IWUSR | S_IWGRP | S_IWOTH)
1588 #define MODE_X_ALL (S_IXUSR | S_IXGRP | S_IXOTH)
1590 #define MODE_W_PRIVATE (S_IWUSR)
1591 #define MODE_W_PUBLIC_FILE (S_IWUSR | S_IWGRP)
1592 #define MODE_W_PUBLIC_DIR (S_IWUSR | S_IWGRP | S_ISGID)
1594 #define DIR_PERMS_PRIVATE (MODE_R_ALL | MODE_X_ALL | MODE_W_PRIVATE)
1595 #define DIR_PERMS_PUBLIC (MODE_R_ALL | MODE_X_ALL | MODE_W_PUBLIC_DIR)
1596 #define DIR_PERMS_PUBLIC_ALL (MODE_R_ALL | MODE_X_ALL | MODE_W_ALL)
1598 #define FILE_PERMS_PRIVATE (MODE_R_ALL | MODE_W_PRIVATE)
1599 #define FILE_PERMS_PUBLIC (MODE_R_ALL | MODE_W_PUBLIC_FILE)
1600 #define FILE_PERMS_PUBLIC_ALL (MODE_R_ALL | MODE_W_ALL)
1603 char *getHomeDir(void)
1605 static char *dir = NULL;
1607 #if defined(PLATFORM_WIN32)
1610 dir = checked_malloc(MAX_PATH + 1);
1612 if (!SUCCEEDED(SHGetFolderPath(NULL, CSIDL_PERSONAL, NULL, 0, dir)))
1615 #elif defined(PLATFORM_EMSCRIPTEN)
1616 dir = "/persistent";
1617 #elif defined(PLATFORM_UNIX)
1620 if ((dir = getenv("HOME")) == NULL)
1622 dir = getUnixHomeDir();
1625 dir = getStringCopy(dir);
1637 char *getCommonDataDir(void)
1639 static char *common_data_dir = NULL;
1641 #if defined(PLATFORM_WIN32)
1642 if (common_data_dir == NULL)
1644 char *dir = checked_malloc(MAX_PATH + 1);
1646 if (SUCCEEDED(SHGetFolderPath(NULL, CSIDL_COMMON_DOCUMENTS, NULL, 0, dir))
1647 && !strEqual(dir, "")) // empty for Windows 95/98
1648 common_data_dir = getPath2(dir, program.userdata_subdir);
1650 common_data_dir = options.rw_base_directory;
1653 if (common_data_dir == NULL)
1654 common_data_dir = options.rw_base_directory;
1657 return common_data_dir;
1660 char *getPersonalDataDir(void)
1662 static char *personal_data_dir = NULL;
1664 #if defined(PLATFORM_MACOSX)
1665 if (personal_data_dir == NULL)
1666 personal_data_dir = getPath2(getHomeDir(), "Documents");
1668 if (personal_data_dir == NULL)
1669 personal_data_dir = getHomeDir();
1672 return personal_data_dir;
1675 char *getMainUserGameDataDir(void)
1677 static char *main_user_data_dir = NULL;
1679 #if defined(PLATFORM_ANDROID)
1680 if (main_user_data_dir == NULL)
1681 main_user_data_dir = (char *)(SDL_AndroidGetExternalStorageState() &
1682 SDL_ANDROID_EXTERNAL_STORAGE_WRITE ?
1683 SDL_AndroidGetExternalStoragePath() :
1684 SDL_AndroidGetInternalStoragePath());
1686 if (main_user_data_dir == NULL)
1687 main_user_data_dir = getPath2(getPersonalDataDir(),
1688 program.userdata_subdir);
1691 return main_user_data_dir;
1694 char *getUserGameDataDir(void)
1697 return getMainUserGameDataDir();
1699 return getUserDir(user.nr);
1702 char *getSetupDir(void)
1704 return getUserGameDataDir();
1707 static mode_t posix_umask(mode_t mask)
1709 #if defined(PLATFORM_UNIX)
1716 static int posix_mkdir(const char *pathname, mode_t mode)
1718 #if defined(PLATFORM_WIN32)
1719 return mkdir(pathname);
1721 return mkdir(pathname, mode);
1725 static boolean posix_process_running_setgid(void)
1727 #if defined(PLATFORM_UNIX)
1728 return (getgid() != getegid());
1734 void createDirectory(char *dir, char *text, int permission_class)
1736 if (directoryExists(dir))
1739 // leave "other" permissions in umask untouched, but ensure group parts
1740 // of USERDATA_DIR_MODE are not masked
1741 mode_t dir_mode = (permission_class == PERMS_PRIVATE ?
1742 DIR_PERMS_PRIVATE : DIR_PERMS_PUBLIC);
1743 mode_t last_umask = posix_umask(0);
1744 mode_t group_umask = ~(dir_mode & S_IRWXG);
1745 int running_setgid = posix_process_running_setgid();
1747 if (permission_class == PERMS_PUBLIC)
1749 // if we're setgid, protect files against "other"
1750 // else keep umask(0) to make the dir world-writable
1753 posix_umask(last_umask & group_umask);
1755 dir_mode = DIR_PERMS_PUBLIC_ALL;
1758 if (posix_mkdir(dir, dir_mode) != 0)
1759 Warn("cannot create %s directory '%s': %s", text, dir, strerror(errno));
1761 if (permission_class == PERMS_PUBLIC && !running_setgid)
1762 chmod(dir, dir_mode);
1764 posix_umask(last_umask); // restore previous umask
1767 void InitMainUserDataDirectory(void)
1769 createDirectory(getMainUserGameDataDir(), "main user data", PERMS_PRIVATE);
1772 void InitUserDataDirectory(void)
1774 createDirectory(getMainUserGameDataDir(), "main user data", PERMS_PRIVATE);
1778 createDirectory(getUserDir(-1), "users", PERMS_PRIVATE);
1779 createDirectory(getUserDir(user.nr), "user data", PERMS_PRIVATE);
1783 void SetFilePermissions(char *filename, int permission_class)
1785 int running_setgid = posix_process_running_setgid();
1786 int perms = (permission_class == PERMS_PRIVATE ?
1787 FILE_PERMS_PRIVATE : FILE_PERMS_PUBLIC);
1789 if (permission_class == PERMS_PUBLIC && !running_setgid)
1790 perms = FILE_PERMS_PUBLIC_ALL;
1792 chmod(filename, perms);
1795 char *getCookie(char *file_type)
1797 static char cookie[MAX_COOKIE_LEN + 1];
1799 if (strlen(program.cookie_prefix) + 1 +
1800 strlen(file_type) + strlen("_FILE_VERSION_x.x") > MAX_COOKIE_LEN)
1801 return "[COOKIE ERROR]"; // should never happen
1803 sprintf(cookie, "%s_%s_FILE_VERSION_%d.%d",
1804 program.cookie_prefix, file_type,
1805 program.version_super, program.version_major);
1810 void fprintFileHeader(FILE *file, char *basename)
1812 char *prefix = "# ";
1815 fprintf_line_with_prefix(file, prefix, sep1, 77);
1816 fprintf(file, "%s%s\n", prefix, basename);
1817 fprintf_line_with_prefix(file, prefix, sep1, 77);
1818 fprintf(file, "\n");
1821 int getFileVersionFromCookieString(const char *cookie)
1823 const char *ptr_cookie1, *ptr_cookie2;
1824 const char *pattern1 = "_FILE_VERSION_";
1825 const char *pattern2 = "?.?";
1826 const int len_cookie = strlen(cookie);
1827 const int len_pattern1 = strlen(pattern1);
1828 const int len_pattern2 = strlen(pattern2);
1829 const int len_pattern = len_pattern1 + len_pattern2;
1830 int version_super, version_major;
1832 if (len_cookie <= len_pattern)
1835 ptr_cookie1 = &cookie[len_cookie - len_pattern];
1836 ptr_cookie2 = &cookie[len_cookie - len_pattern2];
1838 if (strncmp(ptr_cookie1, pattern1, len_pattern1) != 0)
1841 if (ptr_cookie2[0] < '0' || ptr_cookie2[0] > '9' ||
1842 ptr_cookie2[1] != '.' ||
1843 ptr_cookie2[2] < '0' || ptr_cookie2[2] > '9')
1846 version_super = ptr_cookie2[0] - '0';
1847 version_major = ptr_cookie2[2] - '0';
1849 return VERSION_IDENT(version_super, version_major, 0, 0);
1852 boolean checkCookieString(const char *cookie, const char *template)
1854 const char *pattern = "_FILE_VERSION_?.?";
1855 const int len_cookie = strlen(cookie);
1856 const int len_template = strlen(template);
1857 const int len_pattern = strlen(pattern);
1859 if (len_cookie != len_template)
1862 if (strncmp(cookie, template, len_cookie - len_pattern) != 0)
1869 // ----------------------------------------------------------------------------
1870 // setup file list and hash handling functions
1871 // ----------------------------------------------------------------------------
1873 char *getFormattedSetupEntry(char *token, char *value)
1876 static char entry[MAX_LINE_LEN];
1878 // if value is an empty string, just return token without value
1882 // start with the token and some spaces to format output line
1883 sprintf(entry, "%s:", token);
1884 for (i = strlen(entry); i < token_value_position; i++)
1887 // continue with the token's value
1888 strcat(entry, value);
1893 SetupFileList *newSetupFileList(char *token, char *value)
1895 SetupFileList *new = checked_malloc(sizeof(SetupFileList));
1897 new->token = getStringCopy(token);
1898 new->value = getStringCopy(value);
1905 void freeSetupFileList(SetupFileList *list)
1910 checked_free(list->token);
1911 checked_free(list->value);
1914 freeSetupFileList(list->next);
1919 char *getListEntry(SetupFileList *list, char *token)
1924 if (strEqual(list->token, token))
1927 return getListEntry(list->next, token);
1930 SetupFileList *setListEntry(SetupFileList *list, char *token, char *value)
1935 if (strEqual(list->token, token))
1937 checked_free(list->value);
1939 list->value = getStringCopy(value);
1943 else if (list->next == NULL)
1944 return (list->next = newSetupFileList(token, value));
1946 return setListEntry(list->next, token, value);
1949 SetupFileList *addListEntry(SetupFileList *list, char *token, char *value)
1954 if (list->next == NULL)
1955 return (list->next = newSetupFileList(token, value));
1957 return addListEntry(list->next, token, value);
1960 #if ENABLE_UNUSED_CODE
1962 static void printSetupFileList(SetupFileList *list)
1967 Debug("setup:printSetupFileList", "token: '%s'", list->token);
1968 Debug("setup:printSetupFileList", "value: '%s'", list->value);
1970 printSetupFileList(list->next);
1976 DEFINE_HASHTABLE_INSERT(insert_hash_entry, char, char);
1977 DEFINE_HASHTABLE_SEARCH(search_hash_entry, char, char);
1978 DEFINE_HASHTABLE_CHANGE(change_hash_entry, char, char);
1979 DEFINE_HASHTABLE_REMOVE(remove_hash_entry, char, char);
1981 #define insert_hash_entry hashtable_insert
1982 #define search_hash_entry hashtable_search
1983 #define change_hash_entry hashtable_change
1984 #define remove_hash_entry hashtable_remove
1987 unsigned int get_hash_from_key(void *key)
1992 This algorithm (k=33) was first reported by Dan Bernstein many years ago in
1993 'comp.lang.c'. Another version of this algorithm (now favored by Bernstein)
1994 uses XOR: hash(i) = hash(i - 1) * 33 ^ str[i]; the magic of number 33 (why
1995 it works better than many other constants, prime or not) has never been
1996 adequately explained.
1998 If you just want to have a good hash function, and cannot wait, djb2
1999 is one of the best string hash functions i know. It has excellent
2000 distribution and speed on many different sets of keys and table sizes.
2001 You are not likely to do better with one of the "well known" functions
2002 such as PJW, K&R, etc.
2004 Ozan (oz) Yigit [http://www.cs.yorku.ca/~oz/hash.html]
2007 char *str = (char *)key;
2008 unsigned int hash = 5381;
2011 while ((c = *str++))
2012 hash = ((hash << 5) + hash) + c; // hash * 33 + c
2017 static int keys_are_equal(void *key1, void *key2)
2019 return (strEqual((char *)key1, (char *)key2));
2022 SetupFileHash *newSetupFileHash(void)
2024 SetupFileHash *new_hash =
2025 create_hashtable(16, 0.75, get_hash_from_key, keys_are_equal);
2027 if (new_hash == NULL)
2028 Fail("create_hashtable() failed -- out of memory");
2033 void freeSetupFileHash(SetupFileHash *hash)
2038 hashtable_destroy(hash, 1); // 1 == also free values stored in hash
2041 char *getHashEntry(SetupFileHash *hash, char *token)
2046 return search_hash_entry(hash, token);
2049 void setHashEntry(SetupFileHash *hash, char *token, char *value)
2056 value_copy = getStringCopy(value);
2058 // change value; if it does not exist, insert it as new
2059 if (!change_hash_entry(hash, token, value_copy))
2060 if (!insert_hash_entry(hash, getStringCopy(token), value_copy))
2061 Fail("cannot insert into hash -- aborting");
2064 char *removeHashEntry(SetupFileHash *hash, char *token)
2069 return remove_hash_entry(hash, token);
2072 #if ENABLE_UNUSED_CODE
2074 static void printSetupFileHash(SetupFileHash *hash)
2076 BEGIN_HASH_ITERATION(hash, itr)
2078 Debug("setup:printSetupFileHash", "token: '%s'", HASH_ITERATION_TOKEN(itr));
2079 Debug("setup:printSetupFileHash", "value: '%s'", HASH_ITERATION_VALUE(itr));
2081 END_HASH_ITERATION(hash, itr)
2086 #define ALLOW_TOKEN_VALUE_SEPARATOR_BEING_WHITESPACE 1
2087 #define CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING 0
2088 #define CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH 0
2090 static boolean token_value_separator_found = FALSE;
2091 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2092 static boolean token_value_separator_warning = FALSE;
2094 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2095 static boolean token_already_exists_warning = FALSE;
2098 static boolean getTokenValueFromSetupLineExt(char *line,
2099 char **token_ptr, char **value_ptr,
2100 char *filename, char *line_raw,
2102 boolean separator_required)
2104 static char line_copy[MAX_LINE_LEN + 1], line_raw_copy[MAX_LINE_LEN + 1];
2105 char *token, *value, *line_ptr;
2107 // when externally invoked via ReadTokenValueFromLine(), copy line buffers
2108 if (line_raw == NULL)
2110 strncpy(line_copy, line, MAX_LINE_LEN);
2111 line_copy[MAX_LINE_LEN] = '\0';
2114 strcpy(line_raw_copy, line_copy);
2115 line_raw = line_raw_copy;
2118 // cut trailing comment from input line
2119 for (line_ptr = line; *line_ptr; line_ptr++)
2121 if (*line_ptr == '#')
2128 // cut trailing whitespaces from input line
2129 for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
2130 if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
2133 // ignore empty lines
2137 // cut leading whitespaces from token
2138 for (token = line; *token; token++)
2139 if (*token != ' ' && *token != '\t')
2142 // start with empty value as reliable default
2145 token_value_separator_found = FALSE;
2147 // find end of token to determine start of value
2148 for (line_ptr = token; *line_ptr; line_ptr++)
2150 // first look for an explicit token/value separator, like ':' or '='
2151 if (*line_ptr == ':' || *line_ptr == '=')
2153 *line_ptr = '\0'; // terminate token string
2154 value = line_ptr + 1; // set beginning of value
2156 token_value_separator_found = TRUE;
2162 #if ALLOW_TOKEN_VALUE_SEPARATOR_BEING_WHITESPACE
2163 // fallback: if no token/value separator found, also allow whitespaces
2164 if (!token_value_separator_found && !separator_required)
2166 for (line_ptr = token; *line_ptr; line_ptr++)
2168 if (*line_ptr == ' ' || *line_ptr == '\t')
2170 *line_ptr = '\0'; // terminate token string
2171 value = line_ptr + 1; // set beginning of value
2173 token_value_separator_found = TRUE;
2179 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2180 if (token_value_separator_found)
2182 if (!token_value_separator_warning)
2184 Debug("setup", "---");
2186 if (filename != NULL)
2188 Debug("setup", "missing token/value separator(s) in config file:");
2189 Debug("setup", "- config file: '%s'", filename);
2193 Debug("setup", "missing token/value separator(s):");
2196 token_value_separator_warning = TRUE;
2199 if (filename != NULL)
2200 Debug("setup", "- line %d: '%s'", line_nr, line_raw);
2202 Debug("setup", "- line: '%s'", line_raw);
2208 // cut trailing whitespaces from token
2209 for (line_ptr = &token[strlen(token)]; line_ptr >= token; line_ptr--)
2210 if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
2213 // cut leading whitespaces from value
2214 for (; *value; value++)
2215 if (*value != ' ' && *value != '\t')
2224 boolean getTokenValueFromSetupLine(char *line, char **token, char **value)
2226 // while the internal (old) interface does not require a token/value
2227 // separator (for downwards compatibility with existing files which
2228 // don't use them), it is mandatory for the external (new) interface
2230 return getTokenValueFromSetupLineExt(line, token, value, NULL, NULL, 0, TRUE);
2233 static boolean loadSetupFileData(void *setup_file_data, char *filename,
2234 boolean top_recursion_level, boolean is_hash)
2236 static SetupFileHash *include_filename_hash = NULL;
2237 char line[MAX_LINE_LEN], line_raw[MAX_LINE_LEN], previous_line[MAX_LINE_LEN];
2238 char *token, *value, *line_ptr;
2239 void *insert_ptr = NULL;
2240 boolean read_continued_line = FALSE;
2242 int line_nr = 0, token_count = 0, include_count = 0;
2244 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2245 token_value_separator_warning = FALSE;
2248 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2249 token_already_exists_warning = FALSE;
2252 if (!(file = openFile(filename, MODE_READ)))
2254 #if DEBUG_NO_CONFIG_FILE
2255 Debug("setup", "cannot open configuration file '%s'", filename);
2261 // use "insert pointer" to store list end for constant insertion complexity
2263 insert_ptr = setup_file_data;
2265 // on top invocation, create hash to mark included files (to prevent loops)
2266 if (top_recursion_level)
2267 include_filename_hash = newSetupFileHash();
2269 // mark this file as already included (to prevent including it again)
2270 setHashEntry(include_filename_hash, getBaseNamePtr(filename), "true");
2272 while (!checkEndOfFile(file))
2274 // read next line of input file
2275 if (!getStringFromFile(file, line, MAX_LINE_LEN))
2278 // check if line was completely read and is terminated by line break
2279 if (strlen(line) > 0 && line[strlen(line) - 1] == '\n')
2282 // cut trailing line break (this can be newline and/or carriage return)
2283 for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
2284 if ((*line_ptr == '\n' || *line_ptr == '\r') && *(line_ptr + 1) == '\0')
2287 // copy raw input line for later use (mainly debugging output)
2288 strcpy(line_raw, line);
2290 if (read_continued_line)
2292 // append new line to existing line, if there is enough space
2293 if (strlen(previous_line) + strlen(line_ptr) < MAX_LINE_LEN)
2294 strcat(previous_line, line_ptr);
2296 strcpy(line, previous_line); // copy storage buffer to line
2298 read_continued_line = FALSE;
2301 // if the last character is '\', continue at next line
2302 if (strlen(line) > 0 && line[strlen(line) - 1] == '\\')
2304 line[strlen(line) - 1] = '\0'; // cut off trailing backslash
2305 strcpy(previous_line, line); // copy line to storage buffer
2307 read_continued_line = TRUE;
2312 if (!getTokenValueFromSetupLineExt(line, &token, &value, filename,
2313 line_raw, line_nr, FALSE))
2318 if (strEqual(token, "include"))
2320 if (getHashEntry(include_filename_hash, value) == NULL)
2322 char *basepath = getBasePath(filename);
2323 char *basename = getBaseName(value);
2324 char *filename_include = getPath2(basepath, basename);
2326 loadSetupFileData(setup_file_data, filename_include, FALSE, is_hash);
2330 free(filename_include);
2336 Warn("ignoring already processed file '%s'", value);
2343 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2345 getHashEntry((SetupFileHash *)setup_file_data, token);
2347 if (old_value != NULL)
2349 if (!token_already_exists_warning)
2351 Debug("setup", "---");
2352 Debug("setup", "duplicate token(s) found in config file:");
2353 Debug("setup", "- config file: '%s'", filename);
2355 token_already_exists_warning = TRUE;
2358 Debug("setup", "- token: '%s' (in line %d)", token, line_nr);
2359 Debug("setup", " old value: '%s'", old_value);
2360 Debug("setup", " new value: '%s'", value);
2364 setHashEntry((SetupFileHash *)setup_file_data, token, value);
2368 insert_ptr = addListEntry((SetupFileList *)insert_ptr, token, value);
2378 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2379 if (token_value_separator_warning)
2380 Debug("setup", "---");
2383 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2384 if (token_already_exists_warning)
2385 Debug("setup", "---");
2388 if (token_count == 0 && include_count == 0)
2389 Warn("configuration file '%s' is empty", filename);
2391 if (top_recursion_level)
2392 freeSetupFileHash(include_filename_hash);
2397 static int compareSetupFileData(const void *object1, const void *object2)
2399 const struct ConfigInfo *entry1 = (struct ConfigInfo *)object1;
2400 const struct ConfigInfo *entry2 = (struct ConfigInfo *)object2;
2402 return strcmp(entry1->token, entry2->token);
2405 static void saveSetupFileHash(SetupFileHash *hash, char *filename)
2407 int item_count = hashtable_count(hash);
2408 int item_size = sizeof(struct ConfigInfo);
2409 struct ConfigInfo *sort_array = checked_malloc(item_count * item_size);
2413 // copy string pointers from hash to array
2414 BEGIN_HASH_ITERATION(hash, itr)
2416 sort_array[i].token = HASH_ITERATION_TOKEN(itr);
2417 sort_array[i].value = HASH_ITERATION_VALUE(itr);
2421 if (i > item_count) // should never happen
2424 END_HASH_ITERATION(hash, itr)
2426 // sort string pointers from hash in array
2427 qsort(sort_array, item_count, item_size, compareSetupFileData);
2429 if (!(file = fopen(filename, MODE_WRITE)))
2431 Warn("cannot write configuration file '%s'", filename);
2436 fprintf(file, "%s\n\n", getFormattedSetupEntry("program.version",
2437 program.version_string));
2438 for (i = 0; i < item_count; i++)
2439 fprintf(file, "%s\n", getFormattedSetupEntry(sort_array[i].token,
2440 sort_array[i].value));
2443 checked_free(sort_array);
2446 SetupFileList *loadSetupFileList(char *filename)
2448 SetupFileList *setup_file_list = newSetupFileList("", "");
2449 SetupFileList *first_valid_list_entry;
2451 if (!loadSetupFileData(setup_file_list, filename, TRUE, FALSE))
2453 freeSetupFileList(setup_file_list);
2458 first_valid_list_entry = setup_file_list->next;
2460 // free empty list header
2461 setup_file_list->next = NULL;
2462 freeSetupFileList(setup_file_list);
2464 return first_valid_list_entry;
2467 SetupFileHash *loadSetupFileHash(char *filename)
2469 SetupFileHash *setup_file_hash = newSetupFileHash();
2471 if (!loadSetupFileData(setup_file_hash, filename, TRUE, TRUE))
2473 freeSetupFileHash(setup_file_hash);
2478 return setup_file_hash;
2482 // ============================================================================
2484 // ============================================================================
2486 #define TOKEN_STR_LAST_LEVEL_SERIES "last_level_series"
2487 #define TOKEN_STR_LAST_PLAYED_LEVEL "last_played_level"
2488 #define TOKEN_STR_HANDICAP_LEVEL "handicap_level"
2489 #define TOKEN_STR_LAST_USER "last_user"
2491 // level directory info
2492 #define LEVELINFO_TOKEN_IDENTIFIER 0
2493 #define LEVELINFO_TOKEN_NAME 1
2494 #define LEVELINFO_TOKEN_NAME_SORTING 2
2495 #define LEVELINFO_TOKEN_AUTHOR 3
2496 #define LEVELINFO_TOKEN_YEAR 4
2497 #define LEVELINFO_TOKEN_PROGRAM_TITLE 5
2498 #define LEVELINFO_TOKEN_PROGRAM_COPYRIGHT 6
2499 #define LEVELINFO_TOKEN_PROGRAM_COMPANY 7
2500 #define LEVELINFO_TOKEN_IMPORTED_FROM 8
2501 #define LEVELINFO_TOKEN_IMPORTED_BY 9
2502 #define LEVELINFO_TOKEN_TESTED_BY 10
2503 #define LEVELINFO_TOKEN_LEVELS 11
2504 #define LEVELINFO_TOKEN_FIRST_LEVEL 12
2505 #define LEVELINFO_TOKEN_SORT_PRIORITY 13
2506 #define LEVELINFO_TOKEN_LATEST_ENGINE 14
2507 #define LEVELINFO_TOKEN_LEVEL_GROUP 15
2508 #define LEVELINFO_TOKEN_READONLY 16
2509 #define LEVELINFO_TOKEN_GRAPHICS_SET_ECS 17
2510 #define LEVELINFO_TOKEN_GRAPHICS_SET_AGA 18
2511 #define LEVELINFO_TOKEN_GRAPHICS_SET 19
2512 #define LEVELINFO_TOKEN_SOUNDS_SET_DEFAULT 20
2513 #define LEVELINFO_TOKEN_SOUNDS_SET_LOWPASS 21
2514 #define LEVELINFO_TOKEN_SOUNDS_SET 22
2515 #define LEVELINFO_TOKEN_MUSIC_SET 23
2516 #define LEVELINFO_TOKEN_FILENAME 24
2517 #define LEVELINFO_TOKEN_FILETYPE 25
2518 #define LEVELINFO_TOKEN_SPECIAL_FLAGS 26
2519 #define LEVELINFO_TOKEN_HANDICAP 27
2520 #define LEVELINFO_TOKEN_SKIP_LEVELS 28
2521 #define LEVELINFO_TOKEN_USE_EMC_TILES 29
2523 #define NUM_LEVELINFO_TOKENS 30
2525 static LevelDirTree ldi;
2527 static struct TokenInfo levelinfo_tokens[] =
2529 // level directory info
2530 { TYPE_STRING, &ldi.identifier, "identifier" },
2531 { TYPE_STRING, &ldi.name, "name" },
2532 { TYPE_STRING, &ldi.name_sorting, "name_sorting" },
2533 { TYPE_STRING, &ldi.author, "author" },
2534 { TYPE_STRING, &ldi.year, "year" },
2535 { TYPE_STRING, &ldi.program_title, "program_title" },
2536 { TYPE_STRING, &ldi.program_copyright, "program_copyright" },
2537 { TYPE_STRING, &ldi.program_company, "program_company" },
2538 { TYPE_STRING, &ldi.imported_from, "imported_from" },
2539 { TYPE_STRING, &ldi.imported_by, "imported_by" },
2540 { TYPE_STRING, &ldi.tested_by, "tested_by" },
2541 { TYPE_INTEGER, &ldi.levels, "levels" },
2542 { TYPE_INTEGER, &ldi.first_level, "first_level" },
2543 { TYPE_INTEGER, &ldi.sort_priority, "sort_priority" },
2544 { TYPE_BOOLEAN, &ldi.latest_engine, "latest_engine" },
2545 { TYPE_BOOLEAN, &ldi.level_group, "level_group" },
2546 { TYPE_BOOLEAN, &ldi.readonly, "readonly" },
2547 { TYPE_STRING, &ldi.graphics_set_ecs, "graphics_set.ecs" },
2548 { TYPE_STRING, &ldi.graphics_set_aga, "graphics_set.aga" },
2549 { TYPE_STRING, &ldi.graphics_set, "graphics_set" },
2550 { TYPE_STRING, &ldi.sounds_set_default,"sounds_set.default" },
2551 { TYPE_STRING, &ldi.sounds_set_lowpass,"sounds_set.lowpass" },
2552 { TYPE_STRING, &ldi.sounds_set, "sounds_set" },
2553 { TYPE_STRING, &ldi.music_set, "music_set" },
2554 { TYPE_STRING, &ldi.level_filename, "filename" },
2555 { TYPE_STRING, &ldi.level_filetype, "filetype" },
2556 { TYPE_STRING, &ldi.special_flags, "special_flags" },
2557 { TYPE_BOOLEAN, &ldi.handicap, "handicap" },
2558 { TYPE_BOOLEAN, &ldi.skip_levels, "skip_levels" },
2559 { TYPE_BOOLEAN, &ldi.use_emc_tiles, "use_emc_tiles" }
2562 static struct TokenInfo artworkinfo_tokens[] =
2564 // artwork directory info
2565 { TYPE_STRING, &ldi.identifier, "identifier" },
2566 { TYPE_STRING, &ldi.subdir, "subdir" },
2567 { TYPE_STRING, &ldi.name, "name" },
2568 { TYPE_STRING, &ldi.name_sorting, "name_sorting" },
2569 { TYPE_STRING, &ldi.author, "author" },
2570 { TYPE_STRING, &ldi.program_title, "program_title" },
2571 { TYPE_STRING, &ldi.program_copyright, "program_copyright" },
2572 { TYPE_STRING, &ldi.program_company, "program_company" },
2573 { TYPE_INTEGER, &ldi.sort_priority, "sort_priority" },
2574 { TYPE_STRING, &ldi.basepath, "basepath" },
2575 { TYPE_STRING, &ldi.fullpath, "fullpath" },
2576 { TYPE_BOOLEAN, &ldi.in_user_dir, "in_user_dir" },
2577 { TYPE_STRING, &ldi.class_desc, "class_desc" },
2582 static char *optional_tokens[] =
2585 "program_copyright",
2591 static void setTreeInfoToDefaults(TreeInfo *ti, int type)
2595 ti->node_top = (ti->type == TREE_TYPE_LEVEL_DIR ? &leveldir_first :
2596 ti->type == TREE_TYPE_GRAPHICS_DIR ? &artwork.gfx_first :
2597 ti->type == TREE_TYPE_SOUNDS_DIR ? &artwork.snd_first :
2598 ti->type == TREE_TYPE_MUSIC_DIR ? &artwork.mus_first :
2601 ti->node_parent = NULL;
2602 ti->node_group = NULL;
2609 ti->fullpath = NULL;
2610 ti->basepath = NULL;
2611 ti->identifier = NULL;
2612 ti->name = getStringCopy(ANONYMOUS_NAME);
2613 ti->name_sorting = NULL;
2614 ti->author = getStringCopy(ANONYMOUS_NAME);
2617 ti->program_title = NULL;
2618 ti->program_copyright = NULL;
2619 ti->program_company = NULL;
2621 ti->sort_priority = LEVELCLASS_UNDEFINED; // default: least priority
2622 ti->latest_engine = FALSE; // default: get from level
2623 ti->parent_link = FALSE;
2624 ti->is_copy = FALSE;
2625 ti->in_user_dir = FALSE;
2626 ti->user_defined = FALSE;
2628 ti->class_desc = NULL;
2630 ti->infotext = getStringCopy(TREE_INFOTEXT(ti->type));
2632 if (ti->type == TREE_TYPE_LEVEL_DIR)
2634 ti->imported_from = NULL;
2635 ti->imported_by = NULL;
2636 ti->tested_by = NULL;
2638 ti->graphics_set_ecs = NULL;
2639 ti->graphics_set_aga = NULL;
2640 ti->graphics_set = NULL;
2641 ti->sounds_set_default = NULL;
2642 ti->sounds_set_lowpass = NULL;
2643 ti->sounds_set = NULL;
2644 ti->music_set = NULL;
2645 ti->graphics_path = getStringCopy(UNDEFINED_FILENAME);
2646 ti->sounds_path = getStringCopy(UNDEFINED_FILENAME);
2647 ti->music_path = getStringCopy(UNDEFINED_FILENAME);
2649 ti->level_filename = NULL;
2650 ti->level_filetype = NULL;
2652 ti->special_flags = NULL;
2655 ti->first_level = 0;
2657 ti->level_group = FALSE;
2658 ti->handicap_level = 0;
2659 ti->readonly = TRUE;
2660 ti->handicap = TRUE;
2661 ti->skip_levels = FALSE;
2663 ti->use_emc_tiles = FALSE;
2667 static void setTreeInfoToDefaultsFromParent(TreeInfo *ti, TreeInfo *parent)
2671 Warn("setTreeInfoToDefaultsFromParent(): parent == NULL");
2673 setTreeInfoToDefaults(ti, TREE_TYPE_UNDEFINED);
2678 // copy all values from the parent structure
2680 ti->type = parent->type;
2682 ti->node_top = parent->node_top;
2683 ti->node_parent = parent;
2684 ti->node_group = NULL;
2691 ti->fullpath = NULL;
2692 ti->basepath = NULL;
2693 ti->identifier = NULL;
2694 ti->name = getStringCopy(ANONYMOUS_NAME);
2695 ti->name_sorting = NULL;
2696 ti->author = getStringCopy(parent->author);
2697 ti->year = getStringCopy(parent->year);
2699 ti->program_title = getStringCopy(parent->program_title);
2700 ti->program_copyright = getStringCopy(parent->program_copyright);
2701 ti->program_company = getStringCopy(parent->program_company);
2703 ti->sort_priority = parent->sort_priority;
2704 ti->latest_engine = parent->latest_engine;
2705 ti->parent_link = FALSE;
2706 ti->is_copy = FALSE;
2707 ti->in_user_dir = parent->in_user_dir;
2708 ti->user_defined = parent->user_defined;
2709 ti->color = parent->color;
2710 ti->class_desc = getStringCopy(parent->class_desc);
2712 ti->infotext = getStringCopy(parent->infotext);
2714 if (ti->type == TREE_TYPE_LEVEL_DIR)
2716 ti->imported_from = getStringCopy(parent->imported_from);
2717 ti->imported_by = getStringCopy(parent->imported_by);
2718 ti->tested_by = getStringCopy(parent->tested_by);
2720 ti->graphics_set_ecs = getStringCopy(parent->graphics_set_ecs);
2721 ti->graphics_set_aga = getStringCopy(parent->graphics_set_aga);
2722 ti->graphics_set = getStringCopy(parent->graphics_set);
2723 ti->sounds_set_default = getStringCopy(parent->sounds_set_default);
2724 ti->sounds_set_lowpass = getStringCopy(parent->sounds_set_lowpass);
2725 ti->sounds_set = getStringCopy(parent->sounds_set);
2726 ti->music_set = getStringCopy(parent->music_set);
2727 ti->graphics_path = getStringCopy(UNDEFINED_FILENAME);
2728 ti->sounds_path = getStringCopy(UNDEFINED_FILENAME);
2729 ti->music_path = getStringCopy(UNDEFINED_FILENAME);
2731 ti->level_filename = getStringCopy(parent->level_filename);
2732 ti->level_filetype = getStringCopy(parent->level_filetype);
2734 ti->special_flags = getStringCopy(parent->special_flags);
2736 ti->levels = parent->levels;
2737 ti->first_level = parent->first_level;
2738 ti->last_level = parent->last_level;
2739 ti->level_group = FALSE;
2740 ti->handicap_level = parent->handicap_level;
2741 ti->readonly = parent->readonly;
2742 ti->handicap = parent->handicap;
2743 ti->skip_levels = parent->skip_levels;
2745 ti->use_emc_tiles = parent->use_emc_tiles;
2749 static TreeInfo *getTreeInfoCopy(TreeInfo *ti)
2751 TreeInfo *ti_copy = newTreeInfo();
2753 // copy all values from the original structure
2755 ti_copy->type = ti->type;
2757 ti_copy->node_top = ti->node_top;
2758 ti_copy->node_parent = ti->node_parent;
2759 ti_copy->node_group = ti->node_group;
2760 ti_copy->next = ti->next;
2762 ti_copy->cl_first = ti->cl_first;
2763 ti_copy->cl_cursor = ti->cl_cursor;
2765 ti_copy->subdir = getStringCopy(ti->subdir);
2766 ti_copy->fullpath = getStringCopy(ti->fullpath);
2767 ti_copy->basepath = getStringCopy(ti->basepath);
2768 ti_copy->identifier = getStringCopy(ti->identifier);
2769 ti_copy->name = getStringCopy(ti->name);
2770 ti_copy->name_sorting = getStringCopy(ti->name_sorting);
2771 ti_copy->author = getStringCopy(ti->author);
2772 ti_copy->year = getStringCopy(ti->year);
2774 ti_copy->program_title = getStringCopy(ti->program_title);
2775 ti_copy->program_copyright = getStringCopy(ti->program_copyright);
2776 ti_copy->program_company = getStringCopy(ti->program_company);
2778 ti_copy->imported_from = getStringCopy(ti->imported_from);
2779 ti_copy->imported_by = getStringCopy(ti->imported_by);
2780 ti_copy->tested_by = getStringCopy(ti->tested_by);
2782 ti_copy->graphics_set_ecs = getStringCopy(ti->graphics_set_ecs);
2783 ti_copy->graphics_set_aga = getStringCopy(ti->graphics_set_aga);
2784 ti_copy->graphics_set = getStringCopy(ti->graphics_set);
2785 ti_copy->sounds_set_default = getStringCopy(ti->sounds_set_default);
2786 ti_copy->sounds_set_lowpass = getStringCopy(ti->sounds_set_lowpass);
2787 ti_copy->sounds_set = getStringCopy(ti->sounds_set);
2788 ti_copy->music_set = getStringCopy(ti->music_set);
2789 ti_copy->graphics_path = getStringCopy(ti->graphics_path);
2790 ti_copy->sounds_path = getStringCopy(ti->sounds_path);
2791 ti_copy->music_path = getStringCopy(ti->music_path);
2793 ti_copy->level_filename = getStringCopy(ti->level_filename);
2794 ti_copy->level_filetype = getStringCopy(ti->level_filetype);
2796 ti_copy->special_flags = getStringCopy(ti->special_flags);
2798 ti_copy->levels = ti->levels;
2799 ti_copy->first_level = ti->first_level;
2800 ti_copy->last_level = ti->last_level;
2801 ti_copy->sort_priority = ti->sort_priority;
2803 ti_copy->latest_engine = ti->latest_engine;
2805 ti_copy->level_group = ti->level_group;
2806 ti_copy->parent_link = ti->parent_link;
2807 ti_copy->is_copy = ti->is_copy;
2808 ti_copy->in_user_dir = ti->in_user_dir;
2809 ti_copy->user_defined = ti->user_defined;
2810 ti_copy->readonly = ti->readonly;
2811 ti_copy->handicap = ti->handicap;
2812 ti_copy->skip_levels = ti->skip_levels;
2814 ti_copy->use_emc_tiles = ti->use_emc_tiles;
2816 ti_copy->color = ti->color;
2817 ti_copy->class_desc = getStringCopy(ti->class_desc);
2818 ti_copy->handicap_level = ti->handicap_level;
2820 ti_copy->infotext = getStringCopy(ti->infotext);
2825 void freeTreeInfo(TreeInfo *ti)
2830 checked_free(ti->subdir);
2831 checked_free(ti->fullpath);
2832 checked_free(ti->basepath);
2833 checked_free(ti->identifier);
2835 checked_free(ti->name);
2836 checked_free(ti->name_sorting);
2837 checked_free(ti->author);
2838 checked_free(ti->year);
2840 checked_free(ti->program_title);
2841 checked_free(ti->program_copyright);
2842 checked_free(ti->program_company);
2844 checked_free(ti->class_desc);
2846 checked_free(ti->infotext);
2848 if (ti->type == TREE_TYPE_LEVEL_DIR)
2850 checked_free(ti->imported_from);
2851 checked_free(ti->imported_by);
2852 checked_free(ti->tested_by);
2854 checked_free(ti->graphics_set_ecs);
2855 checked_free(ti->graphics_set_aga);
2856 checked_free(ti->graphics_set);
2857 checked_free(ti->sounds_set_default);
2858 checked_free(ti->sounds_set_lowpass);
2859 checked_free(ti->sounds_set);
2860 checked_free(ti->music_set);
2862 checked_free(ti->graphics_path);
2863 checked_free(ti->sounds_path);
2864 checked_free(ti->music_path);
2866 checked_free(ti->level_filename);
2867 checked_free(ti->level_filetype);
2869 checked_free(ti->special_flags);
2872 // recursively free child node
2874 freeTreeInfo(ti->node_group);
2876 // recursively free next node
2878 freeTreeInfo(ti->next);
2883 void setSetupInfo(struct TokenInfo *token_info,
2884 int token_nr, char *token_value)
2886 int token_type = token_info[token_nr].type;
2887 void *setup_value = token_info[token_nr].value;
2889 if (token_value == NULL)
2892 // set setup field to corresponding token value
2897 *(boolean *)setup_value = get_boolean_from_string(token_value);
2901 *(int *)setup_value = get_switch3_from_string(token_value);
2905 *(Key *)setup_value = getKeyFromKeyName(token_value);
2909 *(Key *)setup_value = getKeyFromX11KeyName(token_value);
2913 *(int *)setup_value = get_integer_from_string(token_value);
2917 checked_free(*(char **)setup_value);
2918 *(char **)setup_value = getStringCopy(token_value);
2922 *(int *)setup_value = get_player_nr_from_string(token_value);
2930 static int compareTreeInfoEntries(const void *object1, const void *object2)
2932 const TreeInfo *entry1 = *((TreeInfo **)object1);
2933 const TreeInfo *entry2 = *((TreeInfo **)object2);
2934 int tree_sorting1 = TREE_SORTING(entry1);
2935 int tree_sorting2 = TREE_SORTING(entry2);
2937 if (tree_sorting1 != tree_sorting2)
2938 return (tree_sorting1 - tree_sorting2);
2940 return strcasecmp(entry1->name_sorting, entry2->name_sorting);
2943 static TreeInfo *createParentTreeInfoNode(TreeInfo *node_parent)
2947 if (node_parent == NULL)
2950 ti_new = newTreeInfo();
2951 setTreeInfoToDefaults(ti_new, node_parent->type);
2953 ti_new->node_parent = node_parent;
2954 ti_new->parent_link = TRUE;
2956 setString(&ti_new->identifier, node_parent->identifier);
2957 setString(&ti_new->name, BACKLINK_TEXT_PARENT);
2958 setString(&ti_new->name_sorting, ti_new->name);
2960 setString(&ti_new->subdir, STRING_PARENT_DIRECTORY);
2961 setString(&ti_new->fullpath, node_parent->fullpath);
2963 ti_new->sort_priority = LEVELCLASS_PARENT;
2964 ti_new->latest_engine = node_parent->latest_engine;
2966 setString(&ti_new->class_desc, getLevelClassDescription(ti_new));
2968 pushTreeInfo(&node_parent->node_group, ti_new);
2973 static TreeInfo *createTopTreeInfoNode(TreeInfo *node_first)
2975 if (node_first == NULL)
2978 TreeInfo *ti_new = newTreeInfo();
2979 int type = node_first->type;
2981 setTreeInfoToDefaults(ti_new, type);
2983 ti_new->node_parent = NULL;
2984 ti_new->parent_link = FALSE;
2986 setString(&ti_new->identifier, "top_tree_node");
2987 setString(&ti_new->name, TREE_INFOTEXT(type));
2988 setString(&ti_new->name_sorting, ti_new->name);
2990 setString(&ti_new->subdir, STRING_TOP_DIRECTORY);
2991 setString(&ti_new->fullpath, ".");
2993 ti_new->sort_priority = LEVELCLASS_TOP;
2994 ti_new->latest_engine = node_first->latest_engine;
2996 setString(&ti_new->class_desc, TREE_INFOTEXT(type));
2998 ti_new->node_group = node_first;
2999 ti_new->level_group = TRUE;
3001 TreeInfo *ti_new2 = createParentTreeInfoNode(ti_new);
3003 setString(&ti_new2->name, TREE_BACKLINK_TEXT(type));
3004 setString(&ti_new2->name_sorting, ti_new2->name);
3009 static void setTreeInfoParentNodes(TreeInfo *node, TreeInfo *node_parent)
3013 if (node->node_group)
3014 setTreeInfoParentNodes(node->node_group, node);
3016 node->node_parent = node_parent;
3023 // ----------------------------------------------------------------------------
3024 // functions for handling level and custom artwork info cache
3025 // ----------------------------------------------------------------------------
3027 static void LoadArtworkInfoCache(void)
3029 InitCacheDirectory();
3031 if (artworkinfo_cache_old == NULL)
3033 char *filename = getPath2(getCacheDir(), ARTWORKINFO_CACHE_FILE);
3035 // try to load artwork info hash from already existing cache file
3036 artworkinfo_cache_old = loadSetupFileHash(filename);
3038 // try to get program version that artwork info cache was written with
3039 char *version = getHashEntry(artworkinfo_cache_old, "program.version");
3041 // check program version of artwork info cache against current version
3042 if (!strEqual(version, program.version_string))
3044 freeSetupFileHash(artworkinfo_cache_old);
3046 artworkinfo_cache_old = NULL;
3049 // if no artwork info cache file was found, start with empty hash
3050 if (artworkinfo_cache_old == NULL)
3051 artworkinfo_cache_old = newSetupFileHash();
3056 if (artworkinfo_cache_new == NULL)
3057 artworkinfo_cache_new = newSetupFileHash();
3059 update_artworkinfo_cache = FALSE;
3062 static void SaveArtworkInfoCache(void)
3064 if (!update_artworkinfo_cache)
3067 char *filename = getPath2(getCacheDir(), ARTWORKINFO_CACHE_FILE);
3069 InitCacheDirectory();
3071 saveSetupFileHash(artworkinfo_cache_new, filename);
3076 static char *getCacheTokenPrefix(char *prefix1, char *prefix2)
3078 static char *prefix = NULL;
3080 checked_free(prefix);
3082 prefix = getStringCat2WithSeparator(prefix1, prefix2, ".");
3087 // (identical to above function, but separate string buffer needed -- nasty)
3088 static char *getCacheToken(char *prefix, char *suffix)
3090 static char *token = NULL;
3092 checked_free(token);
3094 token = getStringCat2WithSeparator(prefix, suffix, ".");
3099 static char *getFileTimestampString(char *filename)
3101 return getStringCopy(i_to_a(getFileTimestampEpochSeconds(filename)));
3104 static boolean modifiedFileTimestamp(char *filename, char *timestamp_string)
3106 struct stat file_status;
3108 if (timestamp_string == NULL)
3111 if (!fileExists(filename)) // file does not exist
3112 return (atoi(timestamp_string) != 0);
3114 if (stat(filename, &file_status) != 0) // cannot stat file
3117 return (file_status.st_mtime != atoi(timestamp_string));
3120 static TreeInfo *getArtworkInfoCacheEntry(LevelDirTree *level_node, int type)
3122 char *identifier = level_node->subdir;
3123 char *type_string = ARTWORK_DIRECTORY(type);
3124 char *token_prefix = getCacheTokenPrefix(type_string, identifier);
3125 char *token_main = getCacheToken(token_prefix, "CACHED");
3126 char *cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
3127 boolean cached = (cache_entry != NULL && strEqual(cache_entry, "true"));
3128 TreeInfo *artwork_info = NULL;
3130 if (!use_artworkinfo_cache)
3133 if (optional_tokens_hash == NULL)
3137 // create hash from list of optional tokens (for quick access)
3138 optional_tokens_hash = newSetupFileHash();
3139 for (i = 0; optional_tokens[i] != NULL; i++)
3140 setHashEntry(optional_tokens_hash, optional_tokens[i], "");
3147 artwork_info = newTreeInfo();
3148 setTreeInfoToDefaults(artwork_info, type);
3150 // set all structure fields according to the token/value pairs
3151 ldi = *artwork_info;
3152 for (i = 0; artworkinfo_tokens[i].type != -1; i++)
3154 char *token_suffix = artworkinfo_tokens[i].text;
3155 char *token = getCacheToken(token_prefix, token_suffix);
3156 char *value = getHashEntry(artworkinfo_cache_old, token);
3158 (getHashEntry(optional_tokens_hash, token_suffix) != NULL);
3160 setSetupInfo(artworkinfo_tokens, i, value);
3162 // check if cache entry for this item is mandatory, but missing
3163 if (value == NULL && !optional)
3165 Warn("missing cache entry '%s'", token);
3171 *artwork_info = ldi;
3176 char *filename_levelinfo = getPath2(getLevelDirFromTreeInfo(level_node),
3177 LEVELINFO_FILENAME);
3178 char *filename_artworkinfo = getPath2(getSetupArtworkDir(artwork_info),
3179 ARTWORKINFO_FILENAME(type));
3181 // check if corresponding "levelinfo.conf" file has changed
3182 token_main = getCacheToken(token_prefix, "TIMESTAMP_LEVELINFO");
3183 cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
3185 if (modifiedFileTimestamp(filename_levelinfo, cache_entry))
3188 // check if corresponding "<artworkinfo>.conf" file has changed
3189 token_main = getCacheToken(token_prefix, "TIMESTAMP_ARTWORKINFO");
3190 cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
3192 if (modifiedFileTimestamp(filename_artworkinfo, cache_entry))
3195 checked_free(filename_levelinfo);
3196 checked_free(filename_artworkinfo);
3199 if (!cached && artwork_info != NULL)
3201 freeTreeInfo(artwork_info);
3206 return artwork_info;
3209 static void setArtworkInfoCacheEntry(TreeInfo *artwork_info,
3210 LevelDirTree *level_node, int type)
3212 char *identifier = level_node->subdir;
3213 char *type_string = ARTWORK_DIRECTORY(type);
3214 char *token_prefix = getCacheTokenPrefix(type_string, identifier);
3215 char *token_main = getCacheToken(token_prefix, "CACHED");
3216 boolean set_cache_timestamps = TRUE;
3219 setHashEntry(artworkinfo_cache_new, token_main, "true");
3221 if (set_cache_timestamps)
3223 char *filename_levelinfo = getPath2(getLevelDirFromTreeInfo(level_node),
3224 LEVELINFO_FILENAME);
3225 char *filename_artworkinfo = getPath2(getSetupArtworkDir(artwork_info),
3226 ARTWORKINFO_FILENAME(type));
3227 char *timestamp_levelinfo = getFileTimestampString(filename_levelinfo);
3228 char *timestamp_artworkinfo = getFileTimestampString(filename_artworkinfo);
3230 token_main = getCacheToken(token_prefix, "TIMESTAMP_LEVELINFO");
3231 setHashEntry(artworkinfo_cache_new, token_main, timestamp_levelinfo);
3233 token_main = getCacheToken(token_prefix, "TIMESTAMP_ARTWORKINFO");
3234 setHashEntry(artworkinfo_cache_new, token_main, timestamp_artworkinfo);
3236 checked_free(filename_levelinfo);
3237 checked_free(filename_artworkinfo);
3238 checked_free(timestamp_levelinfo);
3239 checked_free(timestamp_artworkinfo);
3242 ldi = *artwork_info;
3243 for (i = 0; artworkinfo_tokens[i].type != -1; i++)
3245 char *token = getCacheToken(token_prefix, artworkinfo_tokens[i].text);
3246 char *value = getSetupValue(artworkinfo_tokens[i].type,
3247 artworkinfo_tokens[i].value);
3249 setHashEntry(artworkinfo_cache_new, token, value);
3254 // ----------------------------------------------------------------------------
3255 // functions for loading level info and custom artwork info
3256 // ----------------------------------------------------------------------------
3258 int GetZipFileTreeType(char *zip_filename)
3260 static char *top_dir_path = NULL;
3261 static char *top_dir_conf_filename[NUM_BASE_TREE_TYPES] = { NULL };
3262 static char *conf_basename[NUM_BASE_TREE_TYPES] =
3264 GRAPHICSINFO_FILENAME,
3265 SOUNDSINFO_FILENAME,
3271 checked_free(top_dir_path);
3272 top_dir_path = NULL;
3274 for (j = 0; j < NUM_BASE_TREE_TYPES; j++)
3276 checked_free(top_dir_conf_filename[j]);
3277 top_dir_conf_filename[j] = NULL;
3280 char **zip_entries = zip_list(zip_filename);
3282 // check if zip file successfully opened
3283 if (zip_entries == NULL || zip_entries[0] == NULL)
3284 return TREE_TYPE_UNDEFINED;
3286 // first zip file entry is expected to be top level directory
3287 char *top_dir = zip_entries[0];
3289 // check if valid top level directory found in zip file
3290 if (!strSuffix(top_dir, "/"))
3291 return TREE_TYPE_UNDEFINED;
3293 // get filenames of valid configuration files in top level directory
3294 for (j = 0; j < NUM_BASE_TREE_TYPES; j++)
3295 top_dir_conf_filename[j] = getStringCat2(top_dir, conf_basename[j]);
3297 int tree_type = TREE_TYPE_UNDEFINED;
3300 while (zip_entries[e] != NULL)
3302 // check if every zip file entry is below top level directory
3303 if (!strPrefix(zip_entries[e], top_dir))
3304 return TREE_TYPE_UNDEFINED;
3306 // check if this zip file entry is a valid configuration filename
3307 for (j = 0; j < NUM_BASE_TREE_TYPES; j++)
3309 if (strEqual(zip_entries[e], top_dir_conf_filename[j]))
3311 // only exactly one valid configuration file allowed
3312 if (tree_type != TREE_TYPE_UNDEFINED)
3313 return TREE_TYPE_UNDEFINED;
3325 static boolean CheckZipFileForDirectory(char *zip_filename, char *directory,
3328 static char *top_dir_path = NULL;
3329 static char *top_dir_conf_filename = NULL;
3331 checked_free(top_dir_path);
3332 checked_free(top_dir_conf_filename);
3334 top_dir_path = NULL;
3335 top_dir_conf_filename = NULL;
3337 char *conf_basename = (tree_type == TREE_TYPE_LEVEL_DIR ? LEVELINFO_FILENAME :
3338 ARTWORKINFO_FILENAME(tree_type));
3340 // check if valid configuration filename determined
3341 if (conf_basename == NULL || strEqual(conf_basename, ""))
3344 char **zip_entries = zip_list(zip_filename);
3346 // check if zip file successfully opened
3347 if (zip_entries == NULL || zip_entries[0] == NULL)
3350 // first zip file entry is expected to be top level directory
3351 char *top_dir = zip_entries[0];
3353 // check if valid top level directory found in zip file
3354 if (!strSuffix(top_dir, "/"))
3357 // get path of extracted top level directory
3358 top_dir_path = getPath2(directory, top_dir);
3360 // remove trailing directory separator from top level directory path
3361 // (required to be able to check for file and directory in next step)
3362 top_dir_path[strlen(top_dir_path) - 1] = '\0';
3364 // check if zip file's top level directory already exists in target directory
3365 if (fileExists(top_dir_path)) // (checks for file and directory)
3368 // get filename of configuration file in top level directory
3369 top_dir_conf_filename = getStringCat2(top_dir, conf_basename);
3371 boolean found_top_dir_conf_filename = FALSE;
3374 while (zip_entries[i] != NULL)
3376 // check if every zip file entry is below top level directory
3377 if (!strPrefix(zip_entries[i], top_dir))
3380 // check if this zip file entry is the configuration filename
3381 if (strEqual(zip_entries[i], top_dir_conf_filename))
3382 found_top_dir_conf_filename = TRUE;
3387 // check if valid configuration filename was found in zip file
3388 if (!found_top_dir_conf_filename)
3394 char *ExtractZipFileIntoDirectory(char *zip_filename, char *directory,
3397 boolean zip_file_valid = CheckZipFileForDirectory(zip_filename, directory,
3400 if (!zip_file_valid)
3402 Warn("zip file '%s' rejected!", zip_filename);
3407 char **zip_entries = zip_extract(zip_filename, directory);
3409 if (zip_entries == NULL)
3411 Warn("zip file '%s' could not be extracted!", zip_filename);
3416 Info("zip file '%s' successfully extracted!", zip_filename);
3418 // first zip file entry contains top level directory
3419 char *top_dir = zip_entries[0];
3421 // remove trailing directory separator from top level directory
3422 top_dir[strlen(top_dir) - 1] = '\0';
3427 static void ProcessZipFilesInDirectory(char *directory, int tree_type)
3430 DirectoryEntry *dir_entry;
3432 if ((dir = openDirectory(directory)) == NULL)
3434 // display error if directory is main "options.graphics_directory" etc.
3435 if (tree_type == TREE_TYPE_LEVEL_DIR ||
3436 directory == OPTIONS_ARTWORK_DIRECTORY(tree_type))
3437 Warn("cannot read directory '%s'", directory);
3442 while ((dir_entry = readDirectory(dir)) != NULL) // loop all entries
3444 // skip non-zip files (and also directories with zip extension)
3445 if (!strSuffixLower(dir_entry->basename, ".zip") || dir_entry->is_directory)
3448 char *zip_filename = getPath2(directory, dir_entry->basename);
3449 char *zip_filename_extracted = getStringCat2(zip_filename, ".extracted");
3450 char *zip_filename_rejected = getStringCat2(zip_filename, ".rejected");
3452 // check if zip file hasn't already been extracted or rejected
3453 if (!fileExists(zip_filename_extracted) &&
3454 !fileExists(zip_filename_rejected))
3456 char *top_dir = ExtractZipFileIntoDirectory(zip_filename, directory,
3458 char *marker_filename = (top_dir != NULL ? zip_filename_extracted :
3459 zip_filename_rejected);
3462 // create empty file to mark zip file as extracted or rejected
3463 if ((marker_file = fopen(marker_filename, MODE_WRITE)))
3464 fclose(marker_file);
3467 free(zip_filename_extracted);
3468 free(zip_filename_rejected);
3472 closeDirectory(dir);
3475 // forward declaration for recursive call by "LoadLevelInfoFromLevelDir()"
3476 static void LoadLevelInfoFromLevelDir(TreeInfo **, TreeInfo *, char *);
3478 static boolean LoadLevelInfoFromLevelConf(TreeInfo **node_first,
3479 TreeInfo *node_parent,
3480 char *level_directory,
3481 char *directory_name)
3483 char *directory_path = getPath2(level_directory, directory_name);
3484 char *filename = getPath2(directory_path, LEVELINFO_FILENAME);
3485 SetupFileHash *setup_file_hash;
3486 LevelDirTree *leveldir_new = NULL;
3489 // unless debugging, silently ignore directories without "levelinfo.conf"
3490 if (!options.debug && !fileExists(filename))
3492 free(directory_path);
3498 setup_file_hash = loadSetupFileHash(filename);
3500 if (setup_file_hash == NULL)
3502 #if DEBUG_NO_CONFIG_FILE
3503 Debug("setup", "ignoring level directory '%s'", directory_path);
3506 free(directory_path);
3512 leveldir_new = newTreeInfo();
3515 setTreeInfoToDefaultsFromParent(leveldir_new, node_parent);
3517 setTreeInfoToDefaults(leveldir_new, TREE_TYPE_LEVEL_DIR);
3519 leveldir_new->subdir = getStringCopy(directory_name);
3521 // set all structure fields according to the token/value pairs
3522 ldi = *leveldir_new;
3523 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
3524 setSetupInfo(levelinfo_tokens, i,
3525 getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
3526 *leveldir_new = ldi;
3528 if (strEqual(leveldir_new->name, ANONYMOUS_NAME))
3529 setString(&leveldir_new->name, leveldir_new->subdir);
3531 if (leveldir_new->identifier == NULL)
3532 leveldir_new->identifier = getStringCopy(leveldir_new->subdir);
3534 if (leveldir_new->name_sorting == NULL)
3535 leveldir_new->name_sorting = getStringCopy(leveldir_new->name);
3537 if (node_parent == NULL) // top level group
3539 leveldir_new->basepath = getStringCopy(level_directory);
3540 leveldir_new->fullpath = getStringCopy(leveldir_new->subdir);
3542 else // sub level group
3544 leveldir_new->basepath = getStringCopy(node_parent->basepath);
3545 leveldir_new->fullpath = getPath2(node_parent->fullpath, directory_name);
3548 leveldir_new->last_level =
3549 leveldir_new->first_level + leveldir_new->levels - 1;
3551 leveldir_new->in_user_dir =
3552 (!strEqual(leveldir_new->basepath, options.level_directory));
3554 // adjust some settings if user's private level directory was detected
3555 if (leveldir_new->sort_priority == LEVELCLASS_UNDEFINED &&
3556 leveldir_new->in_user_dir &&
3557 (strEqual(leveldir_new->subdir, getLoginName()) ||
3558 strEqual(leveldir_new->name, getLoginName()) ||
3559 strEqual(leveldir_new->author, getRealName())))
3561 leveldir_new->sort_priority = LEVELCLASS_PRIVATE_START;
3562 leveldir_new->readonly = FALSE;
3565 leveldir_new->user_defined =
3566 (leveldir_new->in_user_dir && IS_LEVELCLASS_PRIVATE(leveldir_new));
3568 setString(&leveldir_new->class_desc, getLevelClassDescription(leveldir_new));
3570 leveldir_new->handicap_level = // set handicap to default value
3571 (leveldir_new->user_defined || !leveldir_new->handicap ?
3572 leveldir_new->last_level : leveldir_new->first_level);
3574 DrawInitText(leveldir_new->name, 150, FC_YELLOW);
3576 pushTreeInfo(node_first, leveldir_new);
3578 freeSetupFileHash(setup_file_hash);
3580 if (leveldir_new->level_group)
3582 // create node to link back to current level directory
3583 createParentTreeInfoNode(leveldir_new);
3585 // recursively step into sub-directory and look for more level series
3586 LoadLevelInfoFromLevelDir(&leveldir_new->node_group,
3587 leveldir_new, directory_path);
3590 free(directory_path);
3596 static void LoadLevelInfoFromLevelDir(TreeInfo **node_first,
3597 TreeInfo *node_parent,
3598 char *level_directory)
3600 // ---------- 1st stage: process any level set zip files ----------
3602 ProcessZipFilesInDirectory(level_directory, TREE_TYPE_LEVEL_DIR);
3604 // ---------- 2nd stage: check for level set directories ----------
3607 DirectoryEntry *dir_entry;
3608 boolean valid_entry_found = FALSE;
3610 if ((dir = openDirectory(level_directory)) == NULL)
3612 Warn("cannot read level directory '%s'", level_directory);
3617 while ((dir_entry = readDirectory(dir)) != NULL) // loop all entries
3619 char *directory_name = dir_entry->basename;
3620 char *directory_path = getPath2(level_directory, directory_name);
3622 // skip entries for current and parent directory
3623 if (strEqual(directory_name, ".") ||
3624 strEqual(directory_name, ".."))
3626 free(directory_path);
3631 // find out if directory entry is itself a directory
3632 if (!dir_entry->is_directory) // not a directory
3634 free(directory_path);
3639 free(directory_path);
3641 if (strEqual(directory_name, GRAPHICS_DIRECTORY) ||
3642 strEqual(directory_name, SOUNDS_DIRECTORY) ||
3643 strEqual(directory_name, MUSIC_DIRECTORY))
3646 valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
3651 closeDirectory(dir);
3653 // special case: top level directory may directly contain "levelinfo.conf"
3654 if (node_parent == NULL && !valid_entry_found)
3656 // check if this directory directly contains a file "levelinfo.conf"
3657 valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
3658 level_directory, ".");
3661 if (!valid_entry_found)
3662 Warn("cannot find any valid level series in directory '%s'",
3666 boolean AdjustGraphicsForEMC(void)
3668 boolean settings_changed = FALSE;
3670 settings_changed |= adjustTreeGraphicsForEMC(leveldir_first_all);
3671 settings_changed |= adjustTreeGraphicsForEMC(leveldir_first);
3673 return settings_changed;
3676 boolean AdjustSoundsForEMC(void)
3678 boolean settings_changed = FALSE;
3680 settings_changed |= adjustTreeSoundsForEMC(leveldir_first_all);
3681 settings_changed |= adjustTreeSoundsForEMC(leveldir_first);
3683 return settings_changed;
3686 void LoadLevelInfo(void)
3688 InitUserLevelDirectory(getLoginName());
3690 DrawInitText("Loading level series", 120, FC_GREEN);
3692 LoadLevelInfoFromLevelDir(&leveldir_first, NULL, options.level_directory);
3693 LoadLevelInfoFromLevelDir(&leveldir_first, NULL, getUserLevelDir(NULL));
3695 leveldir_first = createTopTreeInfoNode(leveldir_first);
3697 /* after loading all level set information, clone the level directory tree
3698 and remove all level sets without levels (these may still contain artwork
3699 to be offered in the setup menu as "custom artwork", and are therefore
3700 checked for existing artwork in the function "LoadLevelArtworkInfo()") */
3701 leveldir_first_all = leveldir_first;
3702 cloneTree(&leveldir_first, leveldir_first_all, TRUE);
3704 AdjustGraphicsForEMC();
3705 AdjustSoundsForEMC();
3707 // before sorting, the first entries will be from the user directory
3708 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
3710 if (leveldir_first == NULL)
3711 Fail("cannot find any valid level series in any directory");
3713 sortTreeInfo(&leveldir_first);
3715 #if ENABLE_UNUSED_CODE
3716 dumpTreeInfo(leveldir_first, 0);
3720 static boolean LoadArtworkInfoFromArtworkConf(TreeInfo **node_first,
3721 TreeInfo *node_parent,
3722 char *base_directory,
3723 char *directory_name, int type)
3725 char *directory_path = getPath2(base_directory, directory_name);
3726 char *filename = getPath2(directory_path, ARTWORKINFO_FILENAME(type));
3727 SetupFileHash *setup_file_hash = NULL;
3728 TreeInfo *artwork_new = NULL;
3731 if (fileExists(filename))
3732 setup_file_hash = loadSetupFileHash(filename);
3734 if (setup_file_hash == NULL) // no config file -- look for artwork files
3737 DirectoryEntry *dir_entry;
3738 boolean valid_file_found = FALSE;
3740 if ((dir = openDirectory(directory_path)) != NULL)
3742 while ((dir_entry = readDirectory(dir)) != NULL)
3744 if (FileIsArtworkType(dir_entry->filename, type))
3746 valid_file_found = TRUE;
3752 closeDirectory(dir);
3755 if (!valid_file_found)
3757 #if DEBUG_NO_CONFIG_FILE
3758 if (!strEqual(directory_name, "."))
3759 Debug("setup", "ignoring artwork directory '%s'", directory_path);
3762 free(directory_path);
3769 artwork_new = newTreeInfo();
3772 setTreeInfoToDefaultsFromParent(artwork_new, node_parent);
3774 setTreeInfoToDefaults(artwork_new, type);
3776 artwork_new->subdir = getStringCopy(directory_name);
3778 if (setup_file_hash) // (before defining ".color" and ".class_desc")
3780 // set all structure fields according to the token/value pairs
3782 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
3783 setSetupInfo(levelinfo_tokens, i,
3784 getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
3787 if (strEqual(artwork_new->name, ANONYMOUS_NAME))
3788 setString(&artwork_new->name, artwork_new->subdir);
3790 if (artwork_new->identifier == NULL)
3791 artwork_new->identifier = getStringCopy(artwork_new->subdir);
3793 if (artwork_new->name_sorting == NULL)
3794 artwork_new->name_sorting = getStringCopy(artwork_new->name);
3797 if (node_parent == NULL) // top level group
3799 artwork_new->basepath = getStringCopy(base_directory);
3800 artwork_new->fullpath = getStringCopy(artwork_new->subdir);
3802 else // sub level group
3804 artwork_new->basepath = getStringCopy(node_parent->basepath);
3805 artwork_new->fullpath = getPath2(node_parent->fullpath, directory_name);
3808 artwork_new->in_user_dir =
3809 (!strEqual(artwork_new->basepath, OPTIONS_ARTWORK_DIRECTORY(type)));
3811 setString(&artwork_new->class_desc, getLevelClassDescription(artwork_new));
3813 if (setup_file_hash == NULL) // (after determining ".user_defined")
3815 if (strEqual(artwork_new->subdir, "."))
3817 if (artwork_new->user_defined)
3819 setString(&artwork_new->identifier, "private");
3820 artwork_new->sort_priority = ARTWORKCLASS_PRIVATE;
3824 setString(&artwork_new->identifier, "classic");
3825 artwork_new->sort_priority = ARTWORKCLASS_CLASSICS;
3828 setString(&artwork_new->class_desc,
3829 getLevelClassDescription(artwork_new));
3833 setString(&artwork_new->identifier, artwork_new->subdir);
3836 setString(&artwork_new->name, artwork_new->identifier);
3837 setString(&artwork_new->name_sorting, artwork_new->name);
3840 pushTreeInfo(node_first, artwork_new);
3842 freeSetupFileHash(setup_file_hash);
3844 free(directory_path);
3850 static void LoadArtworkInfoFromArtworkDir(TreeInfo **node_first,
3851 TreeInfo *node_parent,
3852 char *base_directory, int type)
3854 // ---------- 1st stage: process any artwork set zip files ----------
3856 ProcessZipFilesInDirectory(base_directory, type);
3858 // ---------- 2nd stage: check for artwork set directories ----------
3861 DirectoryEntry *dir_entry;
3862 boolean valid_entry_found = FALSE;
3864 if ((dir = openDirectory(base_directory)) == NULL)
3866 // display error if directory is main "options.graphics_directory" etc.
3867 if (base_directory == OPTIONS_ARTWORK_DIRECTORY(type))
3868 Warn("cannot read directory '%s'", base_directory);
3873 while ((dir_entry = readDirectory(dir)) != NULL) // loop all entries
3875 char *directory_name = dir_entry->basename;
3876 char *directory_path = getPath2(base_directory, directory_name);
3878 // skip directory entries for current and parent directory
3879 if (strEqual(directory_name, ".") ||
3880 strEqual(directory_name, ".."))
3882 free(directory_path);
3887 // skip directory entries which are not a directory
3888 if (!dir_entry->is_directory) // not a directory
3890 free(directory_path);
3895 free(directory_path);
3897 // check if this directory contains artwork with or without config file
3898 valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
3900 directory_name, type);
3903 closeDirectory(dir);
3905 // check if this directory directly contains artwork itself
3906 valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
3907 base_directory, ".",
3909 if (!valid_entry_found)
3910 Warn("cannot find any valid artwork in directory '%s'", base_directory);
3913 static TreeInfo *getDummyArtworkInfo(int type)
3915 // this is only needed when there is completely no artwork available
3916 TreeInfo *artwork_new = newTreeInfo();
3918 setTreeInfoToDefaults(artwork_new, type);
3920 setString(&artwork_new->subdir, UNDEFINED_FILENAME);
3921 setString(&artwork_new->fullpath, UNDEFINED_FILENAME);
3922 setString(&artwork_new->basepath, UNDEFINED_FILENAME);
3924 setString(&artwork_new->identifier, UNDEFINED_FILENAME);
3925 setString(&artwork_new->name, UNDEFINED_FILENAME);
3926 setString(&artwork_new->name_sorting, UNDEFINED_FILENAME);
3931 void SetCurrentArtwork(int type)
3933 ArtworkDirTree **current_ptr = ARTWORK_CURRENT_PTR(artwork, type);
3934 ArtworkDirTree *first_node = ARTWORK_FIRST_NODE(artwork, type);
3935 char *setup_set = SETUP_ARTWORK_SET(setup, type);
3936 char *default_subdir = ARTWORK_DEFAULT_SUBDIR(type);
3938 // set current artwork to artwork configured in setup menu
3939 *current_ptr = getTreeInfoFromIdentifier(first_node, setup_set);
3941 // if not found, set current artwork to default artwork
3942 if (*current_ptr == NULL)
3943 *current_ptr = getTreeInfoFromIdentifier(first_node, default_subdir);
3945 // if not found, set current artwork to first artwork in tree
3946 if (*current_ptr == NULL)
3947 *current_ptr = getFirstValidTreeInfoEntry(first_node);
3950 void ChangeCurrentArtworkIfNeeded(int type)
3952 char *current_identifier = ARTWORK_CURRENT_IDENTIFIER(artwork, type);
3953 char *setup_set = SETUP_ARTWORK_SET(setup, type);
3955 if (!strEqual(current_identifier, setup_set))
3956 SetCurrentArtwork(type);
3959 void LoadArtworkInfo(void)
3961 LoadArtworkInfoCache();
3963 DrawInitText("Looking for custom artwork", 120, FC_GREEN);
3965 LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
3966 options.graphics_directory,
3967 TREE_TYPE_GRAPHICS_DIR);
3968 LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
3969 getUserGraphicsDir(),
3970 TREE_TYPE_GRAPHICS_DIR);
3972 LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
3973 options.sounds_directory,
3974 TREE_TYPE_SOUNDS_DIR);
3975 LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
3977 TREE_TYPE_SOUNDS_DIR);
3979 LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
3980 options.music_directory,
3981 TREE_TYPE_MUSIC_DIR);
3982 LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
3984 TREE_TYPE_MUSIC_DIR);
3986 if (artwork.gfx_first == NULL)
3987 artwork.gfx_first = getDummyArtworkInfo(TREE_TYPE_GRAPHICS_DIR);
3988 if (artwork.snd_first == NULL)
3989 artwork.snd_first = getDummyArtworkInfo(TREE_TYPE_SOUNDS_DIR);
3990 if (artwork.mus_first == NULL)
3991 artwork.mus_first = getDummyArtworkInfo(TREE_TYPE_MUSIC_DIR);
3993 // before sorting, the first entries will be from the user directory
3994 SetCurrentArtwork(ARTWORK_TYPE_GRAPHICS);
3995 SetCurrentArtwork(ARTWORK_TYPE_SOUNDS);
3996 SetCurrentArtwork(ARTWORK_TYPE_MUSIC);
3998 artwork.gfx_current_identifier = artwork.gfx_current->identifier;
3999 artwork.snd_current_identifier = artwork.snd_current->identifier;
4000 artwork.mus_current_identifier = artwork.mus_current->identifier;
4002 #if ENABLE_UNUSED_CODE
4003 Debug("setup:LoadArtworkInfo", "graphics set == %s",
4004 artwork.gfx_current_identifier);
4005 Debug("setup:LoadArtworkInfo", "sounds set == %s",
4006 artwork.snd_current_identifier);
4007 Debug("setup:LoadArtworkInfo", "music set == %s",
4008 artwork.mus_current_identifier);
4011 sortTreeInfo(&artwork.gfx_first);
4012 sortTreeInfo(&artwork.snd_first);
4013 sortTreeInfo(&artwork.mus_first);
4015 #if ENABLE_UNUSED_CODE
4016 dumpTreeInfo(artwork.gfx_first, 0);
4017 dumpTreeInfo(artwork.snd_first, 0);
4018 dumpTreeInfo(artwork.mus_first, 0);
4022 static void MoveArtworkInfoIntoSubTree(ArtworkDirTree **artwork_node)
4024 ArtworkDirTree *artwork_new = newTreeInfo();
4025 char *top_node_name = "standalone artwork";
4027 setTreeInfoToDefaults(artwork_new, (*artwork_node)->type);
4029 artwork_new->level_group = TRUE;
4031 setString(&artwork_new->identifier, top_node_name);
4032 setString(&artwork_new->name, top_node_name);
4033 setString(&artwork_new->name_sorting, top_node_name);
4035 // create node to link back to current custom artwork directory
4036 createParentTreeInfoNode(artwork_new);
4038 // move existing custom artwork tree into newly created sub-tree
4039 artwork_new->node_group->next = *artwork_node;
4041 // change custom artwork tree to contain only newly created node
4042 *artwork_node = artwork_new;
4045 static void LoadArtworkInfoFromLevelInfoExt(ArtworkDirTree **artwork_node,
4046 ArtworkDirTree *node_parent,
4047 LevelDirTree *level_node,
4048 boolean empty_level_set_mode)
4050 int type = (*artwork_node)->type;
4052 // recursively check all level directories for artwork sub-directories
4056 boolean empty_level_set = (level_node->levels == 0);
4058 // check all tree entries for artwork, but skip parent link entries
4059 if (!level_node->parent_link && empty_level_set == empty_level_set_mode)
4061 TreeInfo *artwork_new = getArtworkInfoCacheEntry(level_node, type);
4062 boolean cached = (artwork_new != NULL);
4066 pushTreeInfo(artwork_node, artwork_new);
4070 TreeInfo *topnode_last = *artwork_node;
4071 char *path = getPath2(getLevelDirFromTreeInfo(level_node),
4072 ARTWORK_DIRECTORY(type));
4074 LoadArtworkInfoFromArtworkDir(artwork_node, NULL, path, type);
4076 if (topnode_last != *artwork_node) // check for newly added node
4078 artwork_new = *artwork_node;
4080 setString(&artwork_new->identifier, level_node->subdir);
4081 setString(&artwork_new->name, level_node->name);
4082 setString(&artwork_new->name_sorting, level_node->name_sorting);
4084 artwork_new->sort_priority = level_node->sort_priority;
4085 artwork_new->in_user_dir = level_node->in_user_dir;
4087 update_artworkinfo_cache = TRUE;
4093 // insert artwork info (from old cache or filesystem) into new cache
4094 if (artwork_new != NULL)
4095 setArtworkInfoCacheEntry(artwork_new, level_node, type);
4098 DrawInitText(level_node->name, 150, FC_YELLOW);
4100 if (level_node->node_group != NULL)
4102 TreeInfo *artwork_new = newTreeInfo();
4105 setTreeInfoToDefaultsFromParent(artwork_new, node_parent);
4107 setTreeInfoToDefaults(artwork_new, type);
4109 artwork_new->level_group = TRUE;
4111 setString(&artwork_new->identifier, level_node->subdir);
4113 if (node_parent == NULL) // check for top tree node
4115 char *top_node_name = (empty_level_set_mode ?
4116 "artwork for certain level sets" :
4117 "artwork included in level sets");
4119 setString(&artwork_new->name, top_node_name);
4120 setString(&artwork_new->name_sorting, top_node_name);
4124 setString(&artwork_new->name, level_node->name);
4125 setString(&artwork_new->name_sorting, level_node->name_sorting);
4128 pushTreeInfo(artwork_node, artwork_new);
4130 // create node to link back to current custom artwork directory
4131 createParentTreeInfoNode(artwork_new);
4133 // recursively step into sub-directory and look for more custom artwork
4134 LoadArtworkInfoFromLevelInfoExt(&artwork_new->node_group, artwork_new,
4135 level_node->node_group,
4136 empty_level_set_mode);
4138 // if sub-tree has no custom artwork at all, remove it
4139 if (artwork_new->node_group->next == NULL)
4140 removeTreeInfo(artwork_node);
4143 level_node = level_node->next;
4147 static void LoadArtworkInfoFromLevelInfo(ArtworkDirTree **artwork_node)
4149 // move peviously loaded artwork tree into separate sub-tree
4150 MoveArtworkInfoIntoSubTree(artwork_node);
4152 // load artwork from level sets into separate sub-trees
4153 LoadArtworkInfoFromLevelInfoExt(artwork_node, NULL, leveldir_first_all, TRUE);
4154 LoadArtworkInfoFromLevelInfoExt(artwork_node, NULL, leveldir_first_all, FALSE);
4156 // add top tree node over all three separate sub-trees
4157 *artwork_node = createTopTreeInfoNode(*artwork_node);
4159 // set all parent links (back links) in complete artwork tree
4160 setTreeInfoParentNodes(*artwork_node, NULL);
4163 void LoadLevelArtworkInfo(void)
4165 print_timestamp_init("LoadLevelArtworkInfo");
4167 DrawInitText("Looking for custom level artwork", 120, FC_GREEN);
4169 print_timestamp_time("DrawTimeText");
4171 LoadArtworkInfoFromLevelInfo(&artwork.gfx_first);
4172 print_timestamp_time("LoadArtworkInfoFromLevelInfo (gfx)");
4173 LoadArtworkInfoFromLevelInfo(&artwork.snd_first);
4174 print_timestamp_time("LoadArtworkInfoFromLevelInfo (snd)");
4175 LoadArtworkInfoFromLevelInfo(&artwork.mus_first);
4176 print_timestamp_time("LoadArtworkInfoFromLevelInfo (mus)");
4178 SaveArtworkInfoCache();
4180 print_timestamp_time("SaveArtworkInfoCache");
4182 // needed for reloading level artwork not known at ealier stage
4183 ChangeCurrentArtworkIfNeeded(ARTWORK_TYPE_GRAPHICS);
4184 ChangeCurrentArtworkIfNeeded(ARTWORK_TYPE_SOUNDS);
4185 ChangeCurrentArtworkIfNeeded(ARTWORK_TYPE_MUSIC);
4187 print_timestamp_time("getTreeInfoFromIdentifier");
4189 sortTreeInfo(&artwork.gfx_first);
4190 sortTreeInfo(&artwork.snd_first);
4191 sortTreeInfo(&artwork.mus_first);
4193 print_timestamp_time("sortTreeInfo");
4195 #if ENABLE_UNUSED_CODE
4196 dumpTreeInfo(artwork.gfx_first, 0);
4197 dumpTreeInfo(artwork.snd_first, 0);
4198 dumpTreeInfo(artwork.mus_first, 0);
4201 print_timestamp_done("LoadLevelArtworkInfo");
4204 static boolean AddTreeSetToTreeInfoExt(TreeInfo *tree_node_old, char *tree_dir,
4205 char *tree_subdir_new, int type)
4207 if (tree_node_old == NULL)
4209 if (type == TREE_TYPE_LEVEL_DIR)
4211 // get level info tree node of personal user level set
4212 tree_node_old = getTreeInfoFromIdentifier(leveldir_first, getLoginName());
4214 // this may happen if "setup.internal.create_user_levelset" is FALSE
4215 // or if file "levelinfo.conf" is missing in personal user level set
4216 if (tree_node_old == NULL)
4217 tree_node_old = leveldir_first->node_group;
4221 // get artwork info tree node of first artwork set
4222 tree_node_old = ARTWORK_FIRST_NODE(artwork, type);
4226 if (tree_dir == NULL)
4227 tree_dir = TREE_USERDIR(type);
4229 if (tree_node_old == NULL ||
4231 tree_subdir_new == NULL) // should not happen
4234 int draw_deactivation_mask = GetDrawDeactivationMask();
4236 // override draw deactivation mask (temporarily disable drawing)
4237 SetDrawDeactivationMask(REDRAW_ALL);
4239 if (type == TREE_TYPE_LEVEL_DIR)
4241 // load new level set config and add it next to first user level set
4242 LoadLevelInfoFromLevelConf(&tree_node_old->next,
4243 tree_node_old->node_parent,
4244 tree_dir, tree_subdir_new);
4248 // load new artwork set config and add it next to first artwork set
4249 LoadArtworkInfoFromArtworkConf(&tree_node_old->next,
4250 tree_node_old->node_parent,
4251 tree_dir, tree_subdir_new, type);
4254 // set draw deactivation mask to previous value
4255 SetDrawDeactivationMask(draw_deactivation_mask);
4257 // get first node of level or artwork info tree
4258 TreeInfo **tree_node_first = TREE_FIRST_NODE_PTR(type);
4260 // get tree info node of newly added level or artwork set
4261 TreeInfo *tree_node_new = getTreeInfoFromIdentifier(*tree_node_first,
4264 if (tree_node_new == NULL) // should not happen
4267 // correct top link and parent node link of newly created tree node
4268 tree_node_new->node_top = tree_node_old->node_top;
4269 tree_node_new->node_parent = tree_node_old->node_parent;
4271 // sort tree info to adjust position of newly added tree set
4272 sortTreeInfo(tree_node_first);
4277 void AddTreeSetToTreeInfo(TreeInfo *tree_node, char *tree_dir,
4278 char *tree_subdir_new, int type)
4280 if (!AddTreeSetToTreeInfoExt(tree_node, tree_dir, tree_subdir_new, type))
4281 Fail("internal tree info structure corrupted -- aborting");
4284 void AddUserLevelSetToLevelInfo(char *level_subdir_new)
4286 AddTreeSetToTreeInfo(NULL, NULL, level_subdir_new, TREE_TYPE_LEVEL_DIR);
4289 char *getArtworkIdentifierForUserLevelSet(int type)
4291 char *classic_artwork_set = getClassicArtworkSet(type);
4293 // check for custom artwork configured in "levelinfo.conf"
4294 char *leveldir_artwork_set =
4295 *LEVELDIR_ARTWORK_SET_PTR(leveldir_current, type);
4296 boolean has_leveldir_artwork_set =
4297 (leveldir_artwork_set != NULL && !strEqual(leveldir_artwork_set,
4298 classic_artwork_set));
4300 // check for custom artwork in sub-directory "graphics" etc.
4301 TreeInfo *artwork_first_node = ARTWORK_FIRST_NODE(artwork, type);
4302 char *leveldir_identifier = leveldir_current->identifier;
4303 boolean has_artwork_subdir =
4304 (getTreeInfoFromIdentifier(artwork_first_node,
4305 leveldir_identifier) != NULL);
4307 return (has_leveldir_artwork_set ? leveldir_artwork_set :
4308 has_artwork_subdir ? leveldir_identifier :
4309 classic_artwork_set);
4312 TreeInfo *getArtworkTreeInfoForUserLevelSet(int type)
4314 char *artwork_set = getArtworkIdentifierForUserLevelSet(type);
4315 TreeInfo *artwork_first_node = ARTWORK_FIRST_NODE(artwork, type);
4316 TreeInfo *ti = getTreeInfoFromIdentifier(artwork_first_node, artwork_set);
4320 ti = getTreeInfoFromIdentifier(artwork_first_node,
4321 ARTWORK_DEFAULT_SUBDIR(type));
4323 Fail("cannot find default graphics -- should not happen");
4329 boolean checkIfCustomArtworkExistsForCurrentLevelSet(void)
4331 char *graphics_set =
4332 getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_GRAPHICS);
4334 getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_SOUNDS);
4336 getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_MUSIC);
4338 return (!strEqual(graphics_set, GFX_CLASSIC_SUBDIR) ||
4339 !strEqual(sounds_set, SND_CLASSIC_SUBDIR) ||
4340 !strEqual(music_set, MUS_CLASSIC_SUBDIR));
4343 boolean UpdateUserLevelSet(char *level_subdir, char *level_name,
4344 char *level_author, int num_levels)
4346 char *filename = getPath2(getUserLevelDir(level_subdir), LEVELINFO_FILENAME);
4347 char *filename_tmp = getStringCat2(filename, ".tmp");
4349 FILE *file_tmp = NULL;
4350 char line[MAX_LINE_LEN];
4351 boolean success = FALSE;
4352 LevelDirTree *leveldir = getTreeInfoFromIdentifier(leveldir_first,
4354 // update values in level directory tree
4356 if (level_name != NULL)
4357 setString(&leveldir->name, level_name);
4359 if (level_author != NULL)
4360 setString(&leveldir->author, level_author);
4362 if (num_levels != -1)
4363 leveldir->levels = num_levels;
4365 // update values that depend on other values
4367 setString(&leveldir->name_sorting, leveldir->name);
4369 leveldir->last_level = leveldir->first_level + leveldir->levels - 1;
4371 // sort order of level sets may have changed
4372 sortTreeInfo(&leveldir_first);
4374 if ((file = fopen(filename, MODE_READ)) &&
4375 (file_tmp = fopen(filename_tmp, MODE_WRITE)))
4377 while (fgets(line, MAX_LINE_LEN, file))
4379 if (strPrefix(line, "name:") && level_name != NULL)
4380 fprintf(file_tmp, "%-32s%s\n", "name:", level_name);
4381 else if (strPrefix(line, "author:") && level_author != NULL)
4382 fprintf(file_tmp, "%-32s%s\n", "author:", level_author);
4383 else if (strPrefix(line, "levels:") && num_levels != -1)
4384 fprintf(file_tmp, "%-32s%d\n", "levels:", num_levels);
4386 fputs(line, file_tmp);
4399 success = (rename(filename_tmp, filename) == 0);
4407 boolean CreateUserLevelSet(char *level_subdir, char *level_name,
4408 char *level_author, int num_levels,
4409 boolean use_artwork_set)
4411 LevelDirTree *level_info;
4416 // create user level sub-directory, if needed
4417 createDirectory(getUserLevelDir(level_subdir), "user level", PERMS_PRIVATE);
4419 filename = getPath2(getUserLevelDir(level_subdir), LEVELINFO_FILENAME);
4421 if (!(file = fopen(filename, MODE_WRITE)))
4423 Warn("cannot write level info file '%s'", filename);
4430 level_info = newTreeInfo();
4432 // always start with reliable default values
4433 setTreeInfoToDefaults(level_info, TREE_TYPE_LEVEL_DIR);
4435 setString(&level_info->name, level_name);
4436 setString(&level_info->author, level_author);
4437 level_info->levels = num_levels;
4438 level_info->first_level = 1;
4439 level_info->sort_priority = LEVELCLASS_PRIVATE_START;
4440 level_info->readonly = FALSE;
4442 if (use_artwork_set)
4444 level_info->graphics_set =
4445 getStringCopy(getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_GRAPHICS));
4446 level_info->sounds_set =
4447 getStringCopy(getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_SOUNDS));
4448 level_info->music_set =
4449 getStringCopy(getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_MUSIC));
4452 token_value_position = TOKEN_VALUE_POSITION_SHORT;
4454 fprintFileHeader(file, LEVELINFO_FILENAME);
4457 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
4459 if (i == LEVELINFO_TOKEN_NAME ||
4460 i == LEVELINFO_TOKEN_AUTHOR ||
4461 i == LEVELINFO_TOKEN_LEVELS ||
4462 i == LEVELINFO_TOKEN_FIRST_LEVEL ||
4463 i == LEVELINFO_TOKEN_SORT_PRIORITY ||
4464 i == LEVELINFO_TOKEN_READONLY ||
4465 (use_artwork_set && (i == LEVELINFO_TOKEN_GRAPHICS_SET ||
4466 i == LEVELINFO_TOKEN_SOUNDS_SET ||
4467 i == LEVELINFO_TOKEN_MUSIC_SET)))
4468 fprintf(file, "%s\n", getSetupLine(levelinfo_tokens, "", i));
4470 // just to make things nicer :)
4471 if (i == LEVELINFO_TOKEN_AUTHOR ||
4472 i == LEVELINFO_TOKEN_FIRST_LEVEL ||
4473 (use_artwork_set && i == LEVELINFO_TOKEN_READONLY))
4474 fprintf(file, "\n");
4477 token_value_position = TOKEN_VALUE_POSITION_DEFAULT;
4481 SetFilePermissions(filename, PERMS_PRIVATE);
4483 freeTreeInfo(level_info);
4489 static void SaveUserLevelInfo(void)
4491 CreateUserLevelSet(getLoginName(), getLoginName(), getRealName(), 100, FALSE);
4494 char *getSetupValue(int type, void *value)
4496 static char value_string[MAX_LINE_LEN];
4504 strcpy(value_string, (*(boolean *)value ? "true" : "false"));
4508 strcpy(value_string, (*(boolean *)value ? "on" : "off"));
4512 strcpy(value_string, (*(int *)value == AUTO ? "auto" :
4513 *(int *)value == FALSE ? "off" : "on"));
4517 strcpy(value_string, (*(boolean *)value ? "yes" : "no"));
4520 case TYPE_YES_NO_AUTO:
4521 strcpy(value_string, (*(int *)value == AUTO ? "auto" :
4522 *(int *)value == FALSE ? "no" : "yes"));
4526 strcpy(value_string, (*(boolean *)value ? "AGA" : "ECS"));
4530 strcpy(value_string, getKeyNameFromKey(*(Key *)value));
4534 strcpy(value_string, getX11KeyNameFromKey(*(Key *)value));
4538 sprintf(value_string, "%d", *(int *)value);
4542 if (*(char **)value == NULL)
4545 strcpy(value_string, *(char **)value);
4549 sprintf(value_string, "player_%d", *(int *)value + 1);
4553 value_string[0] = '\0';
4557 if (type & TYPE_GHOSTED)
4558 strcpy(value_string, "n/a");
4560 return value_string;
4563 char *getSetupLine(struct TokenInfo *token_info, char *prefix, int token_nr)
4567 static char token_string[MAX_LINE_LEN];
4568 int token_type = token_info[token_nr].type;
4569 void *setup_value = token_info[token_nr].value;
4570 char *token_text = token_info[token_nr].text;
4571 char *value_string = getSetupValue(token_type, setup_value);
4573 // build complete token string
4574 sprintf(token_string, "%s%s", prefix, token_text);
4576 // build setup entry line
4577 line = getFormattedSetupEntry(token_string, value_string);
4579 if (token_type == TYPE_KEY_X11)
4581 Key key = *(Key *)setup_value;
4582 char *keyname = getKeyNameFromKey(key);
4584 // add comment, if useful
4585 if (!strEqual(keyname, "(undefined)") &&
4586 !strEqual(keyname, "(unknown)"))
4588 // add at least one whitespace
4590 for (i = strlen(line); i < token_comment_position; i++)
4594 strcat(line, keyname);
4601 static void InitLastPlayedLevels_ParentNode(void)
4603 LevelDirTree **leveldir_top = &leveldir_first->node_group->next;
4604 LevelDirTree *leveldir_new = NULL;
4606 // check if parent node for last played levels already exists
4607 if (strEqual((*leveldir_top)->identifier, TOKEN_STR_LAST_LEVEL_SERIES))
4610 leveldir_new = newTreeInfo();
4612 setTreeInfoToDefaultsFromParent(leveldir_new, leveldir_first);
4614 leveldir_new->level_group = TRUE;
4615 leveldir_new->sort_priority = LEVELCLASS_LAST_PLAYED_LEVEL;
4617 setString(&leveldir_new->identifier, TOKEN_STR_LAST_LEVEL_SERIES);
4618 setString(&leveldir_new->name, "<< (last played level sets)");
4619 setString(&leveldir_new->name_sorting, leveldir_new->name);
4621 pushTreeInfo(leveldir_top, leveldir_new);
4623 // create node to link back to current level directory
4624 createParentTreeInfoNode(leveldir_new);
4627 void UpdateLastPlayedLevels_TreeInfo(void)
4629 char **last_level_series = setup.level_setup.last_level_series;
4630 LevelDirTree *leveldir_last;
4631 TreeInfo **node_new = NULL;
4634 if (last_level_series[0] == NULL)
4637 InitLastPlayedLevels_ParentNode();
4639 leveldir_last = getTreeInfoFromIdentifierExt(leveldir_first,
4640 TOKEN_STR_LAST_LEVEL_SERIES,
4641 TREE_NODE_TYPE_GROUP);
4642 if (leveldir_last == NULL)
4645 node_new = &leveldir_last->node_group->next;
4647 freeTreeInfo(*node_new);
4651 for (i = 0; last_level_series[i] != NULL; i++)
4653 LevelDirTree *node_last = getTreeInfoFromIdentifier(leveldir_first,
4654 last_level_series[i]);
4655 if (node_last == NULL)
4658 *node_new = getTreeInfoCopy(node_last); // copy complete node
4660 (*node_new)->node_top = &leveldir_first; // correct top node link
4661 (*node_new)->node_parent = leveldir_last; // correct parent node link
4663 (*node_new)->is_copy = TRUE; // mark entry as node copy
4665 (*node_new)->node_group = NULL;
4666 (*node_new)->next = NULL;
4668 (*node_new)->cl_first = -1; // force setting tree cursor
4670 node_new = &((*node_new)->next);
4674 static void UpdateLastPlayedLevels_List(void)
4676 char **last_level_series = setup.level_setup.last_level_series;
4677 int pos = MAX_LEVELDIR_HISTORY - 1;
4680 // search for potentially already existing entry in list of level sets
4681 for (i = 0; last_level_series[i] != NULL; i++)
4682 if (strEqual(last_level_series[i], leveldir_current->identifier))
4685 // move list of level sets one entry down (using potentially free entry)
4686 for (i = pos; i > 0; i--)
4687 setString(&last_level_series[i], last_level_series[i - 1]);
4689 // put last played level set at top position
4690 setString(&last_level_series[0], leveldir_current->identifier);
4693 static TreeInfo *StoreOrRestoreLastPlayedLevels(TreeInfo *node, boolean store)
4695 static char *identifier = NULL;
4699 setString(&identifier, (node && node->is_copy ? node->identifier : NULL));
4701 return NULL; // not used
4705 TreeInfo *node_new = getTreeInfoFromIdentifierExt(leveldir_first,
4707 TREE_NODE_TYPE_COPY);
4708 return (node_new != NULL ? node_new : node);
4712 void StoreLastPlayedLevels(TreeInfo *node)
4714 StoreOrRestoreLastPlayedLevels(node, TRUE);
4717 void RestoreLastPlayedLevels(TreeInfo **node)
4719 *node = StoreOrRestoreLastPlayedLevels(*node, FALSE);
4722 void LoadLevelSetup_LastSeries(void)
4724 // --------------------------------------------------------------------------
4725 // ~/.<program>/levelsetup.conf
4726 // --------------------------------------------------------------------------
4728 char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
4729 SetupFileHash *level_setup_hash = NULL;
4733 // always start with reliable default values
4734 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
4736 // start with empty history of last played level sets
4737 setString(&setup.level_setup.last_level_series[0], NULL);
4739 if (!strEqual(DEFAULT_LEVELSET, UNDEFINED_LEVELSET))
4741 leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
4743 if (leveldir_current == NULL)
4744 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
4747 if ((level_setup_hash = loadSetupFileHash(filename)))
4749 char *last_level_series =
4750 getHashEntry(level_setup_hash, TOKEN_STR_LAST_LEVEL_SERIES);
4752 leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
4754 if (leveldir_current == NULL)
4755 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
4757 for (i = 0; i < MAX_LEVELDIR_HISTORY; i++)
4759 char token[strlen(TOKEN_STR_LAST_LEVEL_SERIES) + 10];
4760 LevelDirTree *leveldir_last;
4762 sprintf(token, "%s.%03d", TOKEN_STR_LAST_LEVEL_SERIES, i);
4764 last_level_series = getHashEntry(level_setup_hash, token);
4766 leveldir_last = getTreeInfoFromIdentifier(leveldir_first,
4768 if (leveldir_last != NULL)
4769 setString(&setup.level_setup.last_level_series[pos++],
4773 setString(&setup.level_setup.last_level_series[pos], NULL);
4775 freeSetupFileHash(level_setup_hash);
4779 Debug("setup", "using default setup values");
4785 static void SaveLevelSetup_LastSeries_Ext(boolean deactivate_last_level_series)
4787 // --------------------------------------------------------------------------
4788 // ~/.<program>/levelsetup.conf
4789 // --------------------------------------------------------------------------
4791 // check if the current level directory structure is available at this point
4792 if (leveldir_current == NULL)
4795 char **last_level_series = setup.level_setup.last_level_series;
4796 char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
4800 InitUserDataDirectory();
4802 UpdateLastPlayedLevels_List();
4804 if (!(file = fopen(filename, MODE_WRITE)))
4806 Warn("cannot write setup file '%s'", filename);
4813 fprintFileHeader(file, LEVELSETUP_FILENAME);
4815 if (deactivate_last_level_series)
4816 fprintf(file, "# %s\n# ", "the following level set may have caused a problem and was deactivated");
4818 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_LAST_LEVEL_SERIES,
4819 leveldir_current->identifier));
4821 for (i = 0; last_level_series[i] != NULL; i++)
4823 char token[strlen(TOKEN_STR_LAST_LEVEL_SERIES) + 10];
4825 sprintf(token, "%s.%03d", TOKEN_STR_LAST_LEVEL_SERIES, i);
4827 fprintf(file, "%s\n", getFormattedSetupEntry(token, last_level_series[i]));
4832 SetFilePermissions(filename, PERMS_PRIVATE);
4837 void SaveLevelSetup_LastSeries(void)
4839 SaveLevelSetup_LastSeries_Ext(FALSE);
4842 void SaveLevelSetup_LastSeries_Deactivate(void)
4844 SaveLevelSetup_LastSeries_Ext(TRUE);
4847 static void checkSeriesInfo(void)
4849 static char *level_directory = NULL;
4852 DirectoryEntry *dir_entry;
4855 checked_free(level_directory);
4857 // check for more levels besides the 'levels' field of 'levelinfo.conf'
4859 level_directory = getPath2((leveldir_current->in_user_dir ?
4860 getUserLevelDir(NULL) :
4861 options.level_directory),
4862 leveldir_current->fullpath);
4864 if ((dir = openDirectory(level_directory)) == NULL)
4866 Warn("cannot read level directory '%s'", level_directory);
4872 while ((dir_entry = readDirectory(dir)) != NULL) // loop all entries
4874 if (strlen(dir_entry->basename) > 4 &&
4875 dir_entry->basename[3] == '.' &&
4876 strEqual(&dir_entry->basename[4], LEVELFILE_EXTENSION))
4878 char levelnum_str[4];
4881 strncpy(levelnum_str, dir_entry->basename, 3);
4882 levelnum_str[3] = '\0';
4884 levelnum_value = atoi(levelnum_str);
4886 if (levelnum_value < leveldir_current->first_level)
4888 Warn("additional level %d found", levelnum_value);
4890 leveldir_current->first_level = levelnum_value;
4892 else if (levelnum_value > leveldir_current->last_level)
4894 Warn("additional level %d found", levelnum_value);
4896 leveldir_current->last_level = levelnum_value;
4902 closeDirectory(dir);
4905 void LoadLevelSetup_SeriesInfo(void)
4908 SetupFileHash *level_setup_hash = NULL;
4909 char *level_subdir = leveldir_current->subdir;
4912 // always start with reliable default values
4913 level_nr = leveldir_current->first_level;
4915 for (i = 0; i < MAX_LEVELS; i++)
4917 LevelStats_setPlayed(i, 0);
4918 LevelStats_setSolved(i, 0);
4923 // --------------------------------------------------------------------------
4924 // ~/.<program>/levelsetup/<level series>/levelsetup.conf
4925 // --------------------------------------------------------------------------
4927 level_subdir = leveldir_current->subdir;
4929 filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
4931 if ((level_setup_hash = loadSetupFileHash(filename)))
4935 // get last played level in this level set
4937 token_value = getHashEntry(level_setup_hash, TOKEN_STR_LAST_PLAYED_LEVEL);
4941 level_nr = atoi(token_value);
4943 if (level_nr < leveldir_current->first_level)
4944 level_nr = leveldir_current->first_level;
4945 if (level_nr > leveldir_current->last_level)
4946 level_nr = leveldir_current->last_level;
4949 // get handicap level in this level set
4951 token_value = getHashEntry(level_setup_hash, TOKEN_STR_HANDICAP_LEVEL);
4955 int level_nr = atoi(token_value);
4957 if (level_nr < leveldir_current->first_level)
4958 level_nr = leveldir_current->first_level;
4959 if (level_nr > leveldir_current->last_level + 1)
4960 level_nr = leveldir_current->last_level;
4962 if (leveldir_current->user_defined || !leveldir_current->handicap)
4963 level_nr = leveldir_current->last_level;
4965 leveldir_current->handicap_level = level_nr;
4968 // get number of played and solved levels in this level set
4970 BEGIN_HASH_ITERATION(level_setup_hash, itr)
4972 char *token = HASH_ITERATION_TOKEN(itr);
4973 char *value = HASH_ITERATION_VALUE(itr);
4975 if (strlen(token) == 3 &&
4976 token[0] >= '0' && token[0] <= '9' &&
4977 token[1] >= '0' && token[1] <= '9' &&
4978 token[2] >= '0' && token[2] <= '9')
4980 int level_nr = atoi(token);
4983 LevelStats_setPlayed(level_nr, atoi(value)); // read 1st column
4985 value = strchr(value, ' ');
4988 LevelStats_setSolved(level_nr, atoi(value)); // read 2nd column
4991 END_HASH_ITERATION(hash, itr)
4993 freeSetupFileHash(level_setup_hash);
4997 Debug("setup", "using default setup values");
5003 void SaveLevelSetup_SeriesInfo(void)
5006 char *level_subdir = leveldir_current->subdir;
5007 char *level_nr_str = int2str(level_nr, 0);
5008 char *handicap_level_str = int2str(leveldir_current->handicap_level, 0);
5012 // --------------------------------------------------------------------------
5013 // ~/.<program>/levelsetup/<level series>/levelsetup.conf
5014 // --------------------------------------------------------------------------
5016 InitLevelSetupDirectory(level_subdir);
5018 filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
5020 if (!(file = fopen(filename, MODE_WRITE)))
5022 Warn("cannot write setup file '%s'", filename);
5029 fprintFileHeader(file, LEVELSETUP_FILENAME);
5031 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_PLAYED_LEVEL,
5033 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_HANDICAP_LEVEL,
5034 handicap_level_str));
5036 for (i = leveldir_current->first_level; i <= leveldir_current->last_level;
5039 if (LevelStats_getPlayed(i) > 0 ||
5040 LevelStats_getSolved(i) > 0)
5045 sprintf(token, "%03d", i);
5046 sprintf(value, "%d %d", LevelStats_getPlayed(i), LevelStats_getSolved(i));
5048 fprintf(file, "%s\n", getFormattedSetupEntry(token, value));
5054 SetFilePermissions(filename, PERMS_PRIVATE);
5059 int LevelStats_getPlayed(int nr)
5061 return (nr >= 0 && nr < MAX_LEVELS ? level_stats[nr].played : 0);
5064 int LevelStats_getSolved(int nr)
5066 return (nr >= 0 && nr < MAX_LEVELS ? level_stats[nr].solved : 0);
5069 void LevelStats_setPlayed(int nr, int value)
5071 if (nr >= 0 && nr < MAX_LEVELS)
5072 level_stats[nr].played = value;
5075 void LevelStats_setSolved(int nr, int value)
5077 if (nr >= 0 && nr < MAX_LEVELS)
5078 level_stats[nr].solved = value;
5081 void LevelStats_incPlayed(int nr)
5083 if (nr >= 0 && nr < MAX_LEVELS)
5084 level_stats[nr].played++;
5087 void LevelStats_incSolved(int nr)
5089 if (nr >= 0 && nr < MAX_LEVELS)
5090 level_stats[nr].solved++;
5093 void LoadUserSetup(void)
5095 // --------------------------------------------------------------------------
5096 // ~/.<program>/usersetup.conf
5097 // --------------------------------------------------------------------------
5099 char *filename = getPath2(getMainUserGameDataDir(), USERSETUP_FILENAME);
5100 SetupFileHash *user_setup_hash = NULL;
5102 // always start with reliable default values
5105 if ((user_setup_hash = loadSetupFileHash(filename)))
5109 // get last selected user number
5110 token_value = getHashEntry(user_setup_hash, TOKEN_STR_LAST_USER);
5113 user.nr = MIN(MAX(0, atoi(token_value)), MAX_PLAYER_NAMES - 1);
5115 freeSetupFileHash(user_setup_hash);
5119 Debug("setup", "using default setup values");
5125 void SaveUserSetup(void)
5127 // --------------------------------------------------------------------------
5128 // ~/.<program>/usersetup.conf
5129 // --------------------------------------------------------------------------
5131 char *filename = getPath2(getMainUserGameDataDir(), USERSETUP_FILENAME);
5134 InitMainUserDataDirectory();
5136 if (!(file = fopen(filename, MODE_WRITE)))
5138 Warn("cannot write setup file '%s'", filename);
5145 fprintFileHeader(file, USERSETUP_FILENAME);
5147 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_USER,
5151 SetFilePermissions(filename, PERMS_PRIVATE);