1 // ============================================================================
2 // Artsoft Retro-Game Library
3 // ----------------------------------------------------------------------------
4 // (c) 1995-2014 by Artsoft Entertainment
7 // https://www.artsoft.org/
8 // ----------------------------------------------------------------------------
10 // ============================================================================
12 #include <sys/types.h>
26 #include "zip/miniunz.h"
29 #define ENABLE_UNUSED_CODE FALSE // for currently unused functions
30 #define DEBUG_NO_CONFIG_FILE FALSE // for extra-verbose debug output
32 #define NUM_LEVELCLASS_DESC 8
34 static char *levelclass_desc[NUM_LEVELCLASS_DESC] =
46 #define TOKEN_VALUE_POSITION_SHORT 32
47 #define TOKEN_VALUE_POSITION_DEFAULT 40
48 #define TOKEN_COMMENT_POSITION_DEFAULT 60
50 #define MAX_COOKIE_LEN 256
52 #define TREE_NODE_TYPE_DEFAULT 0
53 #define TREE_NODE_TYPE_PARENT 1
54 #define TREE_NODE_TYPE_GROUP 2
55 #define TREE_NODE_TYPE_COPY 3
57 #define TREE_NODE_TYPE(ti) (ti->node_group ? TREE_NODE_TYPE_GROUP : \
58 ti->parent_link ? TREE_NODE_TYPE_PARENT : \
59 ti->is_copy ? TREE_NODE_TYPE_COPY : \
60 TREE_NODE_TYPE_DEFAULT)
63 static void setTreeInfoToDefaults(TreeInfo *, int);
64 static TreeInfo *getTreeInfoCopy(TreeInfo *ti);
65 static int compareTreeInfoEntries(const void *, const void *);
67 static int token_value_position = TOKEN_VALUE_POSITION_DEFAULT;
68 static int token_comment_position = TOKEN_COMMENT_POSITION_DEFAULT;
70 static SetupFileHash *artworkinfo_cache_old = NULL;
71 static SetupFileHash *artworkinfo_cache_new = NULL;
72 static SetupFileHash *optional_tokens_hash = NULL;
73 static boolean use_artworkinfo_cache = TRUE;
74 static boolean update_artworkinfo_cache = FALSE;
77 // ----------------------------------------------------------------------------
79 // ----------------------------------------------------------------------------
81 static char *getLevelClassDescription(TreeInfo *ti)
83 int position = ti->sort_priority / 100;
85 if (position >= 0 && position < NUM_LEVELCLASS_DESC)
86 return levelclass_desc[position];
88 return "Unknown Level Class";
91 static char *getCacheDir(void)
93 static char *cache_dir = NULL;
95 if (cache_dir == NULL)
96 cache_dir = getPath2(getMainUserGameDataDir(), CACHE_DIRECTORY);
101 static char *getScoreDir(char *level_subdir)
103 static char *score_dir = NULL;
104 static char *score_level_dir = NULL;
105 char *score_subdir = SCORES_DIRECTORY;
107 if (score_dir == NULL)
108 score_dir = getPath2(getMainUserGameDataDir(), score_subdir);
110 if (level_subdir != NULL)
112 checked_free(score_level_dir);
114 score_level_dir = getPath2(score_dir, level_subdir);
116 return score_level_dir;
122 static char *getScoreCacheDir(char *level_subdir)
124 static char *score_dir = NULL;
125 static char *score_level_dir = NULL;
126 char *score_subdir = SCORES_DIRECTORY;
128 if (score_dir == NULL)
129 score_dir = getPath2(getCacheDir(), score_subdir);
131 if (level_subdir != NULL)
133 checked_free(score_level_dir);
135 score_level_dir = getPath2(score_dir, level_subdir);
137 return score_level_dir;
143 static char *getScoreTapeDir(char *level_subdir, int nr)
145 static char *score_tape_dir = NULL;
146 char tape_subdir[MAX_FILENAME_LEN];
148 checked_free(score_tape_dir);
150 sprintf(tape_subdir, "%03d", nr);
151 score_tape_dir = getPath2(getScoreDir(level_subdir), tape_subdir);
153 return score_tape_dir;
156 static char *getUserSubdir(int nr)
158 static char user_subdir[16] = { 0 };
160 sprintf(user_subdir, "%03d", nr);
165 static char *getUserDir(int nr)
167 static char *user_dir = NULL;
168 char *main_data_dir = getMainUserGameDataDir();
169 char *users_subdir = USERS_DIRECTORY;
170 char *user_subdir = getUserSubdir(nr);
172 checked_free(user_dir);
175 user_dir = getPath3(main_data_dir, users_subdir, user_subdir);
177 user_dir = getPath2(main_data_dir, users_subdir);
182 static char *getLevelSetupDir(char *level_subdir)
184 static char *levelsetup_dir = NULL;
185 char *data_dir = getUserGameDataDir();
186 char *levelsetup_subdir = LEVELSETUP_DIRECTORY;
188 checked_free(levelsetup_dir);
190 if (level_subdir != NULL)
191 levelsetup_dir = getPath3(data_dir, levelsetup_subdir, level_subdir);
193 levelsetup_dir = getPath2(data_dir, levelsetup_subdir);
195 return levelsetup_dir;
198 static char *getNetworkDir(void)
200 static char *network_dir = NULL;
202 if (network_dir == NULL)
203 network_dir = getPath2(getMainUserGameDataDir(), NETWORK_DIRECTORY);
208 char *getLevelDirFromTreeInfo(TreeInfo *node)
210 static char *level_dir = NULL;
213 return options.level_directory;
215 checked_free(level_dir);
217 level_dir = getPath2((node->in_user_dir ? getUserLevelDir(NULL) :
218 options.level_directory), node->fullpath);
223 char *getUserLevelDir(char *level_subdir)
225 static char *userlevel_dir = NULL;
226 char *data_dir = getMainUserGameDataDir();
227 char *userlevel_subdir = LEVELS_DIRECTORY;
229 checked_free(userlevel_dir);
231 if (level_subdir != NULL)
232 userlevel_dir = getPath3(data_dir, userlevel_subdir, level_subdir);
234 userlevel_dir = getPath2(data_dir, userlevel_subdir);
236 return userlevel_dir;
239 char *getNetworkLevelDir(char *level_subdir)
241 static char *network_level_dir = NULL;
242 char *data_dir = getNetworkDir();
243 char *networklevel_subdir = LEVELS_DIRECTORY;
245 checked_free(network_level_dir);
247 if (level_subdir != NULL)
248 network_level_dir = getPath3(data_dir, networklevel_subdir, level_subdir);
250 network_level_dir = getPath2(data_dir, networklevel_subdir);
252 return network_level_dir;
255 char *getCurrentLevelDir(void)
257 return getLevelDirFromTreeInfo(leveldir_current);
260 char *getNewUserLevelSubdir(void)
262 static char *new_level_subdir = NULL;
263 char *subdir_prefix = getLoginName();
264 char subdir_suffix[10];
265 int max_suffix_number = 1000;
268 while (++i < max_suffix_number)
270 sprintf(subdir_suffix, "_%d", i);
272 checked_free(new_level_subdir);
273 new_level_subdir = getStringCat2(subdir_prefix, subdir_suffix);
275 if (!directoryExists(getUserLevelDir(new_level_subdir)))
279 return new_level_subdir;
282 static char *getTapeDir(char *level_subdir)
284 static char *tape_dir = NULL;
285 char *data_dir = getUserGameDataDir();
286 char *tape_subdir = TAPES_DIRECTORY;
288 checked_free(tape_dir);
290 if (level_subdir != NULL)
291 tape_dir = getPath3(data_dir, tape_subdir, level_subdir);
293 tape_dir = getPath2(data_dir, tape_subdir);
298 static char *getSolutionTapeDir(void)
300 static char *tape_dir = NULL;
301 char *data_dir = getCurrentLevelDir();
302 char *tape_subdir = TAPES_DIRECTORY;
304 checked_free(tape_dir);
306 tape_dir = getPath2(data_dir, tape_subdir);
311 static char *getDefaultGraphicsDir(char *graphics_subdir)
313 static char *graphics_dir = NULL;
315 if (graphics_subdir == NULL)
316 return options.graphics_directory;
318 checked_free(graphics_dir);
320 graphics_dir = getPath2(options.graphics_directory, graphics_subdir);
325 static char *getDefaultSoundsDir(char *sounds_subdir)
327 static char *sounds_dir = NULL;
329 if (sounds_subdir == NULL)
330 return options.sounds_directory;
332 checked_free(sounds_dir);
334 sounds_dir = getPath2(options.sounds_directory, sounds_subdir);
339 static char *getDefaultMusicDir(char *music_subdir)
341 static char *music_dir = NULL;
343 if (music_subdir == NULL)
344 return options.music_directory;
346 checked_free(music_dir);
348 music_dir = getPath2(options.music_directory, music_subdir);
353 static char *getClassicArtworkSet(int type)
355 return (type == TREE_TYPE_GRAPHICS_DIR ? GFX_CLASSIC_SUBDIR :
356 type == TREE_TYPE_SOUNDS_DIR ? SND_CLASSIC_SUBDIR :
357 type == TREE_TYPE_MUSIC_DIR ? MUS_CLASSIC_SUBDIR : "");
360 static char *getClassicArtworkDir(int type)
362 return (type == TREE_TYPE_GRAPHICS_DIR ?
363 getDefaultGraphicsDir(GFX_CLASSIC_SUBDIR) :
364 type == TREE_TYPE_SOUNDS_DIR ?
365 getDefaultSoundsDir(SND_CLASSIC_SUBDIR) :
366 type == TREE_TYPE_MUSIC_DIR ?
367 getDefaultMusicDir(MUS_CLASSIC_SUBDIR) : "");
370 char *getUserGraphicsDir(void)
372 static char *usergraphics_dir = NULL;
374 if (usergraphics_dir == NULL)
375 usergraphics_dir = getPath2(getMainUserGameDataDir(), GRAPHICS_DIRECTORY);
377 return usergraphics_dir;
380 char *getUserSoundsDir(void)
382 static char *usersounds_dir = NULL;
384 if (usersounds_dir == NULL)
385 usersounds_dir = getPath2(getMainUserGameDataDir(), SOUNDS_DIRECTORY);
387 return usersounds_dir;
390 char *getUserMusicDir(void)
392 static char *usermusic_dir = NULL;
394 if (usermusic_dir == NULL)
395 usermusic_dir = getPath2(getMainUserGameDataDir(), MUSIC_DIRECTORY);
397 return usermusic_dir;
400 static char *getSetupArtworkDir(TreeInfo *ti)
402 static char *artwork_dir = NULL;
407 checked_free(artwork_dir);
409 artwork_dir = getPath2(ti->basepath, ti->fullpath);
414 char *setLevelArtworkDir(TreeInfo *ti)
416 char **artwork_path_ptr, **artwork_set_ptr;
417 TreeInfo *level_artwork;
419 if (ti == NULL || leveldir_current == NULL)
422 artwork_path_ptr = LEVELDIR_ARTWORK_PATH_PTR(leveldir_current, ti->type);
423 artwork_set_ptr = LEVELDIR_ARTWORK_SET_PTR( leveldir_current, ti->type);
425 checked_free(*artwork_path_ptr);
427 if ((level_artwork = getTreeInfoFromIdentifier(ti, *artwork_set_ptr)))
429 *artwork_path_ptr = getStringCopy(getSetupArtworkDir(level_artwork));
434 No (or non-existing) artwork configured in "levelinfo.conf". This would
435 normally result in using the artwork configured in the setup menu. But
436 if an artwork subdirectory exists (which might contain custom artwork
437 or an artwork configuration file), this level artwork must be treated
438 as relative to the default "classic" artwork, not to the artwork that
439 is currently configured in the setup menu.
441 Update: For "special" versions of R'n'D (like "R'n'D jue"), do not use
442 the "default" artwork (which would be "jue0" for "R'n'D jue"), but use
443 the real "classic" artwork from the original R'n'D (like "gfx_classic").
446 char *dir = getPath2(getCurrentLevelDir(), ARTWORK_DIRECTORY(ti->type));
448 checked_free(*artwork_set_ptr);
450 if (directoryExists(dir))
452 *artwork_path_ptr = getStringCopy(getClassicArtworkDir(ti->type));
453 *artwork_set_ptr = getStringCopy(getClassicArtworkSet(ti->type));
457 *artwork_path_ptr = getStringCopy(UNDEFINED_FILENAME);
458 *artwork_set_ptr = NULL;
464 return *artwork_set_ptr;
467 static char *getLevelArtworkSet(int type)
469 if (leveldir_current == NULL)
472 return LEVELDIR_ARTWORK_SET(leveldir_current, type);
475 static char *getLevelArtworkDir(int type)
477 if (leveldir_current == NULL)
478 return UNDEFINED_FILENAME;
480 return LEVELDIR_ARTWORK_PATH(leveldir_current, type);
483 char *getProgramMainDataPath(char *command_filename, char *base_path)
485 // check if the program's main data base directory is configured
486 if (!strEqual(base_path, "."))
487 return getStringCopy(base_path);
489 /* if the program is configured to start from current directory (default),
490 determine program package directory from program binary (some versions
491 of KDE/Konqueror and Mac OS X (especially "Mavericks") apparently do not
492 set the current working directory to the program package directory) */
493 char *main_data_path = getBasePath(command_filename);
495 #if defined(PLATFORM_MACOSX)
496 if (strSuffix(main_data_path, MAC_APP_BINARY_SUBDIR))
498 char *main_data_path_old = main_data_path;
500 // cut relative path to Mac OS X application binary directory from path
501 main_data_path[strlen(main_data_path) -
502 strlen(MAC_APP_BINARY_SUBDIR)] = '\0';
504 // cut trailing path separator from path (but not if path is root directory)
505 if (strSuffix(main_data_path, "/") && !strEqual(main_data_path, "/"))
506 main_data_path[strlen(main_data_path) - 1] = '\0';
508 // replace empty path with current directory
509 if (strEqual(main_data_path, ""))
510 main_data_path = ".";
512 // add relative path to Mac OS X application resources directory to path
513 main_data_path = getPath2(main_data_path, MAC_APP_FILES_SUBDIR);
515 free(main_data_path_old);
519 return main_data_path;
522 char *getProgramConfigFilename(char *command_filename)
524 static char *config_filename_1 = NULL;
525 static char *config_filename_2 = NULL;
526 static char *config_filename_3 = NULL;
527 static boolean initialized = FALSE;
531 char *command_filename_1 = getStringCopy(command_filename);
533 // strip trailing executable suffix from command filename
534 if (strSuffix(command_filename_1, ".exe"))
535 command_filename_1[strlen(command_filename_1) - 4] = '\0';
537 char *base_path = getProgramMainDataPath(command_filename, BASE_PATH);
538 char *conf_directory = getPath2(base_path, CONF_DIRECTORY);
540 char *command_basepath = getBasePath(command_filename);
541 char *command_basename = getBaseNameNoSuffix(command_filename);
542 char *command_filename_2 = getPath2(command_basepath, command_basename);
544 config_filename_1 = getStringCat2(command_filename_1, ".conf");
545 config_filename_2 = getStringCat2(command_filename_2, ".conf");
546 config_filename_3 = getPath2(conf_directory, SETUP_FILENAME);
548 checked_free(base_path);
549 checked_free(conf_directory);
551 checked_free(command_basepath);
552 checked_free(command_basename);
554 checked_free(command_filename_1);
555 checked_free(command_filename_2);
560 // 1st try: look for config file that exactly matches the binary filename
561 if (fileExists(config_filename_1))
562 return config_filename_1;
564 // 2nd try: look for config file that matches binary filename without suffix
565 if (fileExists(config_filename_2))
566 return config_filename_2;
568 // 3rd try: return setup config filename in global program config directory
569 return config_filename_3;
572 char *getTapeFilename(int nr)
574 static char *filename = NULL;
575 char basename[MAX_FILENAME_LEN];
577 checked_free(filename);
579 sprintf(basename, "%03d.%s", nr, TAPEFILE_EXTENSION);
580 filename = getPath2(getTapeDir(leveldir_current->subdir), basename);
585 char *getTemporaryTapeFilename(void)
587 static char *filename = NULL;
588 char basename[MAX_FILENAME_LEN];
590 checked_free(filename);
592 sprintf(basename, "tmp.%s", TAPEFILE_EXTENSION);
593 filename = getPath2(getTapeDir(NULL), basename);
598 char *getDefaultSolutionTapeFilename(int nr)
600 static char *filename = NULL;
601 char basename[MAX_FILENAME_LEN];
603 checked_free(filename);
605 sprintf(basename, "%03d.%s", nr, TAPEFILE_EXTENSION);
606 filename = getPath2(getSolutionTapeDir(), basename);
611 char *getSokobanSolutionTapeFilename(int nr)
613 static char *filename = NULL;
614 char basename[MAX_FILENAME_LEN];
616 checked_free(filename);
618 sprintf(basename, "%03d.sln", nr);
619 filename = getPath2(getSolutionTapeDir(), basename);
624 char *getSolutionTapeFilename(int nr)
626 char *filename = getDefaultSolutionTapeFilename(nr);
628 if (!fileExists(filename))
630 char *filename2 = getSokobanSolutionTapeFilename(nr);
632 if (fileExists(filename2))
639 char *getScoreFilename(int nr)
641 static char *filename = NULL;
642 char basename[MAX_FILENAME_LEN];
644 checked_free(filename);
646 sprintf(basename, "%03d.%s", nr, SCOREFILE_EXTENSION);
648 // used instead of "leveldir_current->subdir" (for network games)
649 filename = getPath2(getScoreDir(levelset.identifier), basename);
654 char *getScoreCacheFilename(int nr)
656 static char *filename = NULL;
657 char basename[MAX_FILENAME_LEN];
659 checked_free(filename);
661 sprintf(basename, "%03d.%s", nr, SCOREFILE_EXTENSION);
663 // used instead of "leveldir_current->subdir" (for network games)
664 filename = getPath2(getScoreCacheDir(levelset.identifier), basename);
669 char *getScoreTapeBasename(char *name)
671 static char basename[MAX_FILENAME_LEN];
672 char basename_raw[MAX_FILENAME_LEN];
675 sprintf(timestamp, "%s", getCurrentTimestamp());
676 sprintf(basename_raw, "%s-%s", timestamp, name);
677 sprintf(basename, "%s-%08x", timestamp, get_hash_from_key(basename_raw));
682 char *getScoreTapeFilename(char *basename_no_ext, int nr)
684 static char *filename = NULL;
685 char basename[MAX_FILENAME_LEN];
687 checked_free(filename);
689 sprintf(basename, "%s.%s", basename_no_ext, TAPEFILE_EXTENSION);
691 // used instead of "leveldir_current->subdir" (for network games)
692 filename = getPath2(getScoreTapeDir(levelset.identifier, nr), basename);
697 char *getSetupFilename(void)
699 static char *filename = NULL;
701 checked_free(filename);
703 filename = getPath2(getSetupDir(), SETUP_FILENAME);
708 char *getDefaultSetupFilename(void)
710 return program.config_filename;
713 char *getEditorSetupFilename(void)
715 static char *filename = NULL;
717 checked_free(filename);
718 filename = getPath2(getCurrentLevelDir(), EDITORSETUP_FILENAME);
720 if (fileExists(filename))
723 checked_free(filename);
724 filename = getPath2(getSetupDir(), EDITORSETUP_FILENAME);
729 char *getHelpAnimFilename(void)
731 static char *filename = NULL;
733 checked_free(filename);
735 filename = getPath2(getCurrentLevelDir(), HELPANIM_FILENAME);
740 char *getHelpTextFilename(void)
742 static char *filename = NULL;
744 checked_free(filename);
746 filename = getPath2(getCurrentLevelDir(), HELPTEXT_FILENAME);
751 char *getLevelSetInfoFilename(void)
753 static char *filename = NULL;
768 for (i = 0; basenames[i] != NULL; i++)
770 checked_free(filename);
771 filename = getPath2(getCurrentLevelDir(), basenames[i]);
773 if (fileExists(filename))
780 static char *getLevelSetTitleMessageBasename(int nr, boolean initial)
782 static char basename[32];
784 sprintf(basename, "%s_%d.txt",
785 (initial ? "titlemessage_initial" : "titlemessage"), nr + 1);
790 char *getLevelSetTitleMessageFilename(int nr, boolean initial)
792 static char *filename = NULL;
794 boolean skip_setup_artwork = FALSE;
796 checked_free(filename);
798 basename = getLevelSetTitleMessageBasename(nr, initial);
800 if (!gfx.override_level_graphics)
802 // 1st try: look for special artwork in current level series directory
803 filename = getPath3(getCurrentLevelDir(), GRAPHICS_DIRECTORY, basename);
804 if (fileExists(filename))
809 // 2nd try: look for message file in current level set directory
810 filename = getPath2(getCurrentLevelDir(), basename);
811 if (fileExists(filename))
816 // check if there is special artwork configured in level series config
817 if (getLevelArtworkSet(ARTWORK_TYPE_GRAPHICS) != NULL)
819 // 3rd try: look for special artwork configured in level series config
820 filename = getPath2(getLevelArtworkDir(ARTWORK_TYPE_GRAPHICS), basename);
821 if (fileExists(filename))
826 // take missing artwork configured in level set config from default
827 skip_setup_artwork = TRUE;
831 if (!skip_setup_artwork)
833 // 4th try: look for special artwork in configured artwork directory
834 filename = getPath2(getSetupArtworkDir(artwork.gfx_current), basename);
835 if (fileExists(filename))
841 // 5th try: look for default artwork in new default artwork directory
842 filename = getPath2(getDefaultGraphicsDir(GFX_DEFAULT_SUBDIR), basename);
843 if (fileExists(filename))
848 // 6th try: look for default artwork in old default artwork directory
849 filename = getPath2(options.graphics_directory, basename);
850 if (fileExists(filename))
853 return NULL; // cannot find specified artwork file anywhere
856 static char *getCorrectedArtworkBasename(char *basename)
861 char *getCustomImageFilename(char *basename)
863 static char *filename = NULL;
864 boolean skip_setup_artwork = FALSE;
866 checked_free(filename);
868 basename = getCorrectedArtworkBasename(basename);
870 if (!gfx.override_level_graphics)
872 // 1st try: look for special artwork in current level series directory
873 filename = getImg3(getCurrentLevelDir(), GRAPHICS_DIRECTORY, basename);
874 if (fileExists(filename))
879 // check if there is special artwork configured in level series config
880 if (getLevelArtworkSet(ARTWORK_TYPE_GRAPHICS) != NULL)
882 // 2nd try: look for special artwork configured in level series config
883 filename = getImg2(getLevelArtworkDir(ARTWORK_TYPE_GRAPHICS), basename);
884 if (fileExists(filename))
889 // take missing artwork configured in level set config from default
890 skip_setup_artwork = TRUE;
894 if (!skip_setup_artwork)
896 // 3rd try: look for special artwork in configured artwork directory
897 filename = getImg2(getSetupArtworkDir(artwork.gfx_current), basename);
898 if (fileExists(filename))
904 // 4th try: look for default artwork in new default artwork directory
905 filename = getImg2(getDefaultGraphicsDir(GFX_DEFAULT_SUBDIR), basename);
906 if (fileExists(filename))
911 // 5th try: look for default artwork in old default artwork directory
912 filename = getImg2(options.graphics_directory, basename);
913 if (fileExists(filename))
916 if (!strEqual(GFX_FALLBACK_FILENAME, UNDEFINED_FILENAME))
920 Warn("cannot find artwork file '%s' (using fallback)", basename);
922 // 6th try: look for fallback artwork in old default artwork directory
923 // (needed to prevent errors when trying to access unused artwork files)
924 filename = getImg2(options.graphics_directory, GFX_FALLBACK_FILENAME);
925 if (fileExists(filename))
929 return NULL; // cannot find specified artwork file anywhere
932 char *getCustomSoundFilename(char *basename)
934 static char *filename = NULL;
935 boolean skip_setup_artwork = FALSE;
937 checked_free(filename);
939 basename = getCorrectedArtworkBasename(basename);
941 if (!gfx.override_level_sounds)
943 // 1st try: look for special artwork in current level series directory
944 filename = getPath3(getCurrentLevelDir(), SOUNDS_DIRECTORY, basename);
945 if (fileExists(filename))
950 // check if there is special artwork configured in level series config
951 if (getLevelArtworkSet(ARTWORK_TYPE_SOUNDS) != NULL)
953 // 2nd try: look for special artwork configured in level series config
954 filename = getPath2(getLevelArtworkDir(TREE_TYPE_SOUNDS_DIR), basename);
955 if (fileExists(filename))
960 // take missing artwork configured in level set config from default
961 skip_setup_artwork = TRUE;
965 if (!skip_setup_artwork)
967 // 3rd try: look for special artwork in configured artwork directory
968 filename = getPath2(getSetupArtworkDir(artwork.snd_current), basename);
969 if (fileExists(filename))
975 // 4th try: look for default artwork in new default artwork directory
976 filename = getPath2(getDefaultSoundsDir(SND_DEFAULT_SUBDIR), basename);
977 if (fileExists(filename))
982 // 5th try: look for default artwork in old default artwork directory
983 filename = getPath2(options.sounds_directory, basename);
984 if (fileExists(filename))
987 if (!strEqual(SND_FALLBACK_FILENAME, UNDEFINED_FILENAME))
991 Warn("cannot find artwork file '%s' (using fallback)", basename);
993 // 6th try: look for fallback artwork in old default artwork directory
994 // (needed to prevent errors when trying to access unused artwork files)
995 filename = getPath2(options.sounds_directory, SND_FALLBACK_FILENAME);
996 if (fileExists(filename))
1000 return NULL; // cannot find specified artwork file anywhere
1003 char *getCustomMusicFilename(char *basename)
1005 static char *filename = NULL;
1006 boolean skip_setup_artwork = FALSE;
1008 checked_free(filename);
1010 basename = getCorrectedArtworkBasename(basename);
1012 if (!gfx.override_level_music)
1014 // 1st try: look for special artwork in current level series directory
1015 filename = getPath3(getCurrentLevelDir(), MUSIC_DIRECTORY, basename);
1016 if (fileExists(filename))
1021 // check if there is special artwork configured in level series config
1022 if (getLevelArtworkSet(ARTWORK_TYPE_MUSIC) != NULL)
1024 // 2nd try: look for special artwork configured in level series config
1025 filename = getPath2(getLevelArtworkDir(TREE_TYPE_MUSIC_DIR), basename);
1026 if (fileExists(filename))
1031 // take missing artwork configured in level set config from default
1032 skip_setup_artwork = TRUE;
1036 if (!skip_setup_artwork)
1038 // 3rd try: look for special artwork in configured artwork directory
1039 filename = getPath2(getSetupArtworkDir(artwork.mus_current), basename);
1040 if (fileExists(filename))
1046 // 4th try: look for default artwork in new default artwork directory
1047 filename = getPath2(getDefaultMusicDir(MUS_DEFAULT_SUBDIR), basename);
1048 if (fileExists(filename))
1053 // 5th try: look for default artwork in old default artwork directory
1054 filename = getPath2(options.music_directory, basename);
1055 if (fileExists(filename))
1058 if (!strEqual(MUS_FALLBACK_FILENAME, UNDEFINED_FILENAME))
1062 Warn("cannot find artwork file '%s' (using fallback)", basename);
1064 // 6th try: look for fallback artwork in old default artwork directory
1065 // (needed to prevent errors when trying to access unused artwork files)
1066 filename = getPath2(options.music_directory, MUS_FALLBACK_FILENAME);
1067 if (fileExists(filename))
1071 return NULL; // cannot find specified artwork file anywhere
1074 char *getCustomArtworkFilename(char *basename, int type)
1076 if (type == ARTWORK_TYPE_GRAPHICS)
1077 return getCustomImageFilename(basename);
1078 else if (type == ARTWORK_TYPE_SOUNDS)
1079 return getCustomSoundFilename(basename);
1080 else if (type == ARTWORK_TYPE_MUSIC)
1081 return getCustomMusicFilename(basename);
1083 return UNDEFINED_FILENAME;
1086 char *getCustomArtworkConfigFilename(int type)
1088 return getCustomArtworkFilename(ARTWORKINFO_FILENAME(type), type);
1091 char *getCustomArtworkLevelConfigFilename(int type)
1093 static char *filename = NULL;
1095 checked_free(filename);
1097 filename = getPath2(getLevelArtworkDir(type), ARTWORKINFO_FILENAME(type));
1102 char *getCustomMusicDirectory(void)
1104 static char *directory = NULL;
1105 boolean skip_setup_artwork = FALSE;
1107 checked_free(directory);
1109 if (!gfx.override_level_music)
1111 // 1st try: look for special artwork in current level series directory
1112 directory = getPath2(getCurrentLevelDir(), MUSIC_DIRECTORY);
1113 if (directoryExists(directory))
1118 // check if there is special artwork configured in level series config
1119 if (getLevelArtworkSet(ARTWORK_TYPE_MUSIC) != NULL)
1121 // 2nd try: look for special artwork configured in level series config
1122 directory = getStringCopy(getLevelArtworkDir(TREE_TYPE_MUSIC_DIR));
1123 if (directoryExists(directory))
1128 // take missing artwork configured in level set config from default
1129 skip_setup_artwork = TRUE;
1133 if (!skip_setup_artwork)
1135 // 3rd try: look for special artwork in configured artwork directory
1136 directory = getStringCopy(getSetupArtworkDir(artwork.mus_current));
1137 if (directoryExists(directory))
1143 // 4th try: look for default artwork in new default artwork directory
1144 directory = getStringCopy(getDefaultMusicDir(MUS_DEFAULT_SUBDIR));
1145 if (directoryExists(directory))
1150 // 5th try: look for default artwork in old default artwork directory
1151 directory = getStringCopy(options.music_directory);
1152 if (directoryExists(directory))
1155 return NULL; // cannot find specified artwork file anywhere
1158 void InitTapeDirectory(char *level_subdir)
1160 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
1161 createDirectory(getTapeDir(NULL), "main tape", PERMS_PRIVATE);
1162 createDirectory(getTapeDir(level_subdir), "level tape", PERMS_PRIVATE);
1165 void InitScoreDirectory(char *level_subdir)
1167 createDirectory(getMainUserGameDataDir(), "main user data", PERMS_PRIVATE);
1168 createDirectory(getScoreDir(NULL), "main score", PERMS_PRIVATE);
1169 createDirectory(getScoreDir(level_subdir), "level score", PERMS_PRIVATE);
1172 void InitScoreCacheDirectory(char *level_subdir)
1174 createDirectory(getMainUserGameDataDir(), "main user data", PERMS_PRIVATE);
1175 createDirectory(getCacheDir(), "cache data", PERMS_PRIVATE);
1176 createDirectory(getScoreCacheDir(NULL), "main score", PERMS_PRIVATE);
1177 createDirectory(getScoreCacheDir(level_subdir), "level score", PERMS_PRIVATE);
1180 void InitScoreTapeDirectory(char *level_subdir, int nr)
1182 InitScoreDirectory(level_subdir);
1184 createDirectory(getScoreTapeDir(level_subdir, nr), "score tape", PERMS_PRIVATE);
1187 static void SaveUserLevelInfo(void);
1189 void InitUserLevelDirectory(char *level_subdir)
1191 if (!directoryExists(getUserLevelDir(level_subdir)))
1193 createDirectory(getMainUserGameDataDir(), "main user data", PERMS_PRIVATE);
1194 createDirectory(getUserLevelDir(NULL), "main user level", PERMS_PRIVATE);
1195 createDirectory(getUserLevelDir(level_subdir), "user level", PERMS_PRIVATE);
1197 if (setup.internal.create_user_levelset)
1198 SaveUserLevelInfo();
1202 void InitNetworkLevelDirectory(char *level_subdir)
1204 if (!directoryExists(getNetworkLevelDir(level_subdir)))
1206 createDirectory(getMainUserGameDataDir(), "main user data", PERMS_PRIVATE);
1207 createDirectory(getNetworkDir(), "network data", PERMS_PRIVATE);
1208 createDirectory(getNetworkLevelDir(NULL), "main network level", PERMS_PRIVATE);
1209 createDirectory(getNetworkLevelDir(level_subdir), "network level", PERMS_PRIVATE);
1213 void InitLevelSetupDirectory(char *level_subdir)
1215 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
1216 createDirectory(getLevelSetupDir(NULL), "main level setup", PERMS_PRIVATE);
1217 createDirectory(getLevelSetupDir(level_subdir), "level setup", PERMS_PRIVATE);
1220 static void InitCacheDirectory(void)
1222 createDirectory(getMainUserGameDataDir(), "main user data", PERMS_PRIVATE);
1223 createDirectory(getCacheDir(), "cache data", PERMS_PRIVATE);
1227 // ----------------------------------------------------------------------------
1228 // some functions to handle lists of level and artwork directories
1229 // ----------------------------------------------------------------------------
1231 TreeInfo *newTreeInfo(void)
1233 return checked_calloc(sizeof(TreeInfo));
1236 TreeInfo *newTreeInfo_setDefaults(int type)
1238 TreeInfo *ti = newTreeInfo();
1240 setTreeInfoToDefaults(ti, type);
1245 void pushTreeInfo(TreeInfo **node_first, TreeInfo *node_new)
1247 node_new->next = *node_first;
1248 *node_first = node_new;
1251 void removeTreeInfo(TreeInfo **node_first)
1253 TreeInfo *node_old = *node_first;
1255 *node_first = node_old->next;
1256 node_old->next = NULL;
1258 freeTreeInfo(node_old);
1261 int numTreeInfo(TreeInfo *node)
1274 boolean validLevelSeries(TreeInfo *node)
1276 // in a number of cases, tree node is no valid level set
1277 if (node == NULL || node->node_group || node->parent_link || node->is_copy)
1283 TreeInfo *getValidLevelSeries(TreeInfo *node, TreeInfo *default_node)
1285 if (validLevelSeries(node))
1287 else if (node->is_copy)
1288 return getTreeInfoFromIdentifier(leveldir_first, node->identifier);
1290 return getFirstValidTreeInfoEntry(default_node);
1293 TreeInfo *getFirstValidTreeInfoEntry(TreeInfo *node)
1298 if (node->node_group) // enter level group (step down into tree)
1299 return getFirstValidTreeInfoEntry(node->node_group);
1300 else if (node->parent_link) // skip start entry of level group
1302 if (node->next) // get first real level series entry
1303 return getFirstValidTreeInfoEntry(node->next);
1304 else // leave empty level group and go on
1305 return getFirstValidTreeInfoEntry(node->node_parent->next);
1307 else // this seems to be a regular level series
1311 TreeInfo *getTreeInfoFirstGroupEntry(TreeInfo *node)
1316 if (node->node_parent == NULL) // top level group
1317 return *node->node_top;
1318 else // sub level group
1319 return node->node_parent->node_group;
1322 int numTreeInfoInGroup(TreeInfo *node)
1324 return numTreeInfo(getTreeInfoFirstGroupEntry(node));
1327 int getPosFromTreeInfo(TreeInfo *node)
1329 TreeInfo *node_cmp = getTreeInfoFirstGroupEntry(node);
1334 if (node_cmp == node)
1338 node_cmp = node_cmp->next;
1344 TreeInfo *getTreeInfoFromPos(TreeInfo *node, int pos)
1346 TreeInfo *node_default = node;
1358 return node_default;
1361 static TreeInfo *getTreeInfoFromIdentifierExt(TreeInfo *node, char *identifier,
1362 int node_type_wanted)
1364 if (identifier == NULL)
1369 if (TREE_NODE_TYPE(node) == node_type_wanted &&
1370 strEqual(identifier, node->identifier))
1373 if (node->node_group)
1375 TreeInfo *node_group = getTreeInfoFromIdentifierExt(node->node_group,
1388 TreeInfo *getTreeInfoFromIdentifier(TreeInfo *node, char *identifier)
1390 return getTreeInfoFromIdentifierExt(node, identifier, TREE_NODE_TYPE_DEFAULT);
1393 static TreeInfo *cloneTreeNode(TreeInfo **node_top, TreeInfo *node_parent,
1394 TreeInfo *node, boolean skip_sets_without_levels)
1401 if (!node->parent_link && !node->level_group &&
1402 skip_sets_without_levels && node->levels == 0)
1403 return cloneTreeNode(node_top, node_parent, node->next,
1404 skip_sets_without_levels);
1406 node_new = getTreeInfoCopy(node); // copy complete node
1408 node_new->node_top = node_top; // correct top node link
1409 node_new->node_parent = node_parent; // correct parent node link
1411 if (node->level_group)
1412 node_new->node_group = cloneTreeNode(node_top, node_new, node->node_group,
1413 skip_sets_without_levels);
1415 node_new->next = cloneTreeNode(node_top, node_parent, node->next,
1416 skip_sets_without_levels);
1421 static void cloneTree(TreeInfo **ti_new, TreeInfo *ti, boolean skip_empty_sets)
1423 TreeInfo *ti_cloned = cloneTreeNode(ti_new, NULL, ti, skip_empty_sets);
1425 *ti_new = ti_cloned;
1428 static boolean adjustTreeGraphicsForEMC(TreeInfo *node)
1430 boolean settings_changed = FALSE;
1434 boolean want_ecs = (setup.prefer_aga_graphics == FALSE);
1435 boolean want_aga = (setup.prefer_aga_graphics == TRUE);
1436 boolean has_only_ecs = (!node->graphics_set && !node->graphics_set_aga);
1437 boolean has_only_aga = (!node->graphics_set && !node->graphics_set_ecs);
1438 char *graphics_set = NULL;
1440 if (node->graphics_set_ecs && (want_ecs || has_only_ecs))
1441 graphics_set = node->graphics_set_ecs;
1443 if (node->graphics_set_aga && (want_aga || has_only_aga))
1444 graphics_set = node->graphics_set_aga;
1446 if (graphics_set && !strEqual(node->graphics_set, graphics_set))
1448 setString(&node->graphics_set, graphics_set);
1449 settings_changed = TRUE;
1452 if (node->node_group != NULL)
1453 settings_changed |= adjustTreeGraphicsForEMC(node->node_group);
1458 return settings_changed;
1461 static boolean adjustTreeSoundsForEMC(TreeInfo *node)
1463 boolean settings_changed = FALSE;
1467 boolean want_default = (setup.prefer_lowpass_sounds == FALSE);
1468 boolean want_lowpass = (setup.prefer_lowpass_sounds == TRUE);
1469 boolean has_only_default = (!node->sounds_set && !node->sounds_set_lowpass);
1470 boolean has_only_lowpass = (!node->sounds_set && !node->sounds_set_default);
1471 char *sounds_set = NULL;
1473 if (node->sounds_set_default && (want_default || has_only_default))
1474 sounds_set = node->sounds_set_default;
1476 if (node->sounds_set_lowpass && (want_lowpass || has_only_lowpass))
1477 sounds_set = node->sounds_set_lowpass;
1479 if (sounds_set && !strEqual(node->sounds_set, sounds_set))
1481 setString(&node->sounds_set, sounds_set);
1482 settings_changed = TRUE;
1485 if (node->node_group != NULL)
1486 settings_changed |= adjustTreeSoundsForEMC(node->node_group);
1491 return settings_changed;
1494 void dumpTreeInfo(TreeInfo *node, int depth)
1496 char bullet_list[] = { '-', '*', 'o' };
1500 Debug("tree", "Dumping TreeInfo:");
1504 char bullet = bullet_list[depth % ARRAY_SIZE(bullet_list)];
1506 for (i = 0; i < depth * 2; i++)
1507 DebugContinued("", " ");
1509 DebugContinued("tree", "%c '%s' ['%s] [PARENT: '%s'] %s\n",
1510 bullet, node->name, node->identifier,
1511 (node->node_parent ? node->node_parent->identifier : "-"),
1512 (node->node_group ? "[GROUP]" : ""));
1515 // use for dumping artwork info tree
1516 Debug("tree", "subdir == '%s' ['%s', '%s'] [%d])",
1517 node->subdir, node->fullpath, node->basepath, node->in_user_dir);
1520 if (node->node_group != NULL)
1521 dumpTreeInfo(node->node_group, depth + 1);
1527 void sortTreeInfoBySortFunction(TreeInfo **node_first,
1528 int (*compare_function)(const void *,
1531 int num_nodes = numTreeInfo(*node_first);
1532 TreeInfo **sort_array;
1533 TreeInfo *node = *node_first;
1539 // allocate array for sorting structure pointers
1540 sort_array = checked_calloc(num_nodes * sizeof(TreeInfo *));
1542 // writing structure pointers to sorting array
1543 while (i < num_nodes && node) // double boundary check...
1545 sort_array[i] = node;
1551 // sorting the structure pointers in the sorting array
1552 qsort(sort_array, num_nodes, sizeof(TreeInfo *),
1555 // update the linkage of list elements with the sorted node array
1556 for (i = 0; i < num_nodes - 1; i++)
1557 sort_array[i]->next = sort_array[i + 1];
1558 sort_array[num_nodes - 1]->next = NULL;
1560 // update the linkage of the main list anchor pointer
1561 *node_first = sort_array[0];
1565 // now recursively sort the level group structures
1569 if (node->node_group != NULL)
1570 sortTreeInfoBySortFunction(&node->node_group, compare_function);
1576 void sortTreeInfo(TreeInfo **node_first)
1578 sortTreeInfoBySortFunction(node_first, compareTreeInfoEntries);
1582 // ============================================================================
1583 // some stuff from "files.c"
1584 // ============================================================================
1586 #if defined(PLATFORM_WIN32)
1588 #define S_IRGRP S_IRUSR
1591 #define S_IROTH S_IRUSR
1594 #define S_IWGRP S_IWUSR
1597 #define S_IWOTH S_IWUSR
1600 #define S_IXGRP S_IXUSR
1603 #define S_IXOTH S_IXUSR
1606 #define S_IRWXG (S_IRGRP | S_IWGRP | S_IXGRP)
1611 #endif // PLATFORM_WIN32
1613 // file permissions for newly written files
1614 #define MODE_R_ALL (S_IRUSR | S_IRGRP | S_IROTH)
1615 #define MODE_W_ALL (S_IWUSR | S_IWGRP | S_IWOTH)
1616 #define MODE_X_ALL (S_IXUSR | S_IXGRP | S_IXOTH)
1618 #define MODE_W_PRIVATE (S_IWUSR)
1619 #define MODE_W_PUBLIC_FILE (S_IWUSR | S_IWGRP)
1620 #define MODE_W_PUBLIC_DIR (S_IWUSR | S_IWGRP | S_ISGID)
1622 #define DIR_PERMS_PRIVATE (MODE_R_ALL | MODE_X_ALL | MODE_W_PRIVATE)
1623 #define DIR_PERMS_PUBLIC (MODE_R_ALL | MODE_X_ALL | MODE_W_PUBLIC_DIR)
1624 #define DIR_PERMS_PUBLIC_ALL (MODE_R_ALL | MODE_X_ALL | MODE_W_ALL)
1626 #define FILE_PERMS_PRIVATE (MODE_R_ALL | MODE_W_PRIVATE)
1627 #define FILE_PERMS_PUBLIC (MODE_R_ALL | MODE_W_PUBLIC_FILE)
1628 #define FILE_PERMS_PUBLIC_ALL (MODE_R_ALL | MODE_W_ALL)
1631 char *getHomeDir(void)
1633 static char *dir = NULL;
1635 #if defined(PLATFORM_WIN32)
1638 dir = checked_malloc(MAX_PATH + 1);
1640 if (!SUCCEEDED(SHGetFolderPath(NULL, CSIDL_PERSONAL, NULL, 0, dir)))
1643 #elif defined(PLATFORM_EMSCRIPTEN)
1644 dir = "/persistent";
1645 #elif defined(PLATFORM_UNIX)
1648 if ((dir = getenv("HOME")) == NULL)
1650 dir = getUnixHomeDir();
1653 dir = getStringCopy(dir);
1665 char *getPersonalDataDir(void)
1667 static char *personal_data_dir = NULL;
1669 #if defined(PLATFORM_MACOSX)
1670 if (personal_data_dir == NULL)
1671 personal_data_dir = getPath2(getHomeDir(), "Documents");
1673 if (personal_data_dir == NULL)
1674 personal_data_dir = getHomeDir();
1677 return personal_data_dir;
1680 char *getMainUserGameDataDir(void)
1682 static char *main_user_data_dir = NULL;
1684 #if defined(PLATFORM_ANDROID)
1685 if (main_user_data_dir == NULL)
1686 main_user_data_dir = (char *)(SDL_AndroidGetExternalStorageState() &
1687 SDL_ANDROID_EXTERNAL_STORAGE_WRITE ?
1688 SDL_AndroidGetExternalStoragePath() :
1689 SDL_AndroidGetInternalStoragePath());
1691 if (main_user_data_dir == NULL)
1692 main_user_data_dir = getPath2(getPersonalDataDir(),
1693 program.userdata_subdir);
1696 return main_user_data_dir;
1699 char *getUserGameDataDir(void)
1702 return getMainUserGameDataDir();
1704 return getUserDir(user.nr);
1707 char *getSetupDir(void)
1709 return getUserGameDataDir();
1712 static mode_t posix_umask(mode_t mask)
1714 #if defined(PLATFORM_UNIX)
1721 static int posix_mkdir(const char *pathname, mode_t mode)
1723 #if defined(PLATFORM_WIN32)
1724 return mkdir(pathname);
1726 return mkdir(pathname, mode);
1730 static boolean posix_process_running_setgid(void)
1732 #if defined(PLATFORM_UNIX)
1733 return (getgid() != getegid());
1739 void createDirectory(char *dir, char *text, int permission_class)
1741 if (directoryExists(dir))
1744 // leave "other" permissions in umask untouched, but ensure group parts
1745 // of USERDATA_DIR_MODE are not masked
1746 mode_t dir_mode = (permission_class == PERMS_PRIVATE ?
1747 DIR_PERMS_PRIVATE : DIR_PERMS_PUBLIC);
1748 mode_t last_umask = posix_umask(0);
1749 mode_t group_umask = ~(dir_mode & S_IRWXG);
1750 int running_setgid = posix_process_running_setgid();
1752 if (permission_class == PERMS_PUBLIC)
1754 // if we're setgid, protect files against "other"
1755 // else keep umask(0) to make the dir world-writable
1758 posix_umask(last_umask & group_umask);
1760 dir_mode = DIR_PERMS_PUBLIC_ALL;
1763 if (posix_mkdir(dir, dir_mode) != 0)
1764 Warn("cannot create %s directory '%s': %s", text, dir, strerror(errno));
1766 if (permission_class == PERMS_PUBLIC && !running_setgid)
1767 chmod(dir, dir_mode);
1769 posix_umask(last_umask); // restore previous umask
1772 void InitMainUserDataDirectory(void)
1774 createDirectory(getMainUserGameDataDir(), "main user data", PERMS_PRIVATE);
1777 void InitUserDataDirectory(void)
1779 createDirectory(getMainUserGameDataDir(), "main user data", PERMS_PRIVATE);
1783 createDirectory(getUserDir(-1), "users", PERMS_PRIVATE);
1784 createDirectory(getUserDir(user.nr), "user data", PERMS_PRIVATE);
1788 void SetFilePermissions(char *filename, int permission_class)
1790 int running_setgid = posix_process_running_setgid();
1791 int perms = (permission_class == PERMS_PRIVATE ?
1792 FILE_PERMS_PRIVATE : FILE_PERMS_PUBLIC);
1794 if (permission_class == PERMS_PUBLIC && !running_setgid)
1795 perms = FILE_PERMS_PUBLIC_ALL;
1797 chmod(filename, perms);
1800 char *getCookie(char *file_type)
1802 static char cookie[MAX_COOKIE_LEN + 1];
1804 if (strlen(program.cookie_prefix) + 1 +
1805 strlen(file_type) + strlen("_FILE_VERSION_x.x") > MAX_COOKIE_LEN)
1806 return "[COOKIE ERROR]"; // should never happen
1808 sprintf(cookie, "%s_%s_FILE_VERSION_%d.%d",
1809 program.cookie_prefix, file_type,
1810 program.version_super, program.version_major);
1815 void fprintFileHeader(FILE *file, char *basename)
1817 char *prefix = "# ";
1820 fprintf_line_with_prefix(file, prefix, sep1, 77);
1821 fprintf(file, "%s%s\n", prefix, basename);
1822 fprintf_line_with_prefix(file, prefix, sep1, 77);
1823 fprintf(file, "\n");
1826 int getFileVersionFromCookieString(const char *cookie)
1828 const char *ptr_cookie1, *ptr_cookie2;
1829 const char *pattern1 = "_FILE_VERSION_";
1830 const char *pattern2 = "?.?";
1831 const int len_cookie = strlen(cookie);
1832 const int len_pattern1 = strlen(pattern1);
1833 const int len_pattern2 = strlen(pattern2);
1834 const int len_pattern = len_pattern1 + len_pattern2;
1835 int version_super, version_major;
1837 if (len_cookie <= len_pattern)
1840 ptr_cookie1 = &cookie[len_cookie - len_pattern];
1841 ptr_cookie2 = &cookie[len_cookie - len_pattern2];
1843 if (strncmp(ptr_cookie1, pattern1, len_pattern1) != 0)
1846 if (ptr_cookie2[0] < '0' || ptr_cookie2[0] > '9' ||
1847 ptr_cookie2[1] != '.' ||
1848 ptr_cookie2[2] < '0' || ptr_cookie2[2] > '9')
1851 version_super = ptr_cookie2[0] - '0';
1852 version_major = ptr_cookie2[2] - '0';
1854 return VERSION_IDENT(version_super, version_major, 0, 0);
1857 boolean checkCookieString(const char *cookie, const char *template)
1859 const char *pattern = "_FILE_VERSION_?.?";
1860 const int len_cookie = strlen(cookie);
1861 const int len_template = strlen(template);
1862 const int len_pattern = strlen(pattern);
1864 if (len_cookie != len_template)
1867 if (strncmp(cookie, template, len_cookie - len_pattern) != 0)
1874 // ----------------------------------------------------------------------------
1875 // setup file list and hash handling functions
1876 // ----------------------------------------------------------------------------
1878 char *getFormattedSetupEntry(char *token, char *value)
1881 static char entry[MAX_LINE_LEN];
1883 // if value is an empty string, just return token without value
1887 // start with the token and some spaces to format output line
1888 sprintf(entry, "%s:", token);
1889 for (i = strlen(entry); i < token_value_position; i++)
1892 // continue with the token's value
1893 strcat(entry, value);
1898 SetupFileList *newSetupFileList(char *token, char *value)
1900 SetupFileList *new = checked_malloc(sizeof(SetupFileList));
1902 new->token = getStringCopy(token);
1903 new->value = getStringCopy(value);
1910 void freeSetupFileList(SetupFileList *list)
1915 checked_free(list->token);
1916 checked_free(list->value);
1919 freeSetupFileList(list->next);
1924 char *getListEntry(SetupFileList *list, char *token)
1929 if (strEqual(list->token, token))
1932 return getListEntry(list->next, token);
1935 SetupFileList *setListEntry(SetupFileList *list, char *token, char *value)
1940 if (strEqual(list->token, token))
1942 checked_free(list->value);
1944 list->value = getStringCopy(value);
1948 else if (list->next == NULL)
1949 return (list->next = newSetupFileList(token, value));
1951 return setListEntry(list->next, token, value);
1954 SetupFileList *addListEntry(SetupFileList *list, char *token, char *value)
1959 if (list->next == NULL)
1960 return (list->next = newSetupFileList(token, value));
1962 return addListEntry(list->next, token, value);
1965 #if ENABLE_UNUSED_CODE
1967 static void printSetupFileList(SetupFileList *list)
1972 Debug("setup:printSetupFileList", "token: '%s'", list->token);
1973 Debug("setup:printSetupFileList", "value: '%s'", list->value);
1975 printSetupFileList(list->next);
1981 DEFINE_HASHTABLE_INSERT(insert_hash_entry, char, char);
1982 DEFINE_HASHTABLE_SEARCH(search_hash_entry, char, char);
1983 DEFINE_HASHTABLE_CHANGE(change_hash_entry, char, char);
1984 DEFINE_HASHTABLE_REMOVE(remove_hash_entry, char, char);
1986 #define insert_hash_entry hashtable_insert
1987 #define search_hash_entry hashtable_search
1988 #define change_hash_entry hashtable_change
1989 #define remove_hash_entry hashtable_remove
1992 unsigned int get_hash_from_key(void *key)
1997 This algorithm (k=33) was first reported by Dan Bernstein many years ago in
1998 'comp.lang.c'. Another version of this algorithm (now favored by Bernstein)
1999 uses XOR: hash(i) = hash(i - 1) * 33 ^ str[i]; the magic of number 33 (why
2000 it works better than many other constants, prime or not) has never been
2001 adequately explained.
2003 If you just want to have a good hash function, and cannot wait, djb2
2004 is one of the best string hash functions i know. It has excellent
2005 distribution and speed on many different sets of keys and table sizes.
2006 You are not likely to do better with one of the "well known" functions
2007 such as PJW, K&R, etc.
2009 Ozan (oz) Yigit [http://www.cs.yorku.ca/~oz/hash.html]
2012 char *str = (char *)key;
2013 unsigned int hash = 5381;
2016 while ((c = *str++))
2017 hash = ((hash << 5) + hash) + c; // hash * 33 + c
2022 static int keys_are_equal(void *key1, void *key2)
2024 return (strEqual((char *)key1, (char *)key2));
2027 SetupFileHash *newSetupFileHash(void)
2029 SetupFileHash *new_hash =
2030 create_hashtable(16, 0.75, get_hash_from_key, keys_are_equal);
2032 if (new_hash == NULL)
2033 Fail("create_hashtable() failed -- out of memory");
2038 void freeSetupFileHash(SetupFileHash *hash)
2043 hashtable_destroy(hash, 1); // 1 == also free values stored in hash
2046 char *getHashEntry(SetupFileHash *hash, char *token)
2051 return search_hash_entry(hash, token);
2054 void setHashEntry(SetupFileHash *hash, char *token, char *value)
2061 value_copy = getStringCopy(value);
2063 // change value; if it does not exist, insert it as new
2064 if (!change_hash_entry(hash, token, value_copy))
2065 if (!insert_hash_entry(hash, getStringCopy(token), value_copy))
2066 Fail("cannot insert into hash -- aborting");
2069 char *removeHashEntry(SetupFileHash *hash, char *token)
2074 return remove_hash_entry(hash, token);
2077 #if ENABLE_UNUSED_CODE
2079 static void printSetupFileHash(SetupFileHash *hash)
2081 BEGIN_HASH_ITERATION(hash, itr)
2083 Debug("setup:printSetupFileHash", "token: '%s'", HASH_ITERATION_TOKEN(itr));
2084 Debug("setup:printSetupFileHash", "value: '%s'", HASH_ITERATION_VALUE(itr));
2086 END_HASH_ITERATION(hash, itr)
2091 #define ALLOW_TOKEN_VALUE_SEPARATOR_BEING_WHITESPACE 1
2092 #define CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING 0
2093 #define CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH 0
2095 static boolean token_value_separator_found = FALSE;
2096 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2097 static boolean token_value_separator_warning = FALSE;
2099 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2100 static boolean token_already_exists_warning = FALSE;
2103 static boolean getTokenValueFromSetupLineExt(char *line,
2104 char **token_ptr, char **value_ptr,
2105 char *filename, char *line_raw,
2107 boolean separator_required)
2109 static char line_copy[MAX_LINE_LEN + 1], line_raw_copy[MAX_LINE_LEN + 1];
2110 char *token, *value, *line_ptr;
2112 // when externally invoked via ReadTokenValueFromLine(), copy line buffers
2113 if (line_raw == NULL)
2115 strncpy(line_copy, line, MAX_LINE_LEN);
2116 line_copy[MAX_LINE_LEN] = '\0';
2119 strcpy(line_raw_copy, line_copy);
2120 line_raw = line_raw_copy;
2123 // cut trailing comment from input line
2124 for (line_ptr = line; *line_ptr; line_ptr++)
2126 if (*line_ptr == '#')
2133 // cut trailing whitespaces from input line
2134 for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
2135 if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
2138 // ignore empty lines
2142 // cut leading whitespaces from token
2143 for (token = line; *token; token++)
2144 if (*token != ' ' && *token != '\t')
2147 // start with empty value as reliable default
2150 token_value_separator_found = FALSE;
2152 // find end of token to determine start of value
2153 for (line_ptr = token; *line_ptr; line_ptr++)
2155 // first look for an explicit token/value separator, like ':' or '='
2156 if (*line_ptr == ':' || *line_ptr == '=')
2158 *line_ptr = '\0'; // terminate token string
2159 value = line_ptr + 1; // set beginning of value
2161 token_value_separator_found = TRUE;
2167 #if ALLOW_TOKEN_VALUE_SEPARATOR_BEING_WHITESPACE
2168 // fallback: if no token/value separator found, also allow whitespaces
2169 if (!token_value_separator_found && !separator_required)
2171 for (line_ptr = token; *line_ptr; line_ptr++)
2173 if (*line_ptr == ' ' || *line_ptr == '\t')
2175 *line_ptr = '\0'; // terminate token string
2176 value = line_ptr + 1; // set beginning of value
2178 token_value_separator_found = TRUE;
2184 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2185 if (token_value_separator_found)
2187 if (!token_value_separator_warning)
2189 Debug("setup", "---");
2191 if (filename != NULL)
2193 Debug("setup", "missing token/value separator(s) in config file:");
2194 Debug("setup", "- config file: '%s'", filename);
2198 Debug("setup", "missing token/value separator(s):");
2201 token_value_separator_warning = TRUE;
2204 if (filename != NULL)
2205 Debug("setup", "- line %d: '%s'", line_nr, line_raw);
2207 Debug("setup", "- line: '%s'", line_raw);
2213 // cut trailing whitespaces from token
2214 for (line_ptr = &token[strlen(token)]; line_ptr >= token; line_ptr--)
2215 if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
2218 // cut leading whitespaces from value
2219 for (; *value; value++)
2220 if (*value != ' ' && *value != '\t')
2229 boolean getTokenValueFromSetupLine(char *line, char **token, char **value)
2231 // while the internal (old) interface does not require a token/value
2232 // separator (for downwards compatibility with existing files which
2233 // don't use them), it is mandatory for the external (new) interface
2235 return getTokenValueFromSetupLineExt(line, token, value, NULL, NULL, 0, TRUE);
2238 static boolean loadSetupFileData(void *setup_file_data, char *filename,
2239 boolean top_recursion_level, boolean is_hash)
2241 static SetupFileHash *include_filename_hash = NULL;
2242 char line[MAX_LINE_LEN], line_raw[MAX_LINE_LEN], previous_line[MAX_LINE_LEN];
2243 char *token, *value, *line_ptr;
2244 void *insert_ptr = NULL;
2245 boolean read_continued_line = FALSE;
2247 int line_nr = 0, token_count = 0, include_count = 0;
2249 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2250 token_value_separator_warning = FALSE;
2253 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2254 token_already_exists_warning = FALSE;
2257 if (!(file = openFile(filename, MODE_READ)))
2259 #if DEBUG_NO_CONFIG_FILE
2260 Debug("setup", "cannot open configuration file '%s'", filename);
2266 // use "insert pointer" to store list end for constant insertion complexity
2268 insert_ptr = setup_file_data;
2270 // on top invocation, create hash to mark included files (to prevent loops)
2271 if (top_recursion_level)
2272 include_filename_hash = newSetupFileHash();
2274 // mark this file as already included (to prevent including it again)
2275 setHashEntry(include_filename_hash, getBaseNamePtr(filename), "true");
2277 while (!checkEndOfFile(file))
2279 // read next line of input file
2280 if (!getStringFromFile(file, line, MAX_LINE_LEN))
2283 // check if line was completely read and is terminated by line break
2284 if (strlen(line) > 0 && line[strlen(line) - 1] == '\n')
2287 // cut trailing line break (this can be newline and/or carriage return)
2288 for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
2289 if ((*line_ptr == '\n' || *line_ptr == '\r') && *(line_ptr + 1) == '\0')
2292 // copy raw input line for later use (mainly debugging output)
2293 strcpy(line_raw, line);
2295 if (read_continued_line)
2297 // append new line to existing line, if there is enough space
2298 if (strlen(previous_line) + strlen(line_ptr) < MAX_LINE_LEN)
2299 strcat(previous_line, line_ptr);
2301 strcpy(line, previous_line); // copy storage buffer to line
2303 read_continued_line = FALSE;
2306 // if the last character is '\', continue at next line
2307 if (strlen(line) > 0 && line[strlen(line) - 1] == '\\')
2309 line[strlen(line) - 1] = '\0'; // cut off trailing backslash
2310 strcpy(previous_line, line); // copy line to storage buffer
2312 read_continued_line = TRUE;
2317 if (!getTokenValueFromSetupLineExt(line, &token, &value, filename,
2318 line_raw, line_nr, FALSE))
2323 if (strEqual(token, "include"))
2325 if (getHashEntry(include_filename_hash, value) == NULL)
2327 char *basepath = getBasePath(filename);
2328 char *basename = getBaseName(value);
2329 char *filename_include = getPath2(basepath, basename);
2331 loadSetupFileData(setup_file_data, filename_include, FALSE, is_hash);
2335 free(filename_include);
2341 Warn("ignoring already processed file '%s'", value);
2348 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2350 getHashEntry((SetupFileHash *)setup_file_data, token);
2352 if (old_value != NULL)
2354 if (!token_already_exists_warning)
2356 Debug("setup", "---");
2357 Debug("setup", "duplicate token(s) found in config file:");
2358 Debug("setup", "- config file: '%s'", filename);
2360 token_already_exists_warning = TRUE;
2363 Debug("setup", "- token: '%s' (in line %d)", token, line_nr);
2364 Debug("setup", " old value: '%s'", old_value);
2365 Debug("setup", " new value: '%s'", value);
2369 setHashEntry((SetupFileHash *)setup_file_data, token, value);
2373 insert_ptr = addListEntry((SetupFileList *)insert_ptr, token, value);
2383 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2384 if (token_value_separator_warning)
2385 Debug("setup", "---");
2388 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2389 if (token_already_exists_warning)
2390 Debug("setup", "---");
2393 if (token_count == 0 && include_count == 0)
2394 Warn("configuration file '%s' is empty", filename);
2396 if (top_recursion_level)
2397 freeSetupFileHash(include_filename_hash);
2402 static int compareSetupFileData(const void *object1, const void *object2)
2404 const struct ConfigInfo *entry1 = (struct ConfigInfo *)object1;
2405 const struct ConfigInfo *entry2 = (struct ConfigInfo *)object2;
2407 return strcmp(entry1->token, entry2->token);
2410 static void saveSetupFileHash(SetupFileHash *hash, char *filename)
2412 int item_count = hashtable_count(hash);
2413 int item_size = sizeof(struct ConfigInfo);
2414 struct ConfigInfo *sort_array = checked_malloc(item_count * item_size);
2418 // copy string pointers from hash to array
2419 BEGIN_HASH_ITERATION(hash, itr)
2421 sort_array[i].token = HASH_ITERATION_TOKEN(itr);
2422 sort_array[i].value = HASH_ITERATION_VALUE(itr);
2426 if (i > item_count) // should never happen
2429 END_HASH_ITERATION(hash, itr)
2431 // sort string pointers from hash in array
2432 qsort(sort_array, item_count, item_size, compareSetupFileData);
2434 if (!(file = fopen(filename, MODE_WRITE)))
2436 Warn("cannot write configuration file '%s'", filename);
2441 fprintf(file, "%s\n\n", getFormattedSetupEntry("program.version",
2442 program.version_string));
2443 for (i = 0; i < item_count; i++)
2444 fprintf(file, "%s\n", getFormattedSetupEntry(sort_array[i].token,
2445 sort_array[i].value));
2448 checked_free(sort_array);
2451 SetupFileList *loadSetupFileList(char *filename)
2453 SetupFileList *setup_file_list = newSetupFileList("", "");
2454 SetupFileList *first_valid_list_entry;
2456 if (!loadSetupFileData(setup_file_list, filename, TRUE, FALSE))
2458 freeSetupFileList(setup_file_list);
2463 first_valid_list_entry = setup_file_list->next;
2465 // free empty list header
2466 setup_file_list->next = NULL;
2467 freeSetupFileList(setup_file_list);
2469 return first_valid_list_entry;
2472 SetupFileHash *loadSetupFileHash(char *filename)
2474 SetupFileHash *setup_file_hash = newSetupFileHash();
2476 if (!loadSetupFileData(setup_file_hash, filename, TRUE, TRUE))
2478 freeSetupFileHash(setup_file_hash);
2483 return setup_file_hash;
2487 // ============================================================================
2489 // ============================================================================
2491 #define TOKEN_STR_LAST_LEVEL_SERIES "last_level_series"
2492 #define TOKEN_STR_LAST_PLAYED_LEVEL "last_played_level"
2493 #define TOKEN_STR_HANDICAP_LEVEL "handicap_level"
2494 #define TOKEN_STR_LAST_USER "last_user"
2496 // level directory info
2497 #define LEVELINFO_TOKEN_IDENTIFIER 0
2498 #define LEVELINFO_TOKEN_NAME 1
2499 #define LEVELINFO_TOKEN_NAME_SORTING 2
2500 #define LEVELINFO_TOKEN_AUTHOR 3
2501 #define LEVELINFO_TOKEN_YEAR 4
2502 #define LEVELINFO_TOKEN_PROGRAM_TITLE 5
2503 #define LEVELINFO_TOKEN_PROGRAM_COPYRIGHT 6
2504 #define LEVELINFO_TOKEN_PROGRAM_COMPANY 7
2505 #define LEVELINFO_TOKEN_IMPORTED_FROM 8
2506 #define LEVELINFO_TOKEN_IMPORTED_BY 9
2507 #define LEVELINFO_TOKEN_TESTED_BY 10
2508 #define LEVELINFO_TOKEN_LEVELS 11
2509 #define LEVELINFO_TOKEN_FIRST_LEVEL 12
2510 #define LEVELINFO_TOKEN_SORT_PRIORITY 13
2511 #define LEVELINFO_TOKEN_LATEST_ENGINE 14
2512 #define LEVELINFO_TOKEN_LEVEL_GROUP 15
2513 #define LEVELINFO_TOKEN_READONLY 16
2514 #define LEVELINFO_TOKEN_GRAPHICS_SET_ECS 17
2515 #define LEVELINFO_TOKEN_GRAPHICS_SET_AGA 18
2516 #define LEVELINFO_TOKEN_GRAPHICS_SET 19
2517 #define LEVELINFO_TOKEN_SOUNDS_SET_DEFAULT 20
2518 #define LEVELINFO_TOKEN_SOUNDS_SET_LOWPASS 21
2519 #define LEVELINFO_TOKEN_SOUNDS_SET 22
2520 #define LEVELINFO_TOKEN_MUSIC_SET 23
2521 #define LEVELINFO_TOKEN_FILENAME 24
2522 #define LEVELINFO_TOKEN_FILETYPE 25
2523 #define LEVELINFO_TOKEN_SPECIAL_FLAGS 26
2524 #define LEVELINFO_TOKEN_HANDICAP 27
2525 #define LEVELINFO_TOKEN_SKIP_LEVELS 28
2526 #define LEVELINFO_TOKEN_USE_EMC_TILES 29
2528 #define NUM_LEVELINFO_TOKENS 30
2530 static LevelDirTree ldi;
2532 static struct TokenInfo levelinfo_tokens[] =
2534 // level directory info
2535 { TYPE_STRING, &ldi.identifier, "identifier" },
2536 { TYPE_STRING, &ldi.name, "name" },
2537 { TYPE_STRING, &ldi.name_sorting, "name_sorting" },
2538 { TYPE_STRING, &ldi.author, "author" },
2539 { TYPE_STRING, &ldi.year, "year" },
2540 { TYPE_STRING, &ldi.program_title, "program_title" },
2541 { TYPE_STRING, &ldi.program_copyright, "program_copyright" },
2542 { TYPE_STRING, &ldi.program_company, "program_company" },
2543 { TYPE_STRING, &ldi.imported_from, "imported_from" },
2544 { TYPE_STRING, &ldi.imported_by, "imported_by" },
2545 { TYPE_STRING, &ldi.tested_by, "tested_by" },
2546 { TYPE_INTEGER, &ldi.levels, "levels" },
2547 { TYPE_INTEGER, &ldi.first_level, "first_level" },
2548 { TYPE_INTEGER, &ldi.sort_priority, "sort_priority" },
2549 { TYPE_BOOLEAN, &ldi.latest_engine, "latest_engine" },
2550 { TYPE_BOOLEAN, &ldi.level_group, "level_group" },
2551 { TYPE_BOOLEAN, &ldi.readonly, "readonly" },
2552 { TYPE_STRING, &ldi.graphics_set_ecs, "graphics_set.ecs" },
2553 { TYPE_STRING, &ldi.graphics_set_aga, "graphics_set.aga" },
2554 { TYPE_STRING, &ldi.graphics_set, "graphics_set" },
2555 { TYPE_STRING, &ldi.sounds_set_default,"sounds_set.default" },
2556 { TYPE_STRING, &ldi.sounds_set_lowpass,"sounds_set.lowpass" },
2557 { TYPE_STRING, &ldi.sounds_set, "sounds_set" },
2558 { TYPE_STRING, &ldi.music_set, "music_set" },
2559 { TYPE_STRING, &ldi.level_filename, "filename" },
2560 { TYPE_STRING, &ldi.level_filetype, "filetype" },
2561 { TYPE_STRING, &ldi.special_flags, "special_flags" },
2562 { TYPE_BOOLEAN, &ldi.handicap, "handicap" },
2563 { TYPE_BOOLEAN, &ldi.skip_levels, "skip_levels" },
2564 { TYPE_BOOLEAN, &ldi.use_emc_tiles, "use_emc_tiles" }
2567 static struct TokenInfo artworkinfo_tokens[] =
2569 // artwork directory info
2570 { TYPE_STRING, &ldi.identifier, "identifier" },
2571 { TYPE_STRING, &ldi.subdir, "subdir" },
2572 { TYPE_STRING, &ldi.name, "name" },
2573 { TYPE_STRING, &ldi.name_sorting, "name_sorting" },
2574 { TYPE_STRING, &ldi.author, "author" },
2575 { TYPE_STRING, &ldi.program_title, "program_title" },
2576 { TYPE_STRING, &ldi.program_copyright, "program_copyright" },
2577 { TYPE_STRING, &ldi.program_company, "program_company" },
2578 { TYPE_INTEGER, &ldi.sort_priority, "sort_priority" },
2579 { TYPE_STRING, &ldi.basepath, "basepath" },
2580 { TYPE_STRING, &ldi.fullpath, "fullpath" },
2581 { TYPE_BOOLEAN, &ldi.in_user_dir, "in_user_dir" },
2582 { TYPE_STRING, &ldi.class_desc, "class_desc" },
2587 static char *optional_tokens[] =
2590 "program_copyright",
2596 static void setTreeInfoToDefaults(TreeInfo *ti, int type)
2600 ti->node_top = (ti->type == TREE_TYPE_LEVEL_DIR ? &leveldir_first :
2601 ti->type == TREE_TYPE_GRAPHICS_DIR ? &artwork.gfx_first :
2602 ti->type == TREE_TYPE_SOUNDS_DIR ? &artwork.snd_first :
2603 ti->type == TREE_TYPE_MUSIC_DIR ? &artwork.mus_first :
2606 ti->node_parent = NULL;
2607 ti->node_group = NULL;
2614 ti->fullpath = NULL;
2615 ti->basepath = NULL;
2616 ti->identifier = NULL;
2617 ti->name = getStringCopy(ANONYMOUS_NAME);
2618 ti->name_sorting = NULL;
2619 ti->author = getStringCopy(ANONYMOUS_NAME);
2622 ti->program_title = NULL;
2623 ti->program_copyright = NULL;
2624 ti->program_company = NULL;
2626 ti->sort_priority = LEVELCLASS_UNDEFINED; // default: least priority
2627 ti->latest_engine = FALSE; // default: get from level
2628 ti->parent_link = FALSE;
2629 ti->is_copy = FALSE;
2630 ti->in_user_dir = FALSE;
2631 ti->user_defined = FALSE;
2633 ti->class_desc = NULL;
2635 ti->infotext = getStringCopy(TREE_INFOTEXT(ti->type));
2637 if (ti->type == TREE_TYPE_LEVEL_DIR)
2639 ti->imported_from = NULL;
2640 ti->imported_by = NULL;
2641 ti->tested_by = NULL;
2643 ti->graphics_set_ecs = NULL;
2644 ti->graphics_set_aga = NULL;
2645 ti->graphics_set = NULL;
2646 ti->sounds_set_default = NULL;
2647 ti->sounds_set_lowpass = NULL;
2648 ti->sounds_set = NULL;
2649 ti->music_set = NULL;
2650 ti->graphics_path = getStringCopy(UNDEFINED_FILENAME);
2651 ti->sounds_path = getStringCopy(UNDEFINED_FILENAME);
2652 ti->music_path = getStringCopy(UNDEFINED_FILENAME);
2654 ti->level_filename = NULL;
2655 ti->level_filetype = NULL;
2657 ti->special_flags = NULL;
2660 ti->first_level = 0;
2662 ti->level_group = FALSE;
2663 ti->handicap_level = 0;
2664 ti->readonly = TRUE;
2665 ti->handicap = TRUE;
2666 ti->skip_levels = FALSE;
2668 ti->use_emc_tiles = FALSE;
2672 static void setTreeInfoToDefaultsFromParent(TreeInfo *ti, TreeInfo *parent)
2676 Warn("setTreeInfoToDefaultsFromParent(): parent == NULL");
2678 setTreeInfoToDefaults(ti, TREE_TYPE_UNDEFINED);
2683 // copy all values from the parent structure
2685 ti->type = parent->type;
2687 ti->node_top = parent->node_top;
2688 ti->node_parent = parent;
2689 ti->node_group = NULL;
2696 ti->fullpath = NULL;
2697 ti->basepath = NULL;
2698 ti->identifier = NULL;
2699 ti->name = getStringCopy(ANONYMOUS_NAME);
2700 ti->name_sorting = NULL;
2701 ti->author = getStringCopy(parent->author);
2702 ti->year = getStringCopy(parent->year);
2704 ti->program_title = getStringCopy(parent->program_title);
2705 ti->program_copyright = getStringCopy(parent->program_copyright);
2706 ti->program_company = getStringCopy(parent->program_company);
2708 ti->sort_priority = parent->sort_priority;
2709 ti->latest_engine = parent->latest_engine;
2710 ti->parent_link = FALSE;
2711 ti->is_copy = FALSE;
2712 ti->in_user_dir = parent->in_user_dir;
2713 ti->user_defined = parent->user_defined;
2714 ti->color = parent->color;
2715 ti->class_desc = getStringCopy(parent->class_desc);
2717 ti->infotext = getStringCopy(parent->infotext);
2719 if (ti->type == TREE_TYPE_LEVEL_DIR)
2721 ti->imported_from = getStringCopy(parent->imported_from);
2722 ti->imported_by = getStringCopy(parent->imported_by);
2723 ti->tested_by = getStringCopy(parent->tested_by);
2725 ti->graphics_set_ecs = getStringCopy(parent->graphics_set_ecs);
2726 ti->graphics_set_aga = getStringCopy(parent->graphics_set_aga);
2727 ti->graphics_set = getStringCopy(parent->graphics_set);
2728 ti->sounds_set_default = getStringCopy(parent->sounds_set_default);
2729 ti->sounds_set_lowpass = getStringCopy(parent->sounds_set_lowpass);
2730 ti->sounds_set = getStringCopy(parent->sounds_set);
2731 ti->music_set = getStringCopy(parent->music_set);
2732 ti->graphics_path = getStringCopy(UNDEFINED_FILENAME);
2733 ti->sounds_path = getStringCopy(UNDEFINED_FILENAME);
2734 ti->music_path = getStringCopy(UNDEFINED_FILENAME);
2736 ti->level_filename = getStringCopy(parent->level_filename);
2737 ti->level_filetype = getStringCopy(parent->level_filetype);
2739 ti->special_flags = getStringCopy(parent->special_flags);
2741 ti->levels = parent->levels;
2742 ti->first_level = parent->first_level;
2743 ti->last_level = parent->last_level;
2744 ti->level_group = FALSE;
2745 ti->handicap_level = parent->handicap_level;
2746 ti->readonly = parent->readonly;
2747 ti->handicap = parent->handicap;
2748 ti->skip_levels = parent->skip_levels;
2750 ti->use_emc_tiles = parent->use_emc_tiles;
2754 static TreeInfo *getTreeInfoCopy(TreeInfo *ti)
2756 TreeInfo *ti_copy = newTreeInfo();
2758 // copy all values from the original structure
2760 ti_copy->type = ti->type;
2762 ti_copy->node_top = ti->node_top;
2763 ti_copy->node_parent = ti->node_parent;
2764 ti_copy->node_group = ti->node_group;
2765 ti_copy->next = ti->next;
2767 ti_copy->cl_first = ti->cl_first;
2768 ti_copy->cl_cursor = ti->cl_cursor;
2770 ti_copy->subdir = getStringCopy(ti->subdir);
2771 ti_copy->fullpath = getStringCopy(ti->fullpath);
2772 ti_copy->basepath = getStringCopy(ti->basepath);
2773 ti_copy->identifier = getStringCopy(ti->identifier);
2774 ti_copy->name = getStringCopy(ti->name);
2775 ti_copy->name_sorting = getStringCopy(ti->name_sorting);
2776 ti_copy->author = getStringCopy(ti->author);
2777 ti_copy->year = getStringCopy(ti->year);
2779 ti_copy->program_title = getStringCopy(ti->program_title);
2780 ti_copy->program_copyright = getStringCopy(ti->program_copyright);
2781 ti_copy->program_company = getStringCopy(ti->program_company);
2783 ti_copy->imported_from = getStringCopy(ti->imported_from);
2784 ti_copy->imported_by = getStringCopy(ti->imported_by);
2785 ti_copy->tested_by = getStringCopy(ti->tested_by);
2787 ti_copy->graphics_set_ecs = getStringCopy(ti->graphics_set_ecs);
2788 ti_copy->graphics_set_aga = getStringCopy(ti->graphics_set_aga);
2789 ti_copy->graphics_set = getStringCopy(ti->graphics_set);
2790 ti_copy->sounds_set_default = getStringCopy(ti->sounds_set_default);
2791 ti_copy->sounds_set_lowpass = getStringCopy(ti->sounds_set_lowpass);
2792 ti_copy->sounds_set = getStringCopy(ti->sounds_set);
2793 ti_copy->music_set = getStringCopy(ti->music_set);
2794 ti_copy->graphics_path = getStringCopy(ti->graphics_path);
2795 ti_copy->sounds_path = getStringCopy(ti->sounds_path);
2796 ti_copy->music_path = getStringCopy(ti->music_path);
2798 ti_copy->level_filename = getStringCopy(ti->level_filename);
2799 ti_copy->level_filetype = getStringCopy(ti->level_filetype);
2801 ti_copy->special_flags = getStringCopy(ti->special_flags);
2803 ti_copy->levels = ti->levels;
2804 ti_copy->first_level = ti->first_level;
2805 ti_copy->last_level = ti->last_level;
2806 ti_copy->sort_priority = ti->sort_priority;
2808 ti_copy->latest_engine = ti->latest_engine;
2810 ti_copy->level_group = ti->level_group;
2811 ti_copy->parent_link = ti->parent_link;
2812 ti_copy->is_copy = ti->is_copy;
2813 ti_copy->in_user_dir = ti->in_user_dir;
2814 ti_copy->user_defined = ti->user_defined;
2815 ti_copy->readonly = ti->readonly;
2816 ti_copy->handicap = ti->handicap;
2817 ti_copy->skip_levels = ti->skip_levels;
2819 ti_copy->use_emc_tiles = ti->use_emc_tiles;
2821 ti_copy->color = ti->color;
2822 ti_copy->class_desc = getStringCopy(ti->class_desc);
2823 ti_copy->handicap_level = ti->handicap_level;
2825 ti_copy->infotext = getStringCopy(ti->infotext);
2830 void freeTreeInfo(TreeInfo *ti)
2835 checked_free(ti->subdir);
2836 checked_free(ti->fullpath);
2837 checked_free(ti->basepath);
2838 checked_free(ti->identifier);
2840 checked_free(ti->name);
2841 checked_free(ti->name_sorting);
2842 checked_free(ti->author);
2843 checked_free(ti->year);
2845 checked_free(ti->program_title);
2846 checked_free(ti->program_copyright);
2847 checked_free(ti->program_company);
2849 checked_free(ti->class_desc);
2851 checked_free(ti->infotext);
2853 if (ti->type == TREE_TYPE_LEVEL_DIR)
2855 checked_free(ti->imported_from);
2856 checked_free(ti->imported_by);
2857 checked_free(ti->tested_by);
2859 checked_free(ti->graphics_set_ecs);
2860 checked_free(ti->graphics_set_aga);
2861 checked_free(ti->graphics_set);
2862 checked_free(ti->sounds_set_default);
2863 checked_free(ti->sounds_set_lowpass);
2864 checked_free(ti->sounds_set);
2865 checked_free(ti->music_set);
2867 checked_free(ti->graphics_path);
2868 checked_free(ti->sounds_path);
2869 checked_free(ti->music_path);
2871 checked_free(ti->level_filename);
2872 checked_free(ti->level_filetype);
2874 checked_free(ti->special_flags);
2877 // recursively free child node
2879 freeTreeInfo(ti->node_group);
2881 // recursively free next node
2883 freeTreeInfo(ti->next);
2888 void setSetupInfo(struct TokenInfo *token_info,
2889 int token_nr, char *token_value)
2891 int token_type = token_info[token_nr].type;
2892 void *setup_value = token_info[token_nr].value;
2894 if (token_value == NULL)
2897 // set setup field to corresponding token value
2902 *(boolean *)setup_value = get_boolean_from_string(token_value);
2906 *(int *)setup_value = get_switch3_from_string(token_value);
2910 *(Key *)setup_value = getKeyFromKeyName(token_value);
2914 *(Key *)setup_value = getKeyFromX11KeyName(token_value);
2918 *(int *)setup_value = get_integer_from_string(token_value);
2922 checked_free(*(char **)setup_value);
2923 *(char **)setup_value = getStringCopy(token_value);
2927 *(int *)setup_value = get_player_nr_from_string(token_value);
2935 static int compareTreeInfoEntries(const void *object1, const void *object2)
2937 const TreeInfo *entry1 = *((TreeInfo **)object1);
2938 const TreeInfo *entry2 = *((TreeInfo **)object2);
2939 int tree_sorting1 = TREE_SORTING(entry1);
2940 int tree_sorting2 = TREE_SORTING(entry2);
2942 if (tree_sorting1 != tree_sorting2)
2943 return (tree_sorting1 - tree_sorting2);
2945 return strcasecmp(entry1->name_sorting, entry2->name_sorting);
2948 static TreeInfo *createParentTreeInfoNode(TreeInfo *node_parent)
2952 if (node_parent == NULL)
2955 ti_new = newTreeInfo();
2956 setTreeInfoToDefaults(ti_new, node_parent->type);
2958 ti_new->node_parent = node_parent;
2959 ti_new->parent_link = TRUE;
2961 setString(&ti_new->identifier, node_parent->identifier);
2962 setString(&ti_new->name, BACKLINK_TEXT_PARENT);
2963 setString(&ti_new->name_sorting, ti_new->name);
2965 setString(&ti_new->subdir, STRING_PARENT_DIRECTORY);
2966 setString(&ti_new->fullpath, node_parent->fullpath);
2968 ti_new->sort_priority = LEVELCLASS_PARENT;
2969 ti_new->latest_engine = node_parent->latest_engine;
2971 setString(&ti_new->class_desc, getLevelClassDescription(ti_new));
2973 pushTreeInfo(&node_parent->node_group, ti_new);
2978 static TreeInfo *createTopTreeInfoNode(TreeInfo *node_first)
2980 if (node_first == NULL)
2983 TreeInfo *ti_new = newTreeInfo();
2984 int type = node_first->type;
2986 setTreeInfoToDefaults(ti_new, type);
2988 ti_new->node_parent = NULL;
2989 ti_new->parent_link = FALSE;
2991 setString(&ti_new->identifier, "top_tree_node");
2992 setString(&ti_new->name, TREE_INFOTEXT(type));
2993 setString(&ti_new->name_sorting, ti_new->name);
2995 setString(&ti_new->subdir, STRING_TOP_DIRECTORY);
2996 setString(&ti_new->fullpath, ".");
2998 ti_new->sort_priority = LEVELCLASS_TOP;
2999 ti_new->latest_engine = node_first->latest_engine;
3001 setString(&ti_new->class_desc, TREE_INFOTEXT(type));
3003 ti_new->node_group = node_first;
3004 ti_new->level_group = TRUE;
3006 TreeInfo *ti_new2 = createParentTreeInfoNode(ti_new);
3008 setString(&ti_new2->name, TREE_BACKLINK_TEXT(type));
3009 setString(&ti_new2->name_sorting, ti_new2->name);
3014 static void setTreeInfoParentNodes(TreeInfo *node, TreeInfo *node_parent)
3018 if (node->node_group)
3019 setTreeInfoParentNodes(node->node_group, node);
3021 node->node_parent = node_parent;
3028 // ----------------------------------------------------------------------------
3029 // functions for handling level and custom artwork info cache
3030 // ----------------------------------------------------------------------------
3032 static void LoadArtworkInfoCache(void)
3034 InitCacheDirectory();
3036 if (artworkinfo_cache_old == NULL)
3038 char *filename = getPath2(getCacheDir(), ARTWORKINFO_CACHE_FILE);
3040 // try to load artwork info hash from already existing cache file
3041 artworkinfo_cache_old = loadSetupFileHash(filename);
3043 // try to get program version that artwork info cache was written with
3044 char *version = getHashEntry(artworkinfo_cache_old, "program.version");
3046 // check program version of artwork info cache against current version
3047 if (!strEqual(version, program.version_string))
3049 freeSetupFileHash(artworkinfo_cache_old);
3051 artworkinfo_cache_old = NULL;
3054 // if no artwork info cache file was found, start with empty hash
3055 if (artworkinfo_cache_old == NULL)
3056 artworkinfo_cache_old = newSetupFileHash();
3061 if (artworkinfo_cache_new == NULL)
3062 artworkinfo_cache_new = newSetupFileHash();
3064 update_artworkinfo_cache = FALSE;
3067 static void SaveArtworkInfoCache(void)
3069 if (!update_artworkinfo_cache)
3072 char *filename = getPath2(getCacheDir(), ARTWORKINFO_CACHE_FILE);
3074 InitCacheDirectory();
3076 saveSetupFileHash(artworkinfo_cache_new, filename);
3081 static char *getCacheTokenPrefix(char *prefix1, char *prefix2)
3083 static char *prefix = NULL;
3085 checked_free(prefix);
3087 prefix = getStringCat2WithSeparator(prefix1, prefix2, ".");
3092 // (identical to above function, but separate string buffer needed -- nasty)
3093 static char *getCacheToken(char *prefix, char *suffix)
3095 static char *token = NULL;
3097 checked_free(token);
3099 token = getStringCat2WithSeparator(prefix, suffix, ".");
3104 static char *getFileTimestampString(char *filename)
3106 return getStringCopy(i_to_a(getFileTimestampEpochSeconds(filename)));
3109 static boolean modifiedFileTimestamp(char *filename, char *timestamp_string)
3111 struct stat file_status;
3113 if (timestamp_string == NULL)
3116 if (!fileExists(filename)) // file does not exist
3117 return (atoi(timestamp_string) != 0);
3119 if (stat(filename, &file_status) != 0) // cannot stat file
3122 return (file_status.st_mtime != atoi(timestamp_string));
3125 static TreeInfo *getArtworkInfoCacheEntry(LevelDirTree *level_node, int type)
3127 char *identifier = level_node->subdir;
3128 char *type_string = ARTWORK_DIRECTORY(type);
3129 char *token_prefix = getCacheTokenPrefix(type_string, identifier);
3130 char *token_main = getCacheToken(token_prefix, "CACHED");
3131 char *cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
3132 boolean cached = (cache_entry != NULL && strEqual(cache_entry, "true"));
3133 TreeInfo *artwork_info = NULL;
3135 if (!use_artworkinfo_cache)
3138 if (optional_tokens_hash == NULL)
3142 // create hash from list of optional tokens (for quick access)
3143 optional_tokens_hash = newSetupFileHash();
3144 for (i = 0; optional_tokens[i] != NULL; i++)
3145 setHashEntry(optional_tokens_hash, optional_tokens[i], "");
3152 artwork_info = newTreeInfo();
3153 setTreeInfoToDefaults(artwork_info, type);
3155 // set all structure fields according to the token/value pairs
3156 ldi = *artwork_info;
3157 for (i = 0; artworkinfo_tokens[i].type != -1; i++)
3159 char *token_suffix = artworkinfo_tokens[i].text;
3160 char *token = getCacheToken(token_prefix, token_suffix);
3161 char *value = getHashEntry(artworkinfo_cache_old, token);
3163 (getHashEntry(optional_tokens_hash, token_suffix) != NULL);
3165 setSetupInfo(artworkinfo_tokens, i, value);
3167 // check if cache entry for this item is mandatory, but missing
3168 if (value == NULL && !optional)
3170 Warn("missing cache entry '%s'", token);
3176 *artwork_info = ldi;
3181 char *filename_levelinfo = getPath2(getLevelDirFromTreeInfo(level_node),
3182 LEVELINFO_FILENAME);
3183 char *filename_artworkinfo = getPath2(getSetupArtworkDir(artwork_info),
3184 ARTWORKINFO_FILENAME(type));
3186 // check if corresponding "levelinfo.conf" file has changed
3187 token_main = getCacheToken(token_prefix, "TIMESTAMP_LEVELINFO");
3188 cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
3190 if (modifiedFileTimestamp(filename_levelinfo, cache_entry))
3193 // check if corresponding "<artworkinfo>.conf" file has changed
3194 token_main = getCacheToken(token_prefix, "TIMESTAMP_ARTWORKINFO");
3195 cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
3197 if (modifiedFileTimestamp(filename_artworkinfo, cache_entry))
3200 checked_free(filename_levelinfo);
3201 checked_free(filename_artworkinfo);
3204 if (!cached && artwork_info != NULL)
3206 freeTreeInfo(artwork_info);
3211 return artwork_info;
3214 static void setArtworkInfoCacheEntry(TreeInfo *artwork_info,
3215 LevelDirTree *level_node, int type)
3217 char *identifier = level_node->subdir;
3218 char *type_string = ARTWORK_DIRECTORY(type);
3219 char *token_prefix = getCacheTokenPrefix(type_string, identifier);
3220 char *token_main = getCacheToken(token_prefix, "CACHED");
3221 boolean set_cache_timestamps = TRUE;
3224 setHashEntry(artworkinfo_cache_new, token_main, "true");
3226 if (set_cache_timestamps)
3228 char *filename_levelinfo = getPath2(getLevelDirFromTreeInfo(level_node),
3229 LEVELINFO_FILENAME);
3230 char *filename_artworkinfo = getPath2(getSetupArtworkDir(artwork_info),
3231 ARTWORKINFO_FILENAME(type));
3232 char *timestamp_levelinfo = getFileTimestampString(filename_levelinfo);
3233 char *timestamp_artworkinfo = getFileTimestampString(filename_artworkinfo);
3235 token_main = getCacheToken(token_prefix, "TIMESTAMP_LEVELINFO");
3236 setHashEntry(artworkinfo_cache_new, token_main, timestamp_levelinfo);
3238 token_main = getCacheToken(token_prefix, "TIMESTAMP_ARTWORKINFO");
3239 setHashEntry(artworkinfo_cache_new, token_main, timestamp_artworkinfo);
3241 checked_free(filename_levelinfo);
3242 checked_free(filename_artworkinfo);
3243 checked_free(timestamp_levelinfo);
3244 checked_free(timestamp_artworkinfo);
3247 ldi = *artwork_info;
3248 for (i = 0; artworkinfo_tokens[i].type != -1; i++)
3250 char *token = getCacheToken(token_prefix, artworkinfo_tokens[i].text);
3251 char *value = getSetupValue(artworkinfo_tokens[i].type,
3252 artworkinfo_tokens[i].value);
3254 setHashEntry(artworkinfo_cache_new, token, value);
3259 // ----------------------------------------------------------------------------
3260 // functions for loading level info and custom artwork info
3261 // ----------------------------------------------------------------------------
3263 int GetZipFileTreeType(char *zip_filename)
3265 static char *top_dir_path = NULL;
3266 static char *top_dir_conf_filename[NUM_BASE_TREE_TYPES] = { NULL };
3267 static char *conf_basename[NUM_BASE_TREE_TYPES] =
3269 GRAPHICSINFO_FILENAME,
3270 SOUNDSINFO_FILENAME,
3276 checked_free(top_dir_path);
3277 top_dir_path = NULL;
3279 for (j = 0; j < NUM_BASE_TREE_TYPES; j++)
3281 checked_free(top_dir_conf_filename[j]);
3282 top_dir_conf_filename[j] = NULL;
3285 char **zip_entries = zip_list(zip_filename);
3287 // check if zip file successfully opened
3288 if (zip_entries == NULL || zip_entries[0] == NULL)
3289 return TREE_TYPE_UNDEFINED;
3291 // first zip file entry is expected to be top level directory
3292 char *top_dir = zip_entries[0];
3294 // check if valid top level directory found in zip file
3295 if (!strSuffix(top_dir, "/"))
3296 return TREE_TYPE_UNDEFINED;
3298 // get filenames of valid configuration files in top level directory
3299 for (j = 0; j < NUM_BASE_TREE_TYPES; j++)
3300 top_dir_conf_filename[j] = getStringCat2(top_dir, conf_basename[j]);
3302 int tree_type = TREE_TYPE_UNDEFINED;
3305 while (zip_entries[e] != NULL)
3307 // check if every zip file entry is below top level directory
3308 if (!strPrefix(zip_entries[e], top_dir))
3309 return TREE_TYPE_UNDEFINED;
3311 // check if this zip file entry is a valid configuration filename
3312 for (j = 0; j < NUM_BASE_TREE_TYPES; j++)
3314 if (strEqual(zip_entries[e], top_dir_conf_filename[j]))
3316 // only exactly one valid configuration file allowed
3317 if (tree_type != TREE_TYPE_UNDEFINED)
3318 return TREE_TYPE_UNDEFINED;
3330 static boolean CheckZipFileForDirectory(char *zip_filename, char *directory,
3333 static char *top_dir_path = NULL;
3334 static char *top_dir_conf_filename = NULL;
3336 checked_free(top_dir_path);
3337 checked_free(top_dir_conf_filename);
3339 top_dir_path = NULL;
3340 top_dir_conf_filename = NULL;
3342 char *conf_basename = (tree_type == TREE_TYPE_LEVEL_DIR ? LEVELINFO_FILENAME :
3343 ARTWORKINFO_FILENAME(tree_type));
3345 // check if valid configuration filename determined
3346 if (conf_basename == NULL || strEqual(conf_basename, ""))
3349 char **zip_entries = zip_list(zip_filename);
3351 // check if zip file successfully opened
3352 if (zip_entries == NULL || zip_entries[0] == NULL)
3355 // first zip file entry is expected to be top level directory
3356 char *top_dir = zip_entries[0];
3358 // check if valid top level directory found in zip file
3359 if (!strSuffix(top_dir, "/"))
3362 // get path of extracted top level directory
3363 top_dir_path = getPath2(directory, top_dir);
3365 // remove trailing directory separator from top level directory path
3366 // (required to be able to check for file and directory in next step)
3367 top_dir_path[strlen(top_dir_path) - 1] = '\0';
3369 // check if zip file's top level directory already exists in target directory
3370 if (fileExists(top_dir_path)) // (checks for file and directory)
3373 // get filename of configuration file in top level directory
3374 top_dir_conf_filename = getStringCat2(top_dir, conf_basename);
3376 boolean found_top_dir_conf_filename = FALSE;
3379 while (zip_entries[i] != NULL)
3381 // check if every zip file entry is below top level directory
3382 if (!strPrefix(zip_entries[i], top_dir))
3385 // check if this zip file entry is the configuration filename
3386 if (strEqual(zip_entries[i], top_dir_conf_filename))
3387 found_top_dir_conf_filename = TRUE;
3392 // check if valid configuration filename was found in zip file
3393 if (!found_top_dir_conf_filename)
3399 char *ExtractZipFileIntoDirectory(char *zip_filename, char *directory,
3402 boolean zip_file_valid = CheckZipFileForDirectory(zip_filename, directory,
3405 if (!zip_file_valid)
3407 Warn("zip file '%s' rejected!", zip_filename);
3412 char **zip_entries = zip_extract(zip_filename, directory);
3414 if (zip_entries == NULL)
3416 Warn("zip file '%s' could not be extracted!", zip_filename);
3421 Info("zip file '%s' successfully extracted!", zip_filename);
3423 // first zip file entry contains top level directory
3424 char *top_dir = zip_entries[0];
3426 // remove trailing directory separator from top level directory
3427 top_dir[strlen(top_dir) - 1] = '\0';
3432 static void ProcessZipFilesInDirectory(char *directory, int tree_type)
3435 DirectoryEntry *dir_entry;
3437 if ((dir = openDirectory(directory)) == NULL)
3439 // display error if directory is main "options.graphics_directory" etc.
3440 if (tree_type == TREE_TYPE_LEVEL_DIR ||
3441 directory == OPTIONS_ARTWORK_DIRECTORY(tree_type))
3442 Warn("cannot read directory '%s'", directory);
3447 while ((dir_entry = readDirectory(dir)) != NULL) // loop all entries
3449 // skip non-zip files (and also directories with zip extension)
3450 if (!strSuffixLower(dir_entry->basename, ".zip") || dir_entry->is_directory)
3453 char *zip_filename = getPath2(directory, dir_entry->basename);
3454 char *zip_filename_extracted = getStringCat2(zip_filename, ".extracted");
3455 char *zip_filename_rejected = getStringCat2(zip_filename, ".rejected");
3457 // check if zip file hasn't already been extracted or rejected
3458 if (!fileExists(zip_filename_extracted) &&
3459 !fileExists(zip_filename_rejected))
3461 char *top_dir = ExtractZipFileIntoDirectory(zip_filename, directory,
3463 char *marker_filename = (top_dir != NULL ? zip_filename_extracted :
3464 zip_filename_rejected);
3467 // create empty file to mark zip file as extracted or rejected
3468 if ((marker_file = fopen(marker_filename, MODE_WRITE)))
3469 fclose(marker_file);
3472 free(zip_filename_extracted);
3473 free(zip_filename_rejected);
3477 closeDirectory(dir);
3480 // forward declaration for recursive call by "LoadLevelInfoFromLevelDir()"
3481 static void LoadLevelInfoFromLevelDir(TreeInfo **, TreeInfo *, char *);
3483 static boolean LoadLevelInfoFromLevelConf(TreeInfo **node_first,
3484 TreeInfo *node_parent,
3485 char *level_directory,
3486 char *directory_name)
3488 char *directory_path = getPath2(level_directory, directory_name);
3489 char *filename = getPath2(directory_path, LEVELINFO_FILENAME);
3490 SetupFileHash *setup_file_hash;
3491 LevelDirTree *leveldir_new = NULL;
3494 // unless debugging, silently ignore directories without "levelinfo.conf"
3495 if (!options.debug && !fileExists(filename))
3497 free(directory_path);
3503 setup_file_hash = loadSetupFileHash(filename);
3505 if (setup_file_hash == NULL)
3507 #if DEBUG_NO_CONFIG_FILE
3508 Debug("setup", "ignoring level directory '%s'", directory_path);
3511 free(directory_path);
3517 leveldir_new = newTreeInfo();
3520 setTreeInfoToDefaultsFromParent(leveldir_new, node_parent);
3522 setTreeInfoToDefaults(leveldir_new, TREE_TYPE_LEVEL_DIR);
3524 leveldir_new->subdir = getStringCopy(directory_name);
3526 // set all structure fields according to the token/value pairs
3527 ldi = *leveldir_new;
3528 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
3529 setSetupInfo(levelinfo_tokens, i,
3530 getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
3531 *leveldir_new = ldi;
3533 if (strEqual(leveldir_new->name, ANONYMOUS_NAME))
3534 setString(&leveldir_new->name, leveldir_new->subdir);
3536 if (leveldir_new->identifier == NULL)
3537 leveldir_new->identifier = getStringCopy(leveldir_new->subdir);
3539 if (leveldir_new->name_sorting == NULL)
3540 leveldir_new->name_sorting = getStringCopy(leveldir_new->name);
3542 if (node_parent == NULL) // top level group
3544 leveldir_new->basepath = getStringCopy(level_directory);
3545 leveldir_new->fullpath = getStringCopy(leveldir_new->subdir);
3547 else // sub level group
3549 leveldir_new->basepath = getStringCopy(node_parent->basepath);
3550 leveldir_new->fullpath = getPath2(node_parent->fullpath, directory_name);
3553 leveldir_new->last_level =
3554 leveldir_new->first_level + leveldir_new->levels - 1;
3556 leveldir_new->in_user_dir =
3557 (!strEqual(leveldir_new->basepath, options.level_directory));
3559 // adjust some settings if user's private level directory was detected
3560 if (leveldir_new->sort_priority == LEVELCLASS_UNDEFINED &&
3561 leveldir_new->in_user_dir &&
3562 (strEqual(leveldir_new->subdir, getLoginName()) ||
3563 strEqual(leveldir_new->name, getLoginName()) ||
3564 strEqual(leveldir_new->author, getRealName())))
3566 leveldir_new->sort_priority = LEVELCLASS_PRIVATE_START;
3567 leveldir_new->readonly = FALSE;
3570 leveldir_new->user_defined =
3571 (leveldir_new->in_user_dir && IS_LEVELCLASS_PRIVATE(leveldir_new));
3573 setString(&leveldir_new->class_desc, getLevelClassDescription(leveldir_new));
3575 leveldir_new->handicap_level = // set handicap to default value
3576 (leveldir_new->user_defined || !leveldir_new->handicap ?
3577 leveldir_new->last_level : leveldir_new->first_level);
3579 DrawInitText(leveldir_new->name, 150, FC_YELLOW);
3581 pushTreeInfo(node_first, leveldir_new);
3583 freeSetupFileHash(setup_file_hash);
3585 if (leveldir_new->level_group)
3587 // create node to link back to current level directory
3588 createParentTreeInfoNode(leveldir_new);
3590 // recursively step into sub-directory and look for more level series
3591 LoadLevelInfoFromLevelDir(&leveldir_new->node_group,
3592 leveldir_new, directory_path);
3595 free(directory_path);
3601 static void LoadLevelInfoFromLevelDir(TreeInfo **node_first,
3602 TreeInfo *node_parent,
3603 char *level_directory)
3605 // ---------- 1st stage: process any level set zip files ----------
3607 ProcessZipFilesInDirectory(level_directory, TREE_TYPE_LEVEL_DIR);
3609 // ---------- 2nd stage: check for level set directories ----------
3612 DirectoryEntry *dir_entry;
3613 boolean valid_entry_found = FALSE;
3615 if ((dir = openDirectory(level_directory)) == NULL)
3617 Warn("cannot read level directory '%s'", level_directory);
3622 while ((dir_entry = readDirectory(dir)) != NULL) // loop all entries
3624 char *directory_name = dir_entry->basename;
3625 char *directory_path = getPath2(level_directory, directory_name);
3627 // skip entries for current and parent directory
3628 if (strEqual(directory_name, ".") ||
3629 strEqual(directory_name, ".."))
3631 free(directory_path);
3636 // find out if directory entry is itself a directory
3637 if (!dir_entry->is_directory) // not a directory
3639 free(directory_path);
3644 free(directory_path);
3646 if (strEqual(directory_name, GRAPHICS_DIRECTORY) ||
3647 strEqual(directory_name, SOUNDS_DIRECTORY) ||
3648 strEqual(directory_name, MUSIC_DIRECTORY))
3651 valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
3656 closeDirectory(dir);
3658 // special case: top level directory may directly contain "levelinfo.conf"
3659 if (node_parent == NULL && !valid_entry_found)
3661 // check if this directory directly contains a file "levelinfo.conf"
3662 valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
3663 level_directory, ".");
3666 if (!valid_entry_found)
3667 Warn("cannot find any valid level series in directory '%s'",
3671 boolean AdjustGraphicsForEMC(void)
3673 boolean settings_changed = FALSE;
3675 settings_changed |= adjustTreeGraphicsForEMC(leveldir_first_all);
3676 settings_changed |= adjustTreeGraphicsForEMC(leveldir_first);
3678 return settings_changed;
3681 boolean AdjustSoundsForEMC(void)
3683 boolean settings_changed = FALSE;
3685 settings_changed |= adjustTreeSoundsForEMC(leveldir_first_all);
3686 settings_changed |= adjustTreeSoundsForEMC(leveldir_first);
3688 return settings_changed;
3691 void LoadLevelInfo(void)
3693 InitUserLevelDirectory(getLoginName());
3695 DrawInitText("Loading level series", 120, FC_GREEN);
3697 LoadLevelInfoFromLevelDir(&leveldir_first, NULL, options.level_directory);
3698 LoadLevelInfoFromLevelDir(&leveldir_first, NULL, getUserLevelDir(NULL));
3700 leveldir_first = createTopTreeInfoNode(leveldir_first);
3702 /* after loading all level set information, clone the level directory tree
3703 and remove all level sets without levels (these may still contain artwork
3704 to be offered in the setup menu as "custom artwork", and are therefore
3705 checked for existing artwork in the function "LoadLevelArtworkInfo()") */
3706 leveldir_first_all = leveldir_first;
3707 cloneTree(&leveldir_first, leveldir_first_all, TRUE);
3709 AdjustGraphicsForEMC();
3710 AdjustSoundsForEMC();
3712 // before sorting, the first entries will be from the user directory
3713 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
3715 if (leveldir_first == NULL)
3716 Fail("cannot find any valid level series in any directory");
3718 sortTreeInfo(&leveldir_first);
3720 #if ENABLE_UNUSED_CODE
3721 dumpTreeInfo(leveldir_first, 0);
3725 static boolean LoadArtworkInfoFromArtworkConf(TreeInfo **node_first,
3726 TreeInfo *node_parent,
3727 char *base_directory,
3728 char *directory_name, int type)
3730 char *directory_path = getPath2(base_directory, directory_name);
3731 char *filename = getPath2(directory_path, ARTWORKINFO_FILENAME(type));
3732 SetupFileHash *setup_file_hash = NULL;
3733 TreeInfo *artwork_new = NULL;
3736 if (fileExists(filename))
3737 setup_file_hash = loadSetupFileHash(filename);
3739 if (setup_file_hash == NULL) // no config file -- look for artwork files
3742 DirectoryEntry *dir_entry;
3743 boolean valid_file_found = FALSE;
3745 if ((dir = openDirectory(directory_path)) != NULL)
3747 while ((dir_entry = readDirectory(dir)) != NULL)
3749 if (FileIsArtworkType(dir_entry->filename, type))
3751 valid_file_found = TRUE;
3757 closeDirectory(dir);
3760 if (!valid_file_found)
3762 #if DEBUG_NO_CONFIG_FILE
3763 if (!strEqual(directory_name, "."))
3764 Debug("setup", "ignoring artwork directory '%s'", directory_path);
3767 free(directory_path);
3774 artwork_new = newTreeInfo();
3777 setTreeInfoToDefaultsFromParent(artwork_new, node_parent);
3779 setTreeInfoToDefaults(artwork_new, type);
3781 artwork_new->subdir = getStringCopy(directory_name);
3783 if (setup_file_hash) // (before defining ".color" and ".class_desc")
3785 // set all structure fields according to the token/value pairs
3787 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
3788 setSetupInfo(levelinfo_tokens, i,
3789 getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
3792 if (strEqual(artwork_new->name, ANONYMOUS_NAME))
3793 setString(&artwork_new->name, artwork_new->subdir);
3795 if (artwork_new->identifier == NULL)
3796 artwork_new->identifier = getStringCopy(artwork_new->subdir);
3798 if (artwork_new->name_sorting == NULL)
3799 artwork_new->name_sorting = getStringCopy(artwork_new->name);
3802 if (node_parent == NULL) // top level group
3804 artwork_new->basepath = getStringCopy(base_directory);
3805 artwork_new->fullpath = getStringCopy(artwork_new->subdir);
3807 else // sub level group
3809 artwork_new->basepath = getStringCopy(node_parent->basepath);
3810 artwork_new->fullpath = getPath2(node_parent->fullpath, directory_name);
3813 artwork_new->in_user_dir =
3814 (!strEqual(artwork_new->basepath, OPTIONS_ARTWORK_DIRECTORY(type)));
3816 setString(&artwork_new->class_desc, getLevelClassDescription(artwork_new));
3818 if (setup_file_hash == NULL) // (after determining ".user_defined")
3820 if (strEqual(artwork_new->subdir, "."))
3822 if (artwork_new->user_defined)
3824 setString(&artwork_new->identifier, "private");
3825 artwork_new->sort_priority = ARTWORKCLASS_PRIVATE;
3829 setString(&artwork_new->identifier, "classic");
3830 artwork_new->sort_priority = ARTWORKCLASS_CLASSICS;
3833 setString(&artwork_new->class_desc,
3834 getLevelClassDescription(artwork_new));
3838 setString(&artwork_new->identifier, artwork_new->subdir);
3841 setString(&artwork_new->name, artwork_new->identifier);
3842 setString(&artwork_new->name_sorting, artwork_new->name);
3845 pushTreeInfo(node_first, artwork_new);
3847 freeSetupFileHash(setup_file_hash);
3849 free(directory_path);
3855 static void LoadArtworkInfoFromArtworkDir(TreeInfo **node_first,
3856 TreeInfo *node_parent,
3857 char *base_directory, int type)
3859 // ---------- 1st stage: process any artwork set zip files ----------
3861 ProcessZipFilesInDirectory(base_directory, type);
3863 // ---------- 2nd stage: check for artwork set directories ----------
3866 DirectoryEntry *dir_entry;
3867 boolean valid_entry_found = FALSE;
3869 if ((dir = openDirectory(base_directory)) == NULL)
3871 // display error if directory is main "options.graphics_directory" etc.
3872 if (base_directory == OPTIONS_ARTWORK_DIRECTORY(type))
3873 Warn("cannot read directory '%s'", base_directory);
3878 while ((dir_entry = readDirectory(dir)) != NULL) // loop all entries
3880 char *directory_name = dir_entry->basename;
3881 char *directory_path = getPath2(base_directory, directory_name);
3883 // skip directory entries for current and parent directory
3884 if (strEqual(directory_name, ".") ||
3885 strEqual(directory_name, ".."))
3887 free(directory_path);
3892 // skip directory entries which are not a directory
3893 if (!dir_entry->is_directory) // not a directory
3895 free(directory_path);
3900 free(directory_path);
3902 // check if this directory contains artwork with or without config file
3903 valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
3905 directory_name, type);
3908 closeDirectory(dir);
3910 // check if this directory directly contains artwork itself
3911 valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
3912 base_directory, ".",
3914 if (!valid_entry_found)
3915 Warn("cannot find any valid artwork in directory '%s'", base_directory);
3918 static TreeInfo *getDummyArtworkInfo(int type)
3920 // this is only needed when there is completely no artwork available
3921 TreeInfo *artwork_new = newTreeInfo();
3923 setTreeInfoToDefaults(artwork_new, type);
3925 setString(&artwork_new->subdir, UNDEFINED_FILENAME);
3926 setString(&artwork_new->fullpath, UNDEFINED_FILENAME);
3927 setString(&artwork_new->basepath, UNDEFINED_FILENAME);
3929 setString(&artwork_new->identifier, UNDEFINED_FILENAME);
3930 setString(&artwork_new->name, UNDEFINED_FILENAME);
3931 setString(&artwork_new->name_sorting, UNDEFINED_FILENAME);
3936 void SetCurrentArtwork(int type)
3938 ArtworkDirTree **current_ptr = ARTWORK_CURRENT_PTR(artwork, type);
3939 ArtworkDirTree *first_node = ARTWORK_FIRST_NODE(artwork, type);
3940 char *setup_set = SETUP_ARTWORK_SET(setup, type);
3941 char *default_subdir = ARTWORK_DEFAULT_SUBDIR(type);
3943 // set current artwork to artwork configured in setup menu
3944 *current_ptr = getTreeInfoFromIdentifier(first_node, setup_set);
3946 // if not found, set current artwork to default artwork
3947 if (*current_ptr == NULL)
3948 *current_ptr = getTreeInfoFromIdentifier(first_node, default_subdir);
3950 // if not found, set current artwork to first artwork in tree
3951 if (*current_ptr == NULL)
3952 *current_ptr = getFirstValidTreeInfoEntry(first_node);
3955 void ChangeCurrentArtworkIfNeeded(int type)
3957 char *current_identifier = ARTWORK_CURRENT_IDENTIFIER(artwork, type);
3958 char *setup_set = SETUP_ARTWORK_SET(setup, type);
3960 if (!strEqual(current_identifier, setup_set))
3961 SetCurrentArtwork(type);
3964 void LoadArtworkInfo(void)
3966 LoadArtworkInfoCache();
3968 DrawInitText("Looking for custom artwork", 120, FC_GREEN);
3970 LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
3971 options.graphics_directory,
3972 TREE_TYPE_GRAPHICS_DIR);
3973 LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
3974 getUserGraphicsDir(),
3975 TREE_TYPE_GRAPHICS_DIR);
3977 LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
3978 options.sounds_directory,
3979 TREE_TYPE_SOUNDS_DIR);
3980 LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
3982 TREE_TYPE_SOUNDS_DIR);
3984 LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
3985 options.music_directory,
3986 TREE_TYPE_MUSIC_DIR);
3987 LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
3989 TREE_TYPE_MUSIC_DIR);
3991 if (artwork.gfx_first == NULL)
3992 artwork.gfx_first = getDummyArtworkInfo(TREE_TYPE_GRAPHICS_DIR);
3993 if (artwork.snd_first == NULL)
3994 artwork.snd_first = getDummyArtworkInfo(TREE_TYPE_SOUNDS_DIR);
3995 if (artwork.mus_first == NULL)
3996 artwork.mus_first = getDummyArtworkInfo(TREE_TYPE_MUSIC_DIR);
3998 // before sorting, the first entries will be from the user directory
3999 SetCurrentArtwork(ARTWORK_TYPE_GRAPHICS);
4000 SetCurrentArtwork(ARTWORK_TYPE_SOUNDS);
4001 SetCurrentArtwork(ARTWORK_TYPE_MUSIC);
4003 artwork.gfx_current_identifier = artwork.gfx_current->identifier;
4004 artwork.snd_current_identifier = artwork.snd_current->identifier;
4005 artwork.mus_current_identifier = artwork.mus_current->identifier;
4007 #if ENABLE_UNUSED_CODE
4008 Debug("setup:LoadArtworkInfo", "graphics set == %s",
4009 artwork.gfx_current_identifier);
4010 Debug("setup:LoadArtworkInfo", "sounds set == %s",
4011 artwork.snd_current_identifier);
4012 Debug("setup:LoadArtworkInfo", "music set == %s",
4013 artwork.mus_current_identifier);
4016 sortTreeInfo(&artwork.gfx_first);
4017 sortTreeInfo(&artwork.snd_first);
4018 sortTreeInfo(&artwork.mus_first);
4020 #if ENABLE_UNUSED_CODE
4021 dumpTreeInfo(artwork.gfx_first, 0);
4022 dumpTreeInfo(artwork.snd_first, 0);
4023 dumpTreeInfo(artwork.mus_first, 0);
4027 static void MoveArtworkInfoIntoSubTree(ArtworkDirTree **artwork_node)
4029 ArtworkDirTree *artwork_new = newTreeInfo();
4030 char *top_node_name = "standalone artwork";
4032 setTreeInfoToDefaults(artwork_new, (*artwork_node)->type);
4034 artwork_new->level_group = TRUE;
4036 setString(&artwork_new->identifier, top_node_name);
4037 setString(&artwork_new->name, top_node_name);
4038 setString(&artwork_new->name_sorting, top_node_name);
4040 // create node to link back to current custom artwork directory
4041 createParentTreeInfoNode(artwork_new);
4043 // move existing custom artwork tree into newly created sub-tree
4044 artwork_new->node_group->next = *artwork_node;
4046 // change custom artwork tree to contain only newly created node
4047 *artwork_node = artwork_new;
4050 static void LoadArtworkInfoFromLevelInfoExt(ArtworkDirTree **artwork_node,
4051 ArtworkDirTree *node_parent,
4052 LevelDirTree *level_node,
4053 boolean empty_level_set_mode)
4055 int type = (*artwork_node)->type;
4057 // recursively check all level directories for artwork sub-directories
4061 boolean empty_level_set = (level_node->levels == 0);
4063 // check all tree entries for artwork, but skip parent link entries
4064 if (!level_node->parent_link && empty_level_set == empty_level_set_mode)
4066 TreeInfo *artwork_new = getArtworkInfoCacheEntry(level_node, type);
4067 boolean cached = (artwork_new != NULL);
4071 pushTreeInfo(artwork_node, artwork_new);
4075 TreeInfo *topnode_last = *artwork_node;
4076 char *path = getPath2(getLevelDirFromTreeInfo(level_node),
4077 ARTWORK_DIRECTORY(type));
4079 LoadArtworkInfoFromArtworkDir(artwork_node, NULL, path, type);
4081 if (topnode_last != *artwork_node) // check for newly added node
4083 artwork_new = *artwork_node;
4085 setString(&artwork_new->identifier, level_node->subdir);
4086 setString(&artwork_new->name, level_node->name);
4087 setString(&artwork_new->name_sorting, level_node->name_sorting);
4089 artwork_new->sort_priority = level_node->sort_priority;
4090 artwork_new->in_user_dir = level_node->in_user_dir;
4092 update_artworkinfo_cache = TRUE;
4098 // insert artwork info (from old cache or filesystem) into new cache
4099 if (artwork_new != NULL)
4100 setArtworkInfoCacheEntry(artwork_new, level_node, type);
4103 DrawInitText(level_node->name, 150, FC_YELLOW);
4105 if (level_node->node_group != NULL)
4107 TreeInfo *artwork_new = newTreeInfo();
4110 setTreeInfoToDefaultsFromParent(artwork_new, node_parent);
4112 setTreeInfoToDefaults(artwork_new, type);
4114 artwork_new->level_group = TRUE;
4116 setString(&artwork_new->identifier, level_node->subdir);
4118 if (node_parent == NULL) // check for top tree node
4120 char *top_node_name = (empty_level_set_mode ?
4121 "artwork for certain level sets" :
4122 "artwork included in level sets");
4124 setString(&artwork_new->name, top_node_name);
4125 setString(&artwork_new->name_sorting, top_node_name);
4129 setString(&artwork_new->name, level_node->name);
4130 setString(&artwork_new->name_sorting, level_node->name_sorting);
4133 pushTreeInfo(artwork_node, artwork_new);
4135 // create node to link back to current custom artwork directory
4136 createParentTreeInfoNode(artwork_new);
4138 // recursively step into sub-directory and look for more custom artwork
4139 LoadArtworkInfoFromLevelInfoExt(&artwork_new->node_group, artwork_new,
4140 level_node->node_group,
4141 empty_level_set_mode);
4143 // if sub-tree has no custom artwork at all, remove it
4144 if (artwork_new->node_group->next == NULL)
4145 removeTreeInfo(artwork_node);
4148 level_node = level_node->next;
4152 static void LoadArtworkInfoFromLevelInfo(ArtworkDirTree **artwork_node)
4154 // move peviously loaded artwork tree into separate sub-tree
4155 MoveArtworkInfoIntoSubTree(artwork_node);
4157 // load artwork from level sets into separate sub-trees
4158 LoadArtworkInfoFromLevelInfoExt(artwork_node, NULL, leveldir_first_all, TRUE);
4159 LoadArtworkInfoFromLevelInfoExt(artwork_node, NULL, leveldir_first_all, FALSE);
4161 // add top tree node over all three separate sub-trees
4162 *artwork_node = createTopTreeInfoNode(*artwork_node);
4164 // set all parent links (back links) in complete artwork tree
4165 setTreeInfoParentNodes(*artwork_node, NULL);
4168 void LoadLevelArtworkInfo(void)
4170 print_timestamp_init("LoadLevelArtworkInfo");
4172 DrawInitText("Looking for custom level artwork", 120, FC_GREEN);
4174 print_timestamp_time("DrawTimeText");
4176 LoadArtworkInfoFromLevelInfo(&artwork.gfx_first);
4177 print_timestamp_time("LoadArtworkInfoFromLevelInfo (gfx)");
4178 LoadArtworkInfoFromLevelInfo(&artwork.snd_first);
4179 print_timestamp_time("LoadArtworkInfoFromLevelInfo (snd)");
4180 LoadArtworkInfoFromLevelInfo(&artwork.mus_first);
4181 print_timestamp_time("LoadArtworkInfoFromLevelInfo (mus)");
4183 SaveArtworkInfoCache();
4185 print_timestamp_time("SaveArtworkInfoCache");
4187 // needed for reloading level artwork not known at ealier stage
4188 ChangeCurrentArtworkIfNeeded(ARTWORK_TYPE_GRAPHICS);
4189 ChangeCurrentArtworkIfNeeded(ARTWORK_TYPE_SOUNDS);
4190 ChangeCurrentArtworkIfNeeded(ARTWORK_TYPE_MUSIC);
4192 print_timestamp_time("getTreeInfoFromIdentifier");
4194 sortTreeInfo(&artwork.gfx_first);
4195 sortTreeInfo(&artwork.snd_first);
4196 sortTreeInfo(&artwork.mus_first);
4198 print_timestamp_time("sortTreeInfo");
4200 #if ENABLE_UNUSED_CODE
4201 dumpTreeInfo(artwork.gfx_first, 0);
4202 dumpTreeInfo(artwork.snd_first, 0);
4203 dumpTreeInfo(artwork.mus_first, 0);
4206 print_timestamp_done("LoadLevelArtworkInfo");
4209 static boolean AddTreeSetToTreeInfoExt(TreeInfo *tree_node_old, char *tree_dir,
4210 char *tree_subdir_new, int type)
4212 if (tree_node_old == NULL)
4214 if (type == TREE_TYPE_LEVEL_DIR)
4216 // get level info tree node of personal user level set
4217 tree_node_old = getTreeInfoFromIdentifier(leveldir_first, getLoginName());
4219 // this may happen if "setup.internal.create_user_levelset" is FALSE
4220 // or if file "levelinfo.conf" is missing in personal user level set
4221 if (tree_node_old == NULL)
4222 tree_node_old = leveldir_first->node_group;
4226 // get artwork info tree node of first artwork set
4227 tree_node_old = ARTWORK_FIRST_NODE(artwork, type);
4231 if (tree_dir == NULL)
4232 tree_dir = TREE_USERDIR(type);
4234 if (tree_node_old == NULL ||
4236 tree_subdir_new == NULL) // should not happen
4239 int draw_deactivation_mask = GetDrawDeactivationMask();
4241 // override draw deactivation mask (temporarily disable drawing)
4242 SetDrawDeactivationMask(REDRAW_ALL);
4244 if (type == TREE_TYPE_LEVEL_DIR)
4246 // load new level set config and add it next to first user level set
4247 LoadLevelInfoFromLevelConf(&tree_node_old->next,
4248 tree_node_old->node_parent,
4249 tree_dir, tree_subdir_new);
4253 // load new artwork set config and add it next to first artwork set
4254 LoadArtworkInfoFromArtworkConf(&tree_node_old->next,
4255 tree_node_old->node_parent,
4256 tree_dir, tree_subdir_new, type);
4259 // set draw deactivation mask to previous value
4260 SetDrawDeactivationMask(draw_deactivation_mask);
4262 // get first node of level or artwork info tree
4263 TreeInfo **tree_node_first = TREE_FIRST_NODE_PTR(type);
4265 // get tree info node of newly added level or artwork set
4266 TreeInfo *tree_node_new = getTreeInfoFromIdentifier(*tree_node_first,
4269 if (tree_node_new == NULL) // should not happen
4272 // correct top link and parent node link of newly created tree node
4273 tree_node_new->node_top = tree_node_old->node_top;
4274 tree_node_new->node_parent = tree_node_old->node_parent;
4276 // sort tree info to adjust position of newly added tree set
4277 sortTreeInfo(tree_node_first);
4282 void AddTreeSetToTreeInfo(TreeInfo *tree_node, char *tree_dir,
4283 char *tree_subdir_new, int type)
4285 if (!AddTreeSetToTreeInfoExt(tree_node, tree_dir, tree_subdir_new, type))
4286 Fail("internal tree info structure corrupted -- aborting");
4289 void AddUserLevelSetToLevelInfo(char *level_subdir_new)
4291 AddTreeSetToTreeInfo(NULL, NULL, level_subdir_new, TREE_TYPE_LEVEL_DIR);
4294 char *getArtworkIdentifierForUserLevelSet(int type)
4296 char *classic_artwork_set = getClassicArtworkSet(type);
4298 // check for custom artwork configured in "levelinfo.conf"
4299 char *leveldir_artwork_set =
4300 *LEVELDIR_ARTWORK_SET_PTR(leveldir_current, type);
4301 boolean has_leveldir_artwork_set =
4302 (leveldir_artwork_set != NULL && !strEqual(leveldir_artwork_set,
4303 classic_artwork_set));
4305 // check for custom artwork in sub-directory "graphics" etc.
4306 TreeInfo *artwork_first_node = ARTWORK_FIRST_NODE(artwork, type);
4307 char *leveldir_identifier = leveldir_current->identifier;
4308 boolean has_artwork_subdir =
4309 (getTreeInfoFromIdentifier(artwork_first_node,
4310 leveldir_identifier) != NULL);
4312 return (has_leveldir_artwork_set ? leveldir_artwork_set :
4313 has_artwork_subdir ? leveldir_identifier :
4314 classic_artwork_set);
4317 TreeInfo *getArtworkTreeInfoForUserLevelSet(int type)
4319 char *artwork_set = getArtworkIdentifierForUserLevelSet(type);
4320 TreeInfo *artwork_first_node = ARTWORK_FIRST_NODE(artwork, type);
4321 TreeInfo *ti = getTreeInfoFromIdentifier(artwork_first_node, artwork_set);
4325 ti = getTreeInfoFromIdentifier(artwork_first_node,
4326 ARTWORK_DEFAULT_SUBDIR(type));
4328 Fail("cannot find default graphics -- should not happen");
4334 boolean checkIfCustomArtworkExistsForCurrentLevelSet(void)
4336 char *graphics_set =
4337 getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_GRAPHICS);
4339 getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_SOUNDS);
4341 getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_MUSIC);
4343 return (!strEqual(graphics_set, GFX_CLASSIC_SUBDIR) ||
4344 !strEqual(sounds_set, SND_CLASSIC_SUBDIR) ||
4345 !strEqual(music_set, MUS_CLASSIC_SUBDIR));
4348 boolean UpdateUserLevelSet(char *level_subdir, char *level_name,
4349 char *level_author, int num_levels)
4351 char *filename = getPath2(getUserLevelDir(level_subdir), LEVELINFO_FILENAME);
4352 char *filename_tmp = getStringCat2(filename, ".tmp");
4354 FILE *file_tmp = NULL;
4355 char line[MAX_LINE_LEN];
4356 boolean success = FALSE;
4357 LevelDirTree *leveldir = getTreeInfoFromIdentifier(leveldir_first,
4359 // update values in level directory tree
4361 if (level_name != NULL)
4362 setString(&leveldir->name, level_name);
4364 if (level_author != NULL)
4365 setString(&leveldir->author, level_author);
4367 if (num_levels != -1)
4368 leveldir->levels = num_levels;
4370 // update values that depend on other values
4372 setString(&leveldir->name_sorting, leveldir->name);
4374 leveldir->last_level = leveldir->first_level + leveldir->levels - 1;
4376 // sort order of level sets may have changed
4377 sortTreeInfo(&leveldir_first);
4379 if ((file = fopen(filename, MODE_READ)) &&
4380 (file_tmp = fopen(filename_tmp, MODE_WRITE)))
4382 while (fgets(line, MAX_LINE_LEN, file))
4384 if (strPrefix(line, "name:") && level_name != NULL)
4385 fprintf(file_tmp, "%-32s%s\n", "name:", level_name);
4386 else if (strPrefix(line, "author:") && level_author != NULL)
4387 fprintf(file_tmp, "%-32s%s\n", "author:", level_author);
4388 else if (strPrefix(line, "levels:") && num_levels != -1)
4389 fprintf(file_tmp, "%-32s%d\n", "levels:", num_levels);
4391 fputs(line, file_tmp);
4404 success = (rename(filename_tmp, filename) == 0);
4412 boolean CreateUserLevelSet(char *level_subdir, char *level_name,
4413 char *level_author, int num_levels,
4414 boolean use_artwork_set)
4416 LevelDirTree *level_info;
4421 // create user level sub-directory, if needed
4422 createDirectory(getUserLevelDir(level_subdir), "user level", PERMS_PRIVATE);
4424 filename = getPath2(getUserLevelDir(level_subdir), LEVELINFO_FILENAME);
4426 if (!(file = fopen(filename, MODE_WRITE)))
4428 Warn("cannot write level info file '%s'", filename);
4435 level_info = newTreeInfo();
4437 // always start with reliable default values
4438 setTreeInfoToDefaults(level_info, TREE_TYPE_LEVEL_DIR);
4440 setString(&level_info->name, level_name);
4441 setString(&level_info->author, level_author);
4442 level_info->levels = num_levels;
4443 level_info->first_level = 1;
4444 level_info->sort_priority = LEVELCLASS_PRIVATE_START;
4445 level_info->readonly = FALSE;
4447 if (use_artwork_set)
4449 level_info->graphics_set =
4450 getStringCopy(getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_GRAPHICS));
4451 level_info->sounds_set =
4452 getStringCopy(getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_SOUNDS));
4453 level_info->music_set =
4454 getStringCopy(getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_MUSIC));
4457 token_value_position = TOKEN_VALUE_POSITION_SHORT;
4459 fprintFileHeader(file, LEVELINFO_FILENAME);
4462 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
4464 if (i == LEVELINFO_TOKEN_NAME ||
4465 i == LEVELINFO_TOKEN_AUTHOR ||
4466 i == LEVELINFO_TOKEN_LEVELS ||
4467 i == LEVELINFO_TOKEN_FIRST_LEVEL ||
4468 i == LEVELINFO_TOKEN_SORT_PRIORITY ||
4469 i == LEVELINFO_TOKEN_READONLY ||
4470 (use_artwork_set && (i == LEVELINFO_TOKEN_GRAPHICS_SET ||
4471 i == LEVELINFO_TOKEN_SOUNDS_SET ||
4472 i == LEVELINFO_TOKEN_MUSIC_SET)))
4473 fprintf(file, "%s\n", getSetupLine(levelinfo_tokens, "", i));
4475 // just to make things nicer :)
4476 if (i == LEVELINFO_TOKEN_AUTHOR ||
4477 i == LEVELINFO_TOKEN_FIRST_LEVEL ||
4478 (use_artwork_set && i == LEVELINFO_TOKEN_READONLY))
4479 fprintf(file, "\n");
4482 token_value_position = TOKEN_VALUE_POSITION_DEFAULT;
4486 SetFilePermissions(filename, PERMS_PRIVATE);
4488 freeTreeInfo(level_info);
4494 static void SaveUserLevelInfo(void)
4496 CreateUserLevelSet(getLoginName(), getLoginName(), getRealName(), 100, FALSE);
4499 char *getSetupValue(int type, void *value)
4501 static char value_string[MAX_LINE_LEN];
4509 strcpy(value_string, (*(boolean *)value ? "true" : "false"));
4513 strcpy(value_string, (*(boolean *)value ? "on" : "off"));
4517 strcpy(value_string, (*(int *)value == AUTO ? "auto" :
4518 *(int *)value == FALSE ? "off" : "on"));
4522 strcpy(value_string, (*(boolean *)value ? "yes" : "no"));
4525 case TYPE_YES_NO_AUTO:
4526 strcpy(value_string, (*(int *)value == AUTO ? "auto" :
4527 *(int *)value == FALSE ? "no" : "yes"));
4531 strcpy(value_string, (*(boolean *)value ? "AGA" : "ECS"));
4535 strcpy(value_string, getKeyNameFromKey(*(Key *)value));
4539 strcpy(value_string, getX11KeyNameFromKey(*(Key *)value));
4543 sprintf(value_string, "%d", *(int *)value);
4547 if (*(char **)value == NULL)
4550 strcpy(value_string, *(char **)value);
4554 sprintf(value_string, "player_%d", *(int *)value + 1);
4558 value_string[0] = '\0';
4562 if (type & TYPE_GHOSTED)
4563 strcpy(value_string, "n/a");
4565 return value_string;
4568 char *getSetupLine(struct TokenInfo *token_info, char *prefix, int token_nr)
4572 static char token_string[MAX_LINE_LEN];
4573 int token_type = token_info[token_nr].type;
4574 void *setup_value = token_info[token_nr].value;
4575 char *token_text = token_info[token_nr].text;
4576 char *value_string = getSetupValue(token_type, setup_value);
4578 // build complete token string
4579 sprintf(token_string, "%s%s", prefix, token_text);
4581 // build setup entry line
4582 line = getFormattedSetupEntry(token_string, value_string);
4584 if (token_type == TYPE_KEY_X11)
4586 Key key = *(Key *)setup_value;
4587 char *keyname = getKeyNameFromKey(key);
4589 // add comment, if useful
4590 if (!strEqual(keyname, "(undefined)") &&
4591 !strEqual(keyname, "(unknown)"))
4593 // add at least one whitespace
4595 for (i = strlen(line); i < token_comment_position; i++)
4599 strcat(line, keyname);
4606 static void InitLastPlayedLevels_ParentNode(void)
4608 LevelDirTree **leveldir_top = &leveldir_first->node_group->next;
4609 LevelDirTree *leveldir_new = NULL;
4611 // check if parent node for last played levels already exists
4612 if (strEqual((*leveldir_top)->identifier, TOKEN_STR_LAST_LEVEL_SERIES))
4615 leveldir_new = newTreeInfo();
4617 setTreeInfoToDefaultsFromParent(leveldir_new, leveldir_first);
4619 leveldir_new->level_group = TRUE;
4620 leveldir_new->sort_priority = LEVELCLASS_LAST_PLAYED_LEVEL;
4622 setString(&leveldir_new->identifier, TOKEN_STR_LAST_LEVEL_SERIES);
4623 setString(&leveldir_new->name, "<< (last played level sets)");
4624 setString(&leveldir_new->name_sorting, leveldir_new->name);
4626 pushTreeInfo(leveldir_top, leveldir_new);
4628 // create node to link back to current level directory
4629 createParentTreeInfoNode(leveldir_new);
4632 void UpdateLastPlayedLevels_TreeInfo(void)
4634 char **last_level_series = setup.level_setup.last_level_series;
4635 LevelDirTree *leveldir_last;
4636 TreeInfo **node_new = NULL;
4639 if (last_level_series[0] == NULL)
4642 InitLastPlayedLevels_ParentNode();
4644 leveldir_last = getTreeInfoFromIdentifierExt(leveldir_first,
4645 TOKEN_STR_LAST_LEVEL_SERIES,
4646 TREE_NODE_TYPE_GROUP);
4647 if (leveldir_last == NULL)
4650 node_new = &leveldir_last->node_group->next;
4652 freeTreeInfo(*node_new);
4656 for (i = 0; last_level_series[i] != NULL; i++)
4658 LevelDirTree *node_last = getTreeInfoFromIdentifier(leveldir_first,
4659 last_level_series[i]);
4660 if (node_last == NULL)
4663 *node_new = getTreeInfoCopy(node_last); // copy complete node
4665 (*node_new)->node_top = &leveldir_first; // correct top node link
4666 (*node_new)->node_parent = leveldir_last; // correct parent node link
4668 (*node_new)->is_copy = TRUE; // mark entry as node copy
4670 (*node_new)->node_group = NULL;
4671 (*node_new)->next = NULL;
4673 (*node_new)->cl_first = -1; // force setting tree cursor
4675 node_new = &((*node_new)->next);
4679 static void UpdateLastPlayedLevels_List(void)
4681 char **last_level_series = setup.level_setup.last_level_series;
4682 int pos = MAX_LEVELDIR_HISTORY - 1;
4685 // search for potentially already existing entry in list of level sets
4686 for (i = 0; last_level_series[i] != NULL; i++)
4687 if (strEqual(last_level_series[i], leveldir_current->identifier))
4690 // move list of level sets one entry down (using potentially free entry)
4691 for (i = pos; i > 0; i--)
4692 setString(&last_level_series[i], last_level_series[i - 1]);
4694 // put last played level set at top position
4695 setString(&last_level_series[0], leveldir_current->identifier);
4698 static TreeInfo *StoreOrRestoreLastPlayedLevels(TreeInfo *node, boolean store)
4700 static char *identifier = NULL;
4704 setString(&identifier, (node && node->is_copy ? node->identifier : NULL));
4706 return NULL; // not used
4710 TreeInfo *node_new = getTreeInfoFromIdentifierExt(leveldir_first,
4712 TREE_NODE_TYPE_COPY);
4713 return (node_new != NULL ? node_new : node);
4717 void StoreLastPlayedLevels(TreeInfo *node)
4719 StoreOrRestoreLastPlayedLevels(node, TRUE);
4722 void RestoreLastPlayedLevels(TreeInfo **node)
4724 *node = StoreOrRestoreLastPlayedLevels(*node, FALSE);
4727 void LoadLevelSetup_LastSeries(void)
4729 // --------------------------------------------------------------------------
4730 // ~/.<program>/levelsetup.conf
4731 // --------------------------------------------------------------------------
4733 char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
4734 SetupFileHash *level_setup_hash = NULL;
4738 // always start with reliable default values
4739 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
4741 // start with empty history of last played level sets
4742 setString(&setup.level_setup.last_level_series[0], NULL);
4744 if (!strEqual(DEFAULT_LEVELSET, UNDEFINED_LEVELSET))
4746 leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
4748 if (leveldir_current == NULL)
4749 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
4752 if ((level_setup_hash = loadSetupFileHash(filename)))
4754 char *last_level_series =
4755 getHashEntry(level_setup_hash, TOKEN_STR_LAST_LEVEL_SERIES);
4757 leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
4759 if (leveldir_current == NULL)
4760 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
4762 for (i = 0; i < MAX_LEVELDIR_HISTORY; i++)
4764 char token[strlen(TOKEN_STR_LAST_LEVEL_SERIES) + 10];
4765 LevelDirTree *leveldir_last;
4767 sprintf(token, "%s.%03d", TOKEN_STR_LAST_LEVEL_SERIES, i);
4769 last_level_series = getHashEntry(level_setup_hash, token);
4771 leveldir_last = getTreeInfoFromIdentifier(leveldir_first,
4773 if (leveldir_last != NULL)
4774 setString(&setup.level_setup.last_level_series[pos++],
4778 setString(&setup.level_setup.last_level_series[pos], NULL);
4780 freeSetupFileHash(level_setup_hash);
4784 Debug("setup", "using default setup values");
4790 static void SaveLevelSetup_LastSeries_Ext(boolean deactivate_last_level_series)
4792 // --------------------------------------------------------------------------
4793 // ~/.<program>/levelsetup.conf
4794 // --------------------------------------------------------------------------
4796 // check if the current level directory structure is available at this point
4797 if (leveldir_current == NULL)
4800 char **last_level_series = setup.level_setup.last_level_series;
4801 char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
4805 InitUserDataDirectory();
4807 UpdateLastPlayedLevels_List();
4809 if (!(file = fopen(filename, MODE_WRITE)))
4811 Warn("cannot write setup file '%s'", filename);
4818 fprintFileHeader(file, LEVELSETUP_FILENAME);
4820 if (deactivate_last_level_series)
4821 fprintf(file, "# %s\n# ", "the following level set may have caused a problem and was deactivated");
4823 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_LAST_LEVEL_SERIES,
4824 leveldir_current->identifier));
4826 for (i = 0; last_level_series[i] != NULL; i++)
4828 char token[strlen(TOKEN_STR_LAST_LEVEL_SERIES) + 10];
4830 sprintf(token, "%s.%03d", TOKEN_STR_LAST_LEVEL_SERIES, i);
4832 fprintf(file, "%s\n", getFormattedSetupEntry(token, last_level_series[i]));
4837 SetFilePermissions(filename, PERMS_PRIVATE);
4842 void SaveLevelSetup_LastSeries(void)
4844 SaveLevelSetup_LastSeries_Ext(FALSE);
4847 void SaveLevelSetup_LastSeries_Deactivate(void)
4849 SaveLevelSetup_LastSeries_Ext(TRUE);
4852 static void checkSeriesInfo(void)
4854 static char *level_directory = NULL;
4857 DirectoryEntry *dir_entry;
4860 checked_free(level_directory);
4862 // check for more levels besides the 'levels' field of 'levelinfo.conf'
4864 level_directory = getPath2((leveldir_current->in_user_dir ?
4865 getUserLevelDir(NULL) :
4866 options.level_directory),
4867 leveldir_current->fullpath);
4869 if ((dir = openDirectory(level_directory)) == NULL)
4871 Warn("cannot read level directory '%s'", level_directory);
4877 while ((dir_entry = readDirectory(dir)) != NULL) // loop all entries
4879 if (strlen(dir_entry->basename) > 4 &&
4880 dir_entry->basename[3] == '.' &&
4881 strEqual(&dir_entry->basename[4], LEVELFILE_EXTENSION))
4883 char levelnum_str[4];
4886 strncpy(levelnum_str, dir_entry->basename, 3);
4887 levelnum_str[3] = '\0';
4889 levelnum_value = atoi(levelnum_str);
4891 if (levelnum_value < leveldir_current->first_level)
4893 Warn("additional level %d found", levelnum_value);
4895 leveldir_current->first_level = levelnum_value;
4897 else if (levelnum_value > leveldir_current->last_level)
4899 Warn("additional level %d found", levelnum_value);
4901 leveldir_current->last_level = levelnum_value;
4907 closeDirectory(dir);
4910 void LoadLevelSetup_SeriesInfo(void)
4913 SetupFileHash *level_setup_hash = NULL;
4914 char *level_subdir = leveldir_current->subdir;
4917 // always start with reliable default values
4918 level_nr = leveldir_current->first_level;
4920 for (i = 0; i < MAX_LEVELS; i++)
4922 LevelStats_setPlayed(i, 0);
4923 LevelStats_setSolved(i, 0);
4928 // --------------------------------------------------------------------------
4929 // ~/.<program>/levelsetup/<level series>/levelsetup.conf
4930 // --------------------------------------------------------------------------
4932 level_subdir = leveldir_current->subdir;
4934 filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
4936 if ((level_setup_hash = loadSetupFileHash(filename)))
4940 // get last played level in this level set
4942 token_value = getHashEntry(level_setup_hash, TOKEN_STR_LAST_PLAYED_LEVEL);
4946 level_nr = atoi(token_value);
4948 if (level_nr < leveldir_current->first_level)
4949 level_nr = leveldir_current->first_level;
4950 if (level_nr > leveldir_current->last_level)
4951 level_nr = leveldir_current->last_level;
4954 // get handicap level in this level set
4956 token_value = getHashEntry(level_setup_hash, TOKEN_STR_HANDICAP_LEVEL);
4960 int level_nr = atoi(token_value);
4962 if (level_nr < leveldir_current->first_level)
4963 level_nr = leveldir_current->first_level;
4964 if (level_nr > leveldir_current->last_level + 1)
4965 level_nr = leveldir_current->last_level;
4967 if (leveldir_current->user_defined || !leveldir_current->handicap)
4968 level_nr = leveldir_current->last_level;
4970 leveldir_current->handicap_level = level_nr;
4973 // get number of played and solved levels in this level set
4975 BEGIN_HASH_ITERATION(level_setup_hash, itr)
4977 char *token = HASH_ITERATION_TOKEN(itr);
4978 char *value = HASH_ITERATION_VALUE(itr);
4980 if (strlen(token) == 3 &&
4981 token[0] >= '0' && token[0] <= '9' &&
4982 token[1] >= '0' && token[1] <= '9' &&
4983 token[2] >= '0' && token[2] <= '9')
4985 int level_nr = atoi(token);
4988 LevelStats_setPlayed(level_nr, atoi(value)); // read 1st column
4990 value = strchr(value, ' ');
4993 LevelStats_setSolved(level_nr, atoi(value)); // read 2nd column
4996 END_HASH_ITERATION(hash, itr)
4998 freeSetupFileHash(level_setup_hash);
5002 Debug("setup", "using default setup values");
5008 void SaveLevelSetup_SeriesInfo(void)
5011 char *level_subdir = leveldir_current->subdir;
5012 char *level_nr_str = int2str(level_nr, 0);
5013 char *handicap_level_str = int2str(leveldir_current->handicap_level, 0);
5017 // --------------------------------------------------------------------------
5018 // ~/.<program>/levelsetup/<level series>/levelsetup.conf
5019 // --------------------------------------------------------------------------
5021 InitLevelSetupDirectory(level_subdir);
5023 filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
5025 if (!(file = fopen(filename, MODE_WRITE)))
5027 Warn("cannot write setup file '%s'", filename);
5034 fprintFileHeader(file, LEVELSETUP_FILENAME);
5036 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_PLAYED_LEVEL,
5038 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_HANDICAP_LEVEL,
5039 handicap_level_str));
5041 for (i = leveldir_current->first_level; i <= leveldir_current->last_level;
5044 if (LevelStats_getPlayed(i) > 0 ||
5045 LevelStats_getSolved(i) > 0)
5050 sprintf(token, "%03d", i);
5051 sprintf(value, "%d %d", LevelStats_getPlayed(i), LevelStats_getSolved(i));
5053 fprintf(file, "%s\n", getFormattedSetupEntry(token, value));
5059 SetFilePermissions(filename, PERMS_PRIVATE);
5064 int LevelStats_getPlayed(int nr)
5066 return (nr >= 0 && nr < MAX_LEVELS ? level_stats[nr].played : 0);
5069 int LevelStats_getSolved(int nr)
5071 return (nr >= 0 && nr < MAX_LEVELS ? level_stats[nr].solved : 0);
5074 void LevelStats_setPlayed(int nr, int value)
5076 if (nr >= 0 && nr < MAX_LEVELS)
5077 level_stats[nr].played = value;
5080 void LevelStats_setSolved(int nr, int value)
5082 if (nr >= 0 && nr < MAX_LEVELS)
5083 level_stats[nr].solved = value;
5086 void LevelStats_incPlayed(int nr)
5088 if (nr >= 0 && nr < MAX_LEVELS)
5089 level_stats[nr].played++;
5092 void LevelStats_incSolved(int nr)
5094 if (nr >= 0 && nr < MAX_LEVELS)
5095 level_stats[nr].solved++;
5098 void LoadUserSetup(void)
5100 // --------------------------------------------------------------------------
5101 // ~/.<program>/usersetup.conf
5102 // --------------------------------------------------------------------------
5104 char *filename = getPath2(getMainUserGameDataDir(), USERSETUP_FILENAME);
5105 SetupFileHash *user_setup_hash = NULL;
5107 // always start with reliable default values
5110 if ((user_setup_hash = loadSetupFileHash(filename)))
5114 // get last selected user number
5115 token_value = getHashEntry(user_setup_hash, TOKEN_STR_LAST_USER);
5118 user.nr = MIN(MAX(0, atoi(token_value)), MAX_PLAYER_NAMES - 1);
5120 freeSetupFileHash(user_setup_hash);
5124 Debug("setup", "using default setup values");
5130 void SaveUserSetup(void)
5132 // --------------------------------------------------------------------------
5133 // ~/.<program>/usersetup.conf
5134 // --------------------------------------------------------------------------
5136 char *filename = getPath2(getMainUserGameDataDir(), USERSETUP_FILENAME);
5139 InitMainUserDataDirectory();
5141 if (!(file = fopen(filename, MODE_WRITE)))
5143 Warn("cannot write setup file '%s'", filename);
5150 fprintFileHeader(file, USERSETUP_FILENAME);
5152 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_USER,
5156 SetFilePermissions(filename, PERMS_PRIVATE);